[Linux]Kernelメモ プロセス編

スポンサーリンク

参照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
Index of /pub/linux/kernel/v2.6/

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_VFORKvfork(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_STOPPEDTASK_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(&current->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, &regs, 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_ZOMBIEexit後、親がwait()を投げていない
TASK_DEAD2.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(&current->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(&current->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(&current->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(&current->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(&current->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(&current->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(&current->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(&current->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, &regs, 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, &regs, 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, &regs, 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(&regs, 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, &regs, 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 with atexit' 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 with atexit' 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(&current->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(&current->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(&current->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(&current->wait_chldexit,&wait);
1197         return retval;
1198 }

略。

まとめ 「プロセス」や「スレッド」が task によって実現されていることから、 task の挙動を理解することがすなわち「プロセス」や「スレッド」を 理解することである、と考えられる。

具体的には、このメモでは以下について調べた。

  • Linux における task とはどう定義されるか。
  • task はどう関連付けられたデータを管理するか
  • 実際に稼働している task はどう管理されるか。
  • 新しい task はどう生成されるか。
    • 最初の task == プロセス 0 とは何か、どう作られるか。
  • task はどう終了するか
    • 確保されたデータ領域はどう回収されるか。

task(「プロセス」「スレッド」)に関係している 全てのデータが task_struct を中心に回っているのは、 紛れもない事実だろう。

コメント

タイトルとURLをコピーしました