Rustの200ラインの軽量ストリームの説明
軽量スレッド(コルーチン、コルーチン、グリーンスレッド)は、最新のプログラミング言語では非常に強力なメカニズムです。この記事では、Carl Fredrik Samsonが、Rastで軽量ストリームのランタイムを実装しようとしました。その過程で、それらが「内部」でどのように機能するかを説明しました。
また、この記事はそれほど新鮮ではないため、Rustコンパイラの最新のナイトリーバージョンで例を機能させるには、この記事のコードリポジトリにある変更が必要になる可能性が高いことにも注意してください。
彼はほとんど自分で翻訳した。すべてのコメントについて書いてください-私はすぐにそれを修正します。文章をきちんと翻訳しようとしたのですが、読みやすく理解しやすいように書き直したところもありました。
グリーンスレッド
グリーンスレッドは、プログラミングの一般的な問題を解決します。コードがプロセッサをブロックしてリソースを浪費することは望ましくありません。これは、マルチタスクを使用することで解決されます。マルチタスクを使用すると、1つのコードの実行を一時停止し、実行のために別のコードを起動し、「コンテキスト」を切り替えることができます。
, — . — . , .
:
- ()
, . — "-" ( - ). , , 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 .
, . , , . !