参照URL
http://mowa-net.jp/~amedama/cgi-bin/wiki/wiki.cgi?action=SOURCE&page=Kernel%A5%E1%A5%E2+%A5%D7%A5%ED%A5%BB%A5%B9%CA%D4
※既に消えていました。。。その元情報も紛失してしまいました。。。
参考
- 『詳解LINUXカーネル』
- 『Linux Kernel Development』
- Linux Kernel 2.6.8
Linux Core Kernel Commentary 欲しい。
Appendix について
メインの流れから外れた部分、なんとなく独立させたい部分を、 Appendix|Kernelメモ プロセス編 Appendixという形で 別のページに分けた。
概要
user 空間で「プロセス」と呼ばれるものを理解する上で、 Kernel 空間の task_struct の存在を知ることは必須であると思われる。
「プロセス」たる task_struct がどう生成され、どう破棄されるかについて、 その実装を Kernel のソースコードを元に調べる。
着目したのは以下の点である。
- Linux における「プロセス」とはどう定義されるか。
- 「プロセス」が保有するべきデータがどう保有されるか。
- 「スレッド」とは、実装上 何が違うか。
- 実際に稼働している「プロセス」はどう管理されるか。
- 新しい「プロセス」はどう生成されるか。
- 最初の「プロセス」とは何か。
- 「プロセス」はどう終了するか
- 確保されたデータ領域はどう回収されるか。
まず、上記の「プロセス」や「スレッド」が、 実は単位のデータ構造である task_struct 構造体に帰着されることを 述べる。
そうすることで、上記のリストの「プロセス」や「スレッド」は 全て task と呼ばれる Kernel 内の構造に置き換えることが可能だ。
それ以降は、その task がどう生成され、 どう管理され、どう破棄されるか、 を個別に調べていく。 可能な限り詳細にコードを追おうと努力するも、 あまりに巨大な体系故に、うまくいかなかった部分もある。 その場合には、私の(なるべく明示した上で)推測を述べることにする。
一般的な意味での「プロセス」と「スレッド」
「プロセス」
「プロセス」は、プログラムの実行単位であって、プログラム自体ではない。
以下のような情報全てをひっくるめてプロセスと呼ぶ。
- プログラム
- ファイルディスプリプタ
- (HDD上の mount されている)名前空間
- (仮想)メモリ空間
- シグナルハンドラ
- ptrace のトレースの仕方
- グループID(スレッドグループID)
- その他色々
一方、例えば次のようなものは、プロセスではない
- 実行コード本体
- ソースコード
- ユーザ
- (っ´▽`)っ
「スレッド」
スレッドと言う言葉に、厳密には二つの意味があると思う。
OSの授業で出てくるような意味、 ライトウェイトプロセスの如きスレッドと、 「1対1対応のモデル」などと言われる時の ハードウェア的なスレッドである。
ライトウェイトプロセスとしての「スレッド」
一つのプロセスの中に、複数の実行系列があること、 それがこの意味でのスレッドの本質だと思われる。
プロセスの中のスレッドは、メモリ領域や シグナルハンドラと言った一部のリソースを共有する。
ハードウェア的な「スレッド」
ハードウェア上で実際に走っている その処理する機構そのものを言うように思えるが、 実際、こちら側の定義を、私は良く知らない。
正直あんまり深入りしてボロは出したくないが、 Kernel ソースコード内ではこちらの意味でしか 取ることが出来ない thread_hogehoge という言葉が 非常に多く出てくる。
task
「プロセス」を実現する仕組みは、 Linux Kernel 空間内では task と呼ばれることが多い。
『Linux Kernel Development』には、こうある(p16)。
In this book, I will use the terms interchangeable, althohgh I will try to denote the kernel representation of a running program as a task and the user-space representation as a process.
ユーザ空間で「プロセス」と見えるもの、 「スレッド」と見えるものの正体が task である、 というのが現在の私の認識である。
task というのは、POSIX における「プロセス」と「スレッド」とは 関係なく存在する、 Linux Kernel 固有の概念と見るべきだろう。
# なんて主張今まで見たことないよ 🙁
Linux における task は、「プロセス」と「スレッド」 を実現するためにカーネルが提供している構造であって、 「プロセス」そのものではない。
なぜそう言えるのか。それを知るために、 task を生成する時に立てられる、 その挙動を変える種々のフラグを考えることにする。
task の性質を決めるフラグ CLONE_hogehoge
task は clone(2) によって生成される。 clone(2) にフラグを与えることで、 task の性質を細かく制御することが出来る (clone の実装は後述する)。
各フラグの意味は大体以下のようになる。
使用可能なフラグ | 大体の意味 |
---|---|
CLONE_PARENT | 親が同じになる |
CLONE_FS | 親とファイルシステムを共有する |
CLONE_FILES | 親とファイルディスクリプタを共有する |
CLONE_NEWFS | 親とファイルディスクリプタを共有しない |
CLONE_SIGHAND | 親とシグナルハンドラを共有する |
CLONE_PTRACE | 親が ptrace されていたら自分もされる。 |
CLONE_VFORK | vfork(2) の変態挙動を実現する。 |
CLONE_VM | 親とメモリ空間を共有する。 |
CLONE_THREAD | 親(?)と同じスレッドグループに置かれる(tgidが同じ)。 |
CLONE_SETTLS | ?(2.4 にはない) |
CLONE_PARENT_SETTID | ?(2.4 にはない) |
CLONE_CHILD_SET | ?(2.4 にはない) |
CLONE_CHILD_CLEARTID | ?(2.4 にはない) |
CLONE_STOPPED | TASK_STOPPED の状態で作られる(マニュアルにない) |
具体的にはどういうことか。
例えば、 CLONE_SIGHAND フラグについて考える。
このフラグが立つと、Kernel はそのプロセスを生成する際、 シグナルハンドラ用のデータを新たに作る代わりに、 親と同じデータを使うように仕向ける。
シグナルハンドラ用のデータは、 task_struct 構造体の sighand という変数に収められる
(-) linux/kernel/fork.c
793 static inline int copy_sighand(unsigned long clone_flags, struct task_struct - tsk)
794 {
795 struct sighand_struct -sig;
796
797 if (clone_flags & (CLONE_SIGHAND | CLONE_THREAD)) {
798 atomic_inc(¤t->sighand->count);
799 return 0;
800 }
801 sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
802 tsk->sighand = sig;
803 if (*sig)
804 return -ENOMEM;
805 spin_lock_init(&sig->siglock);
806 atomic_set(&sig->count, 1);
807 memcpy(sig->action, current->sighand->action, sizeof(sig->action));
808 return 0;
809 }
CLONE_THREAD は「ぼくはスレッドですよ」フラグだが、 まぁ今回は置いておく。
少なくとも、 CLONE_SIGHAND フラグが立っていれば、current->sighand->count
というこのシグナルハンドラの 参照カウントを一つ増やしてこの関数を終了している、 というのが分かる。
一方、 CLONE_SIGHAND が立っていないときには、 kmem_cache_alloc() で、新しいシグナルハンドラを作る領域を確保し、 その後の行でいくつかの初期化をしている。
上の CLONE_hogehoge の多くは、 そのデータを差すポインタの先を
- 親と全く同じものにするか
- 確保した新しいデータにするか
のどちらにするかを指定するフラグであると見て良い。
task と「プロセス」、「スレッド」の関係
task はフラグによる細かい制御によって、 POSIXにおける「プロセス」と「スレッド」を実現する、 といった感じに近い。
プロセスは親と何も共有していない (全ての情報をコピーした) task である。 プロセスを作る fork(2) は Kernel 内部で以下の関数となる。
(-) linux/arch/i386/kernel/process.c
579 asmlinkage int sys_fork(struct pt_regs regs)
580 {
581 return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
582 }
do_fork() については後述。
#しかしなんで do_fork() なんかな。do_clone() じゃないんかな。
「スレッド」は、上記の一部のフラグを立てることで POSIX における「スレッド」の動作を実現した task である と考えられるが、 「スレッド」生成の例となりそうな、 pthread_create のソースが訳分からないので撤退。
thread_union
2.4系列で言う task_union (詳しくは『詳解LINUXカーネル』を参照のこと) は、thread_union に変わった。
ここで言うスレッドは、ライトウェイトプロセスではなく、 ハードウェア的なスレッドであると思われる。
(-) linux/include/linux/sched.h
710 union thread_union {
711 struct thread_info thread_info;
712 unsigned long stack[THREAD_SIZE/sizeof(long)];
713 };
thread_union の定義から分かる通り、 thread_info は thread_union の先頭にある。
THREAD_SIZE は、以下のように定義される。
(-) linux/include/asm-i386/thread_info.h
55 #ifdef CONFIG_4KSTACKS
56 #define THREAD_SIZE (4096)
57 #else
58 #define THREAD_SIZE (8192)
59 #endif
CONFIG_4KSTACKS はカーネルの configuration で設定され、 以下のような説明がある。
CONFIG_4KSTACKS:
If you say Y here the kernel will use a 4Kb stacksize for the
kernel stack attached to each process/thread. This facilitates
running more threads on a system and also reduces the pressure
on the VM subsystem for higher order allocations. This option
will also use IRQ stacks to compensate for the reduced stackspace.
要するに、thread_info と stack 本体でまとめて 4KB か 8KB だということ。
thread_union の中途をスタックポインタ esp が差す。 このスタックポインタにより、 各プロセスはプロセスカーネルスタックを得る。
thread_info は以下のように定義される。 (#ifdef __ASSEMBLY__
で囲われているが、本質的には同じである)。
(-) linux/include/asm-i386/thread_info.h
27 struct thread_info {
28 struct task_struct -task; /- main task structure -/
29 struct exec_domain -exec_domain; /- execution domain -/
30 unsigned long flags; /- low level flags -/
31 unsigned long status; /- thread-synchronous flags -/
32 __u32 cpu; /- current CPU -/
33 __s32 preempt_count; /- 0 => preemptable, <0 => BUG -/
34
35
36 mm_segment_t addr_limit; /- thread address space:
37 0-0xBFFFFFFF for user-thead <- ?
38 0-0xFFFFFFFF for kernel-thread
39 -/
40 struct restart_block restart_block;
41
42 unsigned long previous_esp; /- ESP of the previous stack in case
43 of nested (IRQ) stacks
44 -/
45 __u8 supervisor_stack[0];
46 };
task は この thread_info に対応する task_struct オブジェクト へのポインタである。 その他のメンバは不明。 名前から見て、かなりハードウェアに近いものが多いようだが。
current
current は、現在、そのCPU上で実行中の task の task_struct オブジェクトへのポインタを返す。
あるアーキテクチャでは普通にグローバル変数だったりする。
例えば ppc64 では
(-) linux/include/asm-ppc64/current.h
13 #define get_current() (get_paca()->__current)
14 #define current get_current()
(-) linux/include/asm-ppc64/paca.h
25 register struct paca_struct -local_paca asm("r13");
26 #define get_paca() local_paca
と言う風に、ある特定のレジスタの値が入るように 仕組まれている。
一方、自由に使えるレジスタが少ない(らしい?)x86系では、 (thread_union から分かるように) カーネルスタックの末尾(メモリアドレスで言えば先頭)に 実行中プロセスの thread_info を置いているという性質を用いて、 スタックポインタの下位ビットをマスクして thread_info の ポインタを得て、 thread_info から task_struct へのポインタを得る。
実装的には以下の通り。
(-) linux/include/asm-i386/current.h
8 static inline struct task_struct - get_current(void)
9 {
10 return current_thread_info()->task;
11 }
12
13 #define current get_current()
(-) linux/include/asm-i386/thread_info.h
86 /- how to get the thread information struct from C -/
87 static inline struct thread_info -current_thread_info(void)
88 {
89 struct thread_info -ti;
90 __asm__("andl %%esp,%0; ":"=r" (ti) : "" (~(THREAD_SIZE - 1)));
91 return ti;
92 }
(-) linux/include/asm-i386/thread_info.h
55 #ifdef CONFIG_4KSTACKS
56 #define THREAD_SIZE (4096)
57 #else
58 #define THREAD_SIZE (8192)
59 #endif
THREAD_SIZE は通常 8192(2.4 におけるカーネルスタックのサイズ)なので、 上の thread_info は次のアセンブラに落とされることになる (らしい。というのはアセンブラを私が良く知らないから……)。
movl $0xffffe000, %eax
andl %esp, %eax
もちろん、make menuconfig 等で CONFIG_4KSTACKS が チェックされていたとすれば、
movl $0xfffff000, %eax
andl %esp, %eax
となっているはずである(マスクされる範囲が変わる)。
esp レジスタにはプロセスカーネルスタックのスタックポインタが入っている。 上位ビットだけ見れば、thread_union オブジェクトの先頭、 すなわち 実行中のプロセスの thread_info オブジェクトのポインタが得られる。
thread_info は task_struct オブジェクトへのポインタを持つので、 最終的に実行中のプロセスの task_struct オブジェクトへの ポインタが得られる。
#下位ビットが全部 0 なら thread_info の先頭のポインタとなる (下位ビットが全部 0 になる仕組みそのものはどこで書かれてるんかね……)。
わざわざ thread_union, thread_info が介在する理由。
2.4 には thread_union, thread_info はなく、task_union があった。
(-) linux-2.4.28/include/linux/sched.h
520 union task_union {
521 struct task_struct task;
522 unsigned long stack[INIT_TASK_SIZE/sizeof(long)];
523 };
この場合、task_struct は task_union のあるメモリアドレスに 線形に取られるのみである。 だから、スタックポインタの値をマスクするだけで、 current を得ることが出来た。
(-) linux-2.4.28/include/asm-i386/current.h
6 static inline struct task_struct - get_current(void)
7 {
8 struct task_struct -current;
9 __asm__("andl %%esp,%0; ":"=r" (current) : "" (~8191UL));
10 return current;
11 }
# どうでもよいけど、スタックのサイズも 8192 で固定だったみたいだ。
一方 2.6 では、この場所に thread_info が来て、 thread_info からポインタを辿って、task_struct を得るという 周りくどい方法になった。
このようにすることで、task_struct オブジェクトを生成する時に、 動的にメモリを割り振る「メモリアロケータ」を経由させることが 可能とするためらしい。
task_union が入っていたり、thread_union が入っていたりするところは、 動的にメモリをアロケートする部分ではない。 ここに、task_struct の代わりとして thread_info を入れる。 そして、thread_info の要素として入っているポインタから task_struct を間接的に差す。 こうすることで、ポインタの先はどこでも (メモリアロケータによって管理される領域でも)良くなる、 ということだ。
Linux ではこのアロケータに スラブアロケータ と呼ばれる ものを採用している。これに関する詳細は私の力では書けないが、 要するに、メモリを動的に確保することで、キャッシュの効率なども 考えたメモリ配置を期待することが出来る、ということだろう。
キャッシュ効率を考慮して task の置き位置を ヒープから取ってくる、ということで良いのだろうか。
(原論文 "The Slab Allocator — An Object-Caching Kernel Memory Allocator –" もオススメ)
task_struct の実装
プロセスカーネルスタック と thread_info を除けば、 2.6 の task の管理も task_struct の天下であると言う点に変化はない。 そういう意味では、task_struct を引続き「プロセスディスクリプタ」 と呼んで良い気がする。
(-) linux/include/linux/sched.h
390 struct task_struct {
391 volatile long state; /- -1 unrunnable, 0 runnable, >0 stopped -/
392 struct thread_info -thread_info;
393 atomic_t usage;
(中略)
526 #ifdef CONFIG_NUMA
527 struct mempolicy -mempolicy;
528 short il_next; /- could be shared with used_math -/
529 #endif
530 };
もちろん全てを説明することは出来ないので(やろうとしたが挫折した)、 私が興味を持った(持たざるを得なかった)メンバについて説明する。
volatile long state == プロセスの状態
特に、スケジューラに関係した状態である。
値 | 意味 |
---|---|
TASK_RUNNING | 実行可能(実行中か runqueue 上にいる) |
TASK_INTERRUPTIBLE | 待ち状態。シグナル受信可 |
TASK_UNINTERRUPTIBLE | 待ち状態。シグナル受信不可 |
TASK_STOPPED | 停止。SIGSTOP等で起こる |
TASK_ZOMBIE | exit後、親がwait()を投げていない |
TASK_DEAD | 2.6系で新登場。いまいち謎! |
set_task_state, set_current_state マクロを使用して変える。
thread_info -thread_info
これは前述した thread_info オブジェクトへのポインタ。 相互参照している。
struct linux_binfmt -binfmt == バイナリのフォーマット
a.out とか elf とか misc とか、 実行バイナリのフォーマットをここに入れる。
一応定義も掲載しておく。
(-) linux/include/linux/binfmts.h
51 /-
52 - This structure defines the functions that are used to load the binary formats that
53 - linux accepts.
54 -/
55 struct linux_binfmt {
56 struct linux_binfmt - next;
57 struct module -module;
58 int (-load_binary)(struct linux_binprm -, struct pt_regs - regs);
59 int (-load_shlib)(struct file -);
60 int (-core_dump)(long signr, struct pt_regs - regs, struct file - file);
61 unsigned long min_coredump; /- minimal dump size -/
62 };
struct list_head tasks
これを用いて、Kernel 内で管理されている task_struct のリストへ アクセス出来る。
2.4 の時と異なり、汎用のリスト構造を用いている。
struct task_struct -real_parent, -parent
(-) linux/include/linux/sched.h
436 struct task_struct -real_parent; /- real parent process (when being debugged) -/
437 struct task_struct -parent; /- parent process -/
real_parent と parent の違いはイマイチ分からない。
pid_t pid
良く言われる通り、pid はtask を識別する番号。
User 空間で言う「プロセス」を識別する ID ではない。
一部、 「2.6 では NPTL(Native POSIX Thread Library)により、 pid がプロセスそのものを表すようになった」 と書いているものがあるが、それは嘘。
実際、sys_getpid() は未だに以下のようになっている。
(-) linux/kernel/timer.c
997 asmlinkage long sys_getpid(void)
998 {
999 return current->tgid;
1000 }
tgid<
スレッドグループID。
この番号が同じであることと、 後一部のデータ(メモリやファイルディスクリプタ等)を 共有していることをもって、 その task は「スレッド」とみなされることになる。
また、この tgid が異なれば、 少なくとも同一の「プロセス」に属する task でないことが分かる。 つまり、これを用いて同一「プロセス」かを判別することも出来る。
CLONE_THREAD を設定すると、 task の tgid が、それを作ったプロセスの tgid となる。 その場合、後述する group_leader も変わる (copy_thread() の 1065行)。
そのスレッドグループに一番最初からいたプロセスの pid が tgid となる。
children, sibling, group_leader
(-) linux/include/linux/sched.h
442 struct list_head children; /- list of my children -/
443 struct list_head sibling; /- linkage in my parent's children list -/
444 struct task_struct -group_leader; /- threadgroup leader -/
スレッドグループのリーダーというのは、 tgid == pid のプロセスのこと。
struct user_struct -user
ユーザというのは、文字通りシステムにアカウントを持つユーザのこと。
struct rlimit rlim[RLIM_NLIMITS]
プロセス資源についての制限を取得/設定するためのもの。
setrlimit(2), getrlimit(2) に直接関係する。
struct thread_struct thread
(-) linux/include/linux/sched.h
478 /- CPU-specific state of this task -/
479 struct thread_struct thread;
これは TSS(Task State Segment) のようなハードウェアコンテクキトを 退避するためのメンバ。
その他気になるもの
- mm_struct -mm はヒープメモリに関するものだろう。
- mm_struct -mm_active って何だ?
- struct fs_struct -fs はファイルシステム
- struct files_struct -files はファイルディスクリプタ
- struct namespace -namespace はネームスペース(mount とかの)
- struct signal_struct -signal はシグナル受け取り口(?)
- struct sighand_struct -sighand はシグナルハンドラ
上記のリストの項目は、多くはプロセス間で共有可能である。
具体的には clone() のフラグで指定すれば、 そのポインタの先が同じになる。
pid から task を探す方法
pid から task を手に入れたい場合には find_task_by_pid() を使う(linux/kernel/pid.c)
kill(2) を実現している sys_kill() で実行される関数 kill_proc_info() では以下のように使われている。
(-) linux/kernel/signal.c
1136 int
1137 kill_proc_info(int sig, struct siginfo -info, pid_t pid)
1138 {
1139 int error;
1140 struct task_struct -p;
1141
1142 read_lock(&tasklist_lock);
1143 p = find_task_by_pid(pid); <----- ここだよ
1144 error = -ESRCH;
1145 if (p)
1146 error = group_send_sig_info(sig, info, p);
1147 read_unlock(&tasklist_lock);
1148 return error;
1149 }
pid.c ではオープンハッシュ(チェーンハッシュ)でもって、 task を管理しているらしい。
なお、実装に使われている構造は static であり、 内部構造へ外から参照することはできない。
(-) linux/kernel/pid.c
30 static struct list_head -pid_hash[PIDTYPE_MAX];
PIDTYPE_MAX 分だけ、検索用のハッシュを作れる、 という意味だが、実際には既に全て のハッシュリストの用途も決まっている。
(-) linux/include/linux/pid.h
4 enum pid_type
5 {
6 PIDTYPE_PID,
7 PIDTYPE_TGID,
8 PIDTYPE_PGID,
9 PIDTYPE_SID,
10 PIDTYPE_MAX
11 };
pid を pid 検索ハッシュに登録する方法
次の attach_pid() 関数を使う
(-) linux/kernel/pid.c 169 int fastcall attach_pid(task_t -task, enum pid_type type, int nr) 170 { 171 struct pid -pid = find_pid(type, nr); 172 173 if (pid) 174 atomic_inc(&pid->count); 175 else { 176 pid = &task->pids[type].pid; 177 pid->nr = nr; 178 atomic_set(&pid->count, 1); 179 INIT_LIST_HEAD(&pid->task_list); 180 pid->task = task; 181 get_task_struct(task); 182 list_add(&pid->hash_chain, &pid_hash[type][pid_hashfn(nr)]); 183 } 184 list_add_tail(&task->pids[type].pid_chain, &pid->task_list); 185 task->pids[type].pidptr = pid; 186 187 return 0; 188 } 189
例えばこう使う(copy_process() より)
attach_pid(p, PIDTYPE_PID, p->pid);
この場合は、 p->pid 番の task が PIDTYPE_PID のハッシュに 登録されていないか確認し、なければ登録する。
ちなみに、 登録から外すには detach_pid() を使う。 これは主にプロセスの終了処理に使われる (linux/kernel/pid.c)。
task の管理
task が正しくプロセススケジューリングされるためには、 適切なオブジェクトがそれを管理する必要がある。
state が TASK_STOPPED, TASK_ZOMBIE, TASK_DEAD の場合
- TASK_STOPPED, TASK_ZOMBIE のときは、親が直接処理する。
- TASK_DEAD のときは、その時点でおそらく即座に消される。
これらの task がスケジューリングのために別のオブジェクトに管理される、 ということはないようだ。なにせ管理される必要自体がないから。
state が TASK_RUNNING である task
スケジューラによって実行中のプロセスに選ばれることがあるので、 専用の runqueue という構造に入れて管理される。
2.6 では 2.4 と比べてスケジューラの実装が大きく変わったため、 runqueue からどう取られてどう収められるかも変わった。
スケジューラが TASK_RUNNING な task をどう処理しているかについては、 http://www.itmedia.co.jp/enterprise/articles/0406/11/news001.html を参照のこと。
この部分はプロセススケジューリングそのものであるから、今回は追究しない。
state が TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE である task
要するに、 sleep 状態に入るプロセスをどう管理して、 どう wake するのか、という話である。
当然、TASK_RUNNING なプロセスと同じ runqueue に入れておくわけには いかない。 実際、 sleep 状態のとき、その task は runqueue からは消される。
そこで、 runqueue から消された task は、その sleep 状態を招いた モジュール(広い意味での)が管理することになる。
sleep を使って task をコントロールするモジュールは、 sleep させた( set_current_state() )ときにその task を 自分の用意した wait queue(待ち行列) に格納し( add_wait_queue() )、 スケジューラを呼ぶ( schedule() )。
すると、この時点でスケジューラはその task を runqueue から外す ( deactivate_task() )。 これにより、この task の(スケジューリングにおける)管理は、 モジュールに移ることになる。
モジュールは、適切なイベントが起こる度 (シグナルやハードウェア割り込み)に、 自分が管理している wait queue 上の task を一旦起こす ( ここから wake_up() )。 そして、その task に、起きるタイミングなのかを確認させる。
もし起きるタイミングでなければ、 task は再び眠る。
もし起きるタイミングであれば、 task はスケジューラに 自分が TASK_RUNNABLE になったことを伝える ( ここまでが wake_up() )。
スケジューラは runqueue にその task を入れ、 その task を管理していたモジュールは、自分の wait queue から task を取り除く ( remove_wait_queue() )。
詳しくはAppendix|Kernelメモ プロセス編 Appendix を参照のこと。
Linux ブート直後の task 周りの初期化について
Linux の初期化時に最初に作られる kernel thread を プロセス 0 と呼ぶ。
ここから、プロセス 0 がどう作られるかについて調べてみる。
プロセス 0 の task_struct
最初の task_struct オブジェクトは init_task と呼ばれる変数に入る。 これが、プロセス 0 のプロセスディスクリプタになる。
(-) linux/include/linux/init_task.h 67 #define INIT_TASK(tsk) \ 68 { \ 69 .state = 0, \ 70 .thread_info = &init_thread_info, \ 71 .usage = ATOMIC_INIT(2), \ (中略) 114 .journal_info = NULL, \ 115 }
(-) linux/arch/i386/kernel/init_task.c 37 struct task_struct init_task = INIT_TASK(init_task);
この init_task そのものは、次に示すように thread_info の 初期化時に利用される。
プロセス 0 の thread_union, thread_info
(-) linux/include/asm-i386/thread_info.h 69 #define INIT_THREAD_INFO(tsk) \ 70 { \ 71 .task = &tsk, \ 72 .exec_domain = &default_exec_domain, \ 73 .flags = 0, \ 74 .cpu = 0, \ 75 .preempt_count = 1, \ 76 .addr_limit = KERNEL_DS, \ 77 .restart_block = { \ 78 .fn = do_no_restart_syscall, \ 79 }, \ 80 }
(-) linux/arch/i386/kernel/init_task.c 21 /- 22 - Initial thread structure. 23 - 24 - We need to make sure that this is THREAD_SIZE aligned due to the 25 - way process stacks are handled. This is done by having a special 26 - "init_task" linker map entry.. 27 -/ 28 union thread_union init_thread_union 29 __attribute__((__section__(".data.init_task"))) = 30 { INIT_THREAD_INFO(init_task) };
要するに、色々初期化した上で、 init_thread_union の先頭にある thread_info のポインタが init_task を差すようになっている。
ここまでがんばって調べた init_thread_union も、 実は直接は参照されない。
代わりに、以下のような define 文がある。
(-) linux/include/asm-i386/thread_info.h 82 #define init_thread_info (init_thread_union.thread_info) 83 #define init_stack (init_thread_union.stack)
もう一度上記の INIT_TASK を見直してみると、 init_task の thread_info メンバが init_thread_info を差しているのが分かる。
プロセス 0 のスタックポインタの情報も当然初期化される。 そのときに init_stack が使われることは容易に想像できる。
そして実際、init_tss (CPU毎のTSS情報を格納) が初期化される時に、 想像通りの操作がなされる。
(-) linux/include/asm-i386/processor.h 433 /- 434 - Note that the .io_bitmap member must be extra-big. This is because 435 - the CPU will access an additional byte beyond the end of the IO 436 - permission bitmap. The extra byte must be all 1 bits, and must 437 - be within the limit. 438 -/ 439 #define INIT_TSS { \ 440 .esp0 = sizeof(init_stack) + (long)&init_stack, \ 441 .ss0 = __KERNEL_DS, \ 442 .esp1 = sizeof(init_tss[0]) + (long)&init_tss[0], \ 443 .ss1 = __KERNEL_CS, \ 444 .ldt = GDT_ENTRY_LDT, \ 445 .io_bitmap_base = INVALID_IO_BITMAP_OFFSET, \ 446 .io_bitmap = { [ 0 ... IO_BITMAP_LONGS] = ~0 }, \ 447 }
(-) linux/arch/i386/kernel/init_task.c 41 /- 42 - per-CPU TSS segments. Threads are completely 'soft' on Linux, 43 - no more per-task TSS's. The TSS size is kept cacheline-aligned 44 - so they are allowed to end up in the .data.cacheline_aligned 45 - section. Since TSS's are completely CPU-local, we want them 46 - on exact cacheline boundaries, to eliminate cacheline ping-pong. 47 -/ 48 struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };
NR_CPUS は OS が対応できる CPU の数。
スタックポインタに各CPUのTSS(Task State Segment) の初期値に init_stack の末尾のアドレスが入る、と想像できるコードだ。
# esp0 と esp の違いはなに?(汗
上記の構造を用い、最終的には プロセス 1 と呼ばれる kernel thread を 呼ぶ。プロセス 1 から様々なプロセスが別れていく。 つまり、通常見える全てのプロセスはプロセス 1 の子どもで、 プロセス 1 はプロセス 0 の子ども、という構造を持った ツリー構造をなしている。
ブート処理全般に関わってくる部分なので、 ここで追求は止めにする。
#やるのならハードウェアを初期化していく部分を眺めてみたいなぁ。
hardware context の保存と退避、管理
task の実行再開時に レジスタ から退避したり、またはレジスタに書き込む 情報を hardware context と呼ぶ。
この hardware context を書き換えて 次の task の実行を再開することを プロセス切替えと呼ぶ。
正直、この部分はアセンブラも入ってきて 訳分からないので、深入りはしない。
CPU の TSS(Task State Segment)
41 /- 42 - per-CPU TSS segments. Threads are completely 'soft' on Linux, 43 - no more per-task TSS's. The TSS size is kept cacheline-aligned 44 - so they are allowed to end up in the .data.cacheline_aligned 45 - section. Since TSS's are completely CPU-local, we want them 46 - on exact cacheline boundaries, to eliminate cacheline ping-pong. 47 -/ 48 struct tss_struct init_tss[NR_CPUS] __cacheline_aligned = { [0 ... NR_CPUS-1] = INIT_TSS };
この場合の TSS とは、 各 CPU のレジスタ等の情報である。 NR_CPUS はその kernel で扱える最大の CPU 数で、 config 時に設定可能。
要するに init_tss に TSS が保存されていると言うこと。
ちなみに、ハードウェアで実装された TSS というセグメントと、 Kernel 上で tss_struct 等で使われる TSS は概念としては 同じ方向を向いているが、別物である。
i386 にはハードウェアに TSS を保存する機構(TSS)があるらしいが、 それは使っていない、ということを上記のコメントは伝えているらしい。
(-) linux/include/asm-i386/processor.h 360 struct tss_struct { 361 unsigned short back_link,__blh; 362 unsigned long esp0; 363 unsigned short ss0,__ss0h; 364 unsigned long esp1; 365 unsigned short ss1,__ss1h; /- ss1 is used to cache MSR_IA32_SYSENTER_CS -/ 366 unsigned long esp2; 367 unsigned short ss2,__ss2h; 368 unsigned long __cr3; 369 unsigned long eip; 370 unsigned long eflags; 371 unsigned long eax,ecx,edx,ebx; 372 unsigned long esp; 373 unsigned long ebp; 374 unsigned long esi; 375 unsigned long edi; 376 unsigned short es, __esh; 377 unsigned short cs, __csh; 378 unsigned short ss, __ssh; 379 unsigned short ds, __dsh; 380 unsigned short fs, __fsh; 381 unsigned short gs, __gsh; 382 unsigned short ldt, __ldth; 383 unsigned short trace, io_bitmap_base; 384 /- 385 - The extra 1 is there because the CPU will access an 386 - additional byte beyond the end of the IO permission 387 - bitmap. The extra byte must be all 1 bits, and must 388 - be within the limit. 389 -/ 390 unsigned long io_bitmap[IO_BITMAP_LONGS + 1]; 391 /- 392 - pads the TSS to be cacheline-aligned (size is 0x100) 393 -/ 394 unsigned long __cacheline_filler[37]; 395 /- 396 - .. and then another 0x100 bytes for emergency kernel stack 397 -/ 398 unsigned long stack[64]; 399 } __attribute__((packed));
TSS を退避しておく場所 thread (task_struct のメンバ)
task_struct の考察でも指摘した thread_struct 型の thread が TSS を格納しておく場所であると考えられる。
例えば、 プロセス切替のコアになる __switch_to() の一部を見てみると
(-) linux/arch/i386/kernel/process.c 505 struct task_struct fastcall - __switch_to(struct task_struct -prev_p, struct task_struct -next_p) 506 { (中略 510 struct tss_struct -tss = init_tss + cpu; 511 (中略 516 /- 517 - Reload esp0, LDT and the page table pointer: 518 -/ 519 load_esp0(tss, next); (中略) 577 }
(-) linux/include/asm-i386/processor.h 449 static inline void load_esp0(struct tss_struct -tss, struct thread_struct -thread) 450 { 451 tss->esp0 = thread->esp0; 452 /- This can only happen when SEP is enabled, no need to test "SEP"arately -/ 453 if (unlikely(tss->ss1 *= thread->sysenter_cs)) { 454 tss->ss1 = thread->sysenter_cs; 455 wrmsr(MSR_IA32_SYSENTER_CS, thread->sysenter_cs, 0); 456 } 457 }
next->thread->esp0 を init_tss[cpu].esp0 に代入し、 アセンブラに何かごにょごにょやってるのだと読める。
アセンブラを含めたプロセス切替の詳細は省略(つーか無理)。
プロセスの生成
起動した直後に生まれる最初のプロセス(task)を プロセス 0 と呼ぶ。 全てのプロセスがこのプロセス 0 から生まれる。
プロセス 0 を除く全ての task は、 他の既存の task が「生み出す」形で生成する。 それ以外に task が生まれる方法はない。
この「生み出す」仕組みには大きく分ければ 4 つある。 システムコールとして3つ。
- fork(2)
- vfork(2)
- clone(2)
カーネル内部で使う方法として1つ。
- kernel_thread()
この生成部分は複雑過ぎて手に余ったので、 検討がかなりいい加減になっている。
4 つの仕組みの元となっている do_fork() について
- プロセスの挙動を追いかけるための ptrace 周りの実装 (CLONE_PTRACE)
- vfork() の「子どもが終了するまで親が待つ」挙動 (CLONE_VFORK)
- 「開始時に TASK_STOPPED 状態で開始する」挙動 (CLONE_STOPPED)
の三点を除くと、以下のように、これ自体は単純な関数だと分かる。
1166 long do_fork(unsigned long clone_flags, 1167 unsigned long stack_start, 1168 struct pt_regs -regs, 1169 unsigned long stack_size, 1170 int __user -parent_tidptr, 1171 int __user -child_tidptr) 1172 { 1173 struct task_struct -p; 1174 int trace = 0; 1175 long pid; 1176 (中略) 1183 p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr); 1188 pid = IS_ERR(p) ? PTR_ERR(p) : p->pid; 1189 1190 if (*IS_ERR(p)) { 1191 struct completion vfork; (中略) 1206 if (*(clone_flags & CLONE_STOPPED)) { (中略) 1218 if (clone_flags & CLONE_VM) 1219 wake_up_forked_thread(p); 1220 else 1221 wake_up_forked_process(p); (中略) 1230 } 1231 ++total_forks; (中略) 1247 set_need_resched(); <- 便宜上インデントを下げた 1248 } 1249 return pid; 1250 }
- copy_process() を呼ぶ
- フォークした回数をインクリメント
- そのプロセス(スレッド)を起動させる
- 再スケジューリングが必要である事をスケジューラに知らせる。
の四点をやっているだけ。
ちなみに、total_forks は /proc/stat の "processes" の項目で見られるようだ。
本質的な作業はすべて copy_process() が行っていると言える。
copy_process()
(-) linux/kernel/fork.c 867 struct task_struct -copy_process(unsigned long clone_flags, 868 unsigned long stack_start, 869 struct pt_regs -regs, 870 unsigned long stack_size, 871 int __user -parent_tidptr, 872 int __user -child_tidptr) 873 { 874 int retval; 875 struct task_struct -p = NULL; 876 877 if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) 878 return ERR_PTR(-EINVAL); 879 880 /- 881 - Thread groups must share signals as well, and detached threads 882 - can only be started up within the thread group. 883 -/ 884 if ((clone_flags & CLONE_THREAD) && *(clone_flags & CLONE_SIGHAND)) 885 return ERR_PTR(-EINVAL); 886 887 /- 888 - Shared signal handlers imply shared VM. By way of the above, 889 - thread groups also imply shared VM. Blocking this case allows 890 - for various simplifications in other code. 891 -/ 892 if ((clone_flags & CLONE_SIGHAND) && *(clone_flags & CLONE_VM)) 893 return ERR_PTR(-EINVAL); 894 895 retval = security_task_create(clone_flags); 896 if (retval) 897 goto fork_out; 898 899 retval = -ENOMEM; 900 p = dup_task_struct(current); 901 if (*p) 902 goto fork_out; 903 904 retval = -EAGAIN; 905 if (atomic_read(&p->user->processes) >= 906 p->rlim[RLIMIT_NPROC].rlim_cur) { 907 if (*capable(CAP_SYS_ADMIN) && *capable(CAP_SYS_RESOURCE) && 908 p->user *= &root_user) 909 goto bad_fork_free; 910 } 911 912 atomic_inc(&p->user->__count); 913 atomic_inc(&p->user->processes); 914 get_group_info(p->group_info); 915 916 /- 917 - If multiple threads are within copy_process(), then this check 918 - triggers too late. This doesn't hurt, the check is only there 919 - to stop root fork bombs. 920 -/ 921 if (nr_threads >= max_threads) 922 goto bad_fork_cleanup_count; 923 924 if (*try_module_get(p->thread_info->exec_domain->module)) 925 goto bad_fork_cleanup_count; 926 927 if (p->binfmt && *try_module_get(p->binfmt->module)) 928 goto bad_fork_cleanup_put_domain; 929 930 p->did_exec = 0; 931 copy_flags(clone_flags, p); 932 if (clone_flags & CLONE_IDLETASK) 933 p->pid = 0; 934 else { 935 p->pid = alloc_pidmap(); 936 if (p->pid == -1) 937 goto bad_fork_cleanup; 938 } 939 retval = -EFAULT; 940 if (clone_flags & CLONE_PARENT_SETTID) 941 if (put_user(p->pid, parent_tidptr)) 942 goto bad_fork_cleanup; 943 944 p->proc_dentry = NULL; 945 946 INIT_LIST_HEAD(&p->children); 947 INIT_LIST_HEAD(&p->sibling); 948 init_waitqueue_head(&p->wait_chldexit); 949 p->vfork_done = NULL; 950 spin_lock_init(&p->alloc_lock); 951 spin_lock_init(&p->proc_lock); 952 953 clear_tsk_thread_flag(p, TIF_SIGPENDING); 954 init_sigpending(&p->pending); 955 956 p->it_real_value = p->it_virt_value = p->it_prof_value = 0; 957 p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0; 958 init_timer(&p->real_timer); 959 p->real_timer.data = (unsigned long) p; 960 961 p->utime = p->stime = 0; 962 p->cutime = p->cstime = 0; 963 p->lock_depth = -1; /- -1 = no lock -/ 964 p->start_time = get_jiffies_64(); 965 p->security = NULL; 966 p->io_context = NULL; 967 p->audit_context = NULL; 968 #ifdef CONFIG_NUMA 969 p->mempolicy = mpol_copy(p->mempolicy); 970 if (IS_ERR(p->mempolicy)) { 971 retval = PTR_ERR(p->mempolicy); 972 p->mempolicy = NULL; 973 goto bad_fork_cleanup; 974 } 975 #endif 976 977 if ((retval = security_task_alloc(p))) 978 goto bad_fork_cleanup_policy; 979 if ((retval = audit_alloc(p))) 980 goto bad_fork_cleanup_security; 981 /- copy all the process information -/ 982 if ((retval = copy_semundo(clone_flags, p))) 983 goto bad_fork_cleanup_audit; 984 if ((retval = copy_files(clone_flags, p))) 985 goto bad_fork_cleanup_semundo; 986 if ((retval = copy_fs(clone_flags, p))) 987 goto bad_fork_cleanup_files; 988 if ((retval = copy_sighand(clone_flags, p))) 989 goto bad_fork_cleanup_fs; 990 if ((retval = copy_signal(clone_flags, p))) 991 goto bad_fork_cleanup_sighand; 992 if ((retval = copy_mm(clone_flags, p))) 993 goto bad_fork_cleanup_signal; 994 if ((retval = copy_namespace(clone_flags, p))) 995 goto bad_fork_cleanup_mm; 996 retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); 997 if (retval) 998 goto bad_fork_cleanup_namespace; 999 1000 p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; 1001 /- 1002 - Clear TID on mm_release()? 1003 -/ 1004 p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL; 1005 1006 /- 1007 - Syscall tracing should be turned off in the child regardless 1008 - of CLONE_PTRACE. 1009 -/ 1010 clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE); 1011 1012 /- Our parent execution domain becomes current domain 1013 These must match for thread signalling to apply -/ 1014 1015 p->parent_exec_id = p->self_exec_id; 1016 1017 /- ok, now we should be set up.. -/ 1018 p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL); 1019 p->pdeath_signal = 0; 1020 1021 /- Perform scheduler related setup -/ 1022 sched_fork(p); 1023 1024 /- 1025 - Ok, make it visible to the rest of the system. 1026 - We dont wake it up yet. 1027 -/ 1028 p->tgid = p->pid; 1029 p->group_leader = p; 1030 INIT_LIST_HEAD(&p->ptrace_children); 1031 INIT_LIST_HEAD(&p->ptrace_list); 1032 1033 /- Need tasklist lock for parent etc handling* -/ 1034 write_lock_irq(&tasklist_lock); 1035 /- 1036 - Check for pending SIGKILL* The new thread should not be allowed 1037 - to slip out of an OOM kill. (or normal SIGKILL.) 1038 -/ 1039 if (sigismember(¤t->pending.signal, SIGKILL)) { 1040 write_unlock_irq(&tasklist_lock); 1041 retval = -EINTR; 1042 goto bad_fork_cleanup_namespace; 1043 } 1044 1045 /- CLONE_PARENT re-uses the old parent -/ 1046 if (clone_flags & CLONE_PARENT) 1047 p->real_parent = current->real_parent; 1048 else 1049 p->real_parent = current; 1050 p->parent = p->real_parent; 1051 1052 if (clone_flags & CLONE_THREAD) { 1053 spin_lock(¤t->sighand->siglock); 1054 /- 1055 - Important: if an exit-all has been started then 1056 - do not create this new thread - the whole thread 1057 - group is supposed to exit anyway. 1058 -/ 1059 if (current->signal->group_exit) { 1060 spin_unlock(¤t->sighand->siglock); 1061 write_unlock_irq(&tasklist_lock); 1062 retval = -EAGAIN; 1063 goto bad_fork_cleanup_namespace; 1064 } 1065 p->tgid = current->tgid; 1066 p->group_leader = current->group_leader; 1067 1068 if (current->signal->group_stop_count > 0) { 1069 /- 1070 - There is an all-stop in progress for the group. 1071 - We ourselves will stop as soon as we check signals. 1072 - Make the new thread part of that group stop too. 1073 -/ 1074 current->signal->group_stop_count++; 1075 set_tsk_thread_flag(p, TIF_SIGPENDING); 1076 } 1077 1078 spin_unlock(¤t->sighand->siglock); 1079 } 1080 1081 SET_LINKS(p); 1082 if (p->ptrace & PT_PTRACED) 1083 __ptrace_link(p, current->parent); 1084 1085 attach_pid(p, PIDTYPE_PID, p->pid); 1086 if (thread_group_leader(p)) { 1087 attach_pid(p, PIDTYPE_TGID, p->tgid); 1088 attach_pid(p, PIDTYPE_PGID, process_group(p)); 1089 attach_pid(p, PIDTYPE_SID, p->signal->session); 1090 if (p->pid) 1091 __get_cpu_var(process_counts)++; 1092 } else 1093 link_pid(p, p->pids + PIDTYPE_TGID, &p->group_leader->pids[PIDTYPE_TGID].pid); 1094 1095 nr_threads++; 1096 write_unlock_irq(&tasklist_lock); 1097 retval = 0; 1098 1099 fork_out: 1100 if (retval) 1101 return ERR_PTR(retval); 1102 return p; 1103 1104 bad_fork_cleanup_namespace: 1105 exit_namespace(p); 1106 bad_fork_cleanup_mm: 1107 exit_mm(p); 1108 if (p->active_mm) 1109 mmdrop(p->active_mm); 1110 bad_fork_cleanup_signal: 1111 exit_signal(p); 1112 bad_fork_cleanup_sighand: 1113 exit_sighand(p); 1114 bad_fork_cleanup_fs: 1115 exit_fs(p); /- blocking -/ 1116 bad_fork_cleanup_files: 1117 exit_files(p); /- blocking -/ 1118 bad_fork_cleanup_semundo: 1119 exit_sem(p); 1120 bad_fork_cleanup_audit: 1121 audit_free(p); 1122 bad_fork_cleanup_security: 1123 security_task_free(p); 1124 bad_fork_cleanup_policy: 1125 #ifdef CONFIG_NUMA 1126 mpol_free(p->mempolicy); 1127 #endif 1128 bad_fork_cleanup: 1129 if (p->pid > 0) 1130 free_pidmap(p->pid); 1131 if (p->binfmt) 1132 module_put(p->binfmt->module); 1133 bad_fork_cleanup_put_domain: 1134 module_put(p->thread_info->exec_domain->module); 1135 bad_fork_cleanup_count: 1136 put_group_info(p->group_info); 1137 atomic_dec(&p->user->processes); 1138 free_uid(p->user); 1139 bad_fork_free: 1140 free_task(p); 1141 goto fork_out; 1142 }
長い関数で、やることもたくさんある。
(-) linux/kernel/fork.c 877 if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) 878 return ERR_PTR(-EINVAL); 884 if ((clone_flags & CLONE_THREAD) && *(clone_flags & CLONE_SIGHAND)) 885 return ERR_PTR(-EINVAL); 892 if ((clone_flags & CLONE_SIGHAND) && *(clone_flags & CLONE_VM)) 893 return ERR_PTR(-EINVAL);
これは clone(2) のマニュアルを参照のこと。 「あり得ない」とされる組合せを弾いている。
895 retval = security_task_create(clone_flags); 896 if (retval) 897 goto fork_out;
これは、SELinuxなどのセキュリティモジュールに関係するもの。
最終的にはモジュールの task_create 関数が呼ばれるようだ。 task_create の説明は以下の通り。
(-) linux/include/linux/security.h 507 - @task_create: 508 - Check permission before creating a child process. See the clone(2) 509 - manual page for definitions of the @clone_flags. 510 - @clone_flags contains the flags indicating what should be shared. 511 - Return 0 if permission is granted.
さて、 copy_process() に戻る。
899 retval = -ENOMEM; 900 p = dup_task_struct(current); 901 if (*p) 902 goto fork_out;
dup_task_struct() は引数に取られたプロセスをメモリレベルで正確にコピーする。 Appendix|Kernelメモ プロセス編 Appendix を参照のこと。
この時点では、二つのプロセスは本当に同じものになる。
当然ながら複製物はまだスケジューラの管理下には置いていないので、 何も問題は起きない。
904 retval = -EAGAIN; 905 if (atomic_read(&p->user->processes) >= 906 p->rlim[RLIMIT_NPROC].rlim_cur) { 907 if (*capable(CAP_SYS_ADMIN) && *capable(CAP_SYS_RESOURCE) && 908 p->user *= &root_user) 909 goto bad_fork_free; 910 }
RLIMIT_NPROC から分かるように、 これは「プロセス数の制限に引っかかっていないか」というチェック。
912 atomic_inc(&p->user->__count); 913 atomic_inc(&p->user->processes);
前述した通り、 user はシステムにアカウントを持つユーザのこと。 __count は参照カウント。
processes は(905行目からも分かる通り) そのユーザの保有しているプロセス数と考えられる。
914 get_group_info(p->group_info);
親プロセスが持っている group_info の参照カウントを1増やす。
916 /- 917 - If multiple threads are within copy_process(), then this check 918 - triggers too late. This doesn't hurt, the check is only there 919 - to stop root fork bombs. 920 -/ 921 if (nr_threads >= max_threads) 922 goto bad_fork_cleanup_count;
nr_threads というのは global 変数で、 現在システム全体で使われているスレッドの数を表すらしい。
max_threads は稼働できるスレッドの限界値。 ちなみに、rlimit[RLIMIT_NPROC] のデフォルト値はこれの半分になる
924 if (*try_module_get(p->thread_info->exec_domain->module)) 925 goto bad_fork_cleanup_count;
そのプロセスの実行ドメインに対応するモジュールが 利用できないならエラー。
実行ドメインについては Appendix|Kernelメモ プロセス編 Appendix を参照のこと。
927 if (p->binfmt && *try_module_get(p->binfmt->module)) 928 goto bad_fork_cleanup_put_domain;
おそらく、p->binfmt で指定されたフォーマットに対応する モジュールが利用できないならエラーを返す、という仕組み。
930 p->did_exec = 0;
このフラグは、親が setpgid(2) を使って子どものグループIDを帰る時、 既に子どもが execve 等をしていないかをチェックするものである。
man setpgid のエラーの項目を見ると良い。
931 copy_flags(clone_flags, p);
これはプロセスのフラグをコピー(初期化?)している。
プロセスのフラグについては Appendix|Kernelメモ プロセス編 Appendix を参照のこと。
932 if (clone_flags & CLONE_IDLETASK) 933 p->pid = 0; 934 else { 935 p->pid = alloc_pidmap(); 936 if (p->pid == -1) 937 goto bad_fork_cleanup; 938 }
CLONE_IDLETASK の IDLE はアイドルプロセスの IDLE。 プロセス 0 を作るためのフラグだろう。 通常は使えない。
alloc_pidmap() については Appendix|Kernelメモ プロセス編 Appendix の pid についての解説部分で触れた通り。
939 retval = -EFAULT; 940 if (clone_flags & CLONE_PARENT_SETTID) 941 if (put_user(p->pid, parent_tidptr)) 942 goto bad_fork_cleanup;
イマイチ意味不明。 parent_tidptr はユーザから渡される模様だが、 子のメモリへはどこで書き込んでいるのだろう?
とりあえず、clone(2) のマニュアルを参考のこと。
ちなみに、put_user() は、 第一引数の値を第二引数に渡されたユーザ空間の メモリアドレスに書き込むマクロ (これは割と汎用気味な関数)。
944 p->proc_dentry = NULL; 945 946 INIT_LIST_HEAD(&p->children); 947 INIT_LIST_HEAD(&p->sibling); 948 init_waitqueue_head(&p->wait_chldexit); 949 p->vfork_done = NULL; 950 spin_lock_init(&p->alloc_lock); 951 spin_lock_init(&p->proc_lock); 952 953 clear_tsk_thread_flag(p, TIF_SIGPENDING); 954 init_sigpending(&p->pending); 955 956 p->it_real_value = p->it_virt_value = p->it_prof_value = 0; 957 p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0; 958 init_timer(&p->real_timer); 959 p->real_timer.data = (unsigned long) p; 960 961 p->utime = p->stime = 0; 962 p->cutime = p->cstime = 0; 963 p->lock_depth = -1; /- -1 = no lock -/ 964 p->start_time = get_jiffies_64(); 965 p->security = NULL; 966 p->io_context = NULL; 967 p->audit_context = NULL;
以上は親プロセスと共有してはいけない変数を初期化している部分だと 考えられる。
詳細は調べたくなかった。
977 if ((retval = security_task_alloc(p))) 978 goto bad_fork_cleanup_policy;
セキュリティモジュール関係の処理。
security_task_alloc()は、セキュリティモジュールが このプロセス用のセキュリティオブジェクトを用意するためのものの ようだが、良く分からない。
Appendix|Kernelメモ プロセス編 Appendix に、もう少しがんばった結果を掲載しておく。
ここからしばらく、 clone() に渡すフラグそれぞれについての処理に移る。
981 /- copy all the process information -/ 982 if ((retval = copy_semundo(clone_flags, p))) 983 goto bad_fork_cleanup_audit;
CLONE_SYSVSEM というド変態フラグが立っていたら処理される。
semop のマニュアルで
""あるプロセスの sem_undo 構造体は fork(2) システムコールの場合には ""子プロセスには継承されないが、execve(2) システムコールの場合は継承される
と書いてある。 CLONE_SYSVSEM は clone() で この sem_undo 構造体の継承をサポートするものらしい。
(が、良く分からない。これだけは man clone しても駄目……)
984 if ((retval = copy_files(clone_flags, p))) 985 goto bad_fork_cleanup_semundo;
CLONE_FILES(ファイルディスクリプタの共有)の処理
986 if ((retval = copy_fs(clone_flags, p))) 987 goto bad_fork_cleanup_files;
CLONE_FS(ファイルシステム つまり current directory 情報等の共有)の処理
988 if ((retval = copy_sighand(clone_flags, p))) 989 goto bad_fork_cleanup_fs;
CLONE_SIGHAND(シグナルハンドラの共有)の処理。 CLONE_THREAD フラグが必ず立っている点も注意。
990 if ((retval = copy_signal(clone_flags, p))) 991 goto bad_fork_cleanup_sighand;
- CLONE_THREAD が立っていたら何もしない
- 立っていなかったら、シグナルの受け取り口(?)を作り直す。
(良く調べていない)
992 if ((retval = copy_mm(clone_flags, p))) 993 goto bad_fork_cleanup_signal;
CLONE_VM(メモリ空間の共有) の処理
994 if ((retval = copy_namespace(clone_flags, p))) 995 goto bad_fork_cleanup_mm;
CLONE_NEWNS(プロセスの名前空間。分からなければ clone(2) のマニュアルを参照) の処理
996 retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); 997 if (retval) 998 goto bad_fork_cleanup_namespace;
TSS の情報を退避する(ハードウェア的な) スレッドをコピー(というか初期化なんだが)する処理。
1000 p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL; 1001 /- 1002 - Clear TID on mm_release()? 1003 -/ 1004 p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL; 1005 1006 /- 1007 - Syscall tracing should be turned off in the child regardless 1008 - of CLONE_PTRACE. 1009 -/ 1010 clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE); 1011 1012 /- Our parent execution domain becomes current domain 1013 These must match for thread signalling to apply -/ 1014 1015 p->parent_exec_id = p->self_exec_id;
上記の CLONE_hogehoge の中で 2.6 から登場したフラグは なんでこう意味が分かりにくいんだろう。
フラグの意味も分かってないのに、初期化処理が分かるわけない。
省略。
1017 /- ok, now we should be set up.. -/ 1018 p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
exit_signal というのは、この task が終了した時、 その親になげるシグナルを保存するためのものである。
CLONE_THREAD がついていれば、 親には実行終了したことを示すシグナルを送る必要はない。
そうでない場合は、clone_flags を CSIGNAL でマスクして、 exit_signal に入れる。
(-) linux/include/linux/sched.h 38 #define CSIGNAL 0x000000ff /- signal mask to be sent at exit -/
一方、シグナルは以下のように定義される。
(-) linux/include/asm-i386/signal.h 34 #define SIGHUP 1 35 #define SIGINT 2 36 #define SIGQUIT 3 37 #define SIGILL 4 38 #define SIGTRAP 5 39 #define SIGABRT 6 40 #define SIGIOT 6 41 #define SIGBUS 7 42 #define SIGFPE 8 43 #define SIGKILL 9 44 #define SIGUSR1 10 45 #define SIGSEGV 11 46 #define SIGUSR2 12 47 #define SIGPIPE 13 48 #define SIGALRM 14 49 #define SIGTERM 15 50 #define SIGSTKFLT 16 51 #define SIGCHLD 17 52 #define SIGCONT 18 53 #define SIGSTOP 19 54 #define SIGTSTP 20 55 #define SIGTTIN 21 56 #define SIGTTOU 22 57 #define SIGURG 23 58 #define SIGXCPU 24 59 #define SIGXFSZ 25 60 #define SIGVTALRM 26 61 #define SIGPROF 27 62 #define SIGWINCH 28 63 #define SIGIO 29 64 #define SIGPOLL SIGIO 65 /- 66 #define SIGLOST 29 67 -/ 68 #define SIGPWR 30 69 #define SIGSYS 31 70 #define SIGUNUSED 31
つまり、 CSIGNAL でマスクすることで、 p->exit_signal にシグナル番号が保存される、 という寸法だ。
ちなみに、fork(2) によって結局呼ばれる sys_fork() 関数は clone_flags が必ず SIGCHLD となる。
1019 p->pdeath_signal = 0;
?
1021 /- Perform scheduler related setup -/ 1022 sched_fork(p);
新しくできたプロセスのタイムスライスを初期化する (current のタイムスライスを調整したりもする) 処理のようだが、詳細は sched_fork() を参照のこと (linux/kernel/sched.c)。
ちなみに、スケジューラの管理下に置くという操作ではないので注意 (つまり、新しくできたプロセスは、 まだスケジューリングの対象とはなっていない)。
1028 p->tgid = p->pid; 1029 p->group_leader = p; 1030 INIT_LIST_HEAD(&p->ptrace_children); 1031 INIT_LIST_HEAD(&p->ptrace_list);
スレッド、子ども辺りの処理。
まるで CLONE_THREAD を無視しているかのような実装だが、 その処理は1052行目以降にされるようだ。
1033 /- Need tasklist lock for parent etc handling* -/ 1034 write_lock_irq(&tasklist_lock);
ロックかけているだけ。
1035 /- 1036 - Check for pending SIGKILL* The new thread should not be allowed 1037 - to slip out of an OOM kill. (or normal SIGKILL.) 1038 -/ 1039 if (sigismember(¤t->pending.signal, SIGKILL)) { 1040 write_unlock_irq(&tasklist_lock); 1041 retval = -EINTR; 1042 goto bad_fork_cleanup_namespace; 1043 }
ヨクワカリマセンネ。
多分コメントのまんまかと……。
1045 /- CLONE_PARENT re-uses the old parent -/ 1046 if (clone_flags & CLONE_PARENT) 1047 p->real_parent = current->real_parent; 1048 else 1049 p->real_parent = current; 1050 p->parent = p->real_parent;
parent と real_parent の初期化。 プロセス起動時点で、両方が同じ値を持つことが分かる。
1052 if (clone_flags & CLONE_THREAD) { 1053 spin_lock(¤t->sighand->siglock); 1054 /- 1055 - Important: if an exit-all has been started then 1056 - do not create this new thread - the whole thread 1057 - group is supposed to exit anyway. 1058 -/ 1059 if (current->signal->group_exit) { 1060 spin_unlock(¤t->sighand->siglock); 1061 write_unlock_irq(&tasklist_lock); 1062 retval = -EAGAIN; 1063 goto bad_fork_cleanup_namespace; 1064 } 1065 p->tgid = current->tgid; 1066 p->group_leader = current->group_leader; 1067 1068 if (current->signal->group_stop_count > 0) { 1069 /- 1070 - There is an all-stop in progress for the group. 1071 - We ourselves will stop as soon as we check signals. 1072 - Make the new thread part of that group stop too. 1073 -/ 1074 current->signal->group_stop_count++; 1075 set_tsk_thread_flag(p, TIF_SIGPENDING); 1076 } 1077 1078 spin_unlock(¤t->sighand->siglock); 1079 }
CLONE_THREAD が立っていたらスレッドグループ周りの処理をやる、 という意味以上ではないと思われる。
特に重要なのは tgid と group_leader を スレッドグループを最初に作ったプロセスに関連付けるところだろう。
1081 SET_LINKS(p);
スレッドグループの group_leader に このプロセスを通知している (linux/include/linux/sched.h のマクロ)。
1082 if (p->ptrace & PT_PTRACED) 1083 __ptrace_link(p, current->parent);
ptrace はもうたくさんだ。
1085 attach_pid(p, PIDTYPE_PID, p->pid); 1086 if (thread_group_leader(p)) { 1087 attach_pid(p, PIDTYPE_TGID, p->tgid); 1088 attach_pid(p, PIDTYPE_PGID, process_group(p)); 1089 attach_pid(p, PIDTYPE_SID, p->signal->session); 1090 if (p->pid) 1091 __get_cpu_var(process_counts)++; 1092 } else 1093 link_pid(p, p->pids + PIDTYPE_TGID, &p->group_leader->pids[PIDTYPE_TGID].pid);
pid を検索するためのハッシュにこの id を登録している。
1095 nr_threads++;
総プロセス数をインクリメント。
1096 write_unlock_irq(&tasklist_lock);
ロック解除
1097 retval = 0; 1098 (1099~1141 はリターン、エラー処理) 1142 }
相当長かったが、copy_process() はこれで全てである。
プロセスは、fork(), vfork(), clone(), kernel_thread() のどの方法を 経由したとしても、必ず do_fork() と copy_process() を経由するので、 プロセス 0 を除く全てのプロセスが上記の全ての処理をされることになる。
fork(2)
(-) linux/arch/i386/kernel/process.c 579 asmlinkage int sys_fork(struct pt_regs regs) 580 { 581 return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 582 }
ここから、プロセスは親と何も共有していない (全ての情報をコピーした) task であるということが確認できる。
前述したが、SIGCHLD は task の exit_signal に入り、 自分が終了した時に親に送られるシグナルとして活用される。
vfork(2)
(-) linux/arch/i386/kernel/process.c 609 asmlinkage int sys_vfork(struct pt_regs regs) 610 { 611 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 612 }
- vfork(2) の変態挙動を実現するために CLONE_VFORK
- 生成速度を上げたいのか何なのか、メモリ空間を共有させるために CLONE_VM
clone(2)
(-) linux/arch/i386/kernel/process.c 584 asmlinkage int sys_clone(struct pt_regs regs) 585 { 586 unsigned long clone_flags; 587 unsigned long newsp; 588 int __user -parent_tidptr, -child_tidptr; 589 590 clone_flags = regs.ebx; 591 newsp = regs.ecx; 592 parent_tidptr = (int __user -)regs.edx; 593 child_tidptr = (int __user -)regs.edi; 594 if (*newsp) 595 newsp = regs.esp; 596 return do_fork(clone_flags & ~CLONE_IDLETASK, newsp, ®s, 0, parent_tidptr, child_tidptr); 597 }
clone(2) の場合はほとんど全てのフラグを そのまんま渡している。
clone(2) の「生成された task は fn から実行が始まる」 という挙動(マニュアル参照)をどう実現しているのかとおもったら、 glibc の clone() の実装に call 命令が入っていた。
多分、上記の関数内に fn を実行させるような仕組みはない。
#多分、と言っているのは、後述する kernel_thread() が理解出来ていないから。
kernel_thread() kernel thread を作る仕組みである。 kernel thread はカーネル空間でのみ動く task である。 定義としては task_struct の mm_struct 型メンバ mm が NULL であることが kernel_thread の条件らしい。
(-) linux/arch/i386/kernel/process.c 268 /- 269 - Create a kernel thread 270 -/ 271 int kernel_thread(int (-fn)(void -), void - arg, unsigned long flags) 272 { 273 struct pt_regs regs; 274 275 memset(®s, 0, sizeof(regs)); 276 277 regs.ebx = (unsigned long) fn; 278 regs.edx = (unsigned long) arg; 279 280 regs.xds = __USER_DS; 281 regs.xes = __USER_DS; 282 regs.orig_eax = -1; 283 regs.eip = (unsigned long) kernel_thread_helper; 284 regs.xcs = __KERNEL_CS; 285 regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2; 286 287 /- Ok, create the new process.. -/ 288 return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); 289 }
これが何を示しているのか全くさっぱりわけ分からんので、 まぁ放置。
プロセスの終了/破棄
exit(3)(ライブラリコールとしての exit()) と _exit(2)(システムコールとしての exit()) exit(3) は POSIX 等の規格に合うように作られたライブラリコールで、 _exit(2) は Linux のシステムコールそのものである (Linux カーネル内部ではあくまで exit システムコール。 glibc のレベルでは exit(3) と名前がぶつかる関係で、 便宜上 _exit(2) という名前になっているようだ)。
ちなみに、 exit(3) は以下のように 定義される。
(-) glibc-2.3.2/stdlib/exit.c /- Call all functions registered withatexit' and
on_exit', in the reverse of the order in which they were registered perform stdio cleanup, and terminate program execution with STATUS. -/ void exit (int status) { /- We do it this way to handle recursive calls to exit () made by the functions registered withatexit' and
on_exit'. We call everyone on the list and use the status value in the last exit (). -/ while (__exit_funcs *= NULL) { struct exit_function_list -old; while (__exit_funcs->idx > 0) { const struct exit_function -const f = &__exit_funcs->fns[--__exit_funcs->idx]; switch (f->flavor) { case ef_free: case ef_us: break; case ef_on: (-f->func.on.fn) (status, f->func.on.arg); break; case ef_at: (-f->func.at) (); break; case ef_cxa: (-f->func.cxa.fn) (f->func.cxa.arg, status); break; } } old = __exit_funcs; __exit_funcs = __exit_funcs->next; if (__exit_funcs *= NULL) /- Don't free the last element in the chain, this is the statically allocate element. -/ free (old); } #ifdef HAVE_GNU_LD RUN_HOOK (__libc_atexit, ()); #else { extern void _cleanup (void); _cleanup (); } #endif _exit (status); }
色々前処理をした上で _exit(2) を呼んでいるのが分かる。
_exit(2) は以下のようになる。
(-) glibc-2.3.2/sysdeps/unix/sysv/linux/i386/_exit.S .text .type _exit,@function .global _exit _exit: movl 4(%esp), %ebx /- Try the new syscall first. -/ #ifdef __NR_exit_group movl $__NR_exit_group, %eax ENTER_KERNEL #endif /- Not available. Now the old one. -/ movl $__NR_exit, %eax /- Don't bother using ENTER_KERNEL here. If the exit_group syscall is not available AT_SYSINFO isn't either. -/ int $0x80 /- This must not fail. Be sure we don't return. -/ hlt .size _exit,.-_exit
exit_group シグナルと exit シグナルを呼んでいるだけ、というのが分かる。 exit_group はスレッドを終了させるための処理のようだが、 ここでは省略。
main() の return ユーザプログラムから exit(3), exit(2) を呼んで終了するのならともかく、 main() から return したらどうなるのかは正確なところが分からない。
コンパイラが exit(2) を入れてくれるという記述もあるが、 実際にどう入れているかまでは不明。 バイナリを objdump してみても、すぐに分かるようなものではなかった。
正常終了の流れ まとめると、ユーザ空間では
main() からの return -> exit(3) -> _exit(2) -> (割り込み 0x02) -> sys_exit()
の、最初の 3 ステップのどこかから終了処理に向かう、ということになる。
(もちろん自分で割り込みをかけても良い。 sys_exit() はカーネル空間の関数なので、sys_exit() を直接呼ぶのは当然無理)
sys_exit() は
(-) linux/kernel/exit.c 861 asmlinkage long sys_exit(int error_code) 862 { 863 do_exit((error_code&0xff)<<8); 864 }
よって、
-----------------ユーザ空間---------------- ------カーネル空間------- main() からの return -> exit(3) -> _exit(2) -> (割り込み 0x02) -> sys_exit() -> do_exit()
異常終了の流れ セグメンテーションフォルト等の異常終了が起きた場合はどうか。
シグナルハンドラがないかをチェックする部分については シグナルのメモを書くときに考えることにして (そんな機会あるとは思えないが)。
最終的に異常な状態で終了するということになったら、 以下のように関数を呼べば良い。
do_exit(SIGSEGV);
do_exit()
(-) linux/kernel/exit.c 796 asmlinkage NORET_TYPE void do_exit(long code) 797 { 798 struct task_struct -tsk = current; 799 800 if (unlikely(in_interrupt())) 801 panic("Aiee, killing interrupt handler*"); 802 if (unlikely(*tsk->pid)) 803 panic("Attempted to kill the idle task*"); 804 if (unlikely(tsk->pid == 1)) 805 panic("Attempted to kill init*"); 806 if (tsk->io_context) 807 exit_io_context(); 808 tsk->flags |= PF_EXITING; 809 del_timer_sync(&tsk->real_timer); 810 811 if (unlikely(in_atomic())) 812 printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n", 813 current->comm, current->pid, 814 preempt_count()); 815 816 profile_exit_task(tsk); 817 818 if (unlikely(current->ptrace & PT_TRACE_EXIT)) { 819 current->ptrace_message = code; 820 ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP); 821 } 822 823 acct_process(code); 824 __exit_mm(tsk); 825 826 exit_sem(tsk); 827 __exit_files(tsk); 828 __exit_fs(tsk); 829 exit_namespace(tsk); 830 exit_thread(); 831 832 if (tsk->signal->leader) 833 disassociate_ctty(1); 834 835 module_put(tsk->thread_info->exec_domain->module); 836 if (tsk->binfmt) 837 module_put(tsk->binfmt->module); 838 839 tsk->exit_code = code; 840 exit_notify(tsk); 841 #ifdef CONFIG_NUMA 842 mpol_free(tsk->mempolicy); 843 tsk->mempolicy = NULL; 844 #endif 845 schedule(); 846 BUG(); 847 /- Avoid "noreturn function does return". -/ 848 for (;;) ; 849 }
また一行ずつコメントするのか…… orz
800 if (unlikely(in_interrupt())) 801 panic("Aiee, killing interrupt handler*");
割り込み中 (つまり、プロセスに関連付けられていない状態。 Interrupt Context とも言う)に exit は出来ない。 何故なら終了するべきプロセスがないのだから。
……というのは名前から考えた予想 🙁
802 if (unlikely(*tsk->pid)) 803 panic("Attempted to kill the idle task*"); 804 if (unlikely(tsk->pid == 1)) 805 panic("Attempted to kill init*");
終了してはいけない二つのプロセス、 プロセス 0 とプロセス 1 が終了されそうになったらここで止まる。
806 if (tsk->io_context) 807 exit_io_context();
IO context というのは多分入出力関係のコンテキストなんだろうが、 詳細には立ち入らない。
808 tsk->flags |= PF_EXITING;
このプロセスが終了状態にあることを示すためにフラグを立てる。 この終了処理中にタイマー割り込みが入っても大丈夫。
809 del_timer_sync(&tsk->real_timer); 810 811 if (unlikely(in_atomic())) 812 printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n", 813 current->comm, current->pid, 814 preempt_count());
疲れた。
816 profile_exit_task(tsk);
oprofile (パフォーマンス監視ツール)等で使えるように linux/kernel/profile.c にはプロファイリング用の 機構がある模様。 その中の一つらしい。
818 if (unlikely(current->ptrace & PT_TRACE_EXIT)) { 819 current->ptrace_message = code; 820 ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP); 821 }
だから ptrace はもういいって。
823 acct_process(code);
BSD-style process accounting に関係するものらしい。 『Linux Kernel Development』によると
""If BSD process accounting is enabled, call acct_process() to ""write out accounting information.
824 __exit_mm(tsk); 825 826 exit_sem(tsk); 827 __exit_files(tsk); 828 __exit_fs(tsk); 829 exit_namespace(tsk); 830 exit_thread();
終了処理。多分メモリ解放とか。
832 if (tsk->signal->leader) 833 disassociate_ctty(1);
(-) linux/drivers/char/tty_io.c 549 /- 550 - This function is typically called only by the session leader, when 551 - it wants to disassociate itself from its controlling tty. 552 - 553 - It performs the following functions: 554 - (1) Sends a SIGHUP and SIGCONT to the foreground process group 555 - (2) Clears the tty from being controlling the session 556 - (3) Clears the controlling tty for all processes in the 557 - session group. 558 - 559 - The argument on_exit is set to 1 if called when a process is 560 - exiting; it is 0 if called by the ioctl TIOCNOTTY. 561 -/ 562 void disassociate_ctty(int on_exit) (以下略)
うーん…… <- 分かってない
835 module_put(tsk->thread_info->exec_domain->module); 836 if (tsk->binfmt) 837 module_put(tsk->binfmt->module);
モジュールの参照カウントを下げている。 exec_domain, binfmt は do_fork() のときにも出てきた。
839 tsk->exit_code = code; 840 exit_notify(tsk);
親に終了したことを伝える。
この関数内で、次の二つの処理を行う。
- 解放するプロセスの子どもの親を変更する。
- 次の親は同一スレッドの誰か、もしくはプロセス 1(INIT プロセス、この場合は child reaper とも呼ぶ)
- 解放するプロセスの state を TASK_ZOMBIE 状態にする。
- 何らかの条件で TASK_DEAD となり、即座に解放されるかもしれない。
845 schedule();
最後に再スケジューリングされる。 二度と戻ってくることはない。
ここまでのどこかでスケジューラの制御からはずれているのだが、 どこだろう……。
この時点で、プロセスに関連付けられたほぼ全てのデータが解放されている。 ただし、まだ thread_union(プロセスカーネルスタック)と task_struct(スラブアロケータで確保されたもの) が解放されていない。 また、pid を検索するためのハッシュからも解放されていない。
これらは、(TASK_DEAD となって解放されない限り) TASK_ZOMBIE な task のデータとして、Kernel 内に残り続ける。
この抜けがらのような task を解放して骨を拾ってあげるのが wait() である。
何故 即座に消去せずに TASK_ZOMBIE を経由するか。 “”親には子どもが死んだ理由を知る権利と義務があるんだ。
wait は、親からすると
- TASK_ZOMBIE 状態の子プロセスを解放。
- その子プロセスの終了ステータスを取得。
という二つの機能を持つ。 後者の目的があって、TASK_ZOMBIE が用意されている。
情報の取得のためだけに新しいデータ構造を用意しろ、 という考えもあるが、
- TASK_ZOMBIE も pid を持っている。
- つまりプロセスの総数に数えられている。
- 別に用意しとかなくてもメモリが酷く無駄使いされているわけではない
- ぶっちゃけ、スタック分だけだ。
という理由で、多分 TASK_ZOMBIE はなくならない。
たくさんある wait wait(2), wait3(2), wait4(2) は全て wait4 システムコールに繋がる。
waitpid(2) はそのまま waitpid システムコールだが、
(-) linux/kernel/exit.c 1202 /- 1203 - sys_waitpid() remains for compatibility. waitpid() should be 1204 - implemented by calling sys_wait4() from libc.a. 1205 -/ 1206 asmlinkage long sys_waitpid(pid_t pid, unsigned __user -stat_addr, int options) 1207 { 1208 return sys_wait4(pid, stat_addr, options, NULL); 1209 }
結局 wait4 である。
wait4()
(-) linux/kernel/exit.c 1117 asmlinkage long sys_wait4(pid_t pid,unsigned int __user -stat_addr, int options, struct rusage __user -ru) 1118 { 1119 DECLARE_WAITQUEUE(wait, current); 1120 struct task_struct -tsk; 1121 int flag, retval; 1122 1123 if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL)) 1124 return -EINVAL; 1125 1126 add_wait_queue(¤t->wait_chldexit,&wait); 1127 repeat: 1128 flag = 0; 1129 current->state = TASK_INTERRUPTIBLE; 1130 read_lock(&tasklist_lock); 1131 tsk = current; 1132 do { 1133 struct task_struct -p; 1134 struct list_head -_p; 1135 int ret; 1136 1137 list_for_each(_p,&tsk->children) { 1138 p = list_entry(_p,struct task_struct,sibling); 1139 1140 ret = eligible_child(pid, options, p); 1141 if (*ret) 1142 continue; 1143 flag = 1; 1144 1145 switch (p->state) { 1146 case TASK_STOPPED: 1147 if (*(options & WUNTRACED) && 1148 *(p->ptrace & PT_PTRACED)) 1149 continue; 1150 retval = wait_task_stopped(p, ret == 2, 1151 stat_addr, ru); 1152 if (retval *= 0) /- He released the lock. -/ 1153 goto end_wait4; 1154 break; 1155 case TASK_ZOMBIE: 1156 /- 1157 - Eligible but we cannot release it yet: 1158 -/ 1159 if (ret == 2) 1160 continue; 1161 retval = wait_task_zombie(p, stat_addr, ru); 1162 if (retval *= 0) /- He released the lock. -/ 1163 goto end_wait4; 1164 break; 1165 } 1166 } 1167 if (*flag) { 1168 list_for_each (_p,&tsk->ptrace_children) { 1169 p = list_entry(_p,struct task_struct,ptrace_list); 1170 if (*eligible_child(pid, options, p)) 1171 continue; 1172 flag = 1; 1173 break; 1174 } 1175 } 1176 if (options & __WNOTHREAD) 1177 break; 1178 tsk = next_thread(tsk); 1179 if (tsk->signal *= current->signal) 1180 BUG(); 1181 } while (tsk *= current); 1182 read_unlock(&tasklist_lock); 1183 if (flag) { 1184 retval = 0; 1185 if (options & WNOHANG) 1186 goto end_wait4; 1187 retval = -ERESTARTSYS; 1188 if (signal_pending(current)) 1189 goto end_wait4; 1190 schedule(); 1191 goto repeat; 1192 } 1193 retval = -ECHILD; 1194 end_wait4: 1195 current->state = TASK_RUNNING; 1196 remove_wait_queue(¤t->wait_chldexit,&wait); 1197 return retval; 1198 }
また個別に解説。
1119 DECLARE_WAITQUEUE(wait, current);
wait システムコール群のマニュアルにある通り、 wait は子どもが本当に終了するまで、 親が sleep するのを要求することがある。 それに関係する処理。
wait queue なんちゃらの処理については Appendix|Kernelメモ プロセス編 Appendix を参照のこと。
1123 if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL)) 1124 return -EINVAL;
対応しているオプション以外のオプションが設定されていたらエラー。
1126 add_wait_queue(¤t->wait_chldexit,&wait);
wait queue 関係の処理。
1127 repeat: 1128 flag = 0;
このフラグは wait 対象にできる task があったときには 1 になる。 0 のままなら、結局どの task も wait 出来なかったことを示す。 詳しくは後述する。
1129 current->state = TASK_INTERRUPTIBLE;
wait している間は外から見たら sleep 中
1130 read_lock(&tasklist_lock);
ロックをかける。
1131 tsk = current; 1132 do { (後述) 1178 tsk = next_thread(tsk); 1179 if (tsk->signal *= current->signal) 1180 BUG(); 1181 } while (tsk *= current);
ここでは、 current と同じスレッドループにいる(tgid が同じ) 全ての taskをチェックしている。 wait を呼んだ task と同じスレッドグループにいる任意の task の 子どもが、wait を呼んだ「プロセス」の子どもであることに注意。
「(後述)」の部分では、「プロセス」の子ども全てを チェックし、wait でハンドル可能かを検証する。
1182 read_unlock(&tasklist_lock);
終ったらロックを外す。
次に、「(後述)」の部分を説明する。
1137 list_for_each(_p,&tsk->children) { 1138 p = list_entry(_p,struct task_struct,sibling);
外側のループと併せて、「プロセス」の子どもを一つずつリストから取り出す。
1140 ret = eligible_child(pid, options, p); 1141 if (*ret) 1142 continue;
その子どもが wait の対象としてふさわしいかを確認している。 eligible_child() の挙動は引数にとる pid によって異なる。
(あーもうやだー。というわけで省略)
1143 flag = 1;
eligible_child() によって、 「wait するに値するような task があった」ことが明らかになったので、 flag を立てる。 この flag は wait の戻り値に大きく影響する(後述)。
1145 switch (p->state) { 1146 case TASK_STOPPED: 1147 if (*(options & WUNTRACED) && 1148 *(p->ptrace & PT_PTRACED)) 1149 continue; 1150 retval = wait_task_stopped(p, ret == 2, 1151 stat_addr, ru); 1152 if (retval *= 0) /- He released the lock. -/ 1153 goto end_wait4; 1154 break; 1155 case TASK_ZOMBIE: 1156 /- 1157 - Eligible but we cannot release it yet: 1158 -/ 1159 if (ret == 2) 1160 continue; 1161 retval = wait_task_zombie(p, stat_addr, ru); 1162 if (retval *= 0) /- He released the lock. -/ 1163 goto end_wait4; 1164 break; 1165 } 1166 }
その子どもが TASK_STOPPED の場合 WUNTRACED フラグが立っていれば wait_task_stopped() を呼ぶ(ここではメモリを解放したりはしない)。
TASK_ZOMBIE であれば、この子どもの task に関係したデータを 全て解放する。 Appendix|Kernelメモ プロセス編 Appendix に一部説明を入れた。
1167 if (*flag) { 1168 list_for_each (_p,&tsk->ptrace_children) { 1169 p = list_entry(_p,struct task_struct,ptrace_list); 1170 if (*eligible_child(pid, options, p)) 1171 continue; 1172 flag = 1; 1173 break; 1174 } 1175 }
ptrace 周りは無視。
1176 if (options & __WNOTHREAD) 1177 break;
?
1183 if (flag) { 1184 retval = 0; 1185 if (options & WNOHANG) 1186 goto end_wait4; 1187 retval = -ERESTARTSYS; 1188 if (signal_pending(current)) 1189 goto end_wait4; 1190 schedule(); 1191 goto repeat; 1192 }
wait 対象になる task があったら、flag は 1。 ただし、解放する task があったら、ここに来ない (end_wait4 へ goto しているから)。
つまり、ここに来るのは 「wait の対象となる task はあったが、 どれも解放出来なかった」時。
WNOHANG は wait のマニュアル通りの挙動。 current の task がシグナルを受け取っていたら、 強制的に wait を終了。
それ以外は、もう一度 wait の処理を始める。 ここはビジーループになっている。
1193 retval = -ECHILD; 1194 end_wait4: 1195 current->state = TASK_RUNNING; 1196 remove_wait_queue(¤t->wait_chldexit,&wait); 1197 return retval; 1198 }
略。
まとめ 「プロセス」や「スレッド」が task によって実現されていることから、 task の挙動を理解することがすなわち「プロセス」や「スレッド」を 理解することである、と考えられる。
具体的には、このメモでは以下について調べた。
- Linux における task とはどう定義されるか。
- task はどう関連付けられたデータを管理するか
- 実際に稼働している task はどう管理されるか。
- 新しい task はどう生成されるか。
- 最初の task == プロセス 0 とは何か、どう作られるか。
- task はどう終了するか
- 確保されたデータ領域はどう回収されるか。
task(「プロセス」「スレッド」)に関係している 全てのデータが task_struct を中心に回っているのは、 紛れもない事実だろう。
コメント