はじめに
プロセスのforkには、Rustからlibcを呼べるlibcというunsafeなライブラリと、それsafeにしたnixというものが使えるようです。unsafeは扱いにくいのでnixを使いました。
Cargo.toml
[dependencies] nix = "0.16.1"
今後rustのlibcを呼び出しているライブラリをlibcクレイトと呼ぶことにします。
この記事は私のRustの学習記録です。なのでRustでシステムプログラミングを始める初学者向けです。ただLinuxのプロセスの説明は省いています。
自分のプロセスIDを表示する
プログラムを起動して自分のプロセスIDを表示します。
src/main.rs
extern crate nix; use nix::unistd::{getpid}; fn main() { println!("PID: {}", getpid()); }
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/fork` PID: 4042
起動したプログラムのPIDが表示できました。
getpid()
nixのソースコードでは下記のようにlibcクレイトのgetpid()を呼び出しています。
/// Get the pid of this process (see /// [getpid(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html)). /// /// Since you are running code, there is always a pid to return, so there /// is no error case that needs to be handled. #[inline] pub fn getpid() -> Pid { Pid(unsafe { libc::getpid() }) }
Pidはlibcクレイトで定義されているpid_t
型をラップするタプル構造体のようです。
/// Process identifier /// /// Newtype pattern around `pid_t` (which is just alias). It prevents bugs caused by accidentally /// passing wrong value. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Pid(pid_t); impl Pid { /// Creates `Pid` from raw `pid_t`. pub fn from_raw(pid: pid_t) -> Self { Pid(pid) } /// Returns PID of calling process pub fn this() -> Self { getpid() } /// Returns PID of parent of calling process pub fn parent() -> Self { getppid() } /// Get the raw `pid_t` wrapped by `self`. pub fn as_raw(self) -> pid_t { self.0 } } impl From<Pid> for pid_t { fn from(pid: Pid) -> Self { pid.0 } } impl fmt::Display for Pid { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } }
単純にラップしているように見えます。nixの中身についてはまたの機会にみていこうと思います。
下記でも同様にPIDを取得できるようです。どっちの書き方がいいのでしょうか。
extern crate nix; use nix::unistd::{Pid}; fn main() { println!("PID: {}", Pid::this()); }
さっそくフォークしてみる
フォークの使い方はドキュメントにサンプルが載っています。
src/main.rs
extern crate nix; use nix::unistd::{fork, getpid, getppid, ForkResult}; fn main() { println!("Main({}) stared", getpid()); match fork() { Ok(ForkResult::Parent { child, .. }) => { println!("Main({}) forked a child({})", getpid(), child); } Ok(ForkResult::Child) => { println!("Child({}) started. PPID is {}", getpid(), getppid()); } Err(_) => println!("Fork failed"), } }
$ cargo run Compiling fork v0.1.0 (/home/vagrant/dev/src/github.com/gkuga/rust-examples/fork) Finished dev [unoptimized + debuginfo] target(s) in 0.34s Running `target/debug/fork` Main(4901) stared Main(4901) forked a child(4923) Child(4923) started. PPID is 1
fork()
でプロセスがコピーされて、元のプロセスではOk(ForkResult::Parent { child, .. })
が実行されます。コピーされた子のプロセスではOk(ForkResult::Child)
が実行されます。ここでChild(4923) started. PPID is 1
となっていますが、本来はChild(4923) started. PPID is 4901
を期待していました。これは親のプロセスが先に終了してしまったので、PIDが1のinitプロセスに付け替えられたようです。
waitpid()
親のプロセスで子のプロセスが終了するまで待ちます。それにはwaitpid()を使います。
extern crate nix; use nix::sys::wait::waitpid; use nix::unistd::{fork, getpid, getppid, ForkResult}; use std::process::exit; fn main() { println!("Main({}) stared", getpid()); let child_pid = match fork() { Ok(ForkResult::Parent { child, .. }) => { println!("Main({}) forked a child({})", getpid(), child); child } Ok(ForkResult::Child) => { println!("Child({}) started. PPID is {}.", getpid(), getppid()); exit(0); } Err(_) => panic!("fork() failed"), }; match waitpid(child_pid, None) { Ok(status) => println!("Child exited ({:?}).", status), Err(_) => println!("waitpid() failed"), } }
fork()のmatchで子のプロセスIDを返すようにします。それをwaitpidに渡しました。実行結果は下記のようになりました。
$ cargo run Compiling fork v0.1.0 (/home/vagrant/dev/src/github.com/gkuga/rust-examples/fork) Finished dev [unoptimized + debuginfo] target(s) in 0.31s Running `target/debug/fork` Main(5187) stared Main(5187) forked a child(5212) Child(5212) started. PPID is 5187. Child exited (Exited(Pid(5212), 0)).
おわりに
何気なくプログラムはLinuxで動かしていましたが、nixという名前の通り様々なUnix系OSに対応しているようです。Macでも動きました。
Nix seeks to provide friendly bindings to various *nix platform APIs (Linux, Darwin, ...).
libcクレイトがrustが動作するプラットフォームで動くように作られているようです。