●callout
前回、何に使うのか(functionに登録するのは、何か)という話題がありました。
特定の時間が経過したら実行する処理として、何を思い浮かべるでしょうか?
そう、sleep(3)です。
カーネルの内部で使う、sleep,wakeupではなくsleep(3)です。
Section3ですので、ライブラリ(システムコールではない)です。
man 3 sleepを実行します。
最後に
SEE ALSO
alarm(2), pause(2)
と表示されます。
これは、sleep(3)がalarm(2), pause(2)の素材から出来ていることを示します。
alarm(2), pause(2)はシステムコールです。
sleep(3)とsleep(2)は全く別物なので注意が必要です。
alarm, pauseシステムコールのソースファイルは以下です。
http://tamacom.com/tour/kernel/unix/S/101.html#L357
354 /*
355 * alarm clock signal
356 */
357 alarm()
/* */
358 {
359 register struct proc *p;
360 register c;
361 register struct a {
362 int deltat;
363 } *uap;
364
365 uap = (struct a *)u.u_ap;
366 p = u.u_procp;
367 c = p->p_clktim;
368 p->p_clktim = uap->deltat;
369 u.u_r.r_val1 = c;
370 }
371
372 /*
373 * indefinite wait.
374 * no one should wakeup(&u)
375 */
376 pause()
/* */
377 {
378
379 for(;;)
380 sleep((caddr_t)&u, PSLEP);
381 }
sleep(3)のソースを見てみます
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/libc/gen/sleep.c
#include <signal.h>
#include <setjmp.h>
static jmp_buf jmp;
sleep(n)
unsigned n;
{
int sleepx();
unsigned altime;
int (*alsig)() = SIG_DFL;
if (n==0)
return;
altime = alarm(1000); /* time to maneuver */
if (setjmp(jmp)) {
signal(SIGALRM, alsig);
alarm(altime);
return;
}
if (altime) {
if (altime > n)
altime -= n;
else {
n = altime;
altime = 1;
}
}
alsig = signal(SIGALRM, sleepx);
alarm(n);
for(;;)
pause();
/*NOTREACHED*/
}
static
sleepx()
{
longjmp(jmp, 1);
}
ざっくり言いますと、sleepはpauseで眠っています。
calloutで、N秒後にSIGALRMを発生させます。
これでsleepから復帰するはずです。
(備考)
alarm(n)は、自分自身にN秒後にSIGALRMを通知する機能です。
http://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B0%E3%83%8A%E3%83%AB_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)
alarmについては、ちょっと不明なので次回再度取り上げます。
●DISKドライバ
1、磁気DISK
まずは、磁気DISKをイメージして下さい。
磁気DISKは円板(プラッタ)が複数毎構成になっており、磁気ヘッドでデータアクセスします。以下のような図です。
Cylinder head sector(CHS シリンダ・ヘッド・セクタ)で磁気ディスクにアクセスします。
(参考)
http://www.it-license.com/memory/harddisc.html
基本情報技術者の資料、そういや昔やった覚えが
http://ja.wikipedia.org/wiki/Cylinder_head_sector
小から大に説明すると
・セクタ:トラックを分割したもの
・トラック:セクタの集まったもの、データの記録単位で円の1周分
⇒陸上のトラックを思い浮かべると理解しやすいかな?
・シリンダ:同じトラックの集合、同じトラックを縦に見ると、筒のように見えることからシリンダと呼ぶようです。
同じシリンダ上のデータは磁気ヘッド動かずにデータの読み書きが出来ます。
よって、特定のデータを読み書きするには、
・シリンダにより磁気ヘッドの移動
・ヘッドによりどのプラッタにアクセスするか
・セクタによって、トラック内のどの部分か
により、実現します。
(備考)LBA
初期のハードディスクもこの方式でアクセスを行っていたが、容量が増大するにつれ、内側と外側のトラックでセクタ数が異なる等の理由で内部で適当な値に変換してアクセスするようになった。
どうせ変換するであれば、先頭から何番目のセクタであるかのみを指定してアクセスできた方があらゆる点で便利であるため、現在ではLogical Block Addressing(LBA)によるアクセスが主に用いられている。
2、DISKアクセスの実現
Lions本をお持ちの方は第16章RKディスクドライバを参考にして下さい。
DISKアクセスも割り込みです。
DISKドライバに読み込み、書き込みを依頼します。処理が終わると割り込みで教えてくれます。
割り込みですからベクターテーブルに記述があるはずです。
Lions本のP336に割り込みベクタの一覧があります。
ベクタ位置 | 周辺デバイス | 割り込み優先度 | プロセス優先度 |
---|---|---|---|
220 | RKディスクドライブ | 5 | 5 |
では、お馴染みのベクターテーブルの220を見てみます。
http://tamacom.com/tour/kernel/unix/S/7.html
41 . = ZERO+220
42 rkio; br5
rkioは
68 .globl _rkintr
69 rkio: jsr r0,call; jmp _rkintr
callで色々準備してから、最終的に_rkintrにジャンプします。
callでの詳細処理は前回のまとめを参照して下さい。
_rkintr(C言語のrkintr)を見てみましょう。
http://tamacom.com/tour/kernel/unix/S/40.html#L96
96 rkintr()
/* */
97 {
98 register struct buf *bp;
99
100 if (rktab.b_active == NULL)
101 return;
102 dk_busy &= ~(1 << DK_N);
103 bp = rktab.b_actf;
104 rktab.b_active = NULL;
105 if (RKADDR->rkcs < 0) { /* error bit */
106 deverror(bp, RKADDR->rker, RKADDR->rkds);
107 RKADDR->rkcs = RESET|GO;
108 while((RKADDR->rkcs&CTLRDY) == 0)
109 ;
110 if (++rktab.b_errcnt <= 10) {
111 rkstart();
112 return;
113 }
114 bp->b_flags |= B_ERROR;
115 }
116 rktab.b_errcnt = 0;
117 rktab.b_actf = bp->av_forw;
118 bp->b_resid = 0;
119 iodone(bp);
120 rkstart();
121 }
100 if (rktab.b_active == NULL)
b_activeはb_bcountです(#defineしてる)ので転送カウント?がNULLならリターンします(通常はここは通らないはず)
105から115まではエラー処理です。
105 if (RKADDR->rkcs < 0) { /* error bit */
エラービットが立っていないかのチェックです。
110から113は10回のリトライ処理です。10回やっても駄目ならb_flagsにB_ERRORを設定します。
ここで実行している、 rkstart()がデバイスに対してIOを発行する部分になります。
●ディスクコントローラのレジスタアドレス定義
ディスクコントローラのレジスタのアドレスがRKADDR(0177400)でdefineされていて、レジスタのdevice構造体にマッピングされています。
このマッピングされているものはrkstartのところで出てきます(後述)
●疑問
ディスクコントローラのレジスタアドレスに対して処理を行うことでディスクIOを行っているようですが、
これは、いわゆる「メモリマップドIO」ということでしょうか?
ディスクコントローラのレジスタアドレスがメモリ空間上にマッピングされているので、ディスクコントローラの仕様に従って操作することでディスクIOが出来る?
メモリマップドIO
http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%83%89I/O
RKADDR
12 #define RKADDR ((struct device *)0177400)
27 /*
28 * Monitoring device bit
29 */
30 #define DK_N 1
31
32 struct device
33 {
34 int rkds;
35 int rker;
36 int rkcs;
37 int rkwc;
38 caddr_t rkba;
39 int rkda;
40 };
正常ならば
116以降、buf構造体に値を設定して、iodone、rkstartを実行
以下にコードを示します。
●iodone
http://tamacom.com/tour/kernel/unix/S/18.html#L374
・b_flagsをB_DONE(転送完了)に設定
・b_flagsにB_ASYNC(非同期:IOの完了を待たない)が立っていたらだったらbpを開放
・B_ASYNCが立っていない場合はB_WANTED(誰かが使っているので欲しいという印)を落として、wakeupでこのbpが開放されるのを待っているプロセスを起こす。
以前第8回のキャッシュバッファのところで出てきました。
http://xiangcai.at.webry.info/201102/article_1.html
の
2、キャッシュヒットしたが誰かが使っている。
getblkで、そのバッファが欲しいという印を付けて、スリープして、待つ。
↓
この待っているのを起こす作業になると思います。
370 /*
371 * Mark I/O complete on a buffer, release it if I/O is asynchronous,
372 * and wake up anyone waiting for it.
373 */
374 iodone(bp)
/* */
375 register struct buf *bp;
376 {
377
378 if(bp->b_flags&B_MAP)
379 mapfree(bp);
380 bp->b_flags |= B_DONE;
381 if (bp->b_flags&B_ASYNC)
382 brelse(bp);
383 else {
384 bp->b_flags &= ~B_WANTED;
385 wakeup((caddr_t)bp);
386 }
387 }
●rkstart
http://tamacom.com/tour/kernel/unix/S/40.html#L68
68 rkstart()
/* */
69 {
70 register struct buf *bp;
71 register com;
72 daddr_t bn;
73 int dn, cn, sn;
74
75 if ((bp = rktab.b_actf) == NULL)
76 return;
77 rktab.b_active++;
78 bn = bp->b_blkno;
79 dn = minor(bp->b_dev);
80 cn = bn/12;
81 sn = bn%12;
82 RKADDR->rkda = (dn << 13) | (cn << 4) | sn;
83 RKADDR->rkba = bp->b_un.b_addr;
84 RKADDR->rkwc = -(bp->b_bcount>>1);
85 com = ((bp->b_xmem&3) << 4) | IENABLE | GO;
86 if(bp->b_flags & B_READ)
87 com |= RCOM; else
88 com |= WCOM;
89 RKADDR->rkcs = com;
90 dk_busy |= 1 << DK_N;
91 dk_numb[DK_N] += 1;
92 com = bp->b_bcount>>6;
93 dk_wds[DK_N] += com;
94 }
・転送カウントをインクリメント
・bn(block number)を取得
・dn(デバイスのマイナー番号)を取得
(補足)
bp->b_devはメジャー番号(8ビット)+マイナー番号(8ビット)が入っています。
⇒同じく第8回で出てきました。
Wikipediaのスペシャルファイルの解説
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%9A%E3%82%B7%E3%83%A3%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB
一般にメジャー番号がデバイスドライバの識別に使われ、マイナー番号がそのドライバが制御する個々の機器の識別に使われる。この場合、システムはドライバに対して引数としてマイナー番号を渡す。
↓
まさにこれのためにマイナー番号を取得していると思います。
・cn(cylinder number)はbnを12で割った値
・sn(sector number)はbnを12で割った余り
1トラック当たりのセクタが12ということだと思います。
Lions本のP382に
セクタ/トラック 12
と記載があります。
Lions本のP383には、データ転送を開始する場合、RKDA、RKBA、RKWCを設定し、その後RKCSを設定する。とあります。
●RKDA、RKBA、RKWC
82 RKADDR->rkda = (dn << 13) | (cn << 4) | sn;
83 RKADDR->rkba = bp->b_un.b_addr;
84 RKADDR->rkwc = -(bp->b_bcount>>1);
・RKDAにマイナー番号、シリンダ番号、セクタ番号を設定しているようです。
・RKBAにbp->b_un.b_addrを設定。読み書きするバッファのアドレス?
⇒buf構造体はヘッダみたいなものでバッファの実体は別にあるはず?
・RKWCには-(bp->b_bcount>>1)を設定。転送カウントを二分の一してマイナスを付けた値?
●RKCS
85 com = ((bp->b_xmem&3) << 4) | IENABLE | GO;
86 if(bp->b_flags & B_READ)
87 com |= RCOM; else
88 com |= WCOM;
89 RKADDR->rkcs = com;
RKCSにはコマンドを記述するようです。
ReadならRCOM、WriteならWCOM
これで、実際にデバイスに対してReadあるいはWriteが実行されるはずです。
ということでrkstartをざっくりまとめると
RKDAで指定したブロック(メジャー番号、シリンダ、トラックに変換してアクセス)に対して、RKBAで指定したバッファで読み書きするってことになるかと思います。
●疑問点
rkintrは、依頼した読み書きが終わった段階で割り込みとして呼ばれるという認識です。
しかしrkintrの最後にrkstartを実行しています。
rkstartは実際にDISKに対してコマンドを送信している処理だと思うのですが、ここで実行している理由は?
readを例にして、流れを書いて見ました。(バッファキャッシュ上にデータがない場合)
この認識で合っていますか?
0 件のコメント:
コメントを投稿