2011年1月30日日曜日

第7回 V7から始めるUNIX講座 復習とまとめ(initの起動)

●第7回目:initの起動

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でも考え方は同じです。

2011年1月3日月曜日

第6回 V7から始めるUNIX講座 復習とまとめ

第6回 V7から始めるUNIX講座 復習とまとめ

●復習

前回でディレクトリファイルにファイル名とinodeが対になって管理している。

てっきり、ディレクトリのツリー構造をどこかに保持していると思ってた(DBのインデックスのように)

これは間違い。
あくまでディレクトファイルで管理している。
しかし、それだと辻褄が合わなくなる場合がある(突然電源がダウンした場合など)
その場合にinode(ファイル)としては存在するが、ディレクトリファイル上に管理情報がない状態になる。
そのファイルをlost+foundに置きます。
ファイル名が分からないので、inode番号のファイル名になります。
(備考)
本当はLost & Found(落とし物あずかり所)の意味らしいですが、&が特別な意味があるので+にしたと聞いたことがあります。

このチェックをするのが、fsckになります。
初期のUNIXでは、毎回fsckでチェックしていました。(この辺は割り切り仕様)

後になって、DBMSの考え方を取り込んで、ファイルシステムのロールバック、ロールフォワードが可能になりました。
それがジャーナルログになります。

●第6回目:ファイルシステム
ブロック単位で読み書きします。1ブロックは512バイトです。
フラットな構成になっていて、構成は以下の通りです。

1、SuperBlock(1ブロックくらいの小さい領域)
ファイルシステム全体で何個のファイルが使われているか、後何個のファイルを作ることが出来るか。空きブロックがどこにどれだけあるかなど。ファイルシステム全体を管理する構造を保持している。

2、inodeBlocks
UNIXファイルシステムでは、ファイルはinodeです。inodeへの操作がファイル操作になります。
inodeを特定するとファイルを特定することが出来ます。
→ファイルの実体はあくまでinodeです。
ただそれだと人間にとって辛いのでツリー構造のディレクトリにて、inodeに名前を付けたもの=ファイル名にてオペレーションを行います。
inodeBlocksにはinodeの配列が順番に並んでいるだけです。
ディレクトリについては、ツリー構造を保持しているわけではなく、ディレクトリファイルという特殊なファイルで構成していまs。(詳細は後述)

3、Block
 文字(ソースファイル)、ディレクトリの一部、a.outなどが入っている。

●SuperBlockの実装
http://www.tamacom.com/tour/kernel/unix/S/59.html


これがSuperBlockの構造になります。
1 /*
2 * Structure of the super-block
3 */
4 struct filsys {
5 unsigned short s_isize; /* size in blocks of i-list */
6 daddr_t s_fsize; /* size in blocks of entire volume */
7 short s_nfree; /* number of addresses in s_free */
8 daddr_t s_free[NICFREE];/* free block list */
9 short s_ninode; /* number of i-nodes in s_inode */
10 ino_t s_inode[NICINOD];/* free i-node list */
11 char s_flock; /* lock during free list manipulation */
12 char s_ilock; /* lock during i-list manipulation */
13 char s_fmod; /* super block modified flag */
14 char s_ronly; /* mounted read-only flag */
15 time_t s_time; /* last super block update */
16 /* remainder not maintained by this version of the system */
17 daddr_t s_tfree; /* total free blocks*/
18 ino_t s_tinode; /* total free inodes */
19 short s_m; /* interleave factor */
20 short s_n; /* " " */
21 char s_fname[6]; /* file system name */
22 char s_fpack[6]; /* file system pack name */
23 };


s_isize:inodeがどれくらいあるか
s_fsize:Blockがどれくらいあるか
SuperBlockのサイズは固定なので、管理情報としては存在しない
この構造はmkfsで作成します。

ファイルシステム全体でinodeとBlockをどれくらいの割合にするかはオプションで決められる。
例:小さいファイルを沢山作りたい。大きなファイルを少なく作りたい。

初期化はmkfsで行うが、マウントしてからは、SuperBlockはカーネルが頻繁に更新する。
例:df(ファイルシステムの空き容量を示す)、 duなんかはここから情報を取って表示している。

●inode
配列になっていて0、1、2・・・・・・・・・・・・・・・・・・・・・・・・・・・
となりますが、0番と1番は使われていない。2番から使います。
→UNIXV6は1番から使っていたらしい。V7から2番になった。
0番を使っていないのは、空き(未使用)を表現するため。
UNIXV7からは1番はBadBlock、Block不良の場合に使うようにしたので2番にしたのではないか。

