C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。前回まで、printf()内の中身をさまざまな方法で探り、write()やint $0x80の呼び出しまでたどり着いた。今回は、さらにその先にあるLinuxカーネル側のシステムコールを見ていく。
書籍の中から有用な技術情報をピックアップして紹介する本シリーズ。今回は、秀和システム発行の書籍『ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ(2015年9月11日発行)』からの抜粋です。
ご注意:本稿は、著者及び出版社の許可を得て、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。
※編集部注:前回記事「printf()のソースコードで、ソースコードリーディングのコツを身に付ける」はこちら
「Linuxカーネルのソースコードを読む」と言われると、ハードルが高いと感じてしまう読者のかたもいるかもしれない。
しかしここで行うのは、本格的なコードリーディングではない。
カーネル・ソースコードを読む際によく言われることは、何を知りたいかを決めて読む、ということだ。つまり、場所を限定して読むことだ。
そしてここではシステムコールの処理に限定して読む。あまり気負わずに、気軽に読んでみよう。
Linuxカーネルの本家ダウンロードサイトは以下だ。
https://www.kernel.org/
mainlineと呼ばれるものが、開発中の最新カーネルだ。本書執筆時点(※2015年9月)では、バージョン4系がmainlineになっている。
それ以外はstable、longtermと呼ばれるものがある。stableは安定バージョン、longtermは長期メンテナンスが行われるバージョンだ。
ところで、本書のCentOS環境のLinuxカーネルのバージョンはいくつだろうか。これはunameコマンドで調べることができる。
[user@localhost ~]$ uname -r 2.6.32-504.16.2.el6.i686 [user@localhost ~]$
バージョンは2.6.32のようだ。
本来ならば実行環境と同じバージョンのカーネルのソースコードを読むべきだが、システムコールの仕様は頻繁に変わるようなものではないので、それほど神経質にはならなくともいいだろう。
本書執筆時点(※2015年9月)では、本家サイトでは2.6系のlong termカーネルの最新版として、2.6.32.65というバージョンのものがダウンロードできた。ということで本書では、2.6.32.65のカーネルを参照することにしよう。
なお上記バージョンのカーネルは、本書のサポートサイトからもダウンロードできるようにしてある。本家サイトで見当たらなかったら、そちらからダウンロードできる。詳しくは連載第1回を参照してほしい。
まずは軽く、ディレクトリ構成を見てみよう。
[user@localhost ~]$ cd linux-2.6.32.65 [user@localhost linux-2.6.32.65]$ ls COPYING MAINTAINERS arch firmware ipc net sound CREDITS Makefile block fs kernel samples tools Documentation README crypto include lib scripts usr Kbuild REPORTING-BUGS drivers init mm security virt [user@localhost linux-2.6.32.65]$
様々なディレクトリがあるようだが、ディレクトリ構成をおおまかに説明しておこう。
まず本書で重要な部分として、archはアーキテクチャ依存の処理、fsはファイルシステム関連、kernelはカーネルのアーキテクチャ共通の処理になる。さらにdriversは各種デバイスドライバ、includeは各種ヘッダファイル、mmは仮想メモリ関連、netはネットワーク関連だ。
さてこの中で、いったいどこを見ていけばいいのであろうか。
見たいのはint $0x80が呼ばれたときの処理だ。つまりシステムコール命令が呼ばれたときの処理だ。
システムコールはCPUに対しての例外発行になる。これはいわゆるソフトウェア割込みなので、割込み処理にその入り口がある。そして割込み処理はCPUごとの独自処理となるため、アーキテクチャ依存の処理となる。
アーキテクチャ依存の処理は、ディレクトリarchの中にある。つまりそこに、目的の処理があるはずだ。
まずはディレクトリarchの中を確認しよう。
[user@localhost linux-2.6.32.65]$ cd arch [user@localhost arch]$ ls Kconfig avr32 frv m32r microblaze parisc score um alpha blackfin h8300 m68k mips powerpc sh x86 arm cris ia64 m68knommu mn10300 s390 sparc xtensa [user@localhost arch]$
様々なアーキテクチャが存在しているようだが、中にはx86というディレクトリがある。ここにx86依存の処理があるのだと思われる。
確認してみよう。
[user@localhost arch]$ cd x86 [user@localhost x86]$ ls Kbuild Kconfig.debug boot ia32 kvm math-emu pci video Kconfig Makefile configs include lguest mm power xen Kconfig.cpu Makefile_32.cpu crypto kernel lib oprofile vdso [user@localhost x86]$
ここにも様々なディレクトリがあるようだ。
ファイルやディレクトリが大量に存在しているのを見ると圧倒されてしまいがちだが、こういうときの調べかたにはコツがある。
基本はgrepという、検索用のコマンドをうまく使うことなのだが、使いかたはそれほど難しくはない。システムコールの処理はどこにあるのか、grepで検索してみよう。
問題は検索のためのキーワードなのだが、ここはセンスが問われるところだ。とりあえずシステムコールの処理なので「SystemCall」や「SYSTEMCALL」で検索してみよう。
[user@localhost x86]$ grep -r SystemCall . [user@localhost x86]$ grep -r SYSTEMCALL . [user@localhost x86]$
とくに何も無いようだ。
grepコマンドは「-r」というオプションを付加することで、ディレクトリを再帰的に追いかけて検索してくれる。つまり上の例では、カレントディレクトリ以下のファイルを全検索している。
次は「syscall」をキーワードにして検索してみよう。
[user@localhost x86]$ grep -r syscall . ./include/asm/ia32_unistd.h: * Only add syscalls here where some part of the kernel needs to know ./include/asm/ia32_unistd.h:#define __NR_ia32_restart_syscall 0 ./include/asm/syscalls.h: * syscalls.h - Linux syscall interfaces (arch-specific) ./include/asm/sys_ia32.h: * sys_ia32.h - Linux ia32 syscall interfaces ./include/asm/unistd_64.h:/* at least 8 syscall per cacheline */ ./include/asm/unistd_64.h:__SYSCALL(__NR_uselib, sys_ni_syscall) ...
今度は大量にヒットした。ヒット数を見てみよう。
[user@localhost x86]$ grep -r syscall . | wc -l 618 [user@localhost x86]$
600件以上もヒットしている。これくらいの数ならば、ヒットしたファイルをすべて調べていくことは不可能ではないが、まずは調べるファイルを限定していくことを考えたほうがいいだろう。
さて、どのようにして範囲を狭めていけばいいだろうか。
Copyright © ITmedia, Inc. All Rights Reserved.