最近golangでCLIツールを作っていたのだけど、Linuxのお作法とかいまいち分かっていなかった。そこでそのあたりのことが学べそうな「ふつうのLinuxプログラミング」を読んだ。
この本はLinuxにおいてC言語でプログラミングする方法を、Linuxでの重要な概念も含めて教えてくれる本。この本を読めばとりあえずC言語を使ってLinux用のプログラムを書き始めることが出来るようになりそうだった。
それでC言語を使わない場合でも役に立つの?ということだけど、非常に役立ちそうで面白かった。なぜなら、単なるプログラミングの方法を教えてくれるだけではなくて、
からである。このあたりの概念はgolangでプログラムを書くときにも役立ちそうで、この本を読む目的を満たせて良かった。おすすめ。
この本の中で個人的に印象に残ったのは以下の二つである。
- バイトストリームという考え方
- Ctrl-Cでのシェルでのプロセス中断を実行する仕組み
バイトストリームという考え方
この本の中ではLinuxの概念をファイルシステム・プロセス・ストリームという言葉で教えてくれるのだけど、その中でも僕は最後のストリームという概念が非常に面白かった。この本におけるストリームとはバイトの流れる通り道のこと。他のLinuxの書籍とかではこの概念は単に「ファイル」と言われていたりするみたい。しかし、僕はストリームという言葉の方がなんとなくしっくりきた。
全てのデータの入出力は、バイトの流れる通り道であるストリームをopenし、read/writeし、closeすると考えれば様々な操作がこのストリームという概念だけで表現できることが分かる。
- ファイルの読み書きは、ファイルとの間にストリームを用意し、read/writeする
- キーボードの入出力は、キーボードと端末との間にストリームを用意し、キーイベントのデータをreadする
- プロセス間通信は、プロセスとプロセスの間にストリームを用意し、データをread/writeする
- ネットワークは、通信したい端末と端末間にストリームを用意し、read/writeする
こうすることで、どのようなデータの入出力も同じopen/read/write/closeのAPIでやりとりが出来て非常にすっきりする。
Ctrl-Cでのシェルでのプロセス中断を実行する仕組み
Ctrl-Cでのシェルでのプロセス中断を実行する仕組みについて解説されていたのも面白かった。Ctrl-Cを実現するためには以下のような仕組みになっている。
- 1. コマンドを実行すると、シェルがパイプを構成するプロセスをfork()する
- パイプでつながれたコマンドはそれぞれ1プロセスとしてforkされる
- 2. シェルがパイプのプロセスグループIDをtcsetpgrp()で端末に通知する
- パイプでつながれたコマンドは1つのプロセスグループIDに属する
- 3. forkされた各プロセスがコマンドをexecする
- 4. ユーザがC-cをおす
- 5. カーネル内の端末ドライバがそれをSIGINTに変換、動作中のプロセスグループに発送する
- 6. プロセスグループがデフォルトの動作に従って終了する
こんな感じで、コマンド実行はすべてシェルからのforkで表現され、パイプで繋がったコマンド群がプロセスグループとして表現されることで、Ctrl-Cはそれらのプロセス全てにシグナルを送れるようになっているという感じ。単純な機能に思えるけど、その中にもいろんな仕組みが入っていて面白い。
ちなみにシェルについては最近読んだ以下の記事が非常に分かりやすかったのでおすすめ。
まとめ
「ふつうのLinuxプログラミング」は、Linuxのお作法を気軽に学ぶのに非常に良い本だった。今後CLIツールとかミドルウェアのコードに関わった時に参考になりそう。おすすめ。
さらにLinuxやカーネルについて知りたければ、詳解UNIXプログラミングや詳解Linuxカーネルがおすすめのようだった。
読書メモ
- Linux世界はファイルシステム、プロセス、ストリームの3つの概念によって成立している 5
- ファイルの種類 39
- ファイルとは 43
- 何らかのデータを保持する
- 付帯情報がついている
- 名前(パス)を指定できる
- バイトストリームという考え方 48 ※
- バイトが流れる通り道をopenし、read/writeし、closeすると考えると、ファイル・デバイス・パイプ・ネットワーク通信何にでも使える
- 「ユーザAとしてアクセスする」とは「ユーザAの属性を持ったプロセスがアクセスする」ということ 60
- open/close/read/writeを使ったストリーム操作でcatを作る 86
- ストリームのつながっている位置のことをファイルオフセットという 96
- Linuxのディレクトリ構造(FHS)と主要ディレクトリの説明 180
- ディレクトリはバイト列であり、ディレクトリエントリという構造体の列 196
- ハードリンクとリンクカウント 208
- ハードリンクはあるファイルに新しい名前を付けること 208
- 実体を指す名前の数をリンクカウントという 209
- rmが消すのはファイルでなくファイル名で、リンクカウントが0になると実体が削除される 210
- シンボリックリンクの特徴(ハードリンクとの違い) 213
- Linuxにとって「ファイルを消す」とは「実体に付けた名前を減らすこと」 216
- Linuxにおいて「ファイルを移動する」とは、「1つの実体に対する名前を付け替える」こととだいたい同じ 218
- 仮想メモリ機構の応用 240
- ページング: HDDやSSDなどのストレージを物理メモリの代わりに使う機構
- メモリマップトファイル: ファイルをメモリとしてアクセスできるようにしてしまう仕組み
- 共有メモリ: 特定の範囲の物理メモリを複数プロセスで共有する機構
- アドレス空間の覗き見 243
- proc/n/mapsをcatで見るとプロセスのメモリ配置が覗き見れる
- メモリの確保の分類 245
- 1. ビルド時にわかっているサイズをBSS領域から取る
- 2. ビルド時にわかっているサイズを実行時にスタック領域から取る
- 3. 実行時に決まるサイズをヒープ領域から取る
- 4. 実行時に決まるサイズをスタック領域から取る
- ゾンビプロセスの処理 267
- 子プロセスが終了したのに親プロセスがwaitで終了値を受け取らない状態
- プロセスグループとセッションの役割 277
- プロセスグループはパイプでつながれたプロセス群全てにシグナルを送ることが出来るような仕組み
- セッションはユーザーのログインからログアウトまでの流れを管理するための概念
- シェルで特殊な働きをするキーはstty -aで見れる
- ctrl-cの挙動 297
- 1. シェルがパイプを構成するプロセスをfork()する
- 2. シェルがパイプのプロセスグループIDをtcsetpgrp()で端末に通知する
- 3. forkされた各プロセスがコマンドをexecする
- 4. ユーザがC-cをおす
- 5. カーネル内の端末ドライバがそれをSIGINTに変換、動作中のプロセスグループに発送する
- 6. プロセスグループがデフォルトの動作に従って終了する
- set-uidビットが立っていると、起動したユーザーにかかわらず、常にプログラムファイルのオーナーの権限で起動される 306
- ログインの流れ 321