Linux Kernel Development Chapter 3: Process Management
Katsuhiro Horiba qoo@sfc.wide.ad.jp 導入
• 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, ¤t-‐>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 が haccar@sfc.wide.ad.jp にメールしてたやつ – 関数やマクロ、構造体などの定義元、参照先がハイパー
テキスト化されている 参考資料
• 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である
© Copyright 2025 Paperzz