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 }