Expliquer les flux légers de 200 lignes dans la rouille
Les threads légers (coroutines, coroutines, threads verts) sont un mécanisme très puissant dans les langages de programmation modernes. Dans cet article, Carl Fredrik Samson a essayé d'implémenter un runtime pour les flux légers sur Rast, en expliquant en cours de route comment ils fonctionnent «sous le capot».
Il convient également de noter que l'article n'est pas très récent.Par conséquent, pour que les exemples fonctionnent dans la version nocturne moderne du compilateur Rust, vous aurez très probablement besoin de modifications qui peuvent être trouvées dans le référentiel de code de cet article.
Il a traduit pour lui-même pour la plupart. Écrivez tous les commentaires - je les corrigerai rapidement. J'ai essayé de traduire le texte de près, mais à certains endroits, je l'ai reformulé pour le rendre plus facile à lire et à comprendre.
Fils verts
Les threads verts résolvent un problème courant dans la programmation: vous ne voulez pas que votre code bloque le processeur, gaspillant des ressources. Ceci est résolu en utilisant le multitâche, qui vous permet de suspendre l'exécution d'un morceau de code, d'en lancer un autre pour l'exécution, de basculer entre les «contextes».
, — . — . , .
:
- ()
, . — "-" ( - ). , , 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 .
, . , , . !