1. 概要

このチュートリアルでは、Linuxでツールstraceを使用して子プロセスを追跡する方法を調べます。

子プロセスを作成するためのスクリプトから始めます。 次に、オプションなしでstraceを使用して追跡します。 最後に、 strace を使用して追跡し、子プロセスの追跡に役立つ特定のオプションを使用します。

2. スクリプトの前提条件

空のディレクトリに、create_child_processes.pyという名前のPythonスクリプトを作成しましょう。

#!/usr/bin/python3
import os

def fork_many_processes():
    print(f"I'm the original process and my id is: {os.getpid()}")
    n = os.fork()

    if n == 0:
        print(f"I'm the child process and my id is: {os.getpid()}")
        n = os.fork()

        if n == 0:
            print(f"I'm the grandchild process and my id is: {os.getpid()}")
            n = os.fork()

            if n == 0:
                print(f"I'm the great-grandchild process and my id is: {os.getpid()}")

fork_many_processes()

Pythonを使用するのは、C /C++ソースコードをコンパイルしてバイナリファイルを実行するよりもはるかに簡単だからです。 ただし、チュートリアルは他の言語でも機能します。

2.1. プロセスの分岐

プロセスをフォークするということは、同一で独立したプロセスを作成することを意味します。 Pythonでは、osモジュールのforkメソッドを使用します。

n = os.fork()

現在のプロセスの場合、メソッドは子プロセスIDを返します。 新しいプロセスも生まれます。 このプロセスは、親プロセスからすべてを継承し、forkメソッドで実行を再開します。 ただし、今回は、メソッドは0を返します。

したがって、 fork メソッドの戻り値を確認することで、どのプロセスが親で、どのプロセスが子であるかを区別できます。 戻り値が0の場合、スクリプトのプロセスは子プロセスです

if n == 0:

もちろん、子プロセスは同じメカニズムを使用して別の子プロセスを作成することもできます。

2.2. スクリプトの実行

まず、ファイルが実行可能であることを確認する必要があります。

chmod +x create_child_processes.py

次に、それを実行できます。

$ ./create_child_processes.py 
I'm the original process and my id is: 912212
I'm the child process and my id is: 912213
I'm the grandchild process and my id is: 912214
I'm the great-grandchild process and my id is: 912215

プロセスは子プロセスを作成しました。 この子プロセスは、元のプロセスの孫である別の子プロセスを作成しました。 次に、この孫プロセスは、元のプロセスのひ孫である別の子プロセスを作成しました。

3. straceを使用する

strace を使用して、Pythonスクリプトによって実行されるシステムコールを監視できます。

$ strace ./create_child_processes.py
...(Preceding output truncated)
read(3, "#!/usr/bin/python3\nimport os\n\nde"..., 4096) = 504
read(3, "", 4096) = 0
close(3) = 0
getpid() = 993199
write(1, "I'm the original process and my "..., 46I'm the original process and my id is: 993199
) = 46
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f687ce1aa10) = 993200
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f687d02c210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f687d02c210}, 8) = 0
I'm the child process and my id is: 993200
I'm the grandchild process and my id is: 993201
I'm the great-grandchild process and my id is: 993202
sigaltstack(NULL, {ss_sp=0x252c460, ss_flags=0, ss_size=16384}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0) = ?
+++ exited with 0 +++

getpid()=993199のようないくつかのシステムコールラインを認識できます。 Pythonコードos.getpid()がこのシステムコールを呼び出しました。 Pythonスクリプトのprintメソッドがwriteシステムコールを実行したことは簡単に推測できます。 そして、ついにcloneシステムコールが表示されます。

ご存知のように、Pythonスクリプトのos.forkメソッドはこのシステムコールを呼び出しました。

ただし、子プロセスによって呼び出されたシステムコールは表示されません。 子プロセスによって生成された出力のみが表示されます。

3.1. straceを使用した子プロセスの追跡

strace には、子プロセスの追跡に役立つオプションがあります。 –fオプションを使用できます

$ strace -f ./create_child_processes.py
...(Preceding output truncated)
getpid()                                = 994450
write(1, "I'm the original process and my "..., 46I'm the original process and my id is: 994450
) = 46
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 994451 attached
, child_tidptr=0x7f48eab43a10) = 994451
[pid 994451] set_robust_list(0x7f48eab43a20, 24) = 0
[pid 994450] rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, 8) = 0
[pid 994451] getpid()                   = 994451
[pid 994451] write(1, "I'm the child process and my id "..., 43I'm the child process and my id is: 994451
) = 43
[pid 994451] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f48eab43a10) = 994452
strace: Process 994452 attached
[pid 994452] set_robust_list(0x7f48eab43a20, 24) = 0
[pid 994451] rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f48ead55210}, 8) = 0
[pid 994452] getpid()                   = 994452
[pid 994452] write(1, "I'm the grandchild process and m"..., 48I'm the grandchild process and my id is: 994452
) = 48
...(Subsequent output truncated)

