initとは
http://ja.wikipedia.org/wiki/Init
initは、UNIX系システムのプログラムのひとつであり、他の全てのプロセスを起動する役目を持つ。デーモンとして動作し、一般にPID 1 を付与される。
全てのプロセスはinitの子プロセスになります。
まず、「init」は誰が起動しているのか?
⇒kernelです。
例えば、BeagleBoardでAndroidを起動する場合、ブートローダからカーネルに対してカーネルパラメータとして「init=/init」を渡しています。
bootargsとしてカーネルに対して色々オプションを指定出来ます。
一般的にinitは「/sbin/init」のようです。
詳しくは、横浜PF部で発表した時の資料を参照して下さい。
https://docs.google.com/a/android-pf.org/viewer?a=v&pid=sites&srcid=YW5kcm9pZC1wZi5vcmd8eW9rb2hhbWF8Z3g6MzhlZDYwMTVmYWM2MTk3YQ
kernelはinitを起動するまでに何をしているか?
ttyをstdin, stdout, stderrorに割り付けて使えるようにしている。
tty(テレタイプライター)と言われても、ピンと来なかったので調べてみました。
tty
http://ja.wikipedia.org/wiki/Tty
テレタイプ端末
http://ja.wikipedia.org/wiki/%E3%83%86%E3%83%AC%E3%82%BF%E3%82%A4%E3%83%97%E7%AB%AF%E6%9C%AB
おお、最初はディスプレイがなくて、こういうタイプライターみたいなのでガシャガシャ打って、結果は紙に出てきたわけですな。
話を戻して
kernelがinitを起動しているのは、分かったけど、どう起動しているか?
最初に書いたように、プロセスを起動するのは、「init」の役割です。
では、まだ「init」が起動していない状況で、どうやって起動するかのが、今回の話のキモです。
kernelのmain.cを見ています。
http://www.tamacom.com/tour/kernel/unix/S/88.html
この辺でデバイスの初期化とルートファイルシステムのマウントを行っています。
46 /*
47 * Initialize devices and
48 * set up 'known' i-nodes
49 */
50
51 clkstart();
52 cinit();
53 binit();
54 iinit();
55 rootdir = iget(rootdev, (ino_t)ROOTINO);
56 rootdir->i_flag &= ~ILOCK;
57 u.u_cdir = iget(rootdev, (ino_t)ROOTINO);
58 u.u_cdir->i_flag &= ~ILOCK;
59 u.u_rdir = NULL;
コメントにあるように、initプロセスの生成を実行しています。
61 /*
62 * make init process
63 * enter scheduling loop
64 * with system process
65 */
66
67 if(newproc()) {
68 expand(USIZE + (int)btoc(szicode));
69 estabur((unsigned)0, btoc(szicode), (unsigned)0, 0, RO);
70 copyout((caddr_t)icode, (caddr_t)0, szicode);
71 /*
72 * Return goes to loc. 0 of user init
73 * code just copied out.
74 */
75 return;
76 }
77 sched();
注目するのは、newproc()です。
これは、以前fork()のところで出てきました。
http://www.tamacom.com/tour/kernel/unix/S/98.html#L457
fork()の中でnewproc()を使って新しいプロセスを作成しています。
なので、fork()相当のことをやっていることになります。
では、新しいプロセスをinitに化けさせる必要があります。
70 copyout((caddr_t)icode, (caddr_t)0, szicode);
がその処理です。
copyoutとは、カーネル空間からユーザ空間へ値をコピーする機能です。
以下参照
http://www.wdic.org/w/TECH/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E7%A9%BA%E9%96%93
icodeの内容をアドレス0にszicodeサイズコピーしています。
icodeの中身は以下の通りです。
ええ!!
exec("/etc/init");と同じことがアセンブラで書かれています。それも配列に!!
13 /*
14 * Icode is the octal bootstrap
15 * program executed in user mode
16 * to bring up the system.
17 */
18 int icode[] =
19 {
20 0104413, /* sys exec; init; initp */
21 0000014,
22 0000010,
23 0000777, /* br . */
24 0000014, /* initp: init; 0 */
25 0000000,
26 0062457, /* init: */
27 0061564,
28 0064457,
29 0064556,
30 0000164,
31 };
なので、
親プロセス(kernel)は、
77 sched();
sched()は無限ループに入って、スケジューラ(スワッパー)として動作する
子プロセスは、exec("/etc/init");を0番地に書いてリターン
68 expand(USIZE + (int)btoc(szicode));
69 estabur((unsigned)0, btoc(szicode), (unsigned)0, 0, RO);
70 copyout((caddr_t)icode, (caddr_t)0, szicode);
71 /*
72 * Return goes to loc. 0 of user init
73 * code just copied out.
74 */
75 return;
ライオンズ本によると、リターンした後で、ユーザモードアドレスの0番地の命令を実行せよ
に相当する命令が走るそうです。315ページ参照
それで、exec("/etc/init");が実行されて、めでたくinitが起動することになります。
●別の話題(割り込み)
基本的には、困った窓口
プログラムはPC(プログラムカウンター)に次に実行する命令のアドレスが書かれています。
しかし例えば
0で除算した
メモリのないところを参照した
などが発生すると、次の命令が実行が出来ないので止まります。
そんなとき、割り込みが発生して、発生原因によって、リカバリあるいは、悪い子プログラムを懲らしめる処理をします。
ちなみにメモリの仮想化も割り込みを使っています。
http://ja.wikipedia.org/wiki/%E4%BB%AE%E6%83%B3%E8%A8%98%E6%86%B6
あるページが使用不可とされている場合(物理メモリに対応しておらず、スワップ領域に内容がある場合など)、CPUがそのページ内のメモリ位置を参照しようとしたとき、ハードウェアの機構がオペレーティングシステムに、一般にページフォールトと呼ばれる例外を通知する。これにより実行コンテキストはオペレーティングシステム内の例外処理ルーチンにジャンプする。そのページがスワップ領域にあるなら、そのルーチンは「ページスワップ」と呼ばれる処理を実行して必要なページの内容を物理メモリに読み込む。
ページフォルトが発生して、例外処理ルーチンで必要なページを読み込んで復帰するので、プログラムとしては裏でそんな処理がなかったかのように処理を継続できるわけですね。
割込処理を実行するために、どこの場所(アドレス)に飛べば良いかを示す情報は、割り込みベクタと呼ばれるテーブルに書かれています。
ターゲットのCPUやボードによって、異なるので仕様を見ながら作成しますが、どのCPUでも考え方は同じです。
0 件のコメント:
コメントを投稿