2011年3月13日日曜日

第13回 V7から始めるUNIX講座 復習とまとめ(シグナル)

第13回 シグナル

●スタックマシン
IBMのSYSTEM360はFORTRAN,COBOLの処理に向けて設計されており、Pascal, ALGOLなどが前提としたスタックの観念を持たない。
SYSTEM360については以下を参照
http://ja.wikipedia.org/wiki/System/360

画像


スタック・マシンはPDP-11で広まりました。
設計したのは、ゴードン・ベル
http://www.ijinden.com/_c_02/Gordon_Bell.html

画像


スタックマシンについては以下を参照して下さい。
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%9E%E3%82%B7%E3%83%B3



●本日の話題の前にマニュアルの話題
V7の良いマニュアルが見つかったので紹介します。
以下からダウンロードできます。
http://plan9.bell-labs.com/7thEdMan/bswv7.html

・v7vol1.pdf
マニュアルです。
Section1がコマンド
Section2がシステムコール

・v7vol2a.pdf
Supplementary Documents(補足資料)とありますが、UNINに関する論文です。
D. M. Ritchie, K. Thompson, Brian W. Kernighanなどが執筆しています。
是非読んだ方が良いでしょう。
・The UNIX Time-Sharing System:Unix に関する最初の論文です。

上記の発表以前の Unix の開発状況を説明した論文がありました。幻のシステムである PDP-7 Unix の仕様について言及しています。
http://cm.bell-labs.com/cm/cs/who/dmr/hist.html

・UNIX For Beginners:UNIXの初心者向けの資料

・Editor:この当時のエディタは当然「ed」です。

・Shell:シェルの使い方

・UNIX Programming:C言語のマニュアルです。

論文は読んだ方が良いですが、英語の敷居が高い人は図書館で共立出版の「bit」を探してみるのも手です。
80年代のバックナンバーには、これらの論文を翻訳したものがあるはずです。

・v7vol2b.pdf
ツールの説明です。
Yacc: Yet Another Compiler-Compiler

RATFOR:Rational Fortran(構造化FORTRAN)
FOTRANで構造化(分岐、ループ)が出来るようにしたプリプロセッサ

SED、AWK
今でも使える。テキスト処理に便利です。
例えば、100個のファイルの頭に特定の文字を埋め込むとか簡単に出来る。
知ってれば簡単に、知ってないと泣きながら手でファイル開いて埋め込んで残業する羽目に><
http://itref.fc2web.com/unix/awk.html



●SIGNAL

SIGNALはUNIXで非同期で通信する手段ですが、UNIXの中で最も互換性がありません。
最近はPOSIXのSIGNALに準拠しているようです。

SIGNALの外部仕様


1、特定のプロセスにシグナルを送る
kill(2)

2、シグナルハンドラを登録する
singal(2)
プロセスはシグナルを受信した場合のデフォルトの動作が決まっていますが、signalを使うとデフォルトの動作を
任意の処理に変更することが出来ます。

SIGNALはソフトウエア割り込みです。
LIONS本でも第13章 ソフトウエア割り込みで説明されています。
今までの(ハードウエア)割り込みの概念をそのまま使っています。

v7vol1.pdfマニュアルのP249のエラーの以下については重要なので、ピックアップしておきます。

4 EINTR Interrupted system call
An asynchronous signal (such as interrupt or quit), which the user has elected to catch, occurred
during a system call. If execution is resumed after processing the signal, it will appear as if the
interrupted system call returned this error condition.

要約すると
システムコールを実行中にSIGNALを受信した場合は、割り込みが発生してSIGNALを実行する場合があります。
割り込まれたシステムコールはエラー4を返します。

Wikipediaにも下記の記述があります。
シグナルは処理中のシステムコールを中断することがあり、アプリケーションは非透過的な再実行をしなければならない。
つまり、実行中のシステムコールはEINTRというエラーを返し、要求した処理はシグナル受信によって中断されて結果を得られていないことを示す。
そのため、処理を続行するには再度同じシステムコールを実行しなければならない。

マニュアルのシステムコールを見てみましょう。

・kill(2)
PDFマニュアルの231ページです。
send signal to a process
kill(pid, sig);
kill(2)は、プロセスにシグナルを送ります。

see also signal(2)となっているのでsignal(2)から出来ていることがわかります。

・signal(2)
PDFマニュアルの252ページです。
signal – catch or ignore signals
(*signal(sig, func))()
(*func)();
signalのパラメータは2つ
シグナルとファンクションです。

