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

2010年12月19日日曜日

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

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

●復習

・2針クロックアルゴリズム
VAX(DECの32ビットマシン)はリファレンスビットがなかった。
ハードウエアによっては、エラー書き込み例外からの再実行が許さないものもあったので、この実装を参考にした。

これは、Copy on Writeのことでしょうか?
コピーした振りをしておいて、どちらかに書き込み(変更)があった場合にエラー書き込み例外が発生
その例外の中で本当に領域を探して割り当てて、再実行することにより、上位には何事もなかったかの
ように見せかける。

http://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%94%E3%83%BC%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%88

・BSD2.9のインストール
問題なし

stty erase '^H'の設定がうまく行かない件
UbuntuのターミナルがVT100と異なるためのよう、ちょっと後で調べます。

●第5回目:ファイルシステムとは?

まずいきなりソースコードを読んでも理解できない。
まずファイルシステムとは何か?という概観を理解して、どう実装しているのかをソースで確認しましょう。
実装詳細は次回以降として、今回はファイルシステムの説明です。
UNIXファイルシステム(UFS)については以下参照
http://ja.wikipedia.org/wiki/Unix_File_System


1、ファイルの実態
まずファイルについては、ハードディスクに格納されているわけですが、ディスクはブロックという単位でリード・ライトされます。
1ブロックは512Byteです。
ファイルの実態はブロック単位でハードディスクのどこかに格納されています。

2、ファイルシステムの役割
おおまかに言うと
1.どこを使うか(上記のブロック単位でどこからどこまで)
2.誰が使うか(ユーザ管理)

初期のころは黒板に書いて管理していたが、これらをマシンに管理されるようにしまた。

3、ファイルを管理(識別)するために
ファイルそのものを示すのはファイル名ではありません。
inodeがファイル管理のために使われます。
ファイル名は人間が分かりやすいようにinodeに対して名前を付けただけのものです。
ディレクトもファイル名の拡張です。
そのため、同じinodeに対して複数のファイル名を付与できます。

例:
$ cd /tmp
$ touch test
$ ls -l test
-rw-r--r-- 1 user user 0 2010- 12- 19 14:05 test

UNIXではハードリンクすれば同じinodeに複数のファイル名を付けられます。
$ ln test test2
$ ls -l test*
-rw-r--r-- 2 user user 0 2010- 12- 19 14:05 test
-rw-r--r-- 2 user user 0 2010- 12- 19 14:05 test2

数字が1から2になっています。これはリンクカウントを示します。
つまりinodeに対してリンクが2(2ファイル)あることになります。
本当に同じinodeなのか確認しています。lsで iオプションを使うとinodeを表示します。
$ ls -il test*
917548 -rw-r--r-- 2 user user 0 2010- 12- 19 14:05 test
917548 -rw-r--r-- 2 user user 0 2010- 12- 19 14:05 test2

ということで同じinodeを使用していることが分かりました。

では、inodeを管理するための構造体だけ確認しておきましょう。
http://www.tamacom.com/tour/kernel/unix/S/61.html
の26行目struct inodeです。

31 ino_t i_number; /* i number, 1-to-1 with device address */
これがinodeの番号です。

今日の説明で重要なのは、
32 unsigned short i_mode;
です。
MODEに格納されているのは、以下ですが、今日の説明で重要なのは、directoryとregularです。
64 #define IFDIR 0040000 /* directory */
65 #define IFCHR 0020000 /* character special */
66 #define IFBLK 0060000 /* block special */
67 #define IFREG 0100000 /* regular */

directoryはそのファイルがディレクトリであること、regularはそのファイルが一般に公開されていることを示します。

●疑問
ファイルのパーミッションはRWXで3ビットづつ(だから8進数の方が都合が良い)で格納されているということでしたがstruct inodeのどこに当たるのでしょう?

3、ファイルシステムの構造
ファイルシステムは以下のように分かれています。

