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, ¤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 が [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である
© Copyright 2024