glibcの_exit()のソースコードを読んでみよう。
そのためには、まずは_exit()が定義されている場所を探さなければならない。しかし単純に「exit」や「_exit」で検索しても、大量にヒットしてしまいそうだ。どのようにして探したらいいだろうか。
このようなとき、まずは探す対象がアーキテクチャ共通の処理なのか、そうではなく特定のアーキテクチャ特有の処理なのかをまず考えるといい。図5.9を見るとhltという命令が呼ばれているが、これはx86特有の命令でコンパイラが出力するようなものではない。ということは、x86依存のディレクトリに_exitの実体があるのではないだろうか。しかもおそらく、アセンブラのファイルだろう。
システムコール・ラッパーを探したとき、glibcではx86特有の処理はsysdeps/unix/sysv/linux/i386というディレクトリにあった。そこでhlt命令を利用している箇所を探してみよう。
[user@localhost ~]$ cd glibc-2.21/sysdeps/unix/sysv/linux/i386 [user@localhost i386]$ grep hlt * _exit.S: hlt makecontext.S: hlt [user@localhost i386]$
_exit.Sというファイルがある。内容を見てみると、以下のようになっていた。
21: .type _exit,@function 22: .global _exit 23:_exit: 24: movl 4(%esp), %ebx 25: 26: /* Try the new syscall first. */ 27:#ifdef __NR_exit_group 28: movl $__NR_exit_group, %eax 29: ENTER_KERNEL 30:#endif 31: 32: /* Not available. Now the old one. */ 33: movl $__NR_exit, %eax 34: /* Don't bother using ENTER_KERNEL here. If the exit_group 35: syscall is not available AT_SYSINFO isn't either. */ 36: int $0x80 37: 38: /* This must not fail. Be sure we don't return. */ 39: hlt
内容はまさに図5.9と一致するので、これが_exitの定義のようだ。システムコールを呼び出しているので、どうやらシステムコール・ラッパーのようだ。
exit_groupの呼び出し処理は#ifdef __NR_exit_groupでくくられているので、exit_groupが存在する場合にはそれが呼ばれるが、そうでない場合にはexitが呼ばれる、ということらしい。コメントにも「Try the new syscall first.」や「Not available. Now the old one.」のように書かれており、まず新しいシステムコール(exit_group)を試すが、ダメなら古いほう(exit)を使う、というように書かれている。
システムコール番号から考えても、exitの1に対してexit_groupは252であり、exitは初期から存在するシステムコールだが、exit_groupはおそらくだいぶ後に追加されたシステムコールだと推測できる。またglibcの_exitは、Linuxカーネルにexit_groupが追加された際にその呼び出しが#ifdefでくくることで追加された、ということも推測できる。
exit_groupの機能を考えると、スレッド機能が追加されたときに追加されたシステムコールなのだろうか。
exit()や_exit()、そしてexit_groupとexitなどいろいろ出てきているが、ここで一度整理しよう。
そしてPOSIXでは、プログラムを終了させるAPIとして、以下の2つが定義されている。なおatexit()はexit()による終了時に呼び出してほしい関数を登録しておくことができる、ライブラリ関数だ。
これらをどのように考えたらいいだろうか。
Linuxカーネルが定義するのはシステムコールのABIであり、APIを提供するのはglibcの役割だ。よってPOSIXのexit()と_exit()を実現するための機能としてLinuxにはexit_groupやexitシステムコールがあり、glibcはexit_groupシステムコールを呼び出すことで、POSIXの_exit()を提供している、と考えられるだろう。_exit()はシステムコール・ラッパーとしてユーザに提供されるAPIなわけだ。
なお、「exit」と表記した場合にはLinuxカーネルが持つexitシステムコールを指し、「exit()」と表記した場合にはglibcが提供する(POSIXの)exit()ライブラリ関数を指している、と考えるべきだろう。括弧の有無によって指すものがまったく異なるものになることに注意してほしい(またglibcの_exitのように、アセンブラ上で定義してあるシンボルを「_exit」のようにして括弧無しで表記する場合もある)。
CentOSでexit()と_exit()をmanコマンドで調べたときの、カテゴリはどうなっているだろうか。
ここで、manコマンドでのカテゴリ指定についてちょっと説明しておこう。
まずUNIXライクなOSでのmanコマンドによるオンラインマニュアルはカテゴリ分けされており、一般にカテゴリ1がコマンド、カテゴリ2がシステムコールのAPI、カテゴリ3がライブラリ関数に相当する。
exit()はatexit()によって登録された関数を呼び出してからプログラムを終了する、ライブラリ関数だ。ということはカテゴリ3にあるはずだ。そしてこれをexit(3)のように表記する。
しかし本書のVM環境でman exitを実行してみると、以下のように表示される。
BASH_BUILTINS(1) BASH_BUILTINS(1) NAME bash, :, ., [, alias, bg, bind, break, builtin, caller, cd, command, compgen, complete, compopt, continue, declare, dirs, disown, echo, enable, eval, exec, exit, export, false, fc, fg, getopts, hash, help, history, jobs, kill, let, local, logout, mapfile, popd, printf, pushd, pwd, read, readonly, return, set, shift, shopt, source, suspend, test, times, trap, true, type, typeset, ulimit, umask, unalias, unset, wait - bash built-in commands, see bash(1) ...
「BASH_BUILTINS(1)」となっており、シェルのビルトイン・コマンドであるexitがヒットしているようだ。このようにmanコマンドには、同じキーワードが別カテゴリに複数登録されていることがある。
そのような場合には、man 3 exitのようにしてカテゴリ指定で参照することができる。「exit(3)を調べるように」と言われたら、それはカテゴリ3のexit、つまりman 3 exitを見てみなさいという意味だ。以下はman 3 exitを実行したときの表示結果だ。
EXIT(3) Linux Programmer's Manual EXIT(3) NAME exit - cause normal process termination SYNOPSIS #include <stdlib.h> void exit(int status); DESCRIPTION The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)). ...
先頭で「EXIT(3)」のように表示されているので、ライブラリ関数のexit(3)がヒットしている。ということは、exit()はやはりライブラリ関数の扱いだ。
_exit()はどうであろうか。man _exitを実行してみよう。
_EXIT(2) Linux Programmer's Manual _EXIT(2) NAME _exit, _Exit - terminate the calling process SYNOPSIS #include <unistd.h> void _exit(int status); ... DESCRIPTION The function _exit() terminates the calling process "immediately". Any open file descriptors belonging to the process are closed; any children of the process are inherited by process 1, init, and the process's par- ent is sent a SIGCHLD signal. ...
こちらは先頭で「_EXIT(2)」と表示されている。つまり_exit(2)なので、_exit()はシステムコールAPIの扱いになっていることになる。
Copyright © ITmedia, Inc. All Rights Reserved.