スーパーブロック:ファイルシステム全体の管理情報を保持しています。
inode:inodeが0からの配列で格納しています。
一般ブロック:実際のファイルをブロック単位で格納しています。

質問1:上記の構造を作るのは誰?
⇒mkfsです。mkfsのパラメータにはフォーマットするデバイスを指定します。mkfsで上記のような構造をディスク上に作成します。

質問2:じゃ、ファイルシステムをOSから使えるようにするには?
⇒mount コマンドでOS上のマウントポイントにマウントして使えるようにします。

UNIXではファイルシステムはひとつのツリー構造にするので、mountコマンドは親のツリーに接ぎ木する感じ。
接ぎ木のルートは必ずinodeが2と決まっています。
例:
$ ls -id /
2 /
となりました。ルートのinodeは2番です。

mountコマンド:inodeの2を指定した場所に接ぎ木します。
umountコマンド:接ぎ木を外します。ただし全てのファイルがクローズしていないとエラーになります。

4、ファイルの作成
では、今までの話を踏まえて、新しくファイル(通常のファイル)を作成すると内部ではどうなるでしょうか?
(1)未使用のinodeをさがして、そこにregular fileと書き込む。
(2)一般ブロック領域をひとつ確保してつなげる。
(3)リファレンスカウントをインクリメントする

●疑問
inodeと一般ブロックの結びつけは、inode構造体の以下の部分にinodeに対応する一般ブロックを書き込むことで行うということでよろしいですか?
ファイルの編集でサイズが変更になった場合は、使用する一般ブロックも更新する?


38 struct {
39 daddr_t i_addr[NADDR]; /* if normal file/directory */
40 daddr_t i_lastr; /* last logical block read (for read-ahead) */
41 };


5、ではファイル名はどこに?
inode構造体でファイル名は管理していませんでした。
つまり、ファイル名とinode番号のペアを管理している台帳があるはずです。
⇒これがディレクトリファイルになります。

試しに、「cat .」を実行すると、ディレクトリファイルが表示されます。
化け化けになりますがファイル名と何らかの情報を保持していることが分かります。

6、ファイルの削除タイミング
リファレンスカウントが0になったらファイルは不要(削除)になる??
いつ消えるかは今後のお楽しみ。

(補足)
以前、SUNのワークステーションを使っていたときに、ディスクが余っているにも関わらずファイルが作成できなくなったことがありました。
つまり、inodeが足らなくなったんですね。
その時は先輩がごにょごにょして、使えるようにしてくれました。

2010年12月12日日曜日

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

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

●復習

・2針クロックアルゴリズム
問題なし。
リファレンスビット(参照ビット)を2つの針でチェックして参照されてないものを
ページ・アウトの対象とする。
→メモリが足らなくなってくると針が動き出す。

VAX(DECの32ビットマシン)はリファレンスビットがなかった。
知らなかった!!ということで調べてみました。

http://tiki.is.os-omicron.org/tiki.cgi?c=v&p=%B2%BE%C1%DB%B5%AD%B2%B1
時計アルゴリズムの実装にはページテーブルエントリに参照ビットが必要であるが,
BSD のターゲットマシンである VAX には存在しなかった.そこで,ダーティ(更新)ビットを利用し,
ソフトウェア的に参照ビットをエミュレートする形で実装された.この方法はページフォールを利用する分,オーバヘッドになった.


・スタックとヒープに関して
問題なし
brk(絶対値)、sbrk(増分)でリミットを変更する。
brk、sbrkで変更したリミットは戻らない
そこからmallocで必要なだけ切り出す。

・タスクスイッチング
基本問題なし
→proc構造体とuser構造体は相互にリンクされている。

上のコードだとr7退避してないような?
→r7(PC)はスタックに保存しています。

レジスタは退避、復元しているようですが、メモリについてはそのままということなんだろうか?
→メモリはそのまま。メモリが足らなくなってきたら、使ってないプロセスを丸ごとスワップ・アウト
スワップ・アウトしてるか分かるので、必要になった時点でスワップ・インする。


