もともとぼくが書籍を書くことの目的として,ネット検索では得られないような 様々な技術的知見を得たいというのがあります (本を出すことで一番勉強になるのは筆者です). なので,このような指摘というか考察を公開していただけるのは, 非常にありがたいしとても勉強になります. (いやほんとありがたいです. Linuxやglibcの実装を見直すきっかけにもなったし, リンク先の筆者の @shinhさん には感謝したい)
考察の内容は上記リンク先を参照してほしいのだが, 読みっぱなしというのも申し訳ないので, それについてぼくなりにちょっと考えてみた.
ちなみに以下のぼくなりの考察ですが,わりと言い訳っぽい話(笑)も入っているの だけど,「俺は悪くない!書籍の内容は間違ってない!」とかいうようなものでは ないです.純粋に技術的に考えた結果,こう思う,というものです. 機会があったらぜひ深く議論させていただきたいです.
主に「POSIXではどうだろうか?」という視点での考察を含めた感じです.
まず,書籍の原文は以下.
なお正式にはexit()は_exit()を呼び出すためのライブラリ関数であり, _exit()が本来のシステムコールのAPIだ.このため「exitシステムコール」ではなく 「_exitシステムコール」と表記すべきで,manのカテゴリはexit(3)と_exit(2)に なっている.まあでもあまり気にせずに,ここでは「exitシステムコール」と 呼んでしまっている. |
これについて,以下の指摘があります.
これは概ね賛成で,すんませんそのとおりですごめんなさいと思ってしまうのだが, ちょっと難しい部分がある,と思う点もある.
まずLinuxカーネルのソースコードでは以下のようになっている.
#define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 ...(中略)... #define __NR__llseek 140まず,システムコールをAPIとして見るかカーネルのサービスとして見るかという 視点の違いがあると思う.APIとして見るならばglibcが定義している System Call Wrapper の名前で言うべき.Linuxカーネルのサービスとして見るの ならば,Linuxカーネル内でのシステムコール名の #define を見るべき.
なのでLinuxカーネルという視点で考えるならば,「exitシステムコール」と表記 すべきかと思う. C言語プログラマに提供するAPIではないので,括弧はつけずに表記する. (その意味では,たとえばreadやwriteなども「readシステムコール」のように書いて, 「read()システムコール」のように書くべきではない). その意味では「Linuxに_exitというシステムコールは無い」というのは正しい.
ただこれは「Linuxカーネルでは」というただし書きがつく. 他のカーネルでは別かもしれないし,また単に「システムコール」とだけ 言ってしまうと,ちょっと話は微妙になる.
POSIX的にはどうかというと,_exit() というのは定義されている (ただしシステムコールとは明記されていなくて,プロセスを終了するためのサービス, くらいのことが書かれている). まあでもこちらは System Call Wrapper によって用意されるプログラマ向けの APIの定義の話になる(そういう意味では,こちらは_exit()のように括弧をつけて表記 すべき)ので,Linuxカーネルの話とはまたちょっと違った話ではある.
書籍のほうでは「_exitシステムコール」となっていて,括弧がついていないので Linuxカーネルのシステムコールと考えて,「そのようなシステムコールは無い」 というのは正しいと思う.ただ書籍では_exit()のように括弧をつけている表記もあり, こちらは「_exit()というシステムコールAPI」と言っているので,POSIXのAPIを指して いると読み取れなくもない...かもしれない. (ただし付近ではLinuxカーネルの話をしているので,そりゃ言い訳だろなんでここで POSIXがいきなり出てくるの?と言われたら,そのとおりかとも思う笑.先述のとおり, 俺は間違ってないとか言いたいわけではなく,技術的にはどうだろうかという話が したいのと,Linux目線だけでなくPOSIX目線でも考えたい,と思うだけです)
あと問題はLinuxの場合に,これを「_exit()システムコール」と呼んでいいか どうかだが,POSIXで定義されている_exit()を glibc のSystem Call Wrapper で サービスとして提供している,という点では,まあ_exit()システムコールと言って しまっても悪くは無い…とおもう部分がある.というのはこれは 「GNU/Linux環境でのsocket()システムコール」という表現を許すか許さないか, という話と同じになり,そう言っちゃってもいいかなあ…という気もするからだ (Linuxはsocketシステムコールは持っていなくて,ソケット関係は socketcall という システムコールに集約されている.それを System Call Wrapper が吸収して, POSIXのsocket()をアプリに提供しているわけだ).「Linuxの_exit()システムコール」 と言うとおかしいけど,「GNU/Linux環境で利用できる_exit()システムコール」と 言って,システムコールというのをカーネルというよりシステムへのサービス要求と 考えるぶんには,まあ_exit()システムコールと言っちゃってもいいかなあ,という 感じだ.「Linuxの」というのを抜いて単に「_exit()システムコール」と呼ぶなら まだマシかなあとも思ったりする.
ただもちろん「技術的に正確に表記すべき」という意見にはものすごく大賛成だし, 書籍中の当該の箇所はシステムコールまわりについて議論しているところなので, やっぱりこれらのことは他以上に正確に表記すべきと思う. なのでやっぱし書籍の記述はまずいと思う.
結局のところ書籍のほうでは,Linuxカーネルの話にぼくのPOSIX寄りの考えが 混ざっているのがそもそもの問題かなあ,と思ったりする.
■ 次の,「exit(3)も_exit(2)もexitシステムコールを呼ばない」という話についての考察
実際には exit_group システムコールが呼ばれる,というものなのだが, これはまさにそのとおりです.
が,2つの点で,これはこれでまたちょっと難しい部分があるかもと思うところが ある. (これに関しては現在ハローワールド入門を執筆していて気がついたことでもある)
まずglibcの System Call Wrapper では_exit()は以下のように定義されている.
(glibc-2.21/sysdeps/unix/sysv/linux/i386/_exit.S)
_exit: movl 4(%esp), %ebx /* Try the new syscall first. */ #ifdef __NR_exit_group movl $__NR_exit_group, %eax ENTER_KERNEL #endif /* Not available. Now the old one. */ movl $__NR_exit, %eax /* Don't bother using ENTER_KERNEL here. If the exit_group syscall is not available AT_SYSINFO isn't either. */ int $0x80 /* This must not fail. Be sure we don't return. */ hltこれは「_exit()からはexit_groupシステムコールが呼ばれる」というよりも,
なので「_exit()からはexit_groupが呼ばれ,exitは呼ばれない」と言い切るのは ちと言いすぎかなあ,とも思う.
あとこれはLinux+glibcの話であって, FreeBSDとかだと話は違って exit() から _exit() が呼ばれていたりする. 以下,FreeBSD の libc より抜粋.
void exit(status) int status; { /* Ensure that the auto-initialization routine is linked in: */ extern int _thread_autoinit_dummy_decl; _thread_autoinit_dummy_decl = 1; __cxa_finalize(NULL); if (__cleanup) (*__cleanup)(); _exit(status); }問題は「システムコール」と言ったときにいくつかの意味があって,
2の場合はC言語のAPIなので括弧をつけて,「_exit()」と表記すべき. これを「_exit()システムコール」と呼んじゃっていいかどうかは 先に述べた通り.
3の場合はやはりC言語のAPIなので括弧をつけて, 「_exit()システムコール」と表記していいかと思う. POSIXではシステムコールとは明記されていない,ということを厳密にしたければ 「_exit()サービス」と表記するべきか.
そして1と2はLinux+glibcの特有の話.3はPOSIX互換環境に当てはまる話. FreeBSDはUNIXの系統を受け継いでいるのでPOSIX色が強く (ていうか順番的にはPOSIXが*BSD色が強く,というべきか), 考え方的には3に入ると考えるべきか. LinuxはPOSIX互換だけど,Linuxカーネルが提供しているのはあくまで POSIX互換のサービスを実現するための基本機能であり,それを利用して実際に POSIXインターフェースを提供しているのはglibcの System Call Wrapper になる. そして実際のLinuxカーネルは,実はかなりPOSIXとはズレがあって (getpriority()とかね),glibcが吸収している部分は大きい.
そういう意味ではLinuxがPOSIX互換かということを考えるときには, 「Linux+glibcでPOSIX互換環境を提供している」と考えるべき. なので「Linux+glibc」によって提供されている_exit()というサービスを 「_exit()システムコール」と呼んでしまうことに,それほど違和感はないともいえる (先述したように,「Linuxの_exit()システムコール」と言ってしまうと間違いだと 思うが,「GNU/Linuxの_exit()システムコール」と言うぶんにはそれほど違和感は無い .まあ「システムコールと言えばそれはカーネルへのリクエストなんだから, それはおかしいだろ!」と言われたら,それはそうねとも思うが)
なので,リンク先の考察はちょっとLinux+glibc寄りになっていて, ぼく自身の考え方は実はPOSIX寄りなのが書籍に如実に出てしまったなあ…とも思う (ただこれは書籍自体はLinux+glibc寄りなのでLinux+glibcを前提に考えるのが 当然と思うし,ぼくの考え方のほうが書籍とズレがある,と思う). 単に「システムコール」と言わずに「Linuxカーネルの…」とか「GNU/Linuxの…」とか 「POSIXの…」とかを明確にして議論すると,もっと話がすっきりするようにも思う.
で,書籍の原文だが,以下のようになっている.
Linuxカーネルのarch/x86/syscalls/syscall_32.tblからexitシステムコールの
番号を調べてみると,どうも「1」のようだ.ということでEAXにシステムコール
番号の1を設定し,exitに渡す引数をEBXに設定して int 0x80 を呼び出せばいい.
正常終了するためには終了コードをゼロにしたいので,EBXにはゼロを設定する
ことになる.
なお正式にはexit()は_exit()を呼び出すためのライブラリ関数であり, _exit()が本来のシステムコールのAPIだ.このため「exitシステムコール」ではなく 「_exitシステムコール」と表記すべきで,manのカテゴリはexit(3)と_exit(2)に なっている.まあでもあまり気にせずに,ここでは「exitシステムコール」と 呼んでしまっている. |
POSIX的には「exit()が_exit()システムコールを呼ぶ」というの は正しいとも言えるように思う. まあ言い訳みたいな話になってしまうけど,上記の 「なお正式には...」の文章ではLinuxとは明示されてない (ああ,言い訳っぽい笑.でもこの明示されてないというのがそもそもの問題で, 書籍の説明でもっと前提をはっきりさせるべき,とも思う) ので,上記の_exit()のくだりはPOSIX寄りの話と考えるならば, (「_exitシステムコール」という表記以外は) あながち間違いではない,とは言えるかもしれない. (かなり言い訳っぽい話なのですが,これは先述したように, 「俺は間違ってない!」とか言いたいのではなく, 技術的にはどうだろうなあ,という議論がしたいだけです)
ただその前の文章では「Linuxカーネルの...」のように言っているし, 書籍自体が全体的にLinuxカーネルを前提としている. なので文章的には間違ってない!とかいう話ではなく, やっぱりちょっと記述的に変かな,と思います.
まあでもそもそも執筆時にはそこまで深く考えて書いたわけではなかった, という反省もありますし,技術書なのだから言葉は正確に書くべき,という 反省もあります.深く考えるきっかけを作っていただいた, @shinhさんには ほんと深謝したい.なかなかこうした深い議論をできる機会って少ないので.
このへんは現在執筆中の新作「ハロー・ワールド入門」で,1節を割いて 詳しく説明してあるので,そっちも期待していてください.