ErklÀren von 200 Line Lightweight Streams in Rust

ErklÀren von 200 Line Lightweight Streams in Rust



Leichte Threads (Coroutinen, Coroutinen, grĂŒne Threads) sind ein sehr leistungsfĂ€higer Mechanismus in modernen Programmiersprachen. In diesem Artikel versuchte Carl Fredrik Samson, eine Laufzeit fĂŒr Lightweight-Streams auf Rast zu implementieren, und erklĂ€rte dabei, wie sie "unter der Haube" funktionieren.







Es sollte auch beachtet werden, dass der Artikel nicht besonders aktuell ist. Damit die Beispiele in der modernen nĂ€chtlichen Version des Rust-Compilers funktionieren, benötigen Sie höchstwahrscheinlich Änderungen, die im Code-Repository fĂŒr diesen Artikel zu finden sind.







Er ĂŒbersetzte grĂ¶ĂŸtenteils fĂŒr sich. Schreiben Sie ĂŒber alle Kommentare - ich werde es umgehend korrigieren. Ich habe versucht, den Text genau zu ĂŒbersetzen, aber an einigen Stellen habe ich ihn neu formuliert, um das Lesen und Verstehen zu erleichtern.


GrĂŒne FĂ€den



GrĂŒne Threads lösen ein hĂ€ufiges Problem bei der Programmierung - Sie möchten nicht, dass Ihr Code den Prozessor blockiert und Ressourcen verschwendet. Dies wird durch Multitasking gelöst, mit dem Sie die AusfĂŒhrung eines Codeteils unterbrechen, einen anderen zur AusfĂŒhrung starten und zwischen "Kontexten" wechseln können.







, — . — . , .







:







  • ()




, . — "-" ( - ). , , UI (User Interface — ) , . , , , .









. , - , , - . (yielding



control) . - , - . , /. , , - , .









, , . , . .







-, . , .. . , .







— x86-64. 16 :













,







, "callee saved". , — , , .. .







, . , . :







mov %rsp, %rax
      
      







. . , , ( )







: AT&T .







AT&T Rust. , . LLVM. LLVM , .







AT&T.







. , . %rax



, :







%rax    # 64   (8 )
%eax    #  32   "rax"
%ax     #  16   "rax"
%ah     #  8   "ax"  "rax"
%al     #  8   "rax"
      
      





+-----------------------------------------------------------------------+
|76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210|
+--------+--------+--------+--------+--------+--------+--------+--------+
|        |        |        |        |        |        |   %ah  |   %al  |
+--------+--------+--------+--------+--------+--------+--------+--------+
|        |        |        |        |        |        |       %ax       |
+--------+--------+--------+--------+--------+--------+-----------------+
|        |        |        |        |               %eax                |
+--------+--------+--------+--------+-----------------------------------+
|                                 %rax                                  |
+-----------------------------------------------------------------------+
      
      





, . 64, 64 .







"" . , 16 , 16 . , .. AT&T q



(quad-word — ) l



(long-word — ). movq



4 * 16 = 64 .







mov



, . AT&T.







16 x86-64. .







,



, .









"green_threads".







cargo init
      
      





- , , :







rustup override set nightly
      
      





main.rs



, llvm_asm!



:







#![feature(llvm_asm)]
      
      





, 48 , .







const SSIZE: isize = 48;
      
      





, . , , , , .







#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rsp: u64,
}
      
      





, "callee saved" , . ABI x86-64. , .







, - , #[repr(C)]



. ABI, , rsp



8 . ABI, .







fn hello() -> ! {
    println!("I LOVE WAKING UP ON A NEW STACK!");

    loop {}
}
      
      





, , .







, :







unsafe fn gt_switch(new: *const ThreadContext) {
    llvm_asm!("
        mov     0x00($0), %rsp
        ret
    "
    :
    : "r"(new)
    :
    : "alignstack" //    ,    
    );
}
      
      





. , . , rsp



( new.rsp



, , ). ?







ret



, . %rsp



, , , . ret



, .







, , .









, . . , :







unsafe



— , , . , .







gt_switch(new: *const ThreadContext)



— ThreadContext



, .