子プロセスによって生成されたシステムコールには、[pid…]が付加されます。 括弧内の数字はプロセスIDです。これは、どの子プロセスがこのシステムコールを生成したかを追跡できることを意味します。 clone システムコールが生成した子プロセスIDを表示したため、この子プロセスがいつ生まれたかを追跡することもできます。

3.2. straceを使用したファイルでの子プロセスの追跡

ターミナルの出力から子プロセスを分析するのは困難です。 おそらく、後で分析するためにファイルに保存する必要がありますか? いいえ、リダイレクト手法を使用できません

$ strace -f ./create_child_processes.py > dump
$ cat dump
I'm the original process and my id is: 32086
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the grandchild process and my id is: 32088
I'm the original process and my id is: 32086
I'm the child process and my id is: 32087
I'm the grandchild process and my id is: 32088
I'm the great-grandchild process and my id is: 32089

どうやら、stracestdoutの出力を表示しません。 出力をファイルに保存するには、-oオプションとファイル名をそのオプションの引数として使用できます

$ strace -o strace_log -f ./create_child_processes.py 
I'm the original process and my id is: 32262
I'm the child process and my id is: 32263
I'm the grandchild process and my id is: 32264
I'm the great-grandchild process and my id is: 32265
$ tail strace_log 
32265 sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0},  <unfinished ...>
32264 sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0},  <unfinished ...>
32265 <... sigaltstack resumed>NULL)    = 0
32264 <... sigaltstack resumed>NULL)    = 0
32265 exit_group(0 <unfinished ...>
32264 exit_group(0 <unfinished ...>
32265 <... exit_group resumed>)         = ?
32264 <... exit_group resumed>)         = ?
32265 +++ exited with 0 +++
32264 +++ exited with 0 +++

ご覧のとおり、子プロセス行は、この例の32265または32264のようにプロセスIDで始まります。

3.3. strace を使用して、個別のファイルで子プロセスを追跡する

以前のように、多くの子プロセスのアクティビティを1か所で追跡することは困難です。 それぞれの子プロセスの活動を別々の場所で見ることができたら素晴らしいと思いませんか? -ffオプションと-oオプションを指定してstraceを使用することでこれを行うことができます

$ strace -o output -ff ./create_child_processes.py 
I'm the original process and my id is: 31117
I'm the child process and my id is: 31118
I'm the grandchild process and my id is: 31119
I'm the great-grandchild process and my id is: 31120

-o オプションは、straceがファイルの作成に使用するファイル名パターンを受け入れます。 これらのファイルには、子プロセスのアクティビティが含まれます。

$ ls output*
output.31117  output.31118  output.31119  output.31120

ファイル拡張子として追加される番号は、元のプロセスと子プロセスのプロセスIDです。 IDが最小のダンプファイル(この例では output.31117 )は、オリジンプロセスのダンプファイルです。 確認できます:

$ grep origin output.31117 
write(1, "I'm the original process and my "..., 45) = 45

他のダンプファイルを開いて、それらが子プロセスのダンプファイルであることを確認できます。

$ cat output.31118 
set_robust_list(0x7f5995cc9a20, 24)     = 0
getpid()                                = 31118
write(1, "I'm the child process and my id "..., 42) = 42
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f5995cc9a10) = 31119
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5995edb210}, {sa_handler=0x629b90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f5995edb210}, 8) = 0
sigaltstack(NULL, {ss_sp=0x167d460, ss_flags=0, ss_size=16384}) = 0
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}, NULL) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

4. 結論

この記事では、straceを使用して子プロセスを追跡する方法について説明しました。

まず、プロセスを追跡するためのオプションなしで strace を使用しましたが、子プロセスによって生成されたシステムコールが欠落していることがわかりました。

次に、 -f オプションを使用すると、子プロセスからのシステムコールが表示されました。 また、-oオプションを使用して出力をファイルに保存しました。

最後に、-ffオプションと-oオプションを使用して、子プロセスのシステムコールの出力を別のファイルにダンプしました。