rust ffi
linux の ps
前回はOpenBSDのpsを調べてrssの取出しにlibkvmが使われている亊を知った。FreeBSDも同様にlibkvmを使ってた。そうなると、野次馬根性でlinuxはどうなってるか知りたくなる。何、単なる老人の暇潰し。まて、老人って言葉は何となくいやだな。ここは、かっこよくシニアの趣味って亊にしておこう。
で、そんなのソース持ってこい。以上。
じゃ、つまんないので、斜めに攻めてみる。
sakae@deb:/tmp/rust-psutil/src$ grep rss -rI . ./process/process.rs: let percent = (memory_info.rss() as f64 / virtual_memory.total() as f64) * 100.0; ./process/os/macos/kinfo.rs: pub e_xrssize: libc::c_short, ./process/os/macos/kinfo.rs: pub e_xswrss: libc::c_short, ./process/os/linux/procfs/stat.rs: pub rss: i64, ./process/os/linux/procfs/stat.rs: pub rsslim: u64, ./process/os/linux/procfs/stat.rs: let rss = parse_i64(fields[23])? * *PAGE_SIZE as i64; ./process/os/linux/procfs/stat.rs: let rsslim = parse_u64(fields[24])?; ./process/os/linux/procfs/stat.rs: rss, ./process/os/linux/procfs/stat.rs: rsslim, ./process/memory.rs: pub(crate) rss: Bytes, ./process/memory.rs: pub fn rss(&self) -> Bytes { ./process/memory.rs: self.rss ./process/memory.rs: rss: statm.resident, ./process/memory.rs: rss: info.pti_resident_size,
ふーん、なんとなくprocfsを調べれば良さそう。man procfs して、其の中でrssを検索。
/proc/[pid]/stat Status information about the process. This is used by ps(1). It is defined in the kernel source file fs/proc/array.c. : (24) rss %ld Resident Set Size: number of pages the process has in real memory. This is just the pages which count toward text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out. This value is inaccurate; see /proc/[pid]/statm below.
cat stat すると、沢山の数字の羅列が出て来る。statだと50個以上。こういう時は、ぷちプログラミングする。
sakae@pen:/proc/2438$ cat stat | tr ' ' '\n' | cat -n sakae@pen:/proc/2438$ cat environ | tr '\000' '\n'
場合によって、上記を使い分るといいぞ。 カーネルの持ってる情報をファイルシステムに載せて見せてくれるって慧眼だなあ。
で、psutilをプチ探検する。memory.rs
#[cfg(target_os = "linux")] impl From<ProcfsStatm> for MemoryInfo { fn from(statm: ProcfsStatm) -> Self { MemoryInfo { rss: statm.resident, vms: statm.size, shared: statm.shared, text: statm.text, data: statm.data, } }
ふむ、statじゃなくて、statmっぽいな。
/proc/[pid]/statm Provides information about memory usage, measured in pages. The columns are: size (1) total program size (same as VmSize in /proc/[pid]/status) resident (2) resident set size (inaccurate; same as VmRSS in /proc/[pid]/status) shared (3) number of resident shared pages (i.e., backed by a file) (inaccurate; same as RssFile+RssShmem in /proc/[pid]/status) text (4) text (code) lib (5) library (unused since Linux 2.6; always 0) data (6) data + stack dt (7) dirty pages (unused since Linux 2.6; always 0)
取出してみる。
sakae@deb:/proc/1674$ cat statm 32734 10278 4910 524 0 8625 0 sakae@deb:/proc/1674$ ps -orss,command -p 1674 RSS COMMAND 41112 emacs -nw ./process/os/linux/procfs/stat.rs ./process/memory.rs
生データはページサイズなんで、多分4096/pageだろうって亊で、そういう演算をしてから、素人風な表示に直してあげる。
sakae@deb:/proc/1674$ bc 10278 * 4.096 * 1000 / 1024 41112
これにて、一件落着。
最初はsegv ffi
FFIをやってみたくて、 他言語関数インターフェイス サンプルページから取ってきた。
sakae@deb:/tmp/ffi$ cargo r Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/ffi` Segmentation fault sakae@deb:/tmp/ffi$ cargo r --release Compiling ffi v0.1.0 (/tmp/ffi) Finished release [optimized] target(s) in 1.51s Running `target/release/ffi` the square root of 0.000000000000000000000000000000000000000000045-1i is -1.4477382-0.000029377756i Segmentation fault
debian(32Bit)も一流なrustだなかろうか? こんな醜態を晒してていいのか。こういう時はあれをやってみるに限るな。
sakae@deb:/tmp/ffi$ rust-gdb -q target/debug/ffi Reading symbols from target/debug/ffi... (gdb) b ffi::main Breakpoint 1 at 0x5f48: file src/main.rs, line 25. (gdb) r Starting program: /tmp/ffi/target/debug/ffi [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". Breakpoint 1, ffi::main () at src/main.rs:25 25 let z = Complex { re: -1., im: 0. }; (gdb) n 28 let z_sqrt = unsafe { csqrtf(z) }; (gdb) n ffi::main () at src/main.rs:30 30 println!("the square root of {:?} is {:?}", z, z_sqrt); (gdb) n Program received signal SIGSEGV, Segmentation fault. core::fmt::write () at library/core/src/fmt/mod.rs:1160 1160 library/core/src/fmt/mod.rs: No such file or directory.
検死する。
(gdb) bt #0 core::fmt::write () at library/core/src/fmt/mod.rs:1160 #1 0x0041c1f6 in std::io::Write::write_fmt<std::io::stdio::StdoutLock> () at library/std/src/io/mod.rs:1696 #2 std::io::stdio::{impl#13}::write_fmt () at library/std/src/io/stdio.rs:855 #3 0x0041c759 in std::io::stdio::{impl#12}::write_fmt () at library/std/src/io/stdio.rs:829 #4 std::io::stdio::print_to<std::io::stdio::Stdout> () at library/std/src/io/stdio.rs:1196 #5 std::io::stdio::_print () at library/std/src/io/stdio.rs:1209 #6 0x0040607f in ffi::main () at src/main.rs:30
(gdb) p z $2 = ffi::Complex {re: 6.40498776e-39, im: -1} (gdb) p z_sqrt $3 = ffi::Complex {re: 0, im: 7.00649232e-45} (gdb) n Program received signal SIGSEGV, Segmentation fault.
こんなものなのか。
複素数をOpenBSDでもd dで
賭場を変えて、ついているか確認してみる。
vbox$ cargo r Compiling ffi v0.1.0 (/tmp/ffi) Finished dev [unoptimized + debuginfo] target(s) in 5.27s Running `target/debug/ffi` the square root of -1+0i is 0+1i cos(-1+0i) = 0.5403023+0i
普通に動いた。けど、複素数なsqrtなんて、馴染がないぞ。取り敢えず、答合わせしとく。
vbox$ gosh gosh$ (sqrt -1+0i ) 0.0+1.0i gosh$ (cos -1+0i ) 0.5403023058681398
csqrtfの定義は、こうなっていた。
#include <complex.h> float complex csqrtf(float complex z);
main.rsの中では、構造体でComplexを表現してる。まっとうなメンバー(re,im)が内容になってる。C語の方は、どこに有るのだろう? てっきりcomplex.hで宣言してるかと思ったらre,im相当の組み合わせはなかった。C99で、 _Complex が導入された。プリミティブなんだな。
ついでなんで、 C, C++ で複素数 とか C言語/標準ライブラリ/complex.h を見ておくと鼻高になれます。そして、普通の人はこう考えます。複素数を構造体で表現。なんだ、rustの人も同様に考えて、rust語で書いてみたら、動いちゃったってのが、真実なんだろうね。
いたずらで、ComplexをMineに書き換えてしまっても、普通に動いた。Mineなんて、おこがましいぞ、敬意を払ってガウスさんぐらいにしておけ。
get working directory pathname
自前のFFIをしてみたいぞ。libcとかnixなんていう木箱を使わないでね。という亊で、簡単そうなやつで、やってみる亊にする。
#include <unistd.h> char * getcwd(char *buf, size_t size);
shell語では、pwdになる。そんなのやって、何が嬉しいってのは、この祭無視。
純粋なrust語では、Function std::env::currentdir こんな風になってる。 そして、Module std::path も、まあ、関係者になるかな。
自前のやつは自爆した
まずは、お手本
#include<stdio.h> #include<unistd.h> #define PATHNAME_SIZE 256 int main(void) { char pathname[PATHNAME_SIZE]; getcwd(pathname, PATHNAME_SIZE); printf("pwd: %s\n", pathname); }
サイズは決めうちだ。普通は、これぐらいあれば十分過ぎてお釣りが來るぞ。それから、注意点として、エラーチェックは、伝統的にやっていない。本質を提示するために、あえて省いてます。コピペして問題が発生しても、責任は負えませんので、あしからず。
// first extern "C" { fn getcwd(buf: Box<[u8]>, size: usize) -> Box<[u8]>; } fn pwd(buf: Box<[u8]>, size: usize) -> Box<[u8]> { unsafe { getcwd(buf, size) } } fn main() { let path: Box<[u8]> = Box::new([0; 256]); let z = pwd(path, 256); println!("pwd: {} {} {} {} {} {}", z[0], z[1], z[2], z[3], z[4], z[5]); // myout(z); } fn myout(z: Box<[u8]>) { let mut i = 0; while z[i] != 0 { print!("{} ", z[i]); i += 1; } }
getcwd用のバッファーをどうやって用意するかよく解らなかったので、C言語のcalloc相当で準備してみた。けど、
warning: `extern` block uses type `Box<[u8]>`, which is not FFI-safe --> src/main.rs:4:20 | 4 | fn getcwd(buf: Box<[u8]>, size: usize) -> Box<[u8]>; | ^^^^^^^^^ not FFI-safe | = note: `#[warn(improper_ctypes)]` on by default = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct = note: this struct has unspecified layout
こんな風に、危いよと警告が出てきた。無理して走らせると、ドッカンとsegvである。根本的に間違っているな。
盗み見
本物はどうなってるか、サンプルをrust-gdbしてみる。
sakae@deb:/tmp/myffi$ rust-gdb -q target/debug/myffi Reading symbols from target/debug/myffi... (gdb) b getcwd Breakpoint 1 at 0x3370 (gdb) r Starting program: /tmp/myffi/target/debug/myffi [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". Breakpoint 1, __GI___getcwd ( buf=0x4593e0 "/ld-2.31.so\nb7ff2000-b7ffd000 r--p 0001e000 08:05 530699 /usr/lib/i386-linux-gnu/ld-2.31.so\nb7ffe000-b7fff000 r--p 00029000 08:05 530699 /usr/lib/i386-linux-gnu/ld-2.31.so\nb7fff000-b8000000 rw"..., size=512) at ../sysdeps/unix/sysv/linux/getcwd.c:47 47 ../sysdeps/unix/sysv/linux/getcwd.c: No such file or directory.
いきなりgetcwdなんて所にBPを置いていいのか。まあ、ちゃんと受領されたし、動いているみたいだから、佳としよう。ああ、リナだとソースが無くて不便。
(gdb) bt #0 __GI___getcwd ( buf=0x4593e0 "/ld-2.31.so\nb7ff2000-b7ffd000 r--p 0001e000 08:05 530699 /usr/lib/i386-linux-gnu/ld-2.31.so\nb7ffe000-b7fff000 r--p 00029000 08:05 530699 /usr/lib/i386-linux-gnu/ld-2.31.so\nb7fff000-b8000000 rw"..., size=512) at ../sysdeps/unix/sysv/linux/getcwd.c:47 #1 0x0041c348 in std::sys::unix::os::getcwd () at library/std/src/sys/unix/os.rs:142 #2 std::env::current_dir () at library/std/src/env.rs:47 #3 0x00407043 in myffi::main () at src/main.rs:4
注目は、フレームの1番、2番あたりだな。
(gdb) directory /home/sakae/src/rust/ Source directories searched: /home/sakae/src/rust:/home/sakae/.rustup/toolchains/stable-i686-unknown-linux-gnu/lib/rustlib/etc:$cdir:$cwd (gdb) f 1 #1 0x0041c348 in std::sys::unix::os::getcwd () at library/std/src/sys/unix/os.rs:142 142 let ptr = buf.as_mut_ptr() as *mut libc::c_char; (gdb) l 137 #[cfg(not(target_os = "espidf"))] 138 pub fn getcwd() -> io::Result<PathBuf> { 139 let mut buf = Vec::with_capacity(512); 140 loop { 141 unsafe { 142 let ptr = buf.as_mut_ptr() as *mut libc::c_char; 143 if !libc::getcwd(ptr, buf.capacity()).is_null() { 144 let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len(); 145 buf.set_len(len); 146 buf.shrink_to_fit();
ソースが有るって、とっても幸せ。これがOSSの醍醐味ですよ。
python方面では、盛んにAIだとか言って、色々な記事が書かれている。けど、オイラーの場合は、教師有り学習を地でいく、AIもどきをやってますよ。ソースが基本はフランス料理もITも変わらんな。
そうそう、最近、顔認証の教科書なんて本を読んだのよ。それによると、コテコテの教師有り学習だそうです。開発者が自ら、人種のるつぼであるニューヨークに繰り出し、色々な人種の顔を撮影させてもらったらしい。
こうした努力の甲斐があって、NIST(アメリカの標準制定機関)が行う顔認証のコンペで、何度もぶっちぎりの優勝を収めているらしい。そして、空港での時間待ちの時、ふと隣に居た医師との雑談で、がんにも顔が有るんですよって話になり、顔認証の技術をがんの発見に役立てる事を思いついた。で、新しい医療診断の分野が生まれた。素晴らしいね。NEC頑張れ。
移植するぞ
use std::io; use std::path::{ PathBuf}; use std::ffi::{CStr, OsString}; use std::os::unix::ffi::OsStringExt; pub fn getcwd() -> io::Result<PathBuf> { let mut buf = Vec::with_capacity(512); loop { unsafe { let ptr = buf.as_mut_ptr() as *mut libc::c_char; if !libc::getcwd(ptr, buf.capacity()).is_null() { let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char) .to_bytes().len(); buf.set_len(len); buf.shrink_to_fit(); return Ok(PathBuf::from(OsString::from_vec(buf))); } else { let error = io::Error::last_os_error(); if error.raw_os_error() != Some(libc::ERANGE) { return Err(error); } } // Trigger the internal buffer resizing logic of `Vec` by requiring // more space than the current capacity. let cap = buf.capacity(); buf.set_len(cap); buf.reserve(1); } } } fn main() -> std::io::Result<()> { let path = getcwd()?; println!("The current directory is {}", path.display()); Ok(()) }
移植と言うより、コピペです。スマソ。Cargo.tomlのdependenciesに、libc = "0.2.112" を追加してから実行してね。
守・破・離
rustは、今迄の言語の集大成になってるんで、習得が難しい、学び初めてからOUTPUTが出て来るまで、最低でも3ケ月はかかる、なんて亊をよく聞く。
だからオイラーも写経(と言うコピペ)から、初めてみたわけだ。stdなんで、ご安全にって方針でコードが書かれている。このご安全って言葉、なかなかイイネ。
建築業界の挨拶らしいけど、この間読んだ、新橋ビル物語に出て来る、飲み屋の女将も使ってた。酔っ払っても、ご安全におうちに帰れますようにっていたわり。なんだかほんわか気分になってくるな。
で、このご安全にってのを破る所から始めよう。守からの転換ね。こういう亊はコードを切り出しておくと、自在に出来る。そう、ご安全にと言う保護機構を取っ払った剥き出しのコードに変身させるのさ。
libc::getcwd(…) が、ifで監視されてる。うざいので安全外ししてみる。その前に一応どんな脅威があるかmanで下調べしとく。
ERRORS The getcwd() function will fail if: [EINVAL] The size argument is zero. : The getcwd() function may fail if: [EACCES] Read or search permission was denied for a component of the pathname. This is only checked in limited cases, depending on implementation details.
当たり前のエラーが列挙されてて、注目はEACCESかな。まあ、特殊なケースだろうから無視。首都圏に直下型地震が來ると言われてるのに住み続けるようなものだからね。
それから、BUFの初期値は512になってるけど、FreeBSDでは PATH_MAX
は 1024になってた。こんなのは調べないと出てこない無駄知識だ。レールを外れようとすると、こういうのがINPUTされてくのさ。
で、コメントの後ろに書かれている調整コード。ここに到達する亊って有るのかな? オイラーにはDead Codeのように思えるんだけど。ひょっとして、loop{} で囲まれているのは、何か連携してるって亊なのかな?
pub fn mygetcwd() -> io::Result<PathBuf> { let mut buf = Vec::with_capacity(512); unsafe { let ptr = buf.as_mut_ptr() as *mut libc::c_char; libc::getcwd(ptr, buf.capacity()); let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char) .to_bytes() .len(); buf.set_len(len); buf.shrink_to_fit(); Ok(PathBuf::from(OsString::from_vec(buf))) } }
unsafeって箱に入れてるけど、これは外せない。無理に外そうとすると怒られる。
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block --> src/main.rs:9:5 | 9 | libc::getcwd(ptr, buf.capacity()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function | = note: consult the function's documentation for information on how to avoid \ undefined behavior
飲み屋の女将が、あの店はぼったくりで危いから、入っちゃだめ。どうしてもって言うなら相応の準備をしてからねってのと一緒だ。
次は、libc外しをしてみたいんだけど、それには余白が少すぎる、な。
etc
確定申告のやり方
年金生活者の友達に教わった。今迄、知らんかったぞ。
e-Taxじゃなくて、PDFに落して書類提出が、スマホ持ってない人向けか。スマホを持っていないと、非人間扱いだなあ。まあ、いいんだけどね。