llvm_asm!("



— . , - AT&T (-).







, — — mov 0x00($0), %rsp



. , , 0x00 ( ; ) $0 , rsp



. rsp



. , , .







$0



. . , 0, 1, 2 .., output



input



. , $0



.







$



, , , ( ), (, , x86 x86-64).







ret



— , , , . , .







output
:
      
      





. , . output



, , .







input
: "r"(new)
      
      





— input



. "r"



— , (constraint



). , (, - ). "r"



, . — , , , .







clobber list
:
      
      





— clobber list



— , , , , . , , , , . .. , .







options
: "alignstack"
      
      





— . : alignstack



, volatile



intel



. , . Windows alignstack



.









fn main() {
    let mut ctx = ThreadContext::default();
    let mut stack = vec![0_u8; SSIZE as usize];

    unsafe {
        let stack_bottom = stack.as_mut_ptr().offset(SSIZE);
        let sb_aligned = (stack_bottom as usize & !15) as *mut u8;
        std::ptr::write(sb_aligned.offset(-16) as *mut u64, hello as u64);
        ctx.rsp = sb_aligned.offset(-16) as u64;
        gt_switch(&mut ctx);
    }
}
      
      





. hello



( ), u64



, 64 , .







, — ( ). 48 0 47, 32 16 / .


|0          1           2          3           4       |4  5
|0123456789 012345|6789 0123456789 01|23456789 01234567|89 0123456789
|                 |                  |XXXXXXXX         |
|                 |                  |                 stack bottom
|0th byte         |16th byte         |32nd byte
      
      





, 16 (, 16 ?)







let sb_aligned = (stack_bottom as usize & !15) as *mut u8;



? Vec<u8>



, , 16 . , 16 . , .

, u64



u8



. u64



32-39, 8 . u64



- 32, , .







rsp



(Stack Pointer — ) 32 . u64



-, , .







cargo run



, :







dau@dau-work-pc:~/Projects/rust-programming-book/green_threads/green_threads$ cargo run
   Compiling green_threads v0.1.0 (/home/dau/Projects/rust-programming-book/green_threads/green_threads)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/green_threads`
I LOVE WAKING UP ON A NEW STACK!
      
      





? hello



, . : . .









. . "" " " — .







, . (push/pop) , . .







— , , Rust Programming Language.









. 64 8 . , u8



, , , 000



, 0008



0016



.













, .







stack pointer



, 16 , , , , 16 ( — ). , , , 0008



(, ).







( gt_switch



), .







: — hello



, , .







print!(
    "hello func address: 0x{addr:016X} ({addr})\n\n",
    addr = hello as usize
);

for i in (0..SSIZE).rev() {
    print!(
        "mem: {}, value: 0x{:02X}\n{}",
        stack.as_ptr().offset(i as isize) as usize,
        *stack.as_ptr().offset(i as isize),
        if i % 8 == 0 { "\n" } else { "" }
    );
}
      
      





:







hello func address: 0x0000560CD80B50B0 (94613164216496)

mem: 94613168839439, value: 0x00
mem: 94613168839438, value: 0x00
mem: 94613168839437, value: 0x00
mem: 94613168839436, value: 0x00
mem: 94613168839435, value: 0x00
mem: 94613168839434, value: 0x00
mem: 94613168839433, value: 0x00
mem: 94613168839432, value: 0x00

mem: 94613168839431, value: 0x00
mem: 94613168839430, value: 0x00
mem: 94613168839429, value: 0x56
mem: 94613168839428, value: 0x0C
mem: 94613168839427, value: 0xD8
mem: 94613168839426, value: 0x0B
mem: 94613168839425, value: 0x50
mem: 94613168839424, value: 0xB0

mem: 94613168839423, value: 0x00
mem: 94613168839422, value: 0x00
mem: 94613168839421, value: 0x00
mem: 94613168839420, value: 0x00
mem: 94613168839419, value: 0x00
mem: 94613168839418, value: 0x00
mem: 94613168839417, value: 0x00
mem: 94613168839416, value: 0x00

mem: 94613168839415, value: 0x00
mem: 94613168839414, value: 0x00
mem: 94613168839413, value: 0x00
mem: 94613168839412, value: 0x00
mem: 94613168839411, value: 0x00
mem: 94613168839410, value: 0x00
mem: 94613168839409, value: 0x00
mem: 94613168839408, value: 0x00

mem: 94613168839407, value: 0x00
mem: 94613168839406, value: 0x00
mem: 94613168839405, value: 0x00
mem: 94613168839404, value: 0x00
mem: 94613168839403, value: 0x00
mem: 94613168839402, value: 0x00
mem: 94613168839401, value: 0x00
mem: 94613168839400, value: 0x00

mem: 94613168839399, value: 0x00
mem: 94613168839398, value: 0x00
mem: 94613168839397, value: 0x00
mem: 94613168839396, value: 0x00
mem: 94613168839395, value: 0x00
mem: 94613168839394, value: 0x00
mem: 94613168839393, value: 0x00
mem: 94613168839392, value: 0x00

I LOVE WAKING UP ON A NEW STACK!
      
      





u64



, , ( — .).







, , , , 94613168839392



94613168839439



.







94613168839424



94613168839431



. — stack pointer



, , %rsp%



. , . ( — !!!)







0xB0, 0x50, 0x0B, 0xD8, 0x0C, 0x56, 0x00, 0x00



— ( ) hello()



, u8



.







, , 48 , , , .









, 8 , . , , , . " " (stack overflow), .







, , . 8 , , , -. , , , , , .









(growable — ) . , . , , , , .







Go. 8 , , . , , , . , GO ( ), .







, : ( Vec<u8>



) . , . , , .

, , - , . - , push()



( — .) . , , .

, , . , .









Windows x86-64 , x86-64 psABI. Windows , , , , , .







psABI :













, %rsp



— . , , base pointer, 16. 8 , , , . , - .







, stack_ptr + SSIZE - 16



, . - SSIZE



— .







. , ( — ) 8 . , rsp



16 , ABI.







- , stack_ptr + SSIZE - 16



. :







  • , stack_ptr + SSIZE



    ( 16 ), .. , .
  • , stack_ptr + SSIZE - 8



    , , 16 .


stack_ptr + SSIZE - 16



. 8 -16, -15, -14, ..., -9



(, , bottom of stack, .. ( — , , — , )).









, , , .







, , , .







, , 1024 , , . .









, , . : BEFORE.txt



( ) AFTER.txt



( ). , .







- , — .


#![feature(llvm_asm)]
#![feature(naked_functions)]

use std::io::Write;

const SSIZE: isize = 1024;
static mut S_PTR: *const u8 = 0 as *const u8;

#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rsp: u64,
    r15: u64,
    r14: u64,
    r13: u64,
    r12: u64,
    rbx: u64,
    rbp: u64,
}

fn print_stack(filename: &str) {
    let mut f = std::fs::File::create(filename).unwrap();
    unsafe {
        for i in (0..SSIZE).rev() {
            writeln!(
                f,
                "mem: {}, val: {}",
                S_PTR.offset(i as isize) as usize,
                *S_PTR.offset( i as isize)
            )
            .expect("Error writing to file.");
        }
    }
}

fn hello() {
    println!("I LOVE WAKING UP ON A NEW STACK!");
    print_stack("AFTER.txt");

    loop {}
}

unsafe fn gt_switch(new: *const ThreadContext) {
    llvm_asm!("
        mov     0x00($0), %rsp
        ret
        "
        :
        : "r"(new)
        :
        : "alignstack"
    );
}

fn main() {

    let mut ctx = ThreadContext::default();
    let mut stack = vec![0_u8; SSIZE as usize];
    let stack_ptr = stack.as_mut_ptr();

    unsafe {
        S_PTR = stack_ptr;
        std::ptr::write(stack_ptr.offset(SSIZE - 16) as *mut u64, hello as u64);
        print_stack("BEFORE.txt");
        ctx.rsp = stack_ptr.offset(SSIZE - 16) as u64;
        gt_switch(&mut ctx);
    }
}
      
      







, , , , , , " " (best practicies) . , , , - , , , PR .









, — main.rs



, , :







#![feature(llvm_asm)]
#![feature(naked_functions)]

use std::ptr;

const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
const MAX_THREADS: usize = 4;

static mut RUNTIME: usize = 0;
      
      





: asm



naked_functions



, .







naked_functions





, "" "", - , . , , . #[naked]



. .







, naked_functions



RFC #1201.



Naked- . , , , .. naked- . ret



naked- ( , ABI), . .

DEFAULT_STACK_SIZE



2 , , . (MAX_THREADS



) 4, .. .







— RUNTIME



— , (, , , , ).







- :







pub struct Runtime {
    threads: Vec<Thread>,
    current: usize,
}

#[derive(Debug, Eq, PartialEq)]
enum State {
    Available,
    Running,
    Ready,
}

struct Thread {
    id: usize,
    stack: Vec<u8>,
    ctx: ThreadContext,
    state: State,
}

#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rps: u64,
    r15: u64,
    r14, u64,
    r13: u64,
    r12: u64,
    rbx: u64,
    rbp: u64,
}
      
      





Runtime



. , . Thread



current



, , .







Thread



. , , id



. stack



, . ctx



, , . state



— .







State



— , :







  • Available



    — , .
  • Running



    —
  • Ready



    — .


ThreadContext



, .







, " " , . x86-64 "callee saved".

:







impl Thread {
    fn new(id: usize) -> Self {
        Thread {
            id,
            stack: vec![0_u8; DEFAULT_STACK_SIZE],
            ctx: ThreadContext::default(),
            state: State::Available,
        }
    }
}
      
      





. Available



, , .







, . , .. . , , .







, , , . push()



, . , .



, Vec<T>



into_boxed_slice()



, Box<[T]>



— , . , .




impl Runtime



, .







impl Runtime {

    pub fn new() -> Self {
        let base_thread = Thread {
            id: 0,
            stack: vec![0_u8; DEFAULT_STACK_SIZE],
            ctx: ThreadContext::default(),
            state: State::Running,
        };

        let mut threads = vec![base_thread];
        let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
        threads.append(&mut available_threads);

        Runtime {
            threads,
            current: 0,
        }
    }

    // code of other methods is here
    // ...
}
      
      





Runtime



Running



. . , 0



, .







// ,       Runtime
//   ,      yield
//        .
//        
pub fn init(&self) {
    unsafe {
        let r_ptr: *const Runtime = self;
        RUNTIME = r_ptr as usize;
    }
}
      
      





. , , , yield



. , , , .







pub fn run(&mut self) -> ! {
    while self.t_yield() {};
    std::process::exit(0);
}
      
      





, . t_yield()



, false



, , , .







fn t_return(&mut self) {
    if self.current != 0 {
        std.threads[self.current].state = State::Available;
        self.t_yield();
    }
}
      
      





, . t_return



, .. return



. , — , , .







, . yield



. (spawned) , , , .. guard



( ). , t_return



— guard



.







Available



, , (task), t_yield



, .







yield



:







fn t_yield(&mut self) -> bool {
    let mut post = self.current;
    while self.threads[pos].state != State::Ready {
        pos += 1;
        if pos == self.threads.len() {
            pos = 0;
        }
        if pos == self.current {
            return false;
        }
    }

    if self.threads[self.current].state != State::Available {
        self.threads[self.current].state = State::Ready;
    }

    self.threads[pos].state = State::Running;
    let old_pos = self.current;
    self.current = pos;

    unsafe {
        let old: *mut ThreadContext = &mut self.threads[old_pos].ctx;
        let new: *const ThreadContext = &self.threads[pos].ctx;

        llvm_asm!(
            "
            mov $0, %rdi
            mov $1, %rsi
            "
            :
            : "r"(old), "r"(new)
            :
            :
        );
        switch();
    }

    self.threads.len() > 0
}
      
      





. t_yield



, .. yield



(. — , ).







, - Ready



, , , . , .







Ready



, . , (round-robin). , .







. , ( Ready



) -, , ?



. , , (poll) . , IsReady



, , Pending



, - . Ready



, . ? , , , .

, , Running



Ready



.







switch



, . , , .







naked





naked . , . , , , . , #[naked]



, . "" "" ThreadContext



, . Linux %rdi



, — %rsi



.

self.threads.len() > 0



— , . Windows, Linux, , . std::hint::black_box



, , , , . , , . , .







spawn()



:







pub fn spawn(&mut self, f: fn()) {
    let available = self
        .threads
        .iter_mut()
        .find(|t| t.state == State::Available)
        .expect("no available thread.");

    let size == available.stack.len();

    unsafe {
        let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
        let s_ptr = (s_ptr as usize & !15) as *mut u8;
        std::ptr::write(s_ptr.offset(-16) as *mut u64, guard as u64);
        std::ptr::write(s_ptr.offset(-24) as *mut u64, skip as u64);
        std::ptr::write(s_ptr.offset(-32) as *mut u64, f as u64);
        available.ctx.rsp = s_ptr.offset(-32) as u64;
    }
    available.state = State::Ready;
}

//       `impl Runtime`
      
      





, t_yield



, spawn



.







, , , , , psABI.







, , (.. Available



). , , , , . .







, ( u8



) .







unsafe-. , 16 . guard



, , . skip



, , f



, guard



16 . , , f



.







, . , f



, . base pointer . skip



guard



. guard



16 , ABI.

, , rsp



( ) . .







, Ready



, , , . , "" .







. , , . , , .







guard



, skip



switch





fn guard() {
    unsafe {
        let rt_ptr = RUNTIME as *mut Runtime;
        (*rt_ptr).t_return();
    };
}
      
      





, , , , , , t_return()



. , , , , t_return



, . Available



( ), t_yield



, .







#[naked]
fn skip() { }
      
      





skip



. #[naked]



, ret



, , . , guard



.







pub fn yield_thread() {
    unsafe {
        let rt_ptr = RUNTIME as *mut Runtime;
        (*rt_ptr).t_yield();
    };
}
      
      





, t_yield



. , , (), . , , .







, . , .







#[naked]
#[inline(never)]
unsafe fn switch() {
    llvm_asm!("
        mov     %rsp, 0x00(%rdi)
        mov     %r15, 0x08(%rdi)
        mov     %r14, 0x10(%rdi)
        mov     %r13, 0x18(%rdi)
        mov     %r12, 0x20(%rdi)
        mov     %rbx, 0x28(%rdi)
        mov     %rbp, 0x30(%rdi)

        mov     0x00(%rsi), %rsp
        mov     0x08(%rsi), %r15
        mov     0x10(%rsi), %r14
        mov     0x18(%rsi), %r13
        mov     0x20(%rsi), %r12
        mov     0x28(%rsi), %rbx
        mov     0x30(%rsi), %rbp
        "
    );
}
      
      





. , , , , , "" .







, , .







#[naked]



. , , , . , .







. #[inline(never)]



, . , , --release



.







main





fn main() {
    let mut runtime = Runtime::new();

    runtime.init();

    runtime.spawn(|| {
        println!("THREAD 1 STARTING");
        let id = 1;
        for i in 1..=10 {
            println!("thread: {} counter: {}", id, i);
            yield_thread();
        }
        println!("THREAD 1 FINISHED");
    });

    runtime.spawn(|| {
        println!("THREAD 2 STARTING");
        let id = 2;
        for i in 1..=15 {
            println!("thread: {} counter: {}", id i);
            yield_thread();
        }
        println!("THREAD 2 FINISHED");
    });

    runtime.run();
}
      
      





, , 0 9 15, . cargo run



, :







THREAD 1 STARTING
thread: 1 counter: 1
THREAD 2 STARTING
thread: 2 counter: 1
thread: 1 counter: 2
thread: 2 counter: 2
thread: 1 counter: 3
thread: 2 counter: 3
thread: 1 counter: 4
thread: 2 counter: 4
thread: 1 counter: 5
thread: 2 counter: 5
thread: 1 counter: 6
thread: 2 counter: 6
thread: 1 counter: 7
thread: 2 counter: 7
thread: 1 counter: 8
thread: 2 counter: 8
thread: 1 counter: 9
thread: 2 counter: 9
thread: 1 counter: 10
thread: 2 counter: 10
THREAD 1 FINISHED
thread: 2 counter: 11
thread: 2 counter: 12
thread: 2 counter: 13
thread: 2 counter: 14
thread: 2 counter: 15
THREAD 2 FINISHED
      
      





. , . 1 , 2 .









, . , , . !








All Articles