シグナルに設定できるのは16種類(以下参照)です。
このシグナルを受信した時に実行するfunctionを登録することが出来ます。

SIGHUP 1 hangup
SIGINT 2 interrupt
SIGQUIT 3* quit
SIGILL 4* illegal instruction (not reset when caught)
SIGTRAP 5* trace trap (not reset when caught)
SIGIOT 6* IOT instruction
SIGEMT 7* EMT instruction
SIGFPE 8* floating point exception
SIGKILL 9 kill (cannot be caught or ignored)
SIGBUS 10* bus error
SIGSEGV 11* segmentation violation
SIGSYS 12* bad argument to system call
SIGPIPE 13 write on a pipe or link with no one to read it
SIGALRM 14 alarm clock
SIGTERM 15 software termination signal
16 unassigned

SIGNALを受信した時のデフォルトの動作(死ぬ、コアを吐く)を変えることが出来ます。



では次にソースコードを見てみます。

●signal(2)
カーネルで実行するのは、ssigになります。
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/libc/sys/signal.s
によればsignalは48番です。

http://www.tamacom.com/tour/kernel/unix/S/102.html#L115
を見ると48番は「ssig」になります。
カーネルに「signal」はありますが別の機能です。

ssigのソースは以下です。
http://www.tamacom.com/tour/kernel/unix/S/101.html#L273

281行目:u構造体からsignalのパラメータを取り出して構造体aにキャストしています。
282行目:シグナルを取り出して
283行目:シグナルが不正(0以下あるいはNSIG(17)以上)、SIGKILの場合はエラーを格納してリターンしています。
SIGKILをチェックしているのは、SIGKILを上書きして変更してしまうと、外部から終了(kill -9 pid)が出来なくなるので変更を許可していません。
288行目:シグナルハンドラをu構造体に代入しています。
289行目:該当するシグナルのビット値を0にします。


273 ssig()
/* */
274 {
275 register a;
276 struct a {
277 int signo;
278 int fun;
279 } *uap;
280
281 uap = (struct a *)u.u_ap;
282 a = uap->signo;
283 if(a<=0 || a>=NSIG || a==SIGKIL) {
284 u.u_error = EINVAL;
285 return;
286 }
287 u.u_r.r_val1 = u.u_signal[a];
288 u.u_signal[a] = uap->fun;
289 u.u_procp->p_sig &= ~(1<<(a-1));
290 }




●kill(2)
http://www.tamacom.com/tour/kernel/unix/S/104.html#L34

ちょっとややこしいので、図で概要を説明します。
まず、kill(2)では、シグナルを設定するだけで、シグナルハンドラの実行はまた別になります。

1、killの処理
killは送信先のプロセスのプロセス構造体(u.p_sig)のシグナルビットを立てて、送信先プロセスを起こすのがメインです。
killの実行後に、outへGOTOで飛びます。
issigでカレントプロセスが実行すべきシグナルハンドラを保持していれば、psig()を実行します。
画像


2、psig
不動小数点レジスタが必要なら保存して
fsig()でプロセス構造体(u.p_sig)のシグナルビットをチェックして
該当するシグナルのシグナルハンドラが有効なら、sendsig()を実行します。

画像


3、sendsig
スタックを4(2個分)増分して、その領域を確保してから、suwordでカーネル空間からユーザ空間へ保存します。
保存するのは、ユーザの現在のPCとPSです。
保存しておいてから、PCを実行すべきシグナルハンドラーに書き換えます。SPも書き換えます。
PSからTBITを落としていますが、目的不明です。

画像


4、実装に関して
つまり、非同期通信という言葉とイメージが一致しませんが、実際には、送信先のプロセスのu構造体とproc構造体の中身を書き換えて、送信先のプロセスが実行を再開した際に書き換えた内容で動作するようです。
シグナルハンドラの実行が終わると、本来の開始するはずだった命令へのポインタ(PC)がスタックに保存されているので、それを戻して、何もなかったように処理が続行します。

画像




図の概要を元にソースコードを見ていきます。

以前やったシステムコールを思い出して下さい。
システムコールのパラメータはtrapの中で「fuiword」を使って読み出して
u構造体のu_apに格納されています。
u_apはパラメータのリストです。

302行目でu.u_apをa構造体にキャストしています。
311行目からfor文で全てのプロセスを舐めまわしています。
312から316行目まではエラーチェックです。
312行:プロセスのステータスがNULL
314行:対象のプロセス(a)ではない
316行:不明
318行:ユーザがスーパーユーザではなく、ユーザ識別が一致しない。

