.. _beforestarting: ソースコード以外の情報源 ======================================================================== :ref:`reading` にて、コードリーディングにおける基底知識が重要であることを 説明しました。ここではコードリーディングを実施せずに得られる基底知識 について説明します。 * 文章 * プログラムの実行結果 * 実行環境 文章 ------------------------------------------------------------------------ 本演習のように訓練としてソースコードを読むこと自体を目的にソースコード を読む、というのは特別です。通常は、何かソフトウェアの動作を理解する、 といった目的がありソースコードを読むのは手段です。別の手段で目的を達成 できるのであれば、その手段を使うことを考えるべきでしょう。自然言語で書 かれた文章を読むというのはその手段の一つです。 ドメイン知識 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ソースコードを読む前に、そのソフトウェアが対象とするドメイン知識を仕入 れておくのが良いでしょう。たとえば会計処理を支援するソフトウェアについ て、会計の知識無しにソースコードを読むのは、不可能ではないにしても現実 的ではありません。会計について学んでおけば、コード全体で何を実現しよう としているのかを推測したり、型名、変数名、関数名からその役割を知ること ができます。 特に公開されている仕様を実装したソフトウェアや公開されている仕様に立脚 したソフトウェアについては、その仕様書が大変参考になります。ただし仕様 書によっては1000ページを越えるものがあります。対象ソフトウェアの包括的 な理解が目的ではなく、局所的な疑問を解決するだけであれば全体を読む必要 は無いかもしれません。仕様書先頭の方に記載されているであろう仕様の目的 に目を通したら、ソースコードを読みはじめます。意味がわからないがしかし 重要と思われる単語が出てきたところで、その単語の意味を仕様書で調べると いうように、ソースコードと仕様書を交互に読むのが良いでしょう。そのため にも、ソースコードを読み始める前に仕様書を入手して手近に置いておきましょ う。 仕様書が絶対正しいわけではないことに注意して下さい。仕様書自体が改訂さ れることがあります。また相互互換性のためといった理由で、仕様書の記述か らわざと逸脱して実装した箇所もあります。そういった箇所はたいていソース コード中にコメントがありますが、気になるところはソースコードと仕様書を 照し合せて下さい。 ハードウェア、例えばCPUの仕様書はそのベンダーのウェブサイトから入手でき ることがあります。 .. [#intel]_ プロトコルを含むネットワーク処理の仕様は RFC_ として公開されていること があります。RFCに準拠するよう作成したソフトウェアであれば、そのソースコー ドのコメント中に、あるいは付属ドキュメント中で、参照したRFCについて記載が あるはずです。bindネームサーバやopenldapのようにソースコードツリーの なかに rfc をそのままコピーして同梱しているものもあります。 次に示すのは linuxカーネルのipv6プロトコルスタック実装中(net/ipv6/udp.c) から抜粋した例です。 .. code-block:: c if (uh->check == 0) { /* RFC 2460 section 8.1 says that we SHOULD log this error. Well, it is reasonable. */ LIMIT_NETDEBUG(KERN_INFO "IPv6: udp checksum is 0\n"); goto discard; } この条件分岐中の処理について、RFC 2460に由来することをの延べています。 .. code-block:: c case AF_INET6: if (addr_len < SIN6_LEN_RFC2133) return -EINVAL; daddr = &sin6->sin6_addr; break; アドレスの長さの上限が RFC 2133 に由来する定数のようです。 次に示すのはipv4プロトコルスタック実装中(net/ipv4/tcp_minisocks.c)にあ るRFCから逸脱していることを説明した箇所です。 .. code-block:: c /* Out of window segment. All the segments are ACKed immediately. The only exception is new SYN. We accept it, if it is not old duplicate and we are not in danger to be killed by delayed old duplicates. RFC check is that it has newer sequence number works at rates <40Mbit/sec. However, if paws works, it is reliable AND even more, we even may relax silly seq space cutoff. RED-PEN: we violate main RFC requirement, if this SYN will appear old duplicate (i.e. we receive RST in reply to SYN-ACK), we must return socket to time-wait state. It is not good, but not fatal yet. */ if (th->syn && !th->rst && !th->ack && !paws_reject && (after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) || (tmp_opt.saw_tstamp && (s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) { u32 isn = tcptw->tw_snd_nxt + 65535 + 2; if (isn == 0) isn++; TCP_SKB_CB(skb)->when = isn; return TCP_TW_SYN; } .. .. [#intel] 具体的なURLを紹介しません。入手先のウェブページのURLが頻繁 .. に変更されるので、必要になるたびに探し直す必要があります。 .. _RFC: http://tools.ietf.org/html/ アルゴリズム、データ構造 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 特別なアルゴリズムやデータ構造を使っている場合、ソースコード中でその実 装にあたり参照した論文や書籍について言及していることがあります。 ソースコードを読む前に読んでおくべきです。 次に示すのは高可用クラスター(パッケージ名corosync)のソースコードから の引用です。クラスターノード間での同報通信において、通信の順序が 全てのノードで同じになる必要があります。そこでtotemと呼ばれる アルゴリズムが用いられています。コメント中にTotemの出典が記載 されています。 .. code-block:: c /* * The first version of this code was based upon Yair Amir's PhD thesis: * http://www.cs.jhu.edu/~yairamir/phd.ps) (ch4,5). * * The current version of totemsrp implements the Totem protocol specified in: * http://citeseer.ist.psu.edu/amir95totem.html ... */ /* 出典: corosync/exec/totemsrp.c */ ソースコードツリーやパッケージに同梱されたドキュメント ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, プログラム自体に説明が内蔵されていたり、あるいはソースコードツリーにド キュメントが同梱されていることもあります。 ソフトウェアがパッケージとして入手できる場合、ソースコードツリー中のド キュメントはパッケージに含まれている、あるいは別のパッケージとして入手 できるかもしれません。多くのソフトウェアにおいて、マニュアルが実行に必 須ではないので、ディスクの占有領域を少しでも減らせるようにマニュアル部 分だけを独立したパッケージとしているソフトウェアもあります。パッケージ *A*に対して、 *A*-docs、*A*-manual といった名前がついています。 パッケージ自体の説明を見る方法を :ref:`querypkg` にて紹介 しました。例を挙げます:: rpm -qi gcc Name : gcc Version : 4.7.2 Release : 2.fc17 Architecture: x86_64 Install Date: 2012年10月01日 10時55分58秒 Group : Development/Languages Size : 33801653 License : GPLv3+ and GPLv3+ with exceptions and GPLv2+ with exceptions and LGPLv2+ and BSD Signature : RSA/SHA256, 2012年09月25日 05時20分45秒, Key ID 50e94c991aca3465 Source RPM : gcc-4.7.2-2.fc17.src.rpm Build Date : 2012年09月22日 01時37分18秒 Build Host : x86-03.phx2.fedoraproject.org Relocations : (not relocatable) Packager : Fedora Project Vendor : Fedora Project URL : http://gcc.gnu.org Summary : Various compilers (C, C++, Objective-C, Java, ...) Description : The gcc package contains the GNU Compiler Collection version 4.7. You'll need this package in order to compile C code. _`オンラインマニュアル(man)` ....................................................................... 専用の閲覧プログラムで読むことを想定したマニュアルがソフトウェアに付属している 場合があります。 主要な閲覧プログラムにmanとinfoがあります。manコマンドの方がより一般的です。 manコマンドは引数で指定したトピックについて、ページャーで表示します。 :: man [セクション番号] TOPIC TOPICにはコマンド名、システムコール名、ライブラリ関数名、設定ファイル名などを指定します。 ある関数名に対して同盟のコマンド名が存在する場合があります。 たとえばprintfという標準Cライブラリの関数と同名のコマンドが /usr/bin/printfにあります。セクション番号を指定することで2つを 区別できます。manのオンラインマニュアルをmanで見ると セクション番号の説明を読めます。以下に引用します。 1 実行プログラムまたはシェルのコマンド 2 システムコール (カーネルが提供する関数) 3 ライブラリコール (システムライブラリに含まれる関数) 4 スペシャルファイル (通常 /dev に置かれている) 5 ファイルのフォーマットとその約束事。例えば /etc/passwd など 6 ゲーム 7 マクロのパッケージとその約束事。例えば man(7), groff(7) など 8 システム管理用のコマンド (通常は root 専用) 9 カーネルルーチン [非標準] manコマンドを使って閲覧できるTOPICのマニュアルのことを「TOPICのmanペー ジ」と呼ぶことがあります。特に TOPIC(N) と表記した場合、セクションNにあ るTOPICのmanページを指します。 正確にTOPICの名称がわからない場合、-kをつけてキーワード検索ができます。 例:: $ man -k compiler B (3pm) - The Perl Compiler Backend B::Deparse (3pm) - Perl compiler backend to produce perl code cgcc (1) - Compiler wrapper to run Sparse after compiling checkmodule (8) - SELinux policy module compiler checkpolicy (8) - SELinux policy compiler compile_et (1) - error table compiler ... manコマンドでオンラインマニュアルを閲覧するには、オンラインマニュアルの元となる データがシステムにインストールされていることが前提となります。データの実体は ファイルであり、「TOPIC名.セクション番号」というファイル名を付けることが慣例 となっています。ソースコードツリーなどを入手してシステムにインストールされて いない生のmanページデータ、すなわち「TOPIC名.セクション番号」という名前を持っ たファイルを読みたい場合、以下の通り-lオプションを使います。 $ man -l ファイル名 _`オンラインマニュアル(info)` ....................................................................... infoパッケージに含まれるinfoコマンドで閲覧できるオンラインマニュアルが あります。manコマンドと比べてその数は少ないのですが、gccコンパイラコレ クション、gdbデバッガ、glibc Cライブラリといった極めて重要なソフトウェ アのオンラインマニュアルがinfo用に用意されています。 :: $ info [TOPIC] で閲覧できます。TOPICを指定しない場合、システムにインストールされた info形式のドキュメントより抽出したTOPICの一覧が提示されます。その なかからTOPICを選んで読むことができます。 manページと異なりinfo形式のドキュメントは複数ページからなり、さらにハイ パーテキスト化されています。カーソルキーでドキュメント中の1ページ内を移 動します。ページ末尾でスペースキーを押すと次のページに進みます。バック スペースを押すと前のページに戻ります。 ``*`` ではじまる文字列はリンク になっていて、カーソルをそこに移動させてリターンキーを押すとその 文字列で指定されたトピックの説明に移動できます。 ヘルプメッセージ ....................................................................... 引数として `--help` あるいは `-h` あるいは `help` を与えて実行すると 説明を表示するプログラムがあります:: $ ls --help 使用法: ls [オプション]... [ファイル]... FILE に関する情報を一覧表示します (デフォルトは現在のディレクトリ)。 -cftuvSUX または --sort が指定されない限り、要素はアルファベット順で並べ替えられます。 長いオプションに必須の引数は短いオプションにも必須です。 -a, --all . で始まる要素を無視しない -A, --almost-all . および .. を一覧表示しない ... Fedoraに含まれるソフトウェアの多くはこの慣例に従っています。ソースコードを 読んででも解決したい疑問の解決のための情報としては不十分ですが、無いより 余程ましです。 ヘルプメッセージの良いところは、それがそのまま痕跡文字列として使える 点です。ただし画面に表示されている説明が翻訳文字列の可能性があります。翻訳 前のメッセージであればソースコード中に埋め込まれていて痕跡文字列として使え るかもしれません。ところが翻訳後のメッセージはソースコードとは別の翻訳カタログ ファイルに由来しているかもしれません。 `LANG` 環境変数を変更した 環境でプログラムを実行することで翻訳メッセージの使用を回避できます。 :: $ LANG=C ls --help Usage: ls [OPTION]... [FILE]... List information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. Mandatory arguments to long options are mandatory for short options too. -a, --all do not ignore entries starting with . -A, --almost-all do not list implied . and .. ... ところで、もし翻訳メッセージを表示する仕掛けに関心があるなら `LANG` が痕跡 文字列となりますね。 _`ソースコードツリー中のドキュメント` ....................................................................... 専用の閲覧プログラムを必要としないテキストファイルやhtmlファイル(以下ド キュメントファイル)がソースコードツリー中に含まれている場合があります。 パッケージによっては、パッケージの一部としてそれらのファイルがインストー ルされている場合 ``/usr/share/doc/パッケージ名`` 以下に配置されます。 パッケージにドキュメントが収録されていない場合、ソースコードツリーを確 認してみましょう。掘り出し物がみつかるかもしれません。 ソースコードツリー中のどこにどのようなドキュメントがあるか、というのは ソフトウェア毎に異なりますが、典型的なものとして :ref:`hello` が参考に なります。 文章全般、特にソースコードツリー中のドキュメントを参照するときは、 それが保守されているかどうかに注意して下さい。ソースコードについて 開発が大きく先行してしまい、その説明が記載された文章が追い付いて いない、あるいは放置されている、ということがあります。良く保守さ れていたとしても間違いがあるかもしれません。最終的にはソースコー ドで動作を確認する必要がある、ということになります。 プログラムの実行結果 ------------------------------------------------------------------------ ログ、エラーあるいは警告メッセージなどプログラムを実行したときに得られ る情報があります。これを実行時情報と呼ぶことにします。実行時情報は読解 を進める上で貴重な手掛かりとなります。逆に得られた実行時情報の意味を調 べるために読解することもあります。ここではFedora上で動作するプログラム の典型的な実行時情報の情報源を紹介します。 実行時情報があれば読解の負担は大幅に減ります。しかし十分な実行時情報を 得られないまま読解を進めないといけないこともあります。例えば特別な環境 やハードウェアを必要とするため実行が困難な場合や再現性の低い障害について、 その原因を調べる場合があてはあります。 実行時情報はある入力を指定して実行すると得られるものです。入力や実行状 況によって情報が変わる可能性があります。特定の実行時情報に頼りすぎて斜 め読みをするとソースコードの意味を誤解してしまうことがあります。 標準出力と標準エラー出力 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, コマンドラインインターフェイスを想定したプログラムをターミナル上で動作 するシェルから起動すると、その実体(プロセス)には2つのファイルが関連付け られています。 標準出力(stdout) 正常に処理が進行した場合に対する、結果の出力先 標準エラー出力(stderr) 異常が発生した場合に対する、エラーや警告メッセージなどの出力先 特に指定しない限り、stdout, stderrはプログラムを起動したターミナルと関 連付けられています。手動で実行した場合、ユーザはその出力を見ることにな ります。 printfは標準出力への書き出しに使う代表的な関数です。 .. code-block:: c printf("%s\n", "something normal data"); fprintfは標準エラー出力への書き出しに使う代表的な関数です。 .. code-block:: c fprintf(stderr, "%s\n", "something error message"); 出力を保存するにはシェルのリダイレクト記号を使います:: 標準出力を保存する。 標準エラー出力の内容はそのままターミナルに出る。 # ./a.out > /tmp/stdout.txt 標準エラー出力の保存する。 標準出力の内容はそのままターミナルに出る。 # ./a.out 2> /tmp/stderr.txt 標準出力と標準エラー出力の内容を別々のファイルに保存する。 # ./a.out > /tmp/stdout.txt 2> /tmp/stderr.txt 標準出力を捨てて標準エラー出力の内容を保存する。 # ./a.out > /dev/null 2> /tmp/stderr.txt 標準出力、標準エラー出力の両方をまとめて保存する。 # ./a.out > /tmp/stdout+err.txt 2>&1 終了ステータス ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, プロセスは終了時、int型の値を一つだけそのプロセスを起動したプロセス(親プロセス)に 伝えることができます。この整数値を終了ステータスと呼びます。main関数の返り値あるいは exit関数(あるいは_exitシステムコール)の引数が終了ステータスとなります。 .. code-block:: c /* foo.c */ void func(void) { /* ... */ exit(42); } int main(int argc, char** argv) { /* ... */ func(); /* ... */ return 17; } ターミナル上で動作するシェルから対話的に起動したプロセスの終了ステータスは、$?に 格納されています。:: $ gcc foo.c $ ./a.out $ echo $? 17 終了ステータスの意味はプログラムによって様々で、正確な意味を知るにはそのプログラムの ソースコードを調べる必要があります。ただ慣例として処理が「成功」したことを表現するの に0を使います。 ログ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, httpdやsendmailのようにデーモンプログラムとしてバックグランドで動作することを 想定したプログラムは、通常起動直後にターミナルとの接続を破棄します。結果的に 標準出力や標準エラーへの書き込みはしないか、しても捨てられます。かわりに システムログへログを残します。ログを残すにはsyslog関数を使います。 .. code-block:: c syslog(priority, "%s", "this is log message\n"); 日時、プログラム名とともにログは /var/log/messages へ記録されます。この ファイルを見るには管理者権限が必要となることに注意して下さい。 :: # cat /var/log/messages ... Nov 7 01:04:16 localhost NetworkManager[631]: dhclient started with pid 16694 Nov 7 01:04:16 localhost NetworkManager[631]: Activation (em1) Stage 3 of 5 (IP Configure Start) complete. Nov 7 01:04:16 localhost dhclient[16694]: Internet Systems Consortium DHCP Client 4.2.4-P2 Nov 7 01:04:16 localhost dhclient[16694]: Copyright 2004-2012 Internet Systems Consortium. Nov 7 01:04:16 localhost dhclient[16694]: All rights reserved. loggerコマンドを使うとターミナルから/var/log/messagesへ書き込むことができます。 :: $ /usr/bin/logger -t name-of-this-program "EXAMPLE" # tail -1 /var/log/messages Nov 7 02:57:12 localhost name-of-this-program: EXAMPLE ログファイルを独自に持つプログラムもあります。独自のログファイルはたい てい/var/log以下に配置されます。 設定ファイル、コマンドラインオプションあるいは環境変数を指定することで、 通常より詳細なログを出力する 「デバッグモード」(あるいは「冗長 (verbose)メッセージモード」)を持っているプログラムもあります。デバッグ モードを有効にすることで、痕跡文字列がみつかるかもしれません。ただしデ バッグモードの存在がドキュメント等に記載されていない可能性があります。 デバッグモードの存在とそれを有効にする方法をみつけるためにソースコード を読む必要に迫られるかもしれません。 .. TODO: debugger 実行環境 ------------------------------------------------------------------------ 調査の対象となるプログラムの実行時の実体であるプロセスはどういったもの で、どのような環境で実行されるでしょうか。プロセスから直接的に詳細な実 行時情報を得られない場合であっても、プロセスを取り囲む環境から荒い実行 時情報を取り出すことができます。この情報を活用するには、プロセスやその 実行環境に対する理解が必要となります。 linuxカーネルとlibc ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .. figure:: runtime-overview.svg linuxカーネル上では複数のプロセスを動作させることができます。linuxカー ネルは互いに独立した、しかもハードウェアを独占しているかのような環境を プロセスに提供します。複数のプロセスが同時にハードウェアへアクセスしよ うとすると、それを調停します。 linuxカーネルは、プロセスに様々な機能を提供します。その機能を呼び出すた めの仕掛けをシステムコールと言います。機能にはそれぞれ名前がついて、そ れを特にシステムコールを経由して呼び出す機能であることを強調して、「名 前」システムコールと呼ぶこともあります。たとえばopenという機能をopenシ ステムコールと呼ぶことがあります。さらにややこしいことにそれらの機能を 総称してシステムコールと呼ぶこともあります。 システムコールを通してプロセスは、別のプロセスを起動したり (fork, clone, execve) 、 ファイルを読み書きしたり (open, read, lseek, write, close...) 、 ネットワーク通信 (socket, recvmsg, sendmsg, listen, bind, connect...) を したりできます。現状でlinuxには300以上のシステムコールがあり ます。 straceコマンドを使うと、あるプロセスがどのようなシステムコールを起動し ているのか、をリアルタイムで観察することができます:: # strace ./a.out とするとa.outが呼び出すシステムコールがターミナル上に表示されます。 a.outがその中で何をしているか、ということはわかりませんが、どのファイル から入力を得ているか、どのファイルに結果を出力しているか、といったプロ セスと「外界」とのやりとりを知ることができます。次に示すのは/bin/echo を引数とした実行例です:: $ strace /bin/echo "hello" execve("/bin/echo", ["/bin/echo", "hello"], [/* 57 vars */]) = 0 brk(0) = 0xdb3000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f168738e000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 ... mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f168738d000 write(1, "hello\n", 6hello ) = 6 close(1) = 0 munmap(0x7f168738d000, 4096) = 0 close(2) = 0 exit_group(0) = ? +++ exited with 0 +++ オプション-o FILE名とするとstraceの結果をファイルに保存できます:: strace -o /tmp/log.strace /usr/bin/pwd 出力をlessで見たい場合、リダイレクトとパイプを活用します:: strace -o /tmp/log.strace /usr/bin/pwd 2>&1 | less 幸いなことにシステムコールは良く文章化されていて(man-pagesパッケージが インストールされていれば) manコマンドで読むことできます。manコマンドに ついては詳細は「ドメイン知識」で説明します。 仕掛けとしてのシステムコールはハードウェアによって異なります。移植性を 考えるとカーネルの機能を呼び出すために、ハードウェア毎に異なる仕掛けを アプリケーションプログラムに内蔵するのは好ましくありません。プロセスと カーネルの間にあるlibcが、ハードウェア毎の差異を隠蔽して、C言語の関数呼 び出しの形式でシステムコールを起動できるラッパーを提供します。特に指示 を与えない限り実行ファイルを作ろうとするとリンカがlibcをアプリケーショ ンプログラムにリンクします。システムコールの種類によってはラッパーが少 し凝ったことをやっているもありますが、基本形にラッパーとシステムコール を区別する必要がありません。そこでそのラッパーを指してシステムコールと 呼ぶ場合があります。 システムコールのラッパー以外に、libcには標準Cライブラリの実装が含まれて います。標準Cライブラリには書式付き出力に使うprintf, fprintf、文字列処 理関数群 (strcmp, strcpy, strstr,...) 、メモリアロケータ (malloc, free,...) などが 含まれています。libcも良く 文章化されていて、man及びinfoコマンドで見ることができます。 なんらかの理由でシステムコールが失敗すると、その理由がint型の大域変数 errno [#errno]_ に格納されます。とりうる理由の種類はシステムコール毎に異 なります。システムコールに対するmanページ中に説明があります。 なおセクション2、すなわちシステムコールに関するmanページ群は、 man-pagesパッケージに含まれています。 .. [#errno] マルチスレッドプログラムの場合、変数ではなくスレッド毎に独立 した値をもてるような仕掛けを隠蔽したマクロとなります。 プロセス ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, プロセスには様々な要素がありますが、知っておくとすぐに役に立つものを3つ紹介します。 .. figure:: runtime-process.svg プロセスID(pid) ....................................................................... カーネルは自身が管理する複数のプロセスについて、それぞれを一意に指し示す ことができるよう、プロセス作成時に整数(pid)を割り振っています。その目的 から、ある瞬間に同じpidを持つプロセスはシステム上に二つと存在しません。 pidがわかると、それを指定してプロセスを終了させたり(kill)、デバッガや straceをアタッチしたり(gdb -p PID, strace -p PID)できます。 psコマンドを使うとシステムで動作するプロセスの一覧を見ることができます:: $ ps ax PID TTY STAT TIME COMMAND 1 ? Ss 0:03 /usr/lib/systemd/systemd --system --deserialize 40 2 ? S 0:00 [kthreadd] 3 ? S 0:01 [ksoftirqd/0] 5 ? S< 0:00 [kworker/0:0H] 7 ? S< 0:00 [kworker/u:0H] 8 ? S 0:09 [migration/0] ... 1857 ? Sl 0:01 /usr/libexec/mission-control-5 1867 ? Sl 1:32 /usr/libexec/ibus-ui-gtk3 1871 ? Sl 0:00 /usr/libexec/goa-daemon 1875 ? Sl 0:01 /usr/libexec/evolution-addressbook-factory 1882 ? Sl 2:06 /usr/libexec/ibus-x11 --kill-daemon 1923 ? Sl 1:38 /usr/libexec/ibus-engine-simple 1947 ? S 0:00 /usr/libexec/gvfsd-metadata ... 16047 ? Ss 0:04 sendmail: accepting connections 16084 ? Ss 0:00 sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue 左端の列がPIDです。 右端の列はそのプロセス自身で特に指定しない限り、そのプロセスで実行 中のプログラムの起動に用いたコマンドラインです [#argv0]_ 。 .. [#argv0] main関数に渡される引数argv配列の最初の要素 argv[0]を書き換えることで変更できます。 メモリ空間 ....................................................................... プログラム(やライブラリ)の実行コードや変数群がメモリ空間に配置されます。 メモリ空間はプロセス毎に独立しています: カーネルに利用の許可を得られれば、 メモリ空間をどのように使うかというのはプロセスの自由です。メモリ空間の内訳 はファイル/proc/PID/mapsで覗き見ることができます。 次のソースコードから実行ファイルa.outを作りバックグラウンドで実行します。 .. code-block:: c #include int main(void) { void *c = malloc(1024); while (1); return 0; } :: $ gcc empty.c $ ./a.out & [1] 5550 $ cat /proc/5550/maps 00400000-00401000 r-xp 00000000 08:02 524331 /tmp/a.out 00600000-00601000 rw-p 00000000 08:02 524331 /tmp/a.out 01dc9000-01dea000 rw-p 00000000 00:00 0 [heap] 3171400000-3171420000 r-xp 00000000 08:02 2228701 /usr/lib64/ld-2.15.so 317161f000-3171620000 r--p 0001f000 08:02 2228701 /usr/lib64/ld-2.15.so 3171620000-3171621000 rw-p 00020000 08:02 2228701 /usr/lib64/ld-2.15.so 3171621000-3171622000 rw-p 00000000 00:00 0 3171800000-31719ac000 r-xp 00000000 08:02 2232931 /usr/lib64/libc-2.15.so 31719ac000-3171bac000 ---p 001ac000 08:02 2232931 /usr/lib64/libc-2.15.so 3171bac000-3171bb0000 r--p 001ac000 08:02 2232931 /usr/lib64/libc-2.15.so 3171bb0000-3171bb2000 rw-p 001b0000 08:02 2232931 /usr/lib64/libc-2.15.so 3171bb2000-3171bb7000 rw-p 00000000 00:00 0 7f045f0b3000-7f045f0b6000 rw-p 00000000 00:00 0 7f045f0d4000-7f045f0d5000 rw-p 00000000 00:00 0 7fff368b5000-7fff368d6000 rw-p 00000000 00:00 0 [stack] 7fff36997000-7fff36998000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] メモリ空間は複数の領域に分割されて、異なる目的で利用されています。 mapsファイル中では、各行が1つの領域を説明しています。 一番左の列、ハイフンで結ばれた2つの数字が、領域のアドレス範囲です。 左から2つのめの列が、領域のメモリ保護の状態です。rは読み込み可能を、 wは書き出し可能を、xは実行可能を意味します。pは共有されていないことを 意味します。 いくつかのコラムを飛ばして一番右のコラムを見て下さい。その領域に実行プ ログラムあるいはライブラリがロードされている場合、そのファイル名が表示 されます(a.out, ld-2.15.so, libc-2.15.so)。同じファイル名が複数回登場し ています。これはファイル中の実行コードの部分と、変数群(大域変数や staticと修飾された変数)の部分が別の箇所にロードされているためです。メモ リ保護の状態にxとなっている領域には、ファイル中のプログラムコード部分が ロードされています。rwとなっている領域には、変数群の部分がロードされて います。 mallocで動的に獲得できるメモリは \[heap\]と表示された領域に確保 されています。\[stack\]と表示された領域は、ローカル変数や関数呼び出しに おける実引数を配置するのに使う箇所です。\[vdso\]と\[vsyscall\]は システムコールの仕掛けの一部です。 ファイル記述子テーブル ....................................................................... Linuxを含むunix-likeなOSでは、デバイス操作、ネットワーク通信、他のプロ セスとの通信(プロセス間通信, IPC)といった処理(I/O処理)をファイルへの読 み書きに使うシステムコール群をそのまま使ってカーネルに依頼できます。 これらのシステムコールは、その第一引数に「ファイル記述子(fd)」と呼ばれる int型の整数を一つとります。その整数の値によってシステムコールの対象 --- ファイルなのか、デバイスなのか、ネットワーク通信なのか、IPCなのか、 さらにファイルであればどのファイルなのか、どのデバイスであればどのデ バイスなのか、ネットワーク通信であればIPアドレスとUDP/TCPポート、IPC であれば相手のPID --- が何か決まります。 ファイルやデバイスであればopenシステムコールを、ネットワーク通信 であればsocketシステムコールあるいはacceptシステムコールを、 IPCであればおもにsocketあるいはpipeシステムコールを呼び出すと ファイル記述子を得ることができます。ファイル記述子を得たら、 以降readやwriteシステムコールで読み書きあるい通信できます。 closeシステムコールを呼び出すと、以降、引数で指定したファイル記述子を 使わないことをカーネルに伝えることができます。 ファイル記述子の値はプロセス毎に独立しています。pid=P0のプロセスがfd=n でネットワーク通信をしていても、pid=P1のプロセスはfd=nをファイルの読み 書きに使っているかもしれません。 ファイル記述子とそれによって決まるI/Oの対象の組は、プロセス 毎に用意された表でカーネルによって管理されています。この表を ファイルファイル記述子テーブルと言います。 procファイルシステムからファイル記述子テーブルの内容見ることができます。 今手元でこの資料を使っているemacsという名前のエディタのpidを調べて、 そらにそのファイル記述子テーブルを見てみます:: $ ps ax | grep emacs 2880 ? Rl 9:58 emacs 13914 pts/3 S+ 0:00 grep --color=auto emacs $ ls -l /proc/2880/fd ls -l /proc/2880/fd 合計 0 lr-x------. 1 yamato yamato 64 11月 8 14:50 0 -> /dev/null lrwx------. 1 yamato yamato 64 11月 8 14:50 1 -> /home/yamato/.xsession-errors lrwx------. 1 yamato yamato 64 11月 8 14:50 10 -> anon_inode:[eventfd] lrwx------. 1 yamato yamato 64 11月 8 14:50 11 -> socket:[38064] lrwx------. 1 yamato yamato 64 11月 8 14:50 12 -> socket:[37310] lrwx------. 1 yamato yamato 64 11月 8 14:50 13 -> /dev/ptmx lrwx------. 1 yamato yamato 64 11月 8 14:50 14 -> /dev/ptmx lrwx------. 1 yamato yamato 64 11月 8 14:50 2 -> /home/yamato/.xsession-errors lrwx------. 1 yamato yamato 64 11月 8 14:50 3 -> socket:[37275] lrwx------. 1 yamato yamato 64 11月 8 14:50 4 -> anon_inode:[eventfd] lrwx------. 1 yamato yamato 64 11月 8 14:50 5 -> socket:[35318] lrwx------. 1 yamato yamato 64 11月 8 14:50 6 -> anon_inode:[eventfd] lrwx------. 1 yamato yamato 64 11月 8 14:50 7 -> socket:[39422] lrwx------. 1 yamato yamato 64 11月 8 14:50 8 -> anon_inode:[eventfd] lrwx------. 1 yamato yamato 64 11月 8 14:50 9 -> socket:[37988]