🍣

読書感想文-Linuxのしくみ

2023/01/21に公開

はじめに

Linux のしくみ カーネルを知ってステップアップを読みました。今後必要になったときにすぐに必要な情報にありつけるように、各章で個人的に知らなかったことやキーワードをメモします。

第 1 章 Linux の概要

strace

発行したシステムコールを確認する。

  • -Tオプションでシステムコールの所要時間をマイクロ秒で出力できるため、ボトルネックの処理があるときの調査などで使えそう。
$ strace -T -o <出力ファイル> <コマンド>

sar

論理 CPU が実行している命令の割合を確認する。
ユーザーモード、カーネルモード、アイドル状態など、それぞれの比率を確認できる。

  • -P 0は論理 CPU0 を表す
  • その次の1は 1 秒ごとにデータを採取する意味
  • その次の1は 1 回だけデータを採取する意味
$ sar -P 0 1 1

taskset

任意のコマンドを指定した論理 CPU で実行する。

$ taskset -c <論理CPU番号> <コマンド>

ldd

実行ファイルにリンクされているライブラリを確認できる。
python など多くの言語は C の上で動いていることは知っていたが、以下コマンドで標準 C ライブラリがリンクされていることを実際に確認した。

$ ldd /usr/bin/python3

静的リンクというのもあり、ビルド時に実行ファイル内にライブラリの関数が組み込まれる。
golangの実行ファイルは基本的には静的リンクで、lddで確認すると共有リンクがないことが分かる。
(私の環境ではgolangの実行ファイルでも、共有ライブラリがリンクされているものもあった。)
静的リンクが使用される理由は以下。

  • サイズが多くなりがちだが、現代の PC ならそれほどの大きな問題ではない。
  • 別の環境での実行も容易
  • 実行時の共有ライブラリリンクがないため速い
  • 共有ライブラリのアップデートで発生しがちな、「DLL 地獄」を回避できる。

第 2 章 プロセス管理(基礎編)

プロセスの終了

プロセスにはいくつかの状態がある。その中で終了時の挙動で知らなかったことがあったためそのあたりのメモ。

子プロセスの終了後、親プロセスは wait などのシステムコールにより以下の情報を得る。

  • プロセスの終了ステータス。256 の余り値。exit(0)などを指定するとそのまま 0 が返る。一般に 0 が成功、1 が失敗かな?
  • シグナルによって終了したかどうか。

書籍にあった以下のサンプルコードで動作を確認。

  • false は 1(異常終了)を常に返す、それをバックグラウンド実行
  • wait で終了情報を取得。$!で最後のバックグラウンドジョブのプロセス ID を指定
  • $?で終了ステータスを取得
#!/bin/bash
false &
wait $!
echo "falseコマンドが終了しました: $?"

https://qiita.com/laikuaut/items/1daa06900ad045d119b4
https://shellscript.sunone.me/exit_status.html

ゾンビプロセスと孤児プロセス

名前は聞いたことはあったが、具体的にどんな状況でこのようなプロセスになるのか知らなかった。

ゾンビプロセス
プロセスは終了したのに、親プロセスから wait 系システムコールが呼ばれていない状態

孤児プロセス
wait 系システムコールを呼ばずに、親プロセスが死んだ状態。init が親になる。
「ゾンビプロセスが init プロセスに襲い掛かる」という書籍の表現が面白い

ジョブ、セッション、プロセスグループ

端末や ssh を識別したり、プロセスを識別したり、複数のプロセスをまとめて扱う仕組み
うまく活用することで以下のようなことができる。

  • プロセスグループにまとめて signal を送る
  • 端末を閉じても実行したプロセスを終了させない。

デーモン

常駐プロセスという理解で良さそう。親プロセスが init である。
ssh のサーバー側のプロセスである sshd とかは常に動いていないと行けないのでデーモンである。
(大学生の時に出会ったおじさんエンジニアに、「デーモンも知らないのか」って鼻で笑われたな・・・)

第 3 章 プロセススケジューラ

タイムスライス

一つの論理 CPU で同時に実行できるプロセスは一つだけ。
CPU はタイムスライスという単位で、複数のプロセスの実行を切り替えている。

コンテキストスイッチ

CPU 上で実行されているプロセスが切り替わること。
プログラムを書くときは、こういう動作があることも頭に入れておきたい。(これに起因するバグの例とかあるのかな?)

第 4 章 メモリ管理システム

仮想記憶

物理メモリを直接プロセスが参照すると発生する問題を解決するために、仮想記憶をはさむ。仮想記憶と物理メモリの対応表がページテーブル。
ページテーブルの一行サイズは CPU アーキテクチャ毎に異なるらしい。

デマンドページング

プロセスは先に仮想記憶のアドレスだけ取得する。実際に使用するときにページフォールトが発生し、物理アドレスを取得する。そもそも仮想記憶外にアクセスしようとした場合は参照エラー。(c や go などではよくあるやつ)

https://zenn.dev/satoru_takeuchi/articles/bdbdeceea00a2888c580

第 5 章 プロセス管理(応用編)

コピーオンライト

fork などでプロセス生成した際に、ページテーブルだけコピーする機能。コストが少なくて済む。

プロセス間通信

排他制御

