ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

Rust で基本的なプロセス作成

Rustでプロセスを作りたくなったので試した。変なとこがあれば教えてください。

Rust でシステム周りのプログラミングをする場合、nixというクレートが便利。システムコール呼び出しを Result<T, E> で返してくれるようになって、極めて扱いやすくなる。

docs.rs

プロセス周りについては、 fork(2)wait(2)/waitpid(2) のラッパーが存在するのでまずは素直に使う。

docs.rs

docs.rs

単純な例

use nix::sys::wait::waitpid;
use nix::unistd::{fork, ForkResult};
use std::thread::sleep;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Hello, world!");

    match unsafe { fork()? } {
        ForkResult::Parent { child } => {
            println!("Child Pid: {}", child);
            let status = waitpid(child, None)?;
            println!("Reaped: {:?}", status);
        }
        ForkResult::Child => {
            println!("I'm a new child process");
            sleep(Duration::from_secs(5));
            println!("Exit!!1");
        }
    }

    Ok(())
}

fork() はnix上でも unsafe であり、返り値は Result で包まれた ForkResult というenum。これを振り分けることで親子プロセスの判定ができる。

上記のコードをLinux上で動かすとたしかによくあるプロセスツリーが作成される。

vagrant    14661  4.7  0.0   3132   860 pts/0    S+   10:38   0:00  |           \_ target/debug/fork-wait-example
vagrant    14663  0.0  0.0   3132   132 pts/0    S+   10:38   0:00  |               \_ target/debug/fork-wait-example

正常終了時にはこう。

Hello, world!
Child Pid: 15207
I'm a new child process
Exit!!1
Reaped: Exited(Pid(15207), 0)

シグナルで子プロセスをkillするとこういう感じ。なお waitpid の返り値もenumである。第2引数では、 WNOHANG など man waitpid にあるオプションを受け付ける模様。

Hello, world!
Child Pid: 15224
I'm a new child process
Reaped: Signaled(Pid(15224), SIGTERM, false)

複数ワーカを作る

ワーカを作る場合は単純に fork() を呼んで、 waitpid(None, None) で待ち受ければOK。なお、 nix::wait::wait というショートカットもある。

use nix::sys::wait::waitpid;
use nix::unistd::{fork, ForkResult};
use std::thread::sleep;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Hello, world!");

    for _ in 0..5 {
        match unsafe { fork()? } {
            ForkResult::Parent { child } => {
                println!("Added child. Pid: {}", child);
            }
            ForkResult::Child => {
                println!("I'm a new child process");
                sleep(Duration::from_secs(30));
                println!("Exit!!1");
                std::process::exit(0)
            }
        }
        sleep(Duration::from_secs(3))
    }

    while let Ok(status) = waitpid(None, None) {
        println!("Reaped child. Status: {:?}", status);
    }

    Ok(())
}

走らせるとこう。

vagrant    15761  1.2  0.0   3132  1752 pts/0    S+   10:59   0:00  |           \_ target/debug/fork-wait-example
vagrant    15763  0.0  0.0   3132   136 pts/0    S+   10:59   0:00  |               \_ target/debug/fork-wait-example
vagrant    15768  0.0  0.0   3132   136 pts/0    S+   10:59   0:00  |               \_ target/debug/fork-wait-example
vagrant    15771  0.0  0.0   3132   136 pts/0    S+   10:59   0:00  |               \_ target/debug/fork-wait-example
vagrant    15774  0.0  0.0   3132   136 pts/0    S+   10:59   0:00  |               \_ target/debug/fork-wait-example
vagrant    15778  0.0  0.0   3132   136 pts/0    S+   10:59   0:00  |               \_ target/debug/fork-wait-example

一つ一つの終了ステータスを確認できる。while let のループで扱いが可能になり、極めて便利。

Hello, world!
I'm a new child process
Added child. Pid: 15763
Added child. Pid: 15768
I'm a new child process
Added child. Pid: 15771
I'm a new child process
Added child. Pid: 15774
I'm a new child process
Added child. Pid: 15778
I'm a new child process
Exit!!1
Reaped child. Status: Exited(Pid(15763), 0)
Exit!!1
Reaped child. Status: Exited(Pid(15768), 0)
Exit!!1
Reaped child. Status: Exited(Pid(15771), 0)
Exit!!1
Reaped child. Status: Exited(Pid(15774), 0)
Exit!!1
Reaped child. Status: Exited(Pid(15778), 0)

forkの留意点

マルチスレッドの場合、子プロセスにおいては async-signal-safe な関数のみを呼び出すべきとしている 。なので、今回のサンプルプログラムのように親子共シングルスレッドなら気にしなくてもいいとは思う*1が、デーモンを書くような場合マルチスレッドのライブラリを内部で使うこともありうる。基本的には即座に execve() するような目的が良いかもしれない。例えば自分自身を execve して $0 で動作を変えるようなパターンは良さそう。

特に、書いてある通り memory allocation may not be async-signal-safe なので、複雑な構造体を生成したりすべきではないだろう。


今度は、プロセス間通信を試す。 socketpair とか。

*1:psのSTATからわかる。