321行目まで来たら、正しくパラメータとして渡されたプロセスIDが取得できているはずです。
プロセス構造体とプロセス番号を渡して、psignalを呼びます。


292 kill()
/* */
293 {
294 register struct proc *p, *q;
295 register a;
296 register struct a {
297 int pid;
298 int signo;
299 } *uap;
300 int f, priv;
301
302 uap = (struct a *)u.u_ap;
303 f = 0;
304 a = uap->pid;
305 priv = 0;
306 if (a==-1 && u.u_uid==0) {
307 priv++;
308 a = 0;
309 }
310 q = u.u_procp;
311 for(p = &proc[0]; p < &proc[NPROC]; p++) {
312 if(p->p_stat == NULL)
313 continue;
314 if(a != 0 && p->p_pid != a)
315 continue;
316 if(a==0 && ((p->p_pgrp!=q->p_pgrp&&priv==0) || p<=&proc[1]))
317 continue;
318 if(u.u_uid != 0 && u.u_uid != p->p_uid)
319 continue;
320 f++;
321 psignal(p, uap->signo);
322 }
323 if(f == 0)
324 u.u_error = ESRCH;
325 }




●psignal
http://www.tamacom.com/tour/kernel/unix/S/95.html#L55

60行目:シグナル番号がNSIG(17)以上の場合はリターンします。
62行目:シグナル番号が0以外だったら
63行目:シグナルはビット列なので、p->p_sigとORを取って該当ビットを立てます。
64行目:プロセスの優先度がPUSER(50)より大きければ、PUSERにします。
66行目:プロセスがスリープ状態で優先度がPZERO(25)より大きければ
67行目:setrunで該当のプロセスを実行状態にします。
→つまり、該当プロセスがスリープ状態であっても、SIGNALを送れば起きて処理を実行します


51 /*
52 * Send the specified signal to
53 * the specified process.
54 */
55 psignal(p, sig)
/* */
56 register struct proc *p;
57 register sig;
58 {
59
60 if((unsigned)sig >= NSIG)
61 return;
62 if(sig)
63 p->p_sig |= 1<<(sig-1);
64 if(p->p_pri > PUSER)
65 p->p_pri = PUSER;
66 if(p->p_stat == SSLEEP && p->p_pri > PZERO)
67 setrun(p);
68 }




●setrun
http://www.tamacom.com/tour/kernel/unix/S/96.html#L140

コメントにあるようにプロセスを実行状態にします。
該当プロセスがスワップアウトされていても、必要ならスワップインします。
つまり必ず実行されます。

144、145行目:プロセスの状態が0かゾンビならパニック
151行目:コメントをそのまま読むと、競合状態のためwへの割り当てが必要?
152行目:該当プロセスのpではなく、w(p->p_wchan)を起こします。
155行目:該当プロセスの状態をSRUN(実行中)に変更
156行目:該当プロセスをランキューに入れます
157行目:該当プロセスの優先度が現在実行中のプロセスの優先度より高ければ
158行目:再スケジューリングフラグを立てて
159行目:実行待ちのプロセスがなくて該当プロセスがメモリ上にない(スワップアウトされている)場合は
161行目:0番(スケジューラ)を起こします。

(備考)
runrun:先度の高い実行待ちプロセスがある
runin:実行待ちプロセスあり
runout:実行待ちプロセスなし(非ゼロは実行可能状態でかつディスクへスワップアウトされたプロセスがないことを示す)


136 /*
137 * Set the process running;
138 * arrange for it to be swapped in if necessary.
139 */
140 setrun(p)
/* */
141 register struct proc *p;
142 {
143 register caddr_t w;
144
145 if (p->p_stat==0 || p->p_stat==SZOMB)
146 panic("Running a dead proc");
147 /*
148 * The assignment to w is necessary because of
149 * race conditions. (Interrupt between test and use)
150 */
151 if (w = p->p_wchan) {
152 wakeup(w);
153 return;
154 }
155 p->p_stat = SRUN;
156 setrq(p);
157 if(p->p_pri < curpri)
158 runrun++;
159 if(runout != 0 && (p->p_flag&SLOAD) == 0) {
160 runout = 0;
161 wakeup((caddr_t)&runout);
162 }
163 }


(参考)
p->p_flagには以下の状態をとります。