第4回目

今後はファイルシステムに行く予定なので
準備も兼ねて今日は2.9BSDをシミュレータ上で実行してみます。

V7はエディタはedしかない。
V7のメンバーがバークレーでビル・ジョイを巻き込んでBSD版チームを設立した。
そこでviを開発した。
BSD2シリーズというのがあるそうです。

http://www.law.co.jp/okamura/OpenSource_Web_Version/chapter03/chapter03.html
によると
ビル・ジョイは、様々なアップデートを反映する形で、"Second Berkeley Software Distribution"を作っている。
そして、すぐに2BSDの略称で呼ばれるようになったこのバージョンのバークレー版UNIXソフトウェアには、
改良強化版のPascalのほかviエディタが含まれていた。
数種類の端末向けのtermcapも用意されていた。
ビル・ジョイは、配布用のテープの作成から、電話の応答、ユーザからのフィードバックの実装まで、全部一人でやってのけた。
だそうです。

BSD2.9はV7+拡張になっているのでV7の勉強を便利なツールを使って行うことが出来る。
V7でedでとかは出来なくはないが、ちょっと辛い(^^;)

●インストール手順

1、BSD2.9のイメージをダウンロード
git clone git://github.com/magoroku15/2.9BSD.git

2、エミュレータのインストール
コマンドラインでpdp11を実行してエラーになるようでしたら、
$ sudo apt-get install simh
を実行してインストールしておきます。

3、起動
BSD2.9をダウンロードしたディレクトリで以下のコマンドを実行します。
$ pdp11 bsd.ini

PDP-11 simulator V3.8-1
Disabling XQ
:boot

70Boot
:

先頭にゴミが入りますが、気にせず以下のコマンドを実行します。
: rl(0,0)rlunix

Berkeley UNIX (Rev. 2.9.1) Sun Nov 20 14:55:50 PST 1983
mem = 1979072

CONFIGURE SYSTEM:
xp 0 csr 176700 vector 254 attached
rk 0 csr 177400 vector 220 attached
hk 0 csr 177440 vector 210 attached
rl 0 csr 174400 vector 160 attached
rp ? csr 176700 vector 254 interrupt vector already in use
ht 0 csr 172440 vector 224 skipped: No CSR
tm 0 csr 172520 vector 224 attached
ts 0 csr 172520 vector 224 interrupt vector already in use
dh ? csr 160020 vector 370 skipped: No CSR
dm ? csr 170500 vector 360 skipped: No autoconfig routines
dz ? csr 160110 vector 320 interrupt vector wrong
dz ? csr 160110 vector 320 interrupt vector wrong
dn 0 csr 175200 vector 300 skipped: No autoconfig routines
vp ? csr 177500 vector 174 skipped: No autoconfig routines
lp 0 csr 177514 vector 200 attached
Erase=^?, kill=^U, intr=^C
#

#はシングルユーザモードなので、コントロール+Dを押します。

# Wed Dec 31 17:16:31 PST 1969
Mounted /usr on /dev/xp0h
Attempt to mount /home on /dev/rl2 FAILED: No such file or directory
init: /dev/tty07: cannot open
init: /dev/tty06: cannot open
init: /dev/tty05: cannot open
init: /dev/tty04: cannot open
init: /dev/tty03: cannot open
init: /dev/tty02: cannot open
init: /dev/tty01: cannot open
init: /dev/tty00: cannot open


Berkeley Unix 2.9BSD

:login:

loginプロンプトが表示されるので、rootでログインします。(パスワードなし)


Welcome to the 2.9BSD (Berkeley) UNIX system.

#

となれば問題なしです。

●sttyの設定
stty all
を実行すれば端末の機能が分かります。
erase kill werase rprnt flush lnext susp intr quit stop eof
# @ ^W ^R ^O ^V ^Z/^Y ^? ^\ ^S/^Q ^D