lock ファイルを使ったおなじみの排他制御とその問題点が説明されている。よく考えれば「lock ファイルの有無を確認」というクリティカルセッションがアトミックになっていない。
CPU ではアトミックに実行する処理がある。詳しくはキーワードで調べてみる。

マルチプロセスとマルチスレッド

マルチプロセスはforkexecveで新プロセスを作って、プロセス間通信を行う。
マルチスレッドは一つのプロセス内に複数の流れを作るイメージで、POSIX には標準 API として POSIX スレッドがある。

マルチプロセスと比較して、起動速い、リソース消費が少ないなどあるが、一方で 1 スレッドが他全てに影響する、スレッドセーフを意識する必要があったりする。
スレッドとは厳密には異なるけど、この辺のマルチスレッドライクなプログラミングを簡単に行えるように coroutine や goroutine があるという認識。(coroutine は他にも中断可能という概念で同期的に書くことができたりもするけど)

第 6 章 デバイスアクセス

デバイスファイル

  • デバイスにアクセスするためのファイル
  • /dev/以下にある

echo hello > /dev/pts/nとかやれば他の端末にシステムコールを発行できる。

第 7 章 ファイルシステム

ファイルシステムはファイルという単位で良い感じに管理してくれる。代表的なものとして、ext4XFSBtrfsなどがある。ただ POSIX にこれらを扱う標準 API があるため、ユーザーはその違いを意識しなくて良い。
ファイルシステムの一般的な機能の中で、アプリプログラマが知ってると良いと思ったのは以下の機能。

  • 容量制限(クォータ)・・ディレクトリ単位で最大容量を決めたりなど
  • 整合性保持・・読み書き中に電源が切れてもなんとかする技術

様々なファイルシステム

システムの現在の状態について把握できる様々なファイルシステムが提供されていて、面白い。今まで適当に見ていた Linux のディレクトリも実はシステム情報を表すファイルシステムだったりした。

  • メモリベースのファイルシステム・・/tmpvar/runなどに作られる、作ることができる。
  • ネットワークファイルシステム・・NFS や CIFS など。
  • procsfs
    • /proc以下にマウントされる。
    • proc/<pid>/cmdlineで実行されたコマンドラインを確認できる(他にもプロセスに関しての情報が詰まっている)

第 8 章 記憶階層

キャッシュメモリ

メモリの内容を一時的に CPU 側のキャッシュメモリに置いておくことで処理の高速化を目指す。
CPU 実行時に、メモリとレジスタ間のデータ交換が遅いので、キャッシュメモリが CPU 側に入る。キャッシュメモリはその中で L1,L2..のように分かれている場合があり、L1 の方が速いが容量が小さいとトレードオフ。

ページキャッシュとバッファキャッシュ

ページキャッシュはファイルを一時的にメモリに置いておくことで処理の高速化を目指す。
バッファキャッシュはファイル以外のキャッシュを行う。

スワップ

メモリが枯渇してきたら、あまり使用していなさそうなメモリをストレージデバイス内のスワップ領域に移動する。ストレージアクセスになると処理が遅くなるが、OOM が発動するよりはマシという感じか。

第 9 章 ブロック層

ブロックデバイス(ストレージデバイス)の性能を引き出すための Linux カーネルの機能。
HDD が磁気で何かしているのは知っていたけど、半径方向と円周方向に広がる磁気ディスクのセクタという領域毎にスイングアームの動きを調整して、データを取得している話を知れて面白かった。
電子ではなく物理的なのでそりゃ遅くなるかと思いつつ、なるべくスイングアームの動く量が少なくなるようにカーネルが頑張っていることを知って話のネタにできそう。

第 10 章 仮想化機能

仮想化機能とは物理マシンの上で、仮想マシンを動かすための機能やハードウェアのサポート。と思っていたが、仮想マシンの上で仮想マシンを動かす Nested Virtualization というのをやっている人もいるらしい。
仮想化ソフトウェアが物理マシンの上に乗り、それぞれの仮想 OS に物理資源を分配する。
仮想 OS は物理 OS から見るとプロセスの一つに過ぎないが、その中で Linux カーネルを含むいい感じの動作が行われている。

第 11 章 コンテナ

独立した実行環境を作ることができる、仮想化より起動が速い。理由は仮想化とは異なり、仮想化ソフトウェアのレイヤーが不要であることなどが大きいと思われる。
プロセスやユーザーを namespace で細分化することで、独立した実行環境を作成するイメージ。なので仮想化機能のように他 OS の実行環境をまねることはできない。
コンテナの方が仮想化機能より一見良いように見えるが、全てのコンテナが同じカーネルを参照するため、セキュリティリスクが比較すると高い。

第 12 章 cgroup

各リソースをどのプロセスにどれだけ配分するかを制御できる。
深く考えたことはなかったが、IaaS などのサービスでは当然必要で、1 人のユーザーにたくさんリソースを使われたら、他のユーザーはたまったもんではない。
cgroup では、CPU やメモリ、ブロック I/O、ネットワーク I/O を制御できる。

今後

低レイヤー詳しくなりたい。
本書では、読み終わった後のおすすめの本としていくつかの書籍が紹介されていました。
その中で私が今読んでいるのはGo ならわかるシステムプログラミングです。
本書よりカーネル(というより Go のランタイム)に少し詳しい内容になり、Go 全体の理解も深まるため個人的におすすめです。

GitHubで編集を提案

Discussion