Linux Kernel Development Chapter 3: Process Management

Linux Kernel Development Chapter 3: Process Management
Katsuhiro Horiba [email protected] 導入
•  Chapter3 では Process のコンセプトについて
紹介する –  Process は Thread と同様に基本的な Unix の概念
である •  Linux Kernel がどのようにプロセスを管理して
いるかを説明する –  Kernel 内でどのように列挙されているか –  どのように作られるのか –  どのように死ぬのか Processとは
•  広義では実行中のプログラム •  実行中のプログラムコード(*1)だけではなく、
様々なリソースをひとまとめにしたものである –  Open File / Pending Signal / internal Kernel Data /
Process State / Memory Space Mappings / Threads of execuOon / Data SecOon •  実際には実行中のプログラムコードの結果(と言
うか成り行き)である •  そしてカーネルはそれを透明かつ効果的に管理
する –  (*1)実行中のプログラムコード(text sec'on ßコードファイルのプログラム領域、分から
ない人は ELF について調べよう) Thread
•  プログラム内で実行中のオブジェクト –  当然ながら複数同時に存在する –  独立したプログラムカウンター、レジスター、スタック
を持つ •  Kernel Scheduler は Thread の管理をしている –  Process ではないことに注意! •  一般的には1 Process = 1 Thread –  しかし現代の OS ではマルチスレッドが普通 –  Linux では Thread と Process を異なるものとして扱わ
ない、Thread = 特殊な Process ßん?んん?? Process と Thread
つまりこう言う事だってばよ? #種明かしは後ほど
Thread = Special Process
Thread
Memory
Process Thread
Thread
Memory
State
Register
Stack
Program Counter State
Process による仮想化概念
•  仮想プロセッサ –  Process は仮想プロセッサを独占的に利用してい
ると錯覚している –  すなわち Process 単体では他の Process との競合
を気にする必要が無い •  仮想メモリ –  Process は仮想プロセッサに付属された仮想メモ
リ全体を独占的に利用していると錯覚している –  すなわち他の Processが利用しているメモリ領域
の上書き等を気にする必要が無い
Process != Program
•  Process = “実行中の”プログラム+リソース –  同じプログラムを実行中のプロセスは存在し得る –  例えば /bin/sh はログインしてるユーザの数だけ
存在しますよね fork() à exec() à exit() à wait()
•  Process を作る = fork() –  fork() は現在の Process の複製を作り出す –  複製元を親プロセス、複製先を子プロセスと呼ぶ •  どのくらい複製かと言うとメモリ空間なども全て同じ •  したがって fork() の前に作った変数やその中身(すなわちスタック)も
全く同じ状態にある •  Process を実行ファイルで上書きする = exec() –  Process に新しいアドレススペースをもたらし、そこに実行ファイ
ルをロードする •  Processを 終了する = exit() –  Process を終了し、関連するリソースを全て解放する –  Process は終了後、 Zombie State に落ちる •  終了 Process を看取る = wait() fork() & exec()
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char **argv){ pid_t p; int status; int buf = 1; p = fork(); if(p == -­‐1) { perror("fork\n"); return -­‐1; } else if(p == 0){ prind("this is child, value of buf = %d\n",buf); buf+=10; prind("child add 10 to the buf, then value of buf = %d\n",buf); execlp("ls", "ls", “-­‐al”, NULL); exit(1); } else { prind("this is parent, value of buf = %d\n",buf); buf+=20; prind("parent add 20 to the buf, then value of buf = %d\n",buf); wait(&status); exit(1); } return 1; } [qoo@mcast:~/src/test/c/syscall/process/]% gcc -­‐o fork fork.c [qoo@mcast:~/src/test/c/syscall/process/]% ./fork this is parent, value of buf = 1 parent add 20 to the buf, then value of buf = 21 this is child, value of buf = 1 child add 10 to the buf, then value of buf = 11 total 40 drwxr-­‐xr-­‐x 2 qoo qoo 4096 Apr 29 18:15 . drwxr-­‐xr-­‐x 3 qoo qoo 4096 Apr 25 20:01 .. -­‐rwxr-­‐xr-­‐x 1 qoo qoo 5182 Apr 29 17:45 a.out -­‐rwxr-­‐xr-­‐x 1 qoo qoo 5304 Apr 29 18:15 fork -­‐rw-­‐r-­‐-­‐r-­‐-­‐ 1 qoo qoo 634 Apr 29 18:15 fork.c -­‐rwxr-­‐xr-­‐x 1 qoo qoo 5323 Apr 29 18:13 waitpid -­‐rw-­‐r-­‐-­‐r-­‐-­‐ 1 qoo qoo 425 Apr 29 18:14 waitpid.c Process の一生
親プロセス
fork()
exec()
子プロセス
親プロセス
子プロセス
wait()
resume
exit()
fork()
waitpid()
exec()
exit()
Zombie State
exit() à Zombie Process à waitpid()
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char **argv){ pid_t p; int status; p = fork(); if(p == -­‐1) { perror("fork\n"); return -­‐1; } else if(p == 0){ prind("this is child, then exit\n"); exit(1); } else { sleep(10); waitpid(p, &status, 0); prind("waitpid done, status = %d\n", status); exit(1); } return 1; } [qoo@mcast:~/src/test/c/syscall/]% gcc –o waitpid waitpid.c [qoo@mcast:~/src/test/c/syscall/]% ./waitpid this is child, then exit waitpid done, status = 256 zsh: suspended ./waitpid [qoo@mcast:~/src/test/c/syscall/]% bg [1] + conOnued ./waitpid [qoo@mcast:~/]% ps ax | grep waitpid 13992 pts/1 S+ 0:00 ./waitpid 13993 pts/1 Z+ 0:00 [waitpid] <defunct> 13995 pts/3 S+ 0:00 grep waitpid [qoo@mcast:~/src/test/c/syscall/]% waitpid done, status = 256 [1] + exit 1 ./waitpid [qoo@mcast:~/src/test/c/syscall/]% man 1 ps /SNIP/ PROCESS STATE CODES Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to describe the state of a process. D UninterrupOble sleep (usually IO) R Running or runnable (on run queue) S InterrupOble sleep (waiOng for an event to complete) T Stopped, either by a job control signal or because it is being traced. W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z Defunct ("zombie") process, terminated but not reaped by its parent. Process Descriptor
•  struct task_struct 構造体 –  Process の属性が記述されている構造体 –  Process Descriptor と呼ばれる –  Kernel は Process Descriptor を list 構造で管理している •  Linux kernel は double linked-­‐list •  一般的な OS では配列だそうです •  struct thread_info 構造体 –  Process Kernel Stack の属性が記述されている –  Process Descriptor へのポインタが記述されている –  thread_info は Process Kernel Stack の底に置かれる •  Stack pointer から余計なレジスターを使わずにアクセス可能にする •  current_thread_info() でアクセス可能 –  当然アーキテクチャ依存 •  linux/arch/${Architecture}/include/asm/thread_info.h •  これらの領域は、slab allocator (*1)によって確保される (*1)Linux Kernel のメモリ領域割り当て方式。Chapter12 を参照。
各 Process Kernel Stack にはその属性が記述された thread_info がスタックの「一番底」 thread_info には Process Descriptor へのポインタが書かれている。
Process Kernel Stack
Head of Stack (highest memory address)
Stack Pointer
struct thread_info
struct thread_info{ struct task_struct *task struct exec_domain … __u32 flags; __u32 status; __u32 cpu; … }
struct task_struct { struct { state; t ask_struct t ask_struct volaOle {l ong struct D
vescriptor> olaOle long state; <Process void *stack; volaOle long state; v
oid *
stack; struct task_struct {
tomic_t usage; void * astack; a
tomic_t usage; volaOle long state; unsigned atomic_t usage; int flags; unsigned flags; void * stack; … int iflnt unsigned ags; … usage; atomic_t …}
}
unsigned int flags; }
… }
task_list 双方向リスト
ちなみに先頭は init_task と呼ばれるグローバル変数 そこを起点に、task_list を順々にアクセスできる #include <linux/init_task.h> extern struct task_struct init_task; #define for_each_process(p) \ for (p = &init_task ; (p = next_task(p)) != &init_task ; ) #define do_each_thread(g, t) \ for (g = t = &init_task ; (g = t = next_task(g)) != &init_task ; ) do Process Kernel Stackとタスク切り替え
•  Linux はタスク切り替えを特定のプロセッサの命
令セットに頼っていない –  プロセッサが提供するマルチタスク用の機能を使って
いない •  例えば x86 では TSS(Task State Segment) と呼ばれる領域に
タスクの状態を格納し、割り込みや特権モードの切り替えと
同時に、CPU が新しいコンテキストをロードする仕組みがあ
る –  主な理由は CPU に依存したコンテキストの制限を回
避するため •  X86 の場合はテーブルサイズ、速さ、ポータビリティーなど
Storing the Process Descriptor
•  各 Process はユニークな PID によって一意に識別される – 
– 
– 
– 
PID の型は pid_t で、数字で表現される デフォルトは MAX 32768(short int) /proc/sys/kernel/pid_max を書き換えることで、400万程度増強可能 (see linux/include/linux/threads.h) • 
• 
• 
• 
/* * This controls the default maximum pid allocated to a process */ #define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000) • 
• 
• 
• 
• 
• 
/* * A maximum of 4 million PIDs should be enough for a while. * [NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.] */ #define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \ (sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT)) current_xxx マクロ
•  前述の通り、現在稼働中のタスクのステータスは
current_threads_info() で取得可能 –  プロセッサ依存なので、current_xxx マクロは当然ながらプロ
セッサ毎に定義されている –  例えば x86 の場合のアセンブラコードはコレ movl $-­‐8192, %eax andl %esp, %eax –  一方 PowerPC とかの場合は Register がたくさんあるので、異な
るやり方で current_threads_info() は実装される •  current_thread_info() マクロで得られた情報から、
task_info 構造体の中身にアクセスする場合は、普通の構
造体アクセス方法と同じ。 –  例えば実行中のタスクの Process Descriptor を得るには
current_thread_info()-­‐>task;
Process State
• 
Process State は全部で5種類 –  全てのプロセスは「必ず」この5種類のうち一つのステートを持つ –  TASK_RUNNNING •  実行可能な状態。実際に動いているか、動くのを待っている状態(run-­‐queue に入ってる状態)かは関
係なし •  ユーザスペースで実行している場合、これ以外のステートは無い、当然カーネルスペースにおいても
実行中 –  TASK_INTERRUPTIBLE •  眠っている状態(ブロックされている状態)、終了待ち状態 •  この状態にある時、カーネルはプロセスのステートを TASK_RUNNING にセットする。 •  プロセスはシグナルを受け取った場合、目覚めて実行可能状態になる。 –  TASK_UNITERRUPTIBLE •  シグナルを受け取っても目覚めて実行可能状態にならない。これ以外は TASK_INTERRUPTIBLE と同
様のステート。 •  インタラプトを待たない、または、イベントが素早く発生する場合を想定する。 •  なぜならタスクはシグナルに反応しないので、 TASK_INTERRUPTBILE より利用頻度は低い。 –  __TASK_TRACED •  プロセスがデバッガなど(ptrace とか)でトレースされている状態。 –  __TASK_STOPPED •  プロセスの実行が止まった状態。実行中でもその資格も無い状態。 •  この状態は SIGSTOP, SIGTSTP, SIGTTIN、SIGTTOU を受け取った、もしくはデバッガが走っている最中
に何らかのシグナルを受け取った状態 Processの一生(ステート編)
既存の task が
fork() を実行して
新しいプロセスを
発行 context_switch() を呼ばれる事で、 実行中タスクの切り替え
STATE: TASK_RUNNING 実行可能状態 (まだ実行されていない)
run-­‐queue に入り 実行待ち状態へ exit()を実行して task終了
STATE: TASK_RUNNING 実際に実行状態 State: TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE 何らかのイベントによって、 休眠中のプロセス。 何らかのイベントによって復帰可能 (TASK_INTERRUPTIBLE の場合は シグナルも含む) wait-­‐queue に入り 休眠待ち状態へ ManipulaOng the Current Process State
•  Kernel コードではしばしばプロセスのステートを変化させる
必要がある、その場合 –  set_task_state(task, state); –  このマクロは thread_info 構造体を触るので、当然ながら architecture 依存マクロであり、linux/arch の下を参照すべし –  なお set_current_tate(state) は上記とほぼ同じ。 –  詳しくは linux/include/linux/sched.h を参照 –  #define TASK_RUNNING 0 –  #define TASK_INTERRUPTIBLE 1 –  #define TASK_UNINTERRUPTIBLE 2 –  #define __TASK_STOPPED 4 –  #define __TASK_TRACED 8 Process Context
•  Process の重要な機能として、プログラムの実行
がある –  具体的には実行ファイルを読み込み、プログラムの
アドレススペースに展開し実行することである –  一般的なプログラムの場合、ユーザスペースで完結
するが、システムコールを発行した場合や、何らかの
割り込み例外処理を処理する場合にカーネルスペー
スに移動する(Context Switch) –  その際にプロセスはユーザースペースでの実行を再
開する。 •  システムコールと例外ハンドラをカーネルスペー
スへ伝えるインターフェースは一つだけ Process Family Tree
•  Unix のプロセスは階層構造になっている –  全てのプロセスは init プロセス(pid=1)の子孫 –  つまり init は一番最初に起動されるプロセス •  カーネルは起動の最後に init プロセスを実行する •  Init はシステムの起動スクリプトを読み込み、順番に他のプ
ログラムを起動していく •  当然、他のプログラムを生成する際 fork() を使う –  全てのプロセスは一つだけ親プロセスを持つ •  逆に全てのプロセスは子プロセスを0から複数持てる •  同じ親から派生した子プロセスは兄弟(sibling)と呼ばれる Processの親子関係
• 
プロセスの親子関係は task_struct 構造体の記述される –  親プロセス struct task_struct __rcu *parent –  子プロセス struct list_head children –  兄弟は struct list_head sibling • 
なお、カーネル内のリンクリストなどは linux/include/linux/{types.h, list.h} を参照
せよ。
linux/include/linux/sched.h struct task_struct { /SNIP/ /* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respecOvely. (p-­‐>father can be replaced with * p-­‐>real_parent-­‐>pid) */ struct task_struct __rcu *real_parent; /* real parent process */ struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */ /* * children/sibling forms the list of my natural children */ struct list_head children; /* list of my children */ struct list_head sibling; /* linkage in my parent's children list */ struct task_struct *group_leader; /* threadgroup leader */ /SNIP/ } <linux/include/linux/types.h > struct list_head { struct list_head *next, *prev; }; <linux/include/linux/list.h > * list_for_each -­‐ iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) \ for (pos = (head)-­‐>next; pos != (head); pos = pos-­‐>next) また、現在実行中のタスクは current マクロで取得可能 <linux/arch/${architecture}=“x86”/include/asm/current.h> staOc __always_inline struct task_struct *get_current(void) { return this_cpu_read_stable(current_task); } #define current get_current() 実際のプロセス探索コード
•  先に上げた道具を利用して、あるプロセスの子プロ
セスを探すコードを書いてみると struct task_struct *task, current; struct list_head *list; list_for_each(list, &current-­‐>children){ task = list_entry(list, struct task_struct, sibling); }
•  さらに先に述べたとおり init の Process Descriptor はグローバル変数 init_task であるため、あるプロセ
スから init まで階層構造を上るコードを書くなら struct task_struct *task, current; for(task = current, task != &init_task, task = task-­‐>parent) à /* task variable is now poinOng init task*/ 実際のプロセス探索に役立つ マクロ、カーネル関数
•  Process Descriptor は双方向リンクなので、一つの Process を始点として、他の全てのプロセスにアクセ
ス可能 •  便利なマクロ next_task(task) / prev_task(task) list_entry(task-­‐>tasks.next, struct task_struct, tasks) list_entry(task-­‐>tasks.prev, struct task_struct, tasks) •  さらに全てのリストを一気に探索するマクロ struct task_struct *task; for_each_process(task) { printk(“%s [%d] \n”, task-­‐>comm, task-­‐>pid); } Process CreaOon
•  一般的な OS は spawn メカニズム –  新しいメモリ領域を確保、実行ファイルをロード、
実行の順序 •  Unix は fork() & exec() メカニズム –  既に実行中のプロセスを fork() でコピーする –  親プロセスと子プロセスで異なるのは PID だけ –  exec() で新しい領域を確保、実行ファイルをロー
ド、実行の順序 –  一見無駄だよね! fork() & exec() の最適化
•  大前提として一般的に親プロセスが fork() で生み出した子プ
ロセスは、「直後に exec() をコールする」 •  Copy-­‐on-­‐Write(COW) –  子プロセスがコピーされたメモリ領域を書き込むまで、実
際のデータをコピーしない –  仮想メモリ的にはコピーしてるけど、実際の物理メモリに
は何も書き込ない –  読み込む場合は親プロセスにマップされてる物理メモリを、
子プロセスからも参照する(Read-­‐Only) •  fork() 後に実行するのは子プロセスが先 –  fork() 直後に exec() を実行してくれれば、COW を利用して
いる関係上、物理メモリのコピーは発生しない 図解 fork() & exec() の最適化
親プロセス
fork()
a b c d e f 親プロセスの VM 空間
a b b c
b b c d e f
子プロセス
d e
f
物理メモリ空間
子プロセスの VM 空間
write(file_desc, “a”,1, )
図解 fork() & exec() の最適化
親プロセス
fork()
a b c d e f 親プロセスの VM 空間
a
b c
d e
f
子プロセスの VM 空間は 全く新しいマッピングへ
子プロセス
exec()
fork() を追いかけてみよう
•  Linux の fork(), vfork(), __clone() は clone() シ
ステムコールのフロントエンド –  fork(2) はライブラリコールだった! –  clone() システムコールが呼ばれると、Kernel 内で
do_fork() 関数が呼ばれる(see linux/kernel/
fork.c) •  do_fork() の引数にある flags で親から子へ受け継ぐパ
ラメーターを決める •  と言うことで do_fork() を読んでみよう do_fork() の流れ
•  引数 flags から trace opOon を決める •  copy_process() を呼ぶ –  dup_task_struct() を呼び thread_info, task_struct 構造体をコピーする –  子プロセスの thread_info.state を TASK_UNINTERRUPTIBLE にセットし、イベン
トがあるまで実行待ち状態にする –  copy_flags() を呼び、 task_struct の flags を update する •  PF_SUPERPRIV(super user privillage) を外す •  PF_FORKNOEXEC(this process has not called exec()) を set する
–  alloc_pid() によって新しい子プロセス用の PID を取得する –  clone() を呼んだ時の引数、flags を copy_process() に私渡し、それによって親
プロセスから引き継ぐリソースを判別し、必要に応じてコピーする(詳しくは後
述) –  以上で copy_process() はゴミを掃除して子プロセスへのポインタを返す •  返ってきた子プロセスへのポインタを基に、子プロセスを動作させる –  前述の通り、kernel は子プロセスを先に動作させる –  いきなり exec() が呼ばれれば儲けもので、メモリ等のコピーをしなくて良い そういやvFork()
•  面倒だからまあいっか。。。
さてと、ようやく The Linux ImplementaOon of Thread
•  はじめに Thread とは –  共有するアドレス空間の中で、同じプログラム内
で、複数のタスクが、複数のタスクが実行される
機能を提供する –  もちろん Open している File などのリソース情報も
共有する –  結果として Concurrent Programming ができるよう
になる Linux Thread ImplementaOon vs Others one.
•  Windows / Soralis –  Thread は Kernel が Process と明示的に別物と扱う –  Lightweight Process なんて呼ばれる事も •  Thread CreaOon / ExecuOon が圧倒的に速いらしい –  Process は内包する各 Thread に共有リソースを教える必要が
あり、各 Thread もまた所有リソースを教える必要がある •  Linux における Thread –  Process と何ら変わりが無い –  Scheduler 的に、データ構造的に特別扱いしない –  二つ以上の Process でリソース共有できてさえいれば、それっ
て Thread じゃん!と言う思想 •  なぜなら、Process は fork() à exec() するも COW のおかげで十分速
いし、なにより特別扱いする子が要らなくてとてもシンプル! Thread ができるまで
•  Process と一緒!なので、clone() で作るよ! –  実は clone() にはフラグによって色々な Process の複
製方法がある –  一般的な fork() を clone() で表すと、 •  clone(SIGCHLD, 0); –  Thread を clone() で表すと •  clone(CLONE_VM | CLONE_FS | CLONE_FILE |
CLONE_SIGHNAD, 0);
•  このオプションは linux/include/linux/sched.h に定義されて
いる –  後は普通の Process と同じように扱われる
改めて Process と Thread
• 
• 
• 
• 
Thread は Process と同等の扱い clone() によって作られる 親プロセスのどのリソースを受け継ぎ共有するか によって Thread にも Process にもなる
Thread Process à exec() Memory
State
clone(CLONE_VM| CLONE_FS| CLONE_FILE| CLONE_SIGHNAD, 0);
Memory
Shared
State
Process Memory
State
clone(SIGCHLD, 0); = fork()
Kernel Thread
•  Kernel Thread とは –  Kernel の中でバックグラウンドで実行される処理
を担当させたい •  ユーザスペースへ出ないà Context Switch しない •  アドレススペースを持たないà task-­‐>mm = NULL •  何が言いたいかというと、メモリ空間は自分で管理しろ –  これ以外は通常の Process と区別無く、Kernel Scheduler は扱う •  その証拠に ps で見える •  % ps -­‐efL Kernel Thread ができるまで
•  Kernel Thread は、Kernel Thread “kthreadd” からのみ生成される –  (kthread と表記されてるのは本文の誤植か?) –  通常の Process と同様に clone() で生成 –  親になるのは kthreadd で、通常 pid=2 •  Process における init Process と同じ –  kthreadd では元々 kernel thread なので、全くもっ
て同じモノを作って、pid を返れば良いはず Kernel Thread が出来るまで
•  kthreadd を親として、clone() を行う –  kthread_create() マクロに対して、thread として働く Kernel 関数、それに与える引数等の情報を基に生成 •  ktherad_create の実態は kthread_create_on_node() •  kthread_create された直後は実行できない状態 •  wake_up_process() で起こされるまで待つ –  これらをひとまとめにしたマクロがkthread_run() •  Kernel Thread を終了させるには –  自ら do_exit() を呼ぶ –  kthread_stop() を Kernel がコールした場合 •  引数は thread を生成した時の task_struct
<linux/include/linux/kthread.h> #define kthread_create(threadfn, data, namefmt, arg...) \ kthread_create_on_node(threadfn, data, -­‐1, namefmt, ##arg) <linux/kernel/kthread.c> struct task_struct *kthreadd_task; struct kthread_create_info { /* InformaOon passed to kthread() from kthreadd. */ int (*threadfn)(void *data); void *data; int node; /* Result passed back to kthread_create() from kthreadd. */ struct task_struct *result; struct compleOon done; struct list_head list; }; <linux/kernel/kthread.c> * kthread_create_on_node -­‐ create a kthread. * @threadfn: the funcOon to run unOl signal_pending(current). * @data: data ptr for @threadfn. * @node: memory node number. * @namefmt: prind-­‐style name for the thread. * * DescripOon: This helper funcOon creates and names a kernel * thread. The thread will be stopped: use wake_up_process() to start * it. See also kthread_run(). * * If thread is going to be bound on a parOcular cpu, give its node * in @node, to get NUMA affinity for kthread stack, or else give -­‐1. * When woken, the thread will run @threadfn() with @data as its * argument. @threadfn() can either call do_exit() directly if it is a * standalone thread for which no one will call kthread_stop(), or * return when 'kthread_should_stop()' is true (which means * kthread_stop() has been called). The return value should be zero * or a negaOve error number; it will be passed to kthread_stop(). * * Returns a task_struct or ERR_PTR(-­‐ENOMEM). */ struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, const char namefmt[], ...) { Process の最期(not 最後)
•  Quote from the original, –  ”It is sad, but eventually processes must die” –  寂しいかな、しかしプロセスは最終的に必ず死ぬのよね。 •  Kernel は Process の終了を看取る –  Process に割り当てたリソースを回収したい –  終了した Process の子プロセスに親が死んだことを教える •  Process が死ぬ条件 –  自発的に死ぬ場合 •  明示的には自分が exit() を呼んだ時 •  暗黙的には main()関数が return した時 (C Compiler が exit() を return 後に置く) –  無意識に死ぬ場合 •  KILL などの Signal を受け取った時 •  例外処理が巧く動かなかったり、無視された時 –  いずれにせよ do_exit() が終了の仕事を請け負う do_exit()の流れ
役だった tool 群
•  unifdef –  ifdef で囲まれた部分を除いたソースを出力 –  Kernel のソースコードは ifdef だらけだが、動作の本質を
知るには不必要な部分が多い –  すっきり読むために利用した –  例)fork.c から #ifdef で囲われた部分を削除する –  % unifdef `egrep '^#ifdef' fork.c | sort | uniq | perl -­‐pe 's/
\#ifdef/-­‐D/g' | perl -­‐pe 's/\n/ /g'` fork.c | less •  Linux Cross Reference –  eden が [email protected] にメールしてたやつ –  関数やマクロ、構造体などの定義元、参照先がハイパー
テキスト化されている 参考資料
•  Linuxカーネルメモ
hˆp://wiki.bit-­‐hive.com/linuxkernelmemo/ •  Linuxの備忘録とか・・・hˆp://wiki.bit-­‐hive.com/north/ •  X86とコンテキストスイッチ
hˆp://www.slideshare.net/masamiichikawa/x86study •  システム奮闘記、CPUの特権モードの話
hˆp://www.geociOes.jp/sugachan1973/doc/
funto46.html •  Linuxメモリー・モデルの探求
hˆp://www.ibm.com/developerworks/jp/linux/library/
l-­‐memmod/ –  カーネル関数的do_fork()内でclone()関数によっ
てプロセスのコピーが行われる –  clone()によって出来るprocessはkernel内では
kernel threadである