メインコンテンツへスキップ
見出し画像

forkとexec──プロセスが生まれる瞬間

    シェルでコマンドを打つとき、何が起きているか考えたことがあるか。

    ls と打つ。結果が返ってくる。それだけに見える。でもその裏で、プロセスが生まれて、死んでいる。

    この「生まれる」瞬間を理解すると、シェルの見え方が変わる。


    forkとは何か

    シェルが外部コマンドを実行するとき、まず自分自身を複製する。これが fork だ。

    # このlsを実行するとき
    ls -la
    

    シェルは fork() システムコールを呼び、自分のコピー(子プロセス)を作る。親と子は、この瞬間まったく同じ状態だ。同じコード、同じ変数、同じファイルディスクリプタを持っている。

    違いは一つだけ。fork() の戻り値だ。親プロセスには子のPIDが返り、子プロセスには0が返る。これで親と子は「自分がどちらか」を知る。


    execとは何か

    子プロセスは fork() の直後に exec() を呼ぶ。

    exec() は、現在のプロセスのイメージを、別のプログラムで置き換える。ls を実行するなら、子プロセスのメモリ空間が ls のバイナリで上書きされる。

    シェル(PID:100)
      └─ fork() → 子プロセス(PID:101) ← シェルのコピー
                     └─ exec("ls") → ls(PID:101) ← 中身が置き換わる
    

    PIDは変わらない。中身だけが変わる。


    なぜforkしてからexecするのか

    「最初からlsを実行すればいいのでは」と思うかもしれない。

    forkしてからexecする設計には理由がある。forkした直後、子プロセスはシェルのすべてを引き継いでいる。その状態でexecする前に、「環境を整える」ことができる。

    リダイレクトはその典型だ。

    ls -la > output.txt
    

    このとき子プロセスはexecの前に、標準出力のファイルディスクリプタを output.txt に付け替える。そしてexecする。ls は何も知らず、標準出力に書くだけだ。

    forkとexecの間の「隙間」が、シェルの柔軟性の源泉だ。


    シェルスクリプトで実感する

    #!/bin/sh
    
    echo "親: PID=$$"
    
    # サブシェルを作る
    (
        echo "子: PID=$$"
        echo "親のPID: $PPID"
    )
    
    echo "戻ってきた: PID=$$"
    

    $$ は現在のプロセスのPIDだ。() でサブシェルを作ると、fork が起きる。子プロセスの $$ は親と異なる値になる。

    sh縛りをしていたとき、このスクリプトを書いて動かした。「変数を変えたのに元に戻っている」という現象の原因が、このforkにあることをそこで理解した。


    forkとexecを知ると、パイプがなぜ動くのかも見えてくる。

    次回は、パイプの正体──ファイルディスクリプタでつながる世界の話をする。

     
     
    コピー機修理から始め、26歳でLinuxに出会い、28歳で直談判して異動。40代で借金1,800万背負って再起。歴史の積み重ねなしに、最新技術は正しく使えない。Engineers Hub㈱ 代表 / 八ヶ岳南麓 / 自社サービス複数βテスト中

    あなたへのおすすめ