今回で10回目です。あっという間でしたね。
今回はクロック割り込みです。
汎用OSでは、どんなプログラムが動くか分かりません。
中には無限ループ(while(1), for(;;))するような、プログラムがいるかもしれません。
そういうプログラムがいてもUNIXは他のプロセスの実行が出来ます。
システムコールの時には自発的に「割り込み」を発生させるわけですが、一定時間ごとに割り込みを発生させる機能もあります=インターバルタイマ割り込み。
Lions本の第11章にはクロック割り込みと記述があります。以降「クロック割り込み」と記述します。
クロック割り込みは「clock」で実装されています。
Lions本のP346に電源周波数クロックかプログラマブルリアルタイムクロックのどちらからの割り込みを処理するとあります。
クロックの呼び出しはベクターテーブルに記載があります。
http://tamacom.com/tour/kernel/unix/S/7.html
34 . = ZERO+100
35 kwlp; br6
36 kwlp; br6
ベクタアドレスが100(34行目)がベクタテーブルです。
Lions本のP336の表を見ると、
ベクタ位置 | 周辺デバイス | 割り込み優先度 | プロセス優先度 |
---|---|---|---|
100 | 電源周波数クロック | 6 | 6 |
104 | プログラマブルクロック | 6 | 6 |
となっています。
どちらも割り込みも、PCはkwlpを実行します。
PSWはbr6(300)、8進数の300なので、11000000
5,6,7がプロセッサ優先度を示すので、110⇒6となります。上記のP336の割り込み優先度と一致しています。
kwlpはもう少し下に定義されています。
64 .globl _clock
65 kwlp: jsr r0,call; jmp _clock
jsr r0,call; jmp _clock
jsrはサブルーチンを実行する命令です。
実行すると以下のようになります。
PC=call サブルーチン:callへ分岐します。
r0=サブルーチンからの戻り先、つまりjsrの次の命令を示します。
この場合、;で命令を続けて記述しています。
よって、r0=jmp _clockとなります。
1、callの実行
callは、m40.sに定義されています。
http://tamacom.com/tour/kernel/unix/S/1.html
114 call:
115 mov PS,-(sp)
116 1:
117 mov r1,-(sp)
118 mfpi sp
119 mov 4(sp),-(sp)
120 bic $!37,(sp)
121 bit $30000,PS
122 beq 1f
123 jsr pc,(r0)+ ⇒前モードがユーザモード
124 tstb _runrun
125 beq 2f
126 mov $12.,(sp) / trap 12 is give up cpu
127 jsr pc,_trap
128 2:
129 tst (sp)+
130 mtpi sp
131 br 2f
132 1:
133 bis $30000,PS
134 jsr pc,(r0)+ ⇒前モードがカーネルモード
135 cmp (sp)+,(sp)+
136 2:
137 mov (sp)+,r1
138 tst (sp)+
139 mov (sp)+,r0
140 rtt
115から順番に
・現在のPSをスタックに積む
・現在のr1をスタックに積む
・前モードのspをスタックに積む
・さっき積んだ現在のPSW(スタック1回で2なので2個前)をもう一度スタック積む
スタック |
---|
現在のPS(マスクされた) |
前モードのsp |
現在のr1 |
現在のPS |
120行目
スタックに乗せた現在のPSに対してマスクを掛けています。
bic命令はソースの非ゼロビットに対応するディスティネーションの各ビットを0クリアします。
37(8進数)は、011111。!でひっくり返しているので、1111111111100000
非ゼロを0にするので、PSWの下位5ビット以外を0クリアすることになります。
121行目
bit命令はソースとディスティネーションの論理積(AND)なので、$30000とPSの論理積
$30000(8進数)は2進数で、11000000000000です。
つまりPSWの12,13桁(前モードの状態を示すビット)をチェックします。
カーネルモードの場合は1fへ分岐します。
●前モードがカーネルモードの場合
132行以降へ分岐
133行目
PSに$30000を設定しています。(ユーザモード)
134行目
jsrでサブルーチンジャンプします。ジャンプ先はr0です。
先ほど書いたようにr0は(jmp _clock)です。
これで_clockつまりC言語のclockがコールされることになります。
●clockの実行
http://tamacom.com/tour/kernel/unix/S/84.html#L28
クロック割り込み(clock)は以下のブログが凄く詳しいので、こちらをどうぞ
http://d.hatena.ne.jp/takahirox/20110211/1297403488
以下に概要を示します。
28 clock(dev, sp, r1, nps, r0, pc, ps)
このパラメータは、こういう形で呼んでくれるようです。Linons本のP344参照
dev, sp, r1, npsは、callでスタックに積んでいました。
「タイマ割り込みのまず最初にやることは次の割り込みの設定。」
41 lks->r[0] = 0115;
callout、p2->c_timeが0より大きい最初のエントリのc_timeをデクリメントします。
54 if(callout[0].c_func == NULL)
55 goto out;
56 p2 = &callout[0];
57 while(p2->c_time<=0 && p2->c_func!=NULL)
58 p2++;
59 p2->c_time--;
callout構造体の定義は以下の通り
時間とルーチン、ルーチン用の引数を格納しています。
http://tamacom.com/tour/kernel/unix/S/53.html#L17
11 struct callo
12 {
13 int c_time; /* incremental time */
14 caddr_t c_arg; /* argument to routine */
15 int (*c_func)(); /* routine */
16 };
17 struct callo callout[NCALL];
優先度を5に設定します。
73~77でc_timeが0以下のもののc_funcを実行します。
78~84で既に実行したものは不要なので後ろから前へつめています。
71 spl5();
72 if(callout[0].c_time <= 0) {
73 p1 = &callout[0];
74 while(p1->c_func != 0 && p1->c_time <= 0) {
75 (*p1->c_func)(p1->c_arg);
76 p1++;
77 }
78 p2 = &callout[0];
79 while(p2->c_func = p1->c_func) {
80 p2->c_time = p1->c_time;
81 p2->c_arg = p1->c_arg;
82 p1++;
83 p2++;
84 }
85 }
86
ユーザモードで動作しているなら、ユーザの時間をインクリメントする
カーネルモードで動作しているなら、システム時間をインクリメントする
93 if (USERMODE(ps)) {
94 u.u_utime++;
95 if(u.u_prof.pr_scale)
96 addupc(pc, &u.u_prof, 1);
97 if(u.u_procp->p_nice > NZERO)
98 a += 8;
99 } else {
100 a += 16;
101 if (pc == waitloc)
102 a += 8;
103 u.u_stime++;
104 }
clockからcallへ戻り、135行以降を実施します。
・まず2回ポップしてspから2ワード取り除きます。
なぜcmpしてるかは、その方が高速だからだろうとV6の読書会で話題になりました。
・r1をspから戻す
・PSを取り除いて
・r0をspから戻す
・割り込みからリターンする
133 bis $30000,PS
134 jsr pc,(r0)+
135 cmp (sp)+,(sp)+
136 2:
137 mov (sp)+,r1
138 tst (sp)+
139 mov (sp)+,r0
140 rtt
●前モードがユーザモードの場合
基本はカーネルモードと同じですが、clock実行後に優先度の高いプロセスがあれば、そちらに切り替えます。
つまり、割り込まれたルーチンが再開するとは限りません。
callルーチン
123行以降を実行
123行目で_clockへジャンプします。これは前モードがカーネルモードと同じなので省略
_clockからcallへ戻り、124行以降を実施します。
runrunで優先度の高いプロセスが実行可能かどうかのチェック
なければ、スタックの内容を戻して、140行目で割り込みからリターンする
優先度の高いプロセスが存在する場合
V6だと_swtchを呼ぶだけですが、V7はちょっと複雑です。
124 tstb _runrun
125 beq 2f
126 mov $12.,(sp) / trap 12 is give up cpu
127 jsr pc,_trap
126,127行目
10進数の12をスタックに積んで、_trap(C言語のtrap)へ分岐します。
●trap
trapのswitch文の206でいきなりgoto out;です。
203 /*
204 * Allow process switch
205 */
206 case USER+12:
207 goto out;
230、231でrunrunをチェックして、qswtch()を呼びます。
225 out:
226 if(issig()) {
227 psig();
228 }
229 curpri = setpri(u.u_procp);
230 if (runrun)
231 qswtch();
232 if(u.u_prof.pr_scale)
233 addupc((caddr_t)pc, &u.u_prof, (int)(u.u_stime-syst));
234 if (u.u_fpsaved)
235 restfp(&u.u_fps);
●qswtch
http://tamacom.com/tour/kernel/unix/S/96.html#L329
qswtchは、swtchを呼んでいるだけです。
setrqは実行するプロセスの選択?
329 qswtch()
/* */
330 {
331
332 setrq(u.u_procp);
333 swtch();
334 }
●swtch
setrqのすぐ下にswtchがあります。
プロセスの切り替えのために、現在のプロセスの状態をsaveで保存し、切り替えるプロセスの状態をresumeで復帰させます。
347 swtch()
/* */
348 {
349 register n;
350 register struct proc *p, *q, *pp, *pq;
351
352 /*
353 * If not the idle process, resume the idle process.
354 */
355 if (u.u_procp != &proc[0]) {
356 if (save(u.u_rsav)) {
357 sureg();
358 return;
359 }
360 if (u.u_fpsaved==0) {
361 savfp(&u.u_fps);
362 u.u_fpsaved = 1;
363 }
364 resume(proc[0].p_addr, u.u_qsav);
365 }
●プロセス状態の保存先
プロセスの保存先はu.u_rsavのようです。
http://tamacom.com/tour/kernel/unix/S/80.html#L19
19 label_t u_rsav; /* save info when exchanging stacks */
コメントにあるとおり、(カーネル)スタックを交換する場合に情報を保存する場所だと思います。
●saveとresume
http://tamacom.com/tour/kernel/unix/S/1.html
saveとresumeは、以下のようにレジスタの値を保存したり、戻したりしています。
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)
725
726 .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)+
0 件のコメント:
コメントを投稿