39 /* flag codes */
40 #define SLOAD 01 /* in core */
41 #define SSYS 02 /* scheduling process */
42 #define SLOCK 04 /* process cannot be swapped */
43 #define SSWAP 010 /* process is being swapped out */
44 #define STRC 020 /* process is being traced */
45 #define SWTED 040 /* another tracing flag */
46 #define SULOCK 0100 /* user settable lock in core */




●setrq
http://www.tamacom.com/tour/kernel/unix/S/96.html#L118

runqは実行しているプロセスを保持しているキューです。
該当のpをランキューに入れます。


112 /*
113 * when you are sure that it
114 * is impossible to get the
115 * 'proc on q' diagnostic, the
116 * diagnostic loop can be removed.
117 */
118 setrq(p)
/* */
119 struct proc *p;
120 {
121 register struct proc *q;
122 register s;
123
124 s = spl6();
125 for(q=runq; q!=NULL; q=q->p_link)
126 if(q == p) {
127 printf("proc on q\n");
128 goto out;
129 }
130 p->p_link = runq;
131 runq = p;
132 out:
133 splx(s);
134 }


●疑問
setrunの152行目
競合が発生しているときは、別のwを起こしてリターンしていますが、元々起こそうとしていたプロセスへの処理は
中断されるってことでしょうか?

setrunで161行目が実行した後は
スケジューラが起動して、ランキューに該当プロセスが入っている。
該当プロセスの優先度が現在のプロセスより高いのでプロセススイッチが発生して、該当プロセスが実行状態になるという理解で正しいですか?



●プロセスハンドラの実行
killはプロセス番号の設定と送信先プロセスを起こすことを行っていました。

プロセスハンドラの実行は別で行っています。
killの実行が終わると、outへGOTOで飛びます。

http://www.tamacom.com/tour/kernel/unix/S/104.html#L227

226行目:issigでカレントプロセスが実行すべきシグナルハンドラを持っているかをチェックします。
227行目:シグナルハンドラがあれば、psigを実行します。


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);
236 }


●psig
http://www.tamacom.com/tour/kernel/unix/S/95.html#L128

148行目:実行すべきシグナルハンドラの実行はsendsigを呼び出します
151行目以降:実行すべきシグナルハンドラがないときは、デフォルトの動作Coreを吐く)をします。


121 /*
122 * Perform the action specified by
123 * the current signal.
124 * The usual sequence is:
125 * if(issig())
126 * psig();
127 */
128 psig()
/* */
129 {
130 register n, p;
131 register struct proc *rp;
132
133 rp = u.u_procp;
134 if (u.u_fpsaved==0) {
135 savfp(&u.u_fps);
136 u.u_fpsaved = 1;
137 }
138 if (rp->p_flag&STRC)
139 stop();
140 n = fsig(rp);
141 if (n==0)
142 return;
143 rp->p_sig &= ~(1<<(n-1));
144 if((p=u.u_signal[n]) != 0) {
145 u.u_error = 0;
146 if(n != SIGINS && n != SIGTRC)
147 u.u_signal[n] = 0;
148 sendsig((caddr_t)p, n);
149 return;
150 }
151 switch(n) {
152
153 case SIGQUIT:
154 case SIGINS:
155 case SIGTRC:
156 case SIGIOT:
157 case SIGEMT:
158 case SIGFPT:
159 case SIGBUS:
160 case SIGSEG:
161 case SIGSYS:
162 if(core())
163 n += 0200;
164 }
165 exit(n);
166 }


●sendsig
http://www.tamacom.com/tour/kernel/unix/S/87.html#L141

146行目:カレントプロセスのスタックを4増やして
147行目:スタック領域を確保して
148行目:現在のPSをスタックに保存
149行目:現在のPCをスタックに保存
150行目:スタックの位置を変更後(4増やした)に設定
151行目:PSからTBITを落とす
152行目:PCをp(シグナルハンドラの関数ポインタ)に設定


138 /*
139 * Let a process handle a signal by simulating an interrupt
140 */
141 sendsig(p, signo)
/* */
142 caddr_t p;
143 {
144 register unsigned n;
145
146 n = u.u_ar0[R6] - 4;
147 grow(n);
148 suword((caddr_t)n+2, u.u_ar0[RPS]);
149 suword((caddr_t)n, u.u_ar0[R7]);
150 u.u_ar0[R6] = n;
151 u.u_ar0[RPS] &= ~TBIT;
152 u.u_ar0[R7] = (int)p;
153 }

0 件のコメント:

コメントを投稿