気が向いたら書くやつ

気が向いたら何か書きます

『試して理解 Linuxのしくみ』読書メモ (3)

続き。

soratobi96.hatenablog.com

第3章 プロセス管理

仮想記憶を利用しない場合の単純なプロセス管理について。

プロセスの生成には大きく2つの目的がある。

  • 同じプログラムの処理を複数のプロセスに分ける
    • Webサーバによる複数リクエストの受付
  • 別のプログラムを生成する
    • bashを介した各種プログラムの実行

fork()

fork()は、発行したプロセス(親プロセス)をもとに新しいプロセス(子プロセス)を生成する(内部的にはシステムコールclone()の発行)。

  1. メモリ領域を作成、親プロセスのメモリをコピーし子プロセスを生成する
  2. 子プロセスを実行する
  3. 必要に応じ、fork()の返り値(0:子プロセス、子のプロセスID(> 1):親プロセス)を元に処理を分岐する

execve()

execve()を発行すると、指定したプログラムを実行する。

  1. 実行ファイルを読み出し、プロセスのメモリマップに必要な情報を読み出す
  2. 呼び出し元プロセスのメモリを新しいプロセスのデータで上書きする
  3. 新しいプロセスの最初から実行する

メモリマップ

実行ファイルには、実行時に利用するコードとデータのほか次のような情報が保持され、この情報に基づいてメモリ上(厳密には仮想記憶上)にマッピングされる。

  • コード領域の情報
    • ファイル上オフセット
    • 領域サイズ
    • メモリマップ開始アドレス
  • データ領域の情報:コード領域と同様
  • エントリポイント:命令実行開始位置のメモリアドレス

ELF

実行ファイルのバイナリコードは、特定のデータごとにセグメントと呼ばれるブロックにグループ化されている。

Linux環境の多くでは、オブジェクトファイルおよび実行ファイルのフォーマットとしてELF (Executable Linkable Format) が採用されている。 ELFファイルは次のようなセグメントを含んでいる。

  • text: 実行可能コード
  • .bss: 初期値がゼロのデータ
  • data: 初期値のあるデータ
  • symtab: コード内の識別子(シンボルテーブル)

readelfコマンドにより、ELFファイルの各種情報を確認できる。

sleepコマンド (/bin/sleep) について、ELFヘッダ (readelf -h) およびセクションヘッダ (readelf -S) の情報を表示し、メモリマップに必要な情報を確認する。

$ readelf -h /bin/sleep
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  ...
  Entry point address:               0x1b70
  Start of program headers:          64 (bytes into file)
  Start of section headers:          33208 (bytes into file)
  ...

エントリポイントが確認できる。

$ readelf -S /bin/sleep
There are 28 section headers, starting at offset 0x81b8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
...
  [14] .text             PROGBITS         00000000000018d0  000018d0
       0000000000003989  0000000000000000  AX       0     0     16
...
  [24] .data             PROGBITS         0000000000208000  00008000
       0000000000000080  0000000000000000  WA       0     0     32
...

.textはコード領域、.dataはデータ領域を示している。

readelfから得られたメモリマップの情報は次のようになる。

情報
コード領域 ファイルオフセット 0x18d0
サイズ 0x3989
メモリマップ開始アドレス 0x18d0
データ領域 ファイルオフセット 0x8000
サイズ 0x80
メモリマップ開始アドレス 0x208000
エントリポイント 0x1b70

プログラム実行時に作成されたプロセスのメモリマップは、ファイル/proc/<PID>/mapsで参照できる。

$ /bin/sleep 10000 &
[1] 3754
$ cat /proc/3754/maps
563b5a090000-563b5a097000 r-xp 00000000 08:02 5636249                    /bin/sleep
563b5a297000-563b5a298000 r--p 00007000 08:02 5636249                    /bin/sleep
563b5a298000-563b5a299000 rw-p 00008000 08:02 5636249                    /bin/sleep
563b5a914000-563b5a935000 rw-p 00000000 00:00 0                          [heap]
...

第3フィールド (perms) についてr-xpがテキストセグメント、rw-pがデータセグメントを示しており、上記のメモリマップ情報に合致している。

あるプロセスから別の新規プロセスを生成する場合は、親プロセスでのfork()後、子プロセスでexecve()を実行する "fork_and_exec" の形を取ることが多い。

_exit(), exit()

プログラムの終了時には_exit()を実行(内部的にはシステムコールexit_group()の発行)し、プロセスに割り当てていたメモリをすべて回収する。

標準Cライブラリのexit()関数は、終了処理として登録されている関数の呼び出しやストリームのフラッシュを行った上で_exit()を発行し、プログラムを終了する。

_exit()システムコールのラッパー関数、exit()は標準ライブラリの関数である。