Lions’ Commentary on UNIX 読書会メモ#7

はじめに

Lions本読書会#7に参加してきました。メモを残しておきます。

今回は13章のトレースから14章のsched()までを読みました。

togetter

関連ツイートをtogetterでまとめました。

トレースの流れについて

トレースの流れが話題に登りました。

この絵は以前のエントリで描いたものの再掲です。

child processがptraceを呼んでSTRCフラグをセットするタイミング

いつchild processはSTRCフラグをセットするんだ?というのが話題に上がりました。

ptraceシステムコールを使っているcdbのソースを覗いてみると、runcom( )のcase'r'のところに以下の記述があります。

http://www.bsdlover.cn/study/UnixTree/V6/usr/source/s1/cdb1.c.html

  if ((pid = fork())==0) {
    ptrace(SETTRC, 0, 0, 0); // SETTRC = 0
    signal(SIGINT, 0);
    signal(SIGINS, 0);
    doexec();
    printf("Can't execute %s\n", symfil);
    exit(0);
  }


libcのfork( )の返り値は、親プロセス(fork実行プロセス)の場合は生成された子プロセスのpid(0以外)、子プロセス(forkで生成されたプロセス)の場合は0です。

なので、子プロセスのみがif文がtrueとなり、ptrace(SETTRC, 0, 0, 0)(=STRCフラグを立てる)が実行されます。

子プロセスはfork -> ptrace -> execという流れになっています。


fork( )からの返り値についてはsystem callのマニュアルに書いてありました。引用しておきます。

From C, the child process receives a 0 return, and the parent receives a non-zero number which is the process
ID of the child; a return of −1 indicates inability to create a new process.

libcのforkソースコードも引用しておきます。1:の部分で返り値であるr0を0クリアしているのがわかります。

http://www.bsdlover.cn/study/UnixTree/V6/usr/source/s4/fork.s.html

/ C library -- fork

/ pid = fork();
/
/ pid == 0 in child process; pid == -1 means error return
/ in child, parents id is in par_uid if needed

.globl	_fork, cerror, _par_uid

_fork:
	mov	r5,-(sp)
	mov	sp,r5
	sys	fork
		br 1f
	bec	2f
	jmp	cerror
1:
	mov	r0,_par_uid
	clr	r0
2:
	mov	(sp)+,r5
	rts	pc
.bss
_par_uid: .=.+2

ちなみにカーネル内のfork( )システムコールハンドラからは、子プロセスには親のpidが返ります(3335行目)

親プロセスが子プロセスに介入するタイミングについて(親プロセスがptrace( )を実行するタイミングについて)

親プロセスがwait( )を抜けた後にptrace( )を実行して子プロセスに介入しているはずという話をしたのですが、それが本当かどうかというのが話題に上がりました。

cdbのソースを見るとbpwait( )(wait( )を呼ぶ関数)の実行後にptrace( )を実行している箇所がいくつかあります。ここで子プロセスに介入しているようです。

推測は当たっていたようです。

procxmt( )のcase '7'について(子プロセスの本来の処理を続行させる方法について)

ptraceでrequest=7(requestはptrace( )の第一引数で、子プロセスにどの種類の処理をさせるかを示す)で子プロセスにシグナルをセットすることができます。この場合procxmt( )からは1が返り、stop( )からissig( )へと戻ります。

issig( )で、自分(子プロセス)が(u_signal[n]が偶数の)シグナルを受け取っているかをチェックし、その結果を返します。通常if( issig( ) ) psig( )という書き方をするので、シグナルはすぐに処理されます。

つまり、request=7でptrace( )を実行するとすぐにシグナル処理がされます。

ただしシグナル0(シグナルは受け取っていない)、もしくは、u_signal[n]が基数のシグナルがセットされるとpsig( )は実行されず子プロセスの本来の処理が続行されます。

cdbのソースを見るとrequest=7は以下のようにしか使われていません。

ptrace(CONTIN, pid, 0, 0); // CONTIN = 7

ptrace( )の第四引数がdata(signal#)で、これが0です。つまり、cdbの中ではrequest=7は子プロセスに処理を続行させる目的でしか使用していないようです。

トレースの解除について

request=8で子プロセスを終了できます。procxmt( )のcase 8を見るとexit( )を実行しているのがわかります。

cdbはこれを実行してあるプログラムのトレース解除(=そのプロセスを殺す)しているようです。トレースする必要なくなったプロセスを殺すというのはデバッガの設計を考えると自然だと思います。

終わりに

次回のLions本読書会は6月19日(日)に開催される予定です。「OSのコードを読んでみたい!」という方は参加してみてはいかがでしょうか。