カーネルのソース・コードでは,あらゆる場所でデータの構造を定義した「構造体」が使われています。構造体の読み方をマスターすればカーネル・ソースが一段と理解しやすくなります。今回は,「構造体とは何か」と「なぜカーネルが構造体を駆使するのか」を解説します。

 本連載の第2回「C言語とライブラリの初歩」で,変数や配列について解説しました。そこで述べたように,変数や配列を定義するときは,その名前の前に「int(整数)」や「char(文字)」のような「型」の種類を指定する必要があります。

 こうした異なる種類のデータを複数個組み合わせて,プログラマが独自に定義した型が構造体です。例えば,あらかじめ「table」という構造体を「struct table」と定義しておけば,プログラムの中で「struct table aaa」と書くだけで,構造体table型の実体aaaを使えます。この場合,tableという箱を「構造体型」,aaaという実体を「構造体」といいます。

 では,具体的に見ていきましょう。

パスワード・ファイルの構造体の中身

 前回と同じく,ユーザー名を表示する「whoamiコマンド」を取り上げます。whoamiコマンドは,ユーザー名を表示するためにユーザーIDを引数にして,getpwuid()というライブラリ関数を実行します。このgetpwuid()は,引数uidで指定したパスワード構造体のアドレス(ポインタという)を取得する関数です。

 今回は,このパスワード構造体を処理するソース・コードを見てみます。

 まず,whoamiコマンドのソースを見つけます。ここは前回も説明しましたが,簡単におさらいしておきます。「rpm -qif /usr/bin/whoami」を実行すると,whoami.cが「coreutils」パッケージの中にあることが分かります。coreutilsパッケージを取得して解凍し, whoami.cのソース・ファイルを見ます(図1)。whoami.cの89行目でgetpwuid()を実行しています。


88 uid = geteuid ();
89 pw = getpwuid (uid);
90 if (pw)
91 {
92 puts (pw->pw_name);
93 exit (EXIT_SUCCESS);
94 }
図1●whoami.cのソース・コードの88行目から94行目

 getpwuid()の処理内容を調べるとき,最初にmanを実行してマニュアルを確認します。マニュアル番号が「3」ですから,ライブラリ関数です。また,以下のように,戻り値は「passwd」という構造体のポインタ(*と記します。詳しくは後述します)と分かります。


struct passwd *getpwuid(uid_t uid);

 次にこのpasswd構造体の定義を見つけます。

 それは簡単です。manコマンドで,ヘッダー・ファイルは「sys/types.h」と「pwd.h」の2つであることも分かります。このpwd.hの実体は「/usr/include/pwd.hヘッダー・ファイル」です。ここに,構造体の箱である構造体型「passwd」(図2)が定義されています。


50 struct passwd
51 {
52 char *pw_name;   /* Username. */
53 char *pw_passwd; /* Password. */
54 __uid_t pw_uid;  /* User ID. */
55 __gid_t pw_gid;  /* Group ID. */
56 char *pw_gecos;  /* Real name. */
57 char *pw_dir;    /* Home directory. */
58 char *pw_shell;  /* Shell program. */
59 };
図2●/usr/include/pwd.hファイルで定義したpasswd構造体

 見やすくするために,/etc/passwdファイル内に記録しているパスワード・データの実体と比べて図示したものが図3です。

図3●passwd構造体のデータ構造
図3●passwd構造体のデータ構造
「*」「#」などはメモリー上の番地(アドレス)。
[画像のクリックで拡大表示]

 このように構造体を図示するとカーネルが読みやすくなります。図2から図3に書き換える方法は次の通りです。

 pw_name,pw_passwdなどpw_で始まる名前は「メンバー名」と呼び,箱の上に記入します。メンバー名の先頭に*記号が付いていれば,そのデータの場所(アドレス)を箱の中に保管するポインタですから,*や#等の記号を記入しておきます。先頭に*記号が無いメンバー名(pw_uidや pw_gid)には,データ値そのものを保持します。

 例えば,pw_nameの箱内に保持している*記号(アドレス)は,ユーザー名「akamatsu」の文字列の先頭番地を示します。pw_passwd の箱内に保持している#の記号はパスワード「x」の先頭番地を示します。この場合はシャドウ・パスワードを利用しています。箱内の記号と実体データの位置を線でつなぐとより理解しやすいと思います。

 一方,pw_uidには,ユーザー名akamatsuのユーザーID番号「500」の値そのものを保持しています。

 getpwuid()ライブラリ関数の処理内容は,/etc/passwdファイルを検索して,引数uid(500)と一致した行のパスワード構造体(実体)の先頭番地「@」を戻り値として返すというものです。関数の定義を確認すると,戻り値は「struct passwd *」ですから,パスワード構造体のポインタを返すことが分かります。

この先は会員の登録が必要です。有料会員(月額プラン)は初月無料!

日経 xTECHには有料記事(有料会員向けまたは定期購読者向け)、無料記事(登録会員向け)、フリー記事(誰でも閲覧可能)があります。有料記事でも、登録会員向け配信期間は登録会員への登録が必要な場合があります。有料会員と登録会員に関するFAQはこちら