確か、stty erase '^H'
でバックスペースが効くようになったかと思いますが、うまく行きません?
$ ls
でコントロール+Hを押下すると
# ls\s
となります?

●Man
英語ですがマニュアルも入っています。
manコマンドです。
ただし、コマンドとシステムコールが同じ名前のものは、番号を指定します。
例:write
システムコールを見たければ man 2 write

●コンパイル
コンパイラも入っているので、コンパイルが出来ます。

例:test.cをviで作成
main()
{

printf("Hello World\n");

}

# cc test.c

a.outファイルが出来るので実行

# a.out
Hello World
#

となります。

●予約語のチェックはしない。
int write;
main()
{

printf("Hello World\n");

}

として再コンパイルして実行すると

# a.out
Bus error (core dumped)
となります。なじぇ
実はprintfは内部でwriteシステムコールを使っているそうです。
そのため、int writeがバッティングしているわけです。

チェックしていないので、printfでint writeの領域をシステムコールとして実行するわけですからBus errorになったということですね。

●シンボルの確認
ファイルのシンボルを確認するのに、nmコマンドがあります。
これで、ファイルにどんなシンボルが含まれているか分かります。

●スタートアップルーチン
Cでプログラムルーチンを書くときは、main()と書けば良いと教わります。
でも、mainを呼び出してくれる、あるいはexit()をコールした時に本当の終了処理をしています。
このスタートアップルーチンが最初に実行するコードで、パラメータを設定してmainを呼ぶ。
あるいはexitの後の終了処理を行います。
組み込み系の場合、これらも含めて準備する必要があります。

●ダンプ
od(octal dump)コマンドを使えばファイルやファイルシステムをダンプできる。
まごろくさんは、これでこの辺がi-nodeでとか分かるみたいです。
8進数表示です。

ぴーたーぱーかーさんから、その後-xオプションを付ければ16進数になるよと教えて頂きました。
a(アスキーコード)オプションもあるとのことでしたが、私の環境では表示されませんでした。

ということで、慣れ親しんだ16進表記で学習が出来そうです。
ぴーたーぱーかーさんありがとうございます。(^^)

2010年12月5日日曜日

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

●復習
以下の内容に対しての復習、補足です。
http://xiangcai.at.webry.info/201011/article_9.html

・LRUに関して
最近使われていないものは今後も使われることがないと仮定する考え方です。
→参照の局所性に基づきます。
最近使われていないPageをページ・アウトして物理メモリを空けます。

当初、2 Way Clock arugorizumuで検索てヒットしませんでしたが
2針クロックアルゴリズムで検索すると資料出てきました。
http://h50146.www5.hp.com/products/software/oe/hpux/developer/document/memmanage/mem8.html
http://docs.sun.com/app/docs/doc/817-0158/6mfvqchsq?l=ja&a=view

時計の針のように2つの針があります。
最初の針は、参照をクリアして参照していない状態にします。
次の針が少し遅れてスキャンしていき(どれくらいの間隔かはパラメータで調整)、ページにプログラムがアクセスするとハードウェアが「ダーティ」ビットを立てるので、ダーティビットが立っていない(参照されていない)ページをページ・アウトの対象とするようです。

・スタックとヒープに関して
基本問題ないが、ヒープを確保するのは、システムコールのbrkとsbrkになる。(mallocの中で使っている)
http://www.ialab.cs.tsukuba.ac.jp/~maeda/class/syspro/syspro3.pdf
この資料の「プロセスのメモリ空間(古典Unix)」が分かりやすいですね。
ヒープ領域を確保するというよりは、ヒープ領域のリミットを変更するというのが正確のようです。

http://yaguchi.txt-nifty.com/blog/2006/07/brk_sbrk_94d5.html

(余談)
mallocとfreeを繰り返すとそのうちフラグメンテーションする。

都市伝説
mallocにはバグがある。
mallocの管理領域を破壊するとfreeした時に障害が発生するので、mallocにバグがあるような挙動になるが、malloc自身のバグではないので注意。

