試して理解 Linuxのしくみ w/Rust
書籍ではUbuntuが指定されているがUbuntu機を持っていなかったので、Raspberry Pi 4にUbuntuをいれて使う。
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
...
$ cat /proc/cpuinfo
...
Hardware : BCM2835
Revision : c03112
Serial : 10000000972ed649
Model : Raspberry Pi 4 Model B Rev 1.2
システムコール発行の可視化
fn main() {
println!("hello world");
}
$ rustc hello.rs
$ strace -o hello.log ./hello
hello world
strace
の出力をチェック。
...
write(1, "hello world\n", 12) = 12
...
ちゃんとwrite()
システムコールが発行されているみたい。
12は何だと思って調べてみたら、バイト数とのこと。たしかに\n
あわせて12ある。
システムコールを処理している時間の割合
Ubuntuにsar
が入っていなかった。
sudo apt install sysstat
普通に無限ループさせてみる
fn main() {
loop {}
}
$ taskset -c 0 ./inf-loop&
$ sar -P 0 1 1
Linux 5.15.0-1042-raspi (raspi4-ubuntu) 2023年11月28日 _aarch64_ (4 CPU)
14時09分47秒 CPU %user %nice %system %iowait %steal %idle
14時09分48秒 0 100.00 0.00 0.00 0.00 0.00 0.00
Average: 0 100.00 0.00 0.00 0.00 0.00 0.00
ユーザモードが100%になった。
今度は、システムコールgetppid()
を無限ループさせてみる。
組み込みのstd::process::id
を使えば十分だけど、Cのシステムコールと名前が対応していた方が見た目がわかりやすいので、外部ライブラリのnix
を使ってみる。はじめて使う!
外部ライブラリを使うにあたり、いちいちcargoでプロジェクトを作るのは面倒なので、補助としてrust-script
も使う。
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! nix = { version = "0.27.1", features = ["process"]}
//! ```
use nix::unistd;
fn main() {
loop {
unistd::getppid();
}
}
$ chmod +x syscall-inf-loop.rs
$ taskset -c 0 ./syscall-inf-loop.rs&
$ sar -P 0 1 1
Linux 5.15.0-1042-raspi (raspi4-ubuntu) 2023年11月28日 _aarch64_ (4 CPU)
15時07分04秒 CPU %user %nice %system %iowait %steal %idle
15時07分05秒 0 49.00 0.00 51.00 0.00 0.00 0.00
Average: 0 49.00 0.00 51.00 0.00 0.00 0.00
システムの占める割合が増えた。
同じプロセスを2つに分裂させるfork()
関数
書籍のサンプルコードはPythonだけど、nixのfork()
関数を使えば問題なくできそう。
気になる点として、上記docs内にSafetyについての注記がある。
n a multithreaded program, only async-signal-safe functions like pause and _exit may be called by the child (the parent isn’t restricted). Note that memory allocation may not be async-signal-safe and thus must be prevented.
Those functions are only a small subset of your operating system’s API, so special care must be taken to only invoke code you can control and audit.
子プロセス内でasync-signal-safeでない関数を呼ばないでね、ということみたい。docsのサンプルコード中に、たとえばprintln!
はダメだよと書いてある。以下の記事にも情報がある
async-signal-safeってなんぞや、と思って調べていたら、強火の記事を見かけたので貼っておく。ここでもasync-signal-safeでない関数としてmalloc()
があげられていて、print系もダメそうだなという雰囲気がわかる。今回はシグナルハンドラの話ではないから、直接の関係はないのだと思うのだけれど。
Safetyの注記はあるが、docsのサンプルコードを利用して、問題なくfork()
の動きを確認できた。注記を受け、unsafeな気持ちを込めて、println!
をunsafe
で囲っておいた。気持ちが大事
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! nix = { version = "0.27.1", features = ["process"]}
//! ```
use nix::libc;
use nix::unistd;
use nix::unistd::ForkResult;
fn main() {
match unsafe { unistd::fork() } {
Ok(ForkResult::Parent { child, .. }) => {
println!(
"親プロセス : pid={}, 子プロセスのpid={}",
unistd::getpid(),
child
);
unsafe { libc::_exit(0) };
}
Ok(ForkResult::Child) => {
#[allow(unused_unsafe)]
unsafe {
println!(
"子プロセス : pid={}, 親プロセスのpid={}",
unistd::getpid(),
unistd::getppid()
)
};
unsafe { libc::_exit(0) };
}
Err(_) => println!("Fork failed"),
}
}
ちょっと気になったのが、nix
にはlibcのexit()
のラッパが存在しないこと。exitしている時点でRustの安全管理の管轄外だから、nix
としてはexit()
に関与しないよ、ということなのかな? サンプルコードでもlibc::_exit(0)
が呼ばれていたので、それに倣った。
別のプログラムを起動するexecve()
関数
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! nix = { version = "0.27.1", features = ["process"]}
//! ```
use nix::libc;
use nix::unistd;
use nix::unistd::ForkResult;
use std::ffi::CString;
fn main() {
match unsafe { unistd::fork() } {
Ok(ForkResult::Parent { child, .. }) => {
println!(
"親プロセス : pid={}, 子プロセスのpid={}",
unistd::getpid(),
child
);
unsafe { libc::_exit(0) };
}
Ok(ForkResult::Child) => {
#[allow(unused_unsafe)]
unsafe {
println!(
"子プロセス : pid={}, 親プロセスのpid={}",
unistd::getpid(),
unistd::getppid()
)
};
let _ = unistd::execve(
CString::new("/bin/echo")
.expect("CString::new failed")
.as_c_str(),
&[
CString::new("echo")
.expect("CString::new failed")
.as_c_str(),
CString::new(format!("pid={} からこんにちは", unistd::getpid()))
.expect("CString::new failed")
.as_c_str(),
],
&[CString::new("").expect("CString::new failed").as_c_str()],
);
unsafe { libc::_exit(0) };
}
Err(_) => println!("Fork failed"),
}
}
$ ./fork-and-exec.rs
親プロセス : pid=476199, 子プロセスのpid=476200
子プロセス : pid=476200, 親プロセスのpid=476199
pid=476200 からこんにちは