自前でコンテキストスイッチしてスレッドの切り替えをしてみる。

ネットでスレッドの実装をするという課題を見つけたのですが、x86版のコードだったのでx86_64で動くようにしてみました。

課題のコードから変えたのはスレッドを作る処理と、コンテキストスイッチの切り替え部分です。
それ以外の部分は、基本的には課題で要求されたとおりになっていると思います・・・

課題のコードはスタックポインタとかの設定はアセンブラコードでやってたんですが、cにしました。

int ThreadCreate(ThreadProc proc, unsigned long arg)
{
	Thread *child;
	unsigned long addr = (unsigned long) ThreadStart;
	unsigned long stack_start = 0;

	child = AllocateThread();
	
	child->stack_top = malloc(STACK_SIZE);
	memset(child->stack_top, 0, STACK_SIZE);

	stack_start = (unsigned long) child->stack_top + STACK_SIZE - sizeof(addr);
	memcpy((char *) stack_start, &addr, sizeof(addr));

	child->context[0] = stack_start;
	child->context[1] = stack_start;
	child->context[2] = (unsigned long) proc;
	child->context[3] = arg;

	child->status = RUNNING;

	LinkThread(child);

	return child->thread_id;

}

スタック用のメモリ領域を確保して、そのアドレスの一番最後にスレッドを開始する関数のアドレスを入れてます。
アドレスの先頭ではなくて、後ろに入れてるのは言わずもがなですが、スタックがメモリアドレスの下位から始まるので最後に入れてます。
あとは、コンテキスト切り替え用の$rsp、$rbp、呼びたい関数のアドレス、その関数の引数をセットしてます。

そして、実際にスレッドを切り替えてる部分はこんな感じです。

// void _ContextSwitch(void* old_context, void* new_context);
.globl ENTRY(_ContextSwitch)
ENTRY(_ContextSwitch):
	movq	%rdi, %rax // old
	movq	%rsp, 0(%rax)
	movq	%rbp, 8(%rax)
	movq	%rdi, 16(%rax)
	movq	%rsi, 24(%rax)
	movq	%rsi, %rax // new
	movq	0(%rax), %rsp
	movq	8(%rax), %rbp
	movq	16(%rax), %rdi // arg1
	movq	24(%rax), %rsi // arg2
	ret

x86_64は関数への引数の渡しかたはレジスタで渡しで、レジスタが足りなかったらスタックに積むというふうになってます。
この関数の引数は全部レジスタで渡されます。old_contextは$rdiに、new_contextは$rsiです。
引数はunsigned long型の要素数5個の配列です。child->contextってやってたやつですが、最後の要素は特につかってません。
やってることは単純で、最初に現在の$rsp、$rbpなどをold_contextに保存して、
次に動かすスレッドのスタックなどをレジスタにコピーしてるだけです。
ここで、$rsp、$rbpがnew_contextで指定した値になることで、retでここを抜けるとスレッドが切り替わって、new_contextで指定したスレッドの実行が始まります。

実際に動かした結果はこんな感じになりました。idが-1ってなっているのはスレッドの終了後に使っているダミーのスレッドです。

[masami@moonlight:~/experiment/thread]% ./test1 
create a new thread (i=1) [id=1]
create a new thread (i=2) [id=2]
switch id 0 to 2
old rsp is 0x0 : old rbp is 0x0 : arg1 is 0x0 : arg2 is 0x0
new rsp is 0x15dc158 : new rbp is 0x15dc158 : arg1 is 0x4006f4 : arg2 is 0x2
thread (i=2) finished.
switch id -1 to 1
old rsp is 0x0 : old rbp is 0x0 : arg1 is 0x0 : arg2 is 0x0
new rsp is 0x15da0f8 : new rbp is 0x15da0f8 : arg1 is 0x4006f4 : arg2 is 0x1
thread (i=1) finished.
switch id -1 to 0
old rsp is 0x15dc0d0 : old rbp is 0x15dc108 : arg1 is 0x601350 : arg2 is 0x15d80c0
new rsp is 0x7fffd6f25078 : new rbp is 0x7fffd6f250b0 : arg1 is 0x15d8020 : arg2 is 0x15da120
main thread finished.