KDOC 181: 『Linuxシステムプログラミング』
この文書のステータス
- 作成
- 2024-06-16 貴島
- レビュー
- 2024-06-20 貴島
概要
Linuxシステムプログラミングは、Linuxを解説する本。
メモ
- ファイル名はファイルに対応してない。ファイルに実際に対応しているのはinode。しかしinodeはファイル名を持っていない。ファイル名を保持しているのはディレクトリ(p10)
- 概念としてのディレクトリは他のファイルと同じように見えるが、内容としてファイル名とinode番号の組のみを持つ点が異なる。カーネルはディレクトリに保存された組をもとに、ファイル名をinode番号へ変換する
- ユーザアプリケーションがファイル名を指定しファイルオープンを要求すると、カーネルは対象ファイル名を持つ親ディレクトリをオープンし、指定されたファイル名を検索する(p10)
- ハードリンクはinode、シンボリックリンクはパス名を保持する(p12)
- ファイルを削除できるかを決定するのは、そのファイルのパーミッションではなく、親ディレクトリのパーミッション(p18)
- Linuxでは動作の大部分で SystemV をお手本にしている、という(p27)
- Unixを開発した Ken Thompson は、Unixの設計の中でこの1文字が最大の後悔だというジョークを残した、という(p29)
- アプリケーションが複数のファイルディスクリプタを処理し、同期が必要になる場合はよくある。このとき多重IOが有効である、という。多重IOを使用すると、複数のファイルディスクリプタの状態を一度にテストでき、いずれかのファイルディスクリプタがI/O可能になったときにアプリケーションが通知を受け取れるようになる。多重I/Oはアプリケーションの処理切り替えポイントになる(p49)
- select()システムコールは同期的多重I/Oの仕組み(p49)
- 可能な限りどんなものでもファイルとして表現しようとしているLinuxのシステムでは、ファイルのオープン、読み取り、書き込み、クローズを把握することは重要である、という(p63)
- /dev/zero は0を無限に返す仮想デバイス(p65)
- ユーザはブロックサイズについて意識することはない。データを書き込むと、プロセス空間内のバッファに書き込まれる。ある決まったサイズに達すると1度の書き込みでバッファ全体を書き出す。読み込みの場合も、ブロックサイズでアラインメントされたサイズでデータを読み取る、という(p67)
- 標準I/Oライブラリ関数の世界では、オープンしたファイルをストリームと呼ぶ、という(p68)
- 通常エラー出力だけはデフォルトでバッファリングしない。標準出力が端末の場合はデフォルトで行単位でバッファリングする、という(p81)
- レベルトリガは信号が届いている間はいつでも発生する。エッジトリガは信号が変化したときのみ発生する(p99)
- メモリの最小単位をページといい、それぞれにアクセス権限と用途がある、という。mmapシステムコールはページにファイルをマッピングする(p101)
- mmapによるファイル操作の利点。read()やwrite()ではユーザ空間のバッファとのコピー処理が発生するが、メモリマッピングの場合には発生しない。メモリへマッピングした場合はシステムコールやコンテキストスイッチのオーバーヘッドが発生せず、単なるメモリアクセスと変わらない。また共有可能、シークが単なるポインタ処理で済む、などの利点がある。欠点もあるが、ファイルサイズが大きい場合には効果が大きい場合が多い(p106)
- アドバイスで、カーネルのリソース管理にヒントを与えられる。動画ストーリミングなど大量のデータを読み書きした場合には、アドバイスを用いてキャッシュを破棄するようにするとよい、というような使い方がある(p115)
- idleプロセスのpidは0。initプロセスのpidは1。(p131)
- プログラムイメージをメモリへロードし実行する処理は、Unixではプロセス作成とは分離されている。まずforkシステムコールによりプロセスを新規作成する。次にexecシステムコールでバイナリをメモリにロードし、それまでそのアドレス空間にあった内容と置き換え、新たなプログラムを実行する(p134)
- プロセスグループによって、グループ内のすべてのプロセスにシグナルを送信できる(p160)
- キーボードの押下、マウスの移動などはI/Oバウンド。応答性のために短いタイムスライスが適切で、スケジューラはI/Oバウンドの傾向を強く持つアプリケーションの優先度を高く設定する(p170)
- 一度プロセスをあるCPU上で実行すると、それ以降はプロセススケジューラはそのプロセスを可能な限り同じCPUで実行しようとする。プロセスを異なるプロセッサ間でマイグレーションするのはコストが高い、という。それでも、負荷がプロセッサによって偏っている場合にはプロセスを移動する意義があり、いつ移動するかの判断をロードバランスという(p177)
- プロセッサアフィニティとは、プロセスが同じプロセッサ上で実行する程度を表す。プロセスによっては、同じプロセッサで動作したほうが有利な場合が多くある、という(p177)
- Unixではディレクトリとは単純な概念で、ファイル名と対応するinode番号のリストを保持するもの、だという。ディレクトリが保持するファイル名をディレクトリエントリ、ファイル名とinodeの対応をリンクとよぶ。ディレクトリの内容とは、そのディレクトリ名とファイル名のリスト(p218)
- ユーザがディレクトリ下のファイルをオープンするときには、カーネルがディレクトリ内のファイル名リストからファイル名を検索し、対応するinode番号を特定する。特定したinode番号をファイルシステムに渡し、ファイルシステムがデバイス上の物理的なファイル保存位置を特定する(p218)
- すべてのディレクトリは2つのディレクトリを含む。
.
および..
である。技術的には、ルートディレクトリでさえも自分自身のサブディレクトリといえる(p219)。 - ユーザがシステムにログインすると、ログインプロセスがカレントディレクトリを
/etc/passwd
に記載されたユーザのホームディレクトリへ設定する(p222) - シェルが以前のディレクトリを蓄えておく、たとえば
cd -
のような場合。カレントディレクトリをオープンしておき、ファイルディスクリプタで戻るのが、パス名を保持するより早い。パス名保持は取得と保存にコストがかかる。ファイルディスクリプタを使うと、メモリ内に持つのはinodeだけ、だという(p224) - シンボリックリンクはファイルシステムがマッピングするものではなく、実行時に解釈される、より上位で処理されるポインタである。シンボリックリンクは異なるファイルシステムに跨って使用できる(p231)
- ハードリンクは既存のファイルoldpathと同じinodeを参照する新規リンクnewpathを作成する。ソフトリンク(シンボリックリンク)は特殊な型を持つ専用ファイルで、他のファイル名のパス名を格納するもので、これをシンボリックリンクのターゲットという(p233)
- Unixではファイル、ディレクトリをコピーするシステムコールもライブラリも提供していない、という。移動はシステムコールがある(p237)
- デバイスノードはアプリケーションからデバイスドライバへアクセスするスペシャルファイル。Unixのファイルオープン、クローズ、読み取り、書き込みを行うと、カーネルは通常のファイルI/Oとしては処理せず、要求をデバイスドライバに渡す。デバイスドライバはI/Oを処理し、結果をアプリケーションに返す。デバイスノードはデバイスを抽象化したもの、だという(p239)
- 特殊なデバイスノード。nullデバイス、zeroデバイス、fullデバイス。それらのデバイスには用途があり、アプリケーションの異常時の動作をテストする際に有用である(p240)
- BSS領域の語源は block started by symbol(p255)
- 動的メモリ割当は渡されたsize自身が変化するため複雑な処理だという。配列要素のサイズは固定だが、割り当てる要素数は変化する(p257)
- malloc()で割り当てた領域の内容は不定だが、calloc()では領域の内容をすべて0で初期化する、という。calloc()はすでにゼロクリアされているページを割り当てるため、メモリ領域をある値で割り当てるmemset()より早い(p257)
- スタック上に自動的に領域が確保され、スタックの破棄と同時に領域が解放されるオート変数などとは異なり、動的に割り当てたメモリ領域は明示的に解放するまで、プロセスアドレス空間の一部として存在し続ける(p260)
- データのアラインメントとは、ハードウェアから見たメモリ領域とアドレスの関係を指す(p263)
- 構造体の場合にはパディングが必要になる。char型のメンバ(1バイト)の直後にint型のメンバ(4バイト)を置いた場合、コンパイラが2者の間に3バイトのパディングを挿入し、int型のメンバが4バイト境界に沿うように配置する(p265)
- 長所と短所から、glibcでは小さいサイズのメモリ割当の場合にはmalloc()を、サイズが大きい場合は無名メモリマッピングを使用している(p267)
- メモリ割当方法の比較表(p278)
- memmem()はメモリ領域から任意のバイト列を検索するライブラリ関数(p282)
- killシステムコールは名前に反して、任意のシグナルを送信できる(p300)
- モノトニック時間は単調かつ厳密に増加するだけの時間。実時間(wall time)は、ユーザが時刻を合わせた,システムが正確な時間を維持するために微調整した、うるう秒が発生した、などの理由によって変わることがある。Linuxではシステムのuptimeを管理していて、変更されることがない。2点間の時間経過の計測に有用である(p317)
- Unixシステムはepochからの経過秒数で絶対時間を表現する。epochはUTCの1970年1月1日の午前0時(p317)
- 急激に時間を変更すると問題が起きる可能性があるので、adjtime()でゆるやかに時間を調整する。定常的に時計のずれを正しく補正するNTPデーモンなどのバックグラウンド処理用に提供されている(p330)
- インライン関数は関数コールの位置に関数コードをそのままコピー(展開)する。コンパイラは、関数を外部へ配置/コール(ジャンプ)せず、関数をコール位置で直接実行するようにコードを生成する。関数コールのオーバーヘッドを排除できるうえに、コール側とコールされた関数側の双方を同時に最適化できる。コードサイズに悪影響を及ぼす。現代のアーキテクチャで真に必要な場面は限られる、という(p350)
- GCCでは式の期待する値に対するヒントを与えられる。例えば条件式が真偽のどちらかを返す場合が多いかをコンパイラに通知できる。GCCはこのヒントをもとにブロックの転置(順序入れ替え)などの最適化を行い、条件分岐のパフォーマンスを向上させられる(p355)