●第3回まとめ(タスクスイッチング)
簡単にいうと、
・メモリ:text, data, bss, ヒープ、スタック
・CPUの状態:GR(汎用レジスタ)、PC(プログラムカウンタ)、SP(スタックポインタ)
が保存されているえば、復元することが出来る。

プロセスAにスイッチ、プロセスAを復元、プロセスAの命令実行、プロセスAの状態を保存
プロセスBにスイッチ、プロセスBを復元、プロセスBの命令実行、プロセスBの状態を保存
プロセスCにスイッチ、プロセスCを復元、プロセスCの命令実行、プロセスCの状態を保存
というのを様々な要素で優先度を決めて、ひたすらカーネル(スケジュール)がやっている。

全体のイメージ
画像


保存する場所
CPUの状態:user構造体(uでアクセス)の「u_ssav」に保存している。
http://www.tamacom.com/tour/kernel/unix/S/80.html

保存は、save関数:保存先は(u.u_ssav)


713 .globl _save
714 _save:
715 mov (sp)+,r1
716 mov (sp),r0
717 mov r2,(r0)+
718 mov r3,(r0)+
719 mov r4,(r0)+
720 mov r5,(r0)+
721 mov sp,(r0)+
722 mov r1,(r0)+
723 clr r0
724 jmp (r1)


●疑問
PDP11では、r0~r7の汎用レジスタがある。
r6がスタックポインタ(SP)でr7がPC(プログラムカウンタ)のはず。Lions本の260ページ
上のコードだとr7退避してないような?

ついでに、復帰するresumeも


.globl _resume
727 _resume:
728 mov 2(sp),r0 / new process
729 mov 4(sp),r1 / new stack
730 bis $HIPRI,PS
731 mov r0,KISA6 / In new process
732 mov (r1)+,r2
733 mov (r1)+,r3
734 mov (r1)+,r4
735 mov (r1)+,r5
736 mov (r1)+,sp
737 mov $1,r0
738 bic $HIPRI,PS
739 jmp *(r1)+


退避した領域から汎用レジスタに値を入れ直しているのが分かります。

resumeをしているのは、sleep, swtch, newproc, expand
このうち、swtchがタスクスイッチを行っているようです。

プロセスのメモリはどこに?
proc構造体にp_addrがあって、これがユーザプロセスへのポインタになっているように思いますが、正しいだろうか?

●プロセス切り替えに関して
画像


カーネルがユーザプロセスにスイッチする時にタイマーを設定して実行する。
無限ループするようなプロセスがいた場合に他のプロセスへのスイッチが出来なくなるため。

以下の場合、ユーザプロセスからカーネルへ切り替わる。
1、タイマーが切れた場合
2、システムコールをコールした場合

3、カーネルに戻って、スケジューラが条件により優先度を決めて次に実行するプロセスを決定する。
4、現在実行中のプロセスの状態(レジスタ)を保存する、保存先はu.u_ssav※。

※user構造体については、現在実行中のものに対して:変数uにてアクセス出来る。

5、次に実行するプロセスの状態をresumeで復元する。
6、次のプロセスを実行する。

疑問
レジスタは退避、復元しているようですが、メモリについてはそのままということなんだろうか?

(余談)
forkした場合に、procのp_addrをコピーしています。


507 a2 = malloc(coremap, n);
508 /*
509 * If there is not enough core for the
510 * new process, swap out the current process to generate the
511 * copy.
512 */
513 if(a2 == NULL) {
514 rip->p_stat = SIDL;
515 rpp->p_addr = a1;
516 xswap(rpp, 0, 0);
517 rip->p_stat = SRUN;
518 } else {
519 /*
520 * There is core, so just copy.
521 */
522 rpp->p_addr = a2;
523 while(n--)
524 copyseg(a1++, a2++);
525 }

2010年11月28日日曜日

第2回 V7から始めるUNIX講座 まとめ