ルートは2番を使います。
$ cd /
$ ls -di
2 .
iオプション(inode番号表示)を付けると/のinode番号が2であることが分かります。

/procや/sysなどマウントポイントになっているディレクトリが1になっていました。

●ファイルのリンク
UNIXでは、ハードリンクを使うと同じinodeに別の名前を複数付与できます。
$ echo > a
$ ln a b
$ ls -li
aとbは同じinode番号でリンクカウントが2になります。
$ ln b c
$ ls -li
aとbとcは同じinode番号でリンクカウントが3になります。

●ディレクトリのリンク
$ mkdir a
ディレクトリを作っただけでリンクカウントが2になっている。

$ ls -al

..
.はカレントディレクトリ、..は親ディレクトリ
ディレクトリもディレクトリという属性を持ったファイルでしかないが、ディレクトリとして存在するには、自分自身と親ディレクトリから参照されるので、作成しただけでリンクカウントが2になる。

そのため、
1、mkdirをしてディレクトリファイルの作成
2、親から作成したディレクトリを参照する設定
1と2の間で電源ダウンなどが発生すると、ファイルシステムに矛盾が発生する。
→そのため、ジャーナルのような概念がDBMSから入ってきた。

矛盾は発生するが、そこは割り切り。
矛盾が発生した場合はfsckでリカバリーする。

●ディレクトリとは
lsを実行すると、ファイルが表示される。
lsは「cat .」相当のことをやっている。(カレントディレクトリファイルの中身を表示)

V7で$ cat .を実行すると、inodeとファイル名の対で表示される。
16バイトで1エントリー。2バイトがinode、14バイトがファイル名(固定長)
→そのため、昔のUNIXのファイル名の長さが14文字という制限はここから。

ディレクトリファイルというのは、inodeとファイル名を対にして並べただけのシンプルな構成。

今後、これをベースにして、ファイル名の長さなどを拡張が行われていった。

inode番号が0のエントリは削除されたファイル(inode)

こういう構造なので、簡単に矛盾が発生するので当時のUNIXは起動時に必ずfsckが実行された。
後に、正しくシャットダウンされたという印がスーパーブロックに書いてあったら、fsckはスルーするように実装が追加された。

●dinode
http://www.tamacom.com/tour/kernel/unix/S/60.html


1 /*
2 * Inode structure as it appears on
3 * a disk block.
4 */
5 struct dinode
6 {
7 unsigned short di_mode; /* mode and type of file */
8 short di_nlink; /* number of links to file */
9 short di_uid; /* owner's user id */
10 short di_gid; /* owner's group id */
11 off_t di_size; /* number of bytes in file */
12 char di_addr[40]; /* disk block addresses */
13 time_t di_atime; /* time last accessed */
14 time_t di_mtime; /* time last modified */
15 time_t di_ctime; /* time created */
16 };
17 #define INOPB 8 /* 8 inodes per block */
18 /*
19 * the 40 address bytes:
20 * 39 used; 13 addresses
21 * of 3 bytes each.
22 */


di_modeにread, write, executeの3ビットが3つとファイルの種類(regular、directory)などが格納されている。
di_addrにBlockのどこに実体があるかが書いてある。

ディスクをダンプしてみると、一定のパターンで755が出現するのが分かる。
→inodeBlocksにinodeが順番に並んでいることが分かる。

●ディレクトリの作成(構造に従って記述)
1、inode Blocksから空いてるinodeを1つ確保する。
2、確保したinodeのdi_modeにdirectoryと書く。
3、自分と親の参照によりリンクカウントを2にする
4、実体はBlockに書き込むのでBlockから空いてるものを1つ確保する。
5、di_addrから確保したBlockを指すようにする。
6、Blockの中に2バイトのinode番号と14バイトのファイル名を並べる
7、使わなくなったら、inode番号を0にする。

●namei
http://www.tamacom.com/tour/kernel/unix/S/90.html

ファイル名(パス名)からinodeに変換する。
UNIXの核になる関数

●補足
SDをUbuntuでext3でmkfsした時のログです。

OS type: Linux
Block size=4096 (log=2)→ブロックサイズは4096のようです。
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
235248 inodes, 939802 blocks→inodeBlocksが235248、Blockが939802作成されています
46990 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=964689920
29 block groups
32768 blocks per group, 32768 fragments per group
8112 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736

Checking for bad blocks (read-only test): done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done