linuxのスレッド生成処理に関するメモ
linuxのスレッド生成処理に関するメモ。
sys_cloneから呼び出されるdo_forkあたりについて。
1. スレッドの生成
以下のプログラムを実行すると、スレッドを一つ生成する。
#include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/syscall.h> #define THREADMAX 1 pid_t gettid(void) { return syscall(SYS_gettid); } void *thread_function(void* arg) { int *n = (int*)arg; pid_t pid = gettid(); printf("[%d] %d\n", pid, *n); return arg; } int main(int argc, char **argv) { int i; pthread_t thread[THREADMAX]; int data[THREADMAX]; pid_t pid = gettid(); printf("[%d] %p\n", pid, &argc); for(i = 0; i < THREADMAX; ++i) { data[i] = i; pthread_create(&thread[i], NULL, thread_function, &data[i]); } for(i = 0; i < THREADMAX; ++i) { pthread_join(thread[i], NULL); } return 0; }
straceで上記プログラムから呼び出すシステムコールを監視すると、pthread_create()が実際にはclone()を呼び出している事がわかる。
$strace ./a.out ... clone(child_stack=0x7f0c9cf99fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f0c9cf9a9d0, tls=0x7f0c9cf9a700, child_tidptr=0x7f0c9cf9a9d0) = 9067 ...
clone()はsys_clone()を呼び出して子プロセスを作成する。
この後、詳解Linuxカーネル(P125)によると、子プロセスのスタックを書き換えて、引数を積み、
子プロセスの戻り値アドレスをpthread_createで指定した関数ポインタに変更する。
2. do_fork呼び出し
sys_clone()は以下のように実装されており(linux 3.5.0)、さらにdo_fork()を呼び出す。
long sys_clone(unsigned long clone_flags, unsigned long newsp, void __user *parent_tid, void __user *child_tid) { long ret; if (!newsp) newsp = UPT_SP(¤t->thread.regs.regs); current->thread.forking = 1; ret = do_fork(clone_flags, newsp, ¤t->thread.regs, 0, parent_tid, child_tid); current->thread.forking = 0; return ret; }
以下のsys_clone.stpを用いて、上記スレッド作成プログラムでdo_fork呼び出し時の引数を確認する。
probe kernel.function("do_fork@kernel/fork.c") { printf("do_fork %s\n", $$vars); } probe kernel.function("sys_clone*") { printf("sys_clone %s\n", $$vars); } probe kernel.function("do_fork@kernel/fork.c").return { printf("<- do_fork %d %d\n", kernel_int($parent_tidptr), kernel_int($child_tidptr)); } probe kernel.function("sys_clone*").return { printf("<- sys_clone %d %d\n", kernel_int($parent_tid), kernel_int($child_tid)); }
$ sudo stap sys_clone.stp -c ./a.out [9602] 0x7fff675580ec [9605] 0 sys_clone clone_flags=0x3d0f00 newsp=0x7ff643872fb0 parent_tid=0x7ff6438739d0 child_tid=0x7ff6438739d0 regs=0xffff88008141df58 do_fork clone_flags=0x3d0f00 stack_start=0x7ff643872fb0 regs=0xffff88008141df58 stack_size=0x0 parent_tidptr=0x7ff6438739d0 child_tidptr=0x7ff6438739d0 p=0x3d28620000 trace=? nr=? <- do_fork 9605 9605 <- sys_clone 9605 9605
3. do_forkの処理
do_forkの処理は以下のような処理を行う。
3.1 do_forkのインタフェースと、変数の宣言
long do_fork(unsigned long clone_flags, /* clone呼び出し時フラグ */ unsigned long stack_start, /* 子プロセスに割り当てるスタックの開始位置 */ struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr;
3.2 do_forkのフラグ有効性確認
/* * Do some preliminary argument and permissions checking before we * actually start allocating stuff */ if (clone_flags & CLONE_NEWUSER) { if (clone_flags & CLONE_THREAD) return -EINVAL; /* hopefully this check will go away when userns support is * complete */ if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) || !capable(CAP_SETGID)) return -EPERM; }
3.3 トレース状態の確認
ユーザーモードでかつ、非トレースでなければ、trace変数にトレースの状態を設定する。
このtrace変数は、後で呼ばれるcopy_process()で使う。
/* * Determine whether and which event to report to ptracer. When * called from kernel_thread or CLONE_UNTRACED is explicitly * requested, no event is reported; otherwise, report if the event * for the type of forking is enabled. */ if (likely(user_mode(regs)) && !(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if ((clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; }
3.4 プロセスディスクリプタをコピーする
プロセスディスクリプタのコピーを作成する。
戻り値は新しくコピーされたtask_structオブジェクトへのポインタ。
p = copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL, trace);
3.5 新しいタスクを起床する
新しいPIDをユーザー空間にコピーして、新しいプロセスを起床する。
/* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ if (!IS_ERR(p)) { struct completion vfork; trace_sched_process_fork(current, p); nr = task_pid_vnr(p); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ if (unlikely(trace)) ptrace_event(trace, nr); if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); } } else { nr = PTR_ERR(p); } return nr; }