第2回はexec()です。
前回まででfork()をざっと見ましたので、新しく作ったプロセスを別プロセスに化けさせるexec()です。

ソースコードはこちら
http://www.tamacom.com/tour/kernel/unix/S/98.html#L22

Wikiによれば
http://ja.wikipedia.org/wiki/Fork#Fork-Exec
fork()によって子プロセスが親プロセスのコピーとして生成され、exec()システムコールを呼び出すことで(子プロセス)自身の内容を置き換える。
子プロセスがexec()を呼び出すと、そのアドレス空間の内容は全て失われ、指定されたプログラムを実行するためのアドレス空間のマッピングが設定される。これをオーバーレイと呼ぶ。
とあります。
→なのでexec()するとfork()した時にコピーした(環境変数:スタック?)が消えてしまうので、とっといて積み直す。
exec()をまたがって保持するようにしている。
ソースのどこだったか、ちょっと不明です。

ではソースコードを見ていきますと


22 exec()
23 {
24 ((struct execa *)u.u_ap)->envp = NULL;
25 exece();
26 }


実体はexece()ですがパラメータがない?
放送では、filename, agrc, argp, envを渡すと言われたような?

37 if ((ip = namei(uchar, 0)) == NULL)

namei関数でOSのファイルをサーチする機能をコールします。
ファイル名からinodeを読み込んで、それのメモリ上のポインタを返します。inodeについては後述。

●inodeに関して
UNIXではファイル名はファイルの実体を示していない。
PATHもファイル名の拡張でしかない。
例えばハードリンクを使えば同じ実体に複数のファイル名を付与することが可能になる。
そのため、ファイル名では実体を特定できない。

ファイルの実体はinode(iはindexのi)で管理している。
inodeについては、inode.hを参照
http://www.tamacom.com/tour/kernel/unix/S/61.html

構造体inodeにファイルを管理するための情報(userid,groupid,inode)などを保持しています。
13個(NADDR)分のエントリ?を確保しているとのことでしたが、ちょっと理解が追いついてないです。

lsの-iオプションでinode番号が見られる。
inodeの個数はファイルシステムを作るとき(mkfs)に決まる。
→以前inodeの数が上限に達してDISK容量が余っていたにも関わらずファイルが作れなくなったことがあります。
DISKはあっても管理情報がいっぱいになるとファイルが作れなくなります。

v7の時にはファイルシステムは一種類しかなかった。
当時の他のOSではファイルを作る時にファイル名の他にあらかじめサイズを決めるとか色々制約があった。Unixではサイズを自動的に拡張できるので画期的だった。

現在のUnixのファイルシステムは全てv7の頃のものの派生。
→そのためまずV7でファイルシステムを勉強してから他のファイルシステム(LongFileName,NFS)の理解も早くなる。
Ext2,3はBSDの拡張のひとつ。

カーネルの中でのファイルの読み込み:User構造体に情報をセットしてreadi を呼ぶ。
readi
http://www.tamacom.com/tour/kernel/unix/S/94.html#L19

inodeについてはWiki参照
http://ja.wikipedia.org/wiki/Inode

41 if(access(ip, IEXEC))
access関数で実行権限をチェックします。

50~93行 余りわかりませんでした。

96 if (getxfile(ip, nc) || u.u_error)
gettxfile: 新しいテキストをinodeから読み込む。

168 readi(ip);
としている。
execはreadの変形といえます。

text領域(機械語のコードを格納)にも読み込める。
そのため他のプロセスに化けるというか、自分自身を他のプロセスのイメージで塗り替えるというのが実体?
プログラムを実行するには、PC(プログラムカウンタ)に次に実行するアドレス(例:関数呼び出ししたらリターンしたときに戻る場所をセットしておく)を指定した実行ファイルのものに書き換えてやるってことになる?

実行ファイルのヘッダにエントリポイントが書いてあってそれをプログラムカウンタにセットするするってことのようですがそれだと実行ファイルを生成(コンパイル)した際に実行するアドレスが決まっているということ?

別プログラムなら別の仮想空間で実行されるので同じアドレスでも問題なし。
同じプログラムを複数実行しても、実体は別として複数のプロセスが動くので同じアドレスでも問題なし。
ってことのように思えます。

第1回 V7から始めるUNIX講座 復習

先々週から始まった、V7から始めるUNIX講座

まだまだ知識不足ですね。
第2回目が始まる前に30分ほど復習の時間をとっていただけましたので、メモを書いておきます。

第1回のまとめは以下参照
http://xiangcai.at.webry.info/201011/article_7.html


●SWAP、Paging
SWAP、Pagingについては問題なし
仮想記憶を実現するには、ハードウエアサポート(MMU)が必要になる。

(余談)
SUN1は68000を使っていたが、68000はMMUを搭載していない。
どうやって仮想記憶を実現したかというと68000を2つ搭載していて
1つめのCPUがこけたら2つめのCPUで復旧するという方法で実現していた。

SUN1について
http://ja.wikipedia.org/wiki/Sun-1

68000の仮想記憶については以下の「MC68000 での仮想記憶のサポート」参照
http://ja.wikipedia.org/wiki/MC68000

仮想記憶について
http://ja.wikipedia.org/wiki/%E4%BB%AE%E6%83%B3%E8%A8%98%E6%86%B6


●仮想記憶に関する補足
仮想記憶は参照の局所性(プログラムが限られた時間内では特定のページのみアクセスする)を利用して必要なページのみメインメモリ内に保持することで、実メモリ以上のプログラムを見かけ上同時に実行しているかのようにみせることが出来る。

使わないページのHDDへの退避について
LRUアルゴリズムに従ってページをHDDへ退避します。
FreeBSDでは、2 Way Clock arugorizumu?を使っている
→2 Way Clock arugorizumuで検索しましたがヒットしませんでした。

下記のページの「ページ置換え」のところに「クロックアルゴリズム」という記述がありますが、これだろうか?
http://www.multisoft-lab.com/os/memory.html

●Pageについて
最初から4KBに分かれていて、問答無用でぶった切り


●スタックとヒープ
C言語の実行可能ファイルにおけるフォーマットを理解する必要がある。
→リンカ・ローダ実践開発テクニック(坂井 弘亮著)が参考になります。
ネットだとこの辺とか
http://www.coins.tsukuba.ac.jp/~yas/coins/syspro-2000/2000-04-17/process-memory.html
http://www.ertl.jp/~takayuki/readings/info/no02.html

以下のセクションに分かれている
header
text
data
bss
heap
stack
以下の図参照(こんなイメージ?)
画像


新たな疑問
forkはプロセスID以外まったく同じコピーを作るとのことでしたが、これらのセグメントも全てコピーしている?
heapは下に伸びる、stackは上に積むと、どっかでぶつかったりしないのだろうか?
1つのプロセスで確保できるメモリは2の16乗(65536)つまり64KB(1024×64)?

●forkについて
大体OK
forkが二度リターンする(とっても重要)

以下のコードで実装しているのですが


503 if (save(u.u_ssav)) {
504 sureg();
505 return(1);
506 }



ここのsaveが数少ないアセンブラのコード
http://www.tamacom.com/tour/kernel/unix/S/1.html


.globl _save
714 _save:
715 mov (sp)+,r1
716 mov (sp),r0
717 mov r2,(r0)+
718 mov r3,(r0)+
719 mov r4,(r0)+
720 mov r5,(r0)+
721 mov sp,(r0)+
722 mov r1,(r0)+
723 clr r0
724 jmp (r1)


スタックにレジスタの値を退避して、r1へジャンプしているのは分かるのですがどうやらこの辺は核心部分らしく、まだピンと来ません。
何度も出てくるそうなので、今のところは、ここまでとしておきます。
カーネルにおけるタスクのスイッチングについても、今後の課題