Open29

入門 モダンLinux めも

sifi_bordersifi_border

はじめに

本書の使い方

  1. 本をただ読むだけでなく、無意味な入力や不正な入力をして”壊そう”としてみる。その際にコマンド実行前に仮説を立てる。
  2. 常に疑問を持つ。出力の由来となるコンポーネントは何であるか理解に努める。

サンプルコード

https://github.com/mhausenblas/modern-linux.info

sifi_bordersifi_border

1章

Linuxは最も広く利用されているOS。
そもそもOSってなんだ?→この章でお話しします...

1.1 モダンな環境とは?

本書におけるモダンとは、クラウドコンピューティングからRaspberry Piまで色々。
AndroidもLinuxベースだし、IoTの文脈など広い分野でLinuxが動作しているか使われている。

1.2 Linuxの歴史

1990年代

1991-08-25にリーナストーバルズが送ったメールから始まった。
そこから三年足らずでLinux 1.0.0がリリースされた。この時点でほとんどのUnix/GNUソフトウェアを実行可能であり、当初の目標は達成されていた。
1990年代にはRedHatLinuxが初の商用Linuxとして提供された。

200x年代

UNIXを超えて成長。Google, Amazon, IBMなど大手企業が商用利用。
distro warsのピーク。

201x年代

Linuxの地位が確立された。(distro warsの終焉)

sifi_bordersifi_border

1.3 なぜOSなのか?

Q. OSが使えなかったら?
A. メモリ管理、割込み処理、I/Oデバイスとのやり取り、ファイル管理、ネットワークスタックの設定&管理、etc...を自分でやる必要がある

実はOSは必須ではない!Rustならコアライブラリと標準ライブラリのみでbare metal上で行けます

なので、回答としては「OSは上記の処理をやってくれるので、ロジックに集中できる」となる。ハードウェアを抽象化(デバイスドライバ?)し、API(システムコール)を提供。
例えばgetuid(2) 現在のユーザのIDを表示するAPI.
コマンドラインでは$ id --user

1.4 Linuxディストリビューション

Linuxカーネルorカーネル:システムコールとデバイスドライバの集合
Linuxディストリビューション:カーネルとパッケージ管理、initシステム、shellなどのコンポーネントをまとめたもの。

ディストリビューションを用いなくても、自前で諸々構成はできるが、今ではすでに完成され配布されてるのを用いるのが一般的。

https://distrowatch.com/ で色々みれるようだ。

sifi_bordersifi_border

1.5 リソースの可視性

(可視性の話はコンテナと関わってくる)
Q. リソースとは?
Linuxではデフォルトではすべてのプロセスにシステムの全リソースが見える。
A. ソフトウェアを実行するために使用できる諸々。
例1:CPU情報を確認

❯ bat /proc/cpuinfo| rg "model name"
model name      : Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
model name      : Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
model name      : Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
model name      : Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
…(略)

core i7が12個。

例2:プロセス情報を確認

❯ bat /proc/$$/status | head -n6
Name:   zsh
Umask:  0022
State:  S (sleeping)
Tgid:   2025
Ngid:   0
Pid:    2025

$$ は現在のプロセスを参照する変数

PidはプロセスIDであり、LinuxはこのIDを通してプロセスを識別している。
じゃあ重複はないのかというと、そうでもない。
コンテナ化された環境があればnamespaceは異なるので、同じPIDを持つプロセスが存在しうる。

namespaceを通じてユーザやプロセスに見せるリソースの範囲を制限できる。

また、プロセス間でリソースを分離することで互いに影響を及ぼさないようにすることもできる。(ex: out-of-memory killer)

sifi_bordersifi_border

1.6 Linuxの全体像

Linuxディストリビューションの中心にはカーネル、そしてそれが提供するAPIがある。
POSIX(Portable Operating System Interface)とはUNIX系OSのサービスインターフェースを提供するIEEE規格。
POSIX準拠であれば移植可能ということです。
LinuxはPOSIXおおよそ準拠。

sifi_bordersifi_border

2章

OSはデバイスを抽象化し、プログラムにAPIを提供する。
この章ではLinuxカーネルとは何かについて見ていく。

2.1 Linuxアーキテクチャ

Linuxアーキテクチャは大別して以下の3つの階層に分けられる。

  • ハードウェア
    CPU、メモリ、ディスクドライブ、ネットワークインターフェース、キーボード、モニタなど
  • カーネル
    コア
  • ユーザ空間
    OSコンポーネント(シェルなど)、ユーティリティ(ps, sshなど)、GUIを含む多くのアプリケーションが動作している。

各層の間のインターフェースは定義されている。カーネルとユーザ空間の間にはシステムコールがある。
ハードウェアとカーネルの間は、各ハードウェアごとにちゃんとある。

sifi_bordersifi_border

ユーザ空間に関して、カーネルモードとユーザモードという概念がある。
カーネルモードではハードウェアに特権的にアクセスできる。一方ユーザモードではデバイスファイルを通じて制限されてアクセスとなる。
当然前者の方が高速だが、後者の方が抽象化を重ねており安全である。

ただ全てのアプリはユーザ空間で実行されるので(?)、カーネルモードは気にしなくてよい。

sifi_bordersifi_border

2.2 CPUアーキテクチャ

コンピュータアーキテクチャ=CPUファミリ。
Linuxカーネルは 汎用的なコードとドライバ と アーキテクチャに特化したコード からなる。
だからLinuxの移植は容易なんですね~

sifi_bordersifi_border

2.2.x

lscpuコマンドでハードウェアに関する情報を確認できる

$ lscpu
Architecture:            x86_64
  CPU op-mode(s):        32-bit, 64-bit
  Address sizes:         39 bits physical, 48 bits virtual
  Byte Order:            Little Endian
CPU(s):                  12
  On-line CPU(s) list:   0-11
Vendor ID:               GenuineIntel
  Model name:            Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    CPU family:          6
...

x86アーキテクチャはIntelが開発し、AMDにライセンス供与された。x86_64は64bitプロセッサ。
広く利用されているが、エネルギー効率が良くない。

ARMはRISCアーキテクチャの一種。高速で安価、x86より発熱が少ない。
RISC (Reduced Instruction Set Computing)は通常多くの汎用CPUレジスタと小さな命令セットで構成されている。

RISC-Vは比較的新しい新しいRISC規格。多くの実装が存在。

sifi_bordersifi_border

2.3 カーネルコンポーネント

Linux カーネルはモノリシック(各コンポーネントは単一のバイナリに含まれる)。

sifi_bordersifi_border

2.3.1 プロセス管理

セッション

複数のプロセスグループを含む。tty(制御ターミナル)を持つことがあり、セッション内のプロセス全てに共有される。
一つのフォアグラウンドプロセスグループを持つ。
SIDで識別される。

プロセスグループ

複数のプロセスからなる。PGIDで識別される。

プロセス

プログラムの実行に必要なリソース(スレッド、アドレス空間、ソケットなど)をまとめたもの。
/proc/selfにてプロセス情報が提供される。PIDで識別される。

スレッド

実装としてはプロセスと同じ。カーネルはスレッドを他のプロセスとリソースを共有するプロセスとして扱う。

タスク

task_structというデータ構造をベースにプロセスやスレッドが実装される。
この構造体にはPIDやシグナルハンドラ、パフォーマンスやセキュリティ関連情報などを保持しているが、一部しかユーザ空間には提供されない。
スケジューリングに関連する情報も含まれている。大別すると待機、実行中、待ち、終了があり、イベントによって状態が遷移する。

図はhttps://www.coins.tsukuba.ac.jp/~yas/classes/os2-2010/2010-12-14/index.html より引用


❯ ps -j
  PID  PGID   SID TTY          TIME CMD
   11    11    11 pts/0    00:00:01 zsh
  548   548    11 pts/0    00:00:00 ps

より詳細な情報はls -al /proc/$$/task/$$にて確認できる。

❯ ls -al /proc/$$/task/$$
.r--r--r-- 0 yukari 26 Jan 02:17 arch_status
dr-xr-xr-x - yukari 26 Jan 02:17 attr
.r-------- 0 yukari 26 Jan 02:17 auxv
...
sifi_bordersifi_border

2.3.2 メモリ管理

仮想メモリ機能によって実際の物理メモリ以上のメモリを扱える。
物理メモリと仮想メモリはページと呼ばれる固定長(4KB)のチャンクに分割されている。

❯ getconf PAGESIZE
4096

仮想ページはページテーブルによって物理ページによりマッピングされる。
プロセスごとにページテーブルが存在し、同じ物理ページを指すこともあり得る。

プロセスが仮想ページにアクセスするたびに、CPUが物理ページに変換する必要がある。これを補助するために、TLB(Translation Lookaside Buffer)が内蔵されており、キャッシュの役割を果たす。

Linux2.6.3以降ではHugepageがサポートされており、TLBのミスが減るためパフォーマンス向上が期待できる。

❯ grep MemTotal /proc/meminfo # 物理メモリ
MemTotal:        8088756 kB
❯ grep VmallocTotal /proc/meminfo # 仮想メモリ
VmallocTotal:   34359738367 kB
❯ grep page /proc/meminfo
Hugepagesize:       2048 kB
sifi_bordersifi_border

2.3.3 ネットワーク

階層化されたアーキテクチャに従っている。

ソケット

通信を抽象化する

TCP/UDP (Transmission Control Protocol/User Datagram Protocol)

コネクション指向通信/コネクションレス通信によるデータ転送

インターネットプロトコル(IP)

アドレスに基づいたマシン間の通信

カーネルの役割は上記三つの動作のみで、HTTPやSSHなどアプリケーション層のプロトコルはユーザ空間で実装される。

sifi_bordersifi_border

2.3.4 ファイルシステム

HDD,SSD,フラッシュメモリなどの記憶装置上にファイルやディレクトリを構築するシステムのこと。
ext4, btrfs, NTFSなど種類は多岐にわたるが、VFS(Virtual File System)によって異なるファイルシステムの共存が可能になっている。

VFSの上位層(ユーザ空間側)はopen, close, read, writeなどの操作を共通API(ファイルシステムインターフェース)で抽象化されている。下位層(ファイルシステム側)には各ファイルシステムのためのプラグインによって抽象化されている。

sifi_bordersifi_border

2.3.5 デバイスドライバ

キーボード、マウス、HDD、GPUなどのハードウェアや/dev/pts/いかに存在する仮想デバイスを制御する。
デバイスドライバはカーネルに静的に組み込まれるorカーネルモジュールとして組み込まれ必要に応じて動的にロードされる。

❯ ls -al /sys/devices # デバイスを確認
drwxr-xr-x - root 26 Jan 12:18 breakpoint
drwxr-xr-x - root 26 Jan 12:18 cpu
drwxr-xr-x - root 26 Jan 12:18 kprobe
drwxr-xr-x - root 25 Jan 18:36 LNXSYSTM:00
drwxr-xr-x - root 26 Jan 12:18 msr
drwxr-xr-x - root 26 Jan 12:18 platform
drwxr-xr-x - root 26 Jan 12:18 pnp0
drwxr-xr-x - root 26 Jan 12:18 software
drwxr-xr-x - root 25 Jan 18:36 system
drwxr-xr-x - root 26 Jan 12:18 tracepoint
drwxr-xr-x - root 26 Jan 12:18 uprobe
drwxr-xr-x - root 26 Jan 12:18 virtual
❯ mount # マウントされているデバイス一覧
sifi_bordersifi_border

2.3.6 システムコール

アプリケーションで行われるいかなる操作も、最終的にはシステムコールに変換される。
Linuxには数百ものシスコールが存在し、glibcmuslに代表される標準ライブラリがラッパー関数を提供する。

システムコールはソフトウェア割込みとして実装されており、例外を発生させて例外ハンドラが呼ばれる。
システムコールテーブルと呼ばれるメモリ上の関数ポインタの配列があり、システムコールとその関数(ハンドラ)が登録されている。

システムコールの呼び出しは以下の手順で行われる。

  1. system_call()の呼び出しがあると、ハードウェアコンテキスト(状態?)をスタックに保存し、トレースの有無などをチェックし、sys_call_tableのindexに対応するハンドラへジャンプする。
  2. sysexitによってシステムコールが終了すると、ハードウェアコンテキストを復元し、プログラムの実行がユーザ空間で再開される。

上記過程でユーザ空間モードとカーネルモードの切り替えが行われているが、これは時間のかかる処理であることが知られている。

システムコールのトレースはstraceコマンドで可能。-cオプションを付けると実行時間の割合などが確認できる。

malloc関数システムコールではなく、内部でbrk, mmapなどのシステムコールを呼んでいる。

sifi_bordersifi_border

2.4 カーネルの拡張

カーネルの情報を確認(s,r,mオプションはそれぞれカーネルネーム、リリースバージョン、ハードウェアネームに対応)

❯ uname -srm
Linux 5.15.167.4-microsoft-standard-WSL2 x86_64
sifi_bordersifi_border

2.4.1 モジュール

モジュールとは、再コンパイルやマシンの再起動なしにカーネルにロードできるプログラムのこと。
Linuxではハードウェアを自動で検出し、対応するモジュールをロードしてくれる。

利用可能なモジュール一覧

find /lib/modules/$(uname -r) -type f -name '*.ko*'
/lib/modules/5.15.167.4-microsoft-standard-WSL2/kernel/drivers/block/nbd.ko
/lib/modules/5.15.167.4-microsoft-standard-WSL2/kernel/drivers/net/bonding/bonding.ko
/lib/modules/5.15.167.4-microsoft-standard-WSL2/kernel/drivers/net/dummy.ko
...

カーネルがロードしているモジュールはlsmodにて確認できる。/proc/modulesの情報を整形して表示してくれるらしいが、wslでは空だった。

モジュールの情報取得、操作にはmodprobeを使用する。

❯ modprobe --show-depends async_memcpy
builtin async_memcpy
sifi_bordersifi_border

2.4.2 モダンなカーネル拡張:eBPF

Berkrley Packet Filterとして知られていた。現在ではextended BPFとしてLinuxカーネルの機能として組み込まれている。カスタム64bitRISC命令セットを用いたカーネル内仮想マシンとして実装されている。

sifi_bordersifi_border

2.5 まとめ

LinuxカーネルはLinuxの中核である(小泉構文みがある)。
カーネルはハードウェアを抽象化して差分を吸収し、高い移植性を実現している。ユーザ空間にはプロセス管理やファイルシステム操作、メモリ管理といった機能をシステムコールを通じてユーザ空間にインターフェースとして提供している。

sifi_bordersifi_border

3.1 基本用語

3.1.1 ターミナル

テキストベースのUIを提供するプログラム。
基本的な文字の入出力に加えて、エスケープシーケンス(もしくはエスケープコード)をサポートしている。

3.1.2 シェル

ターミナル内で動作し、コマンドインタプリタとして機能するプログラム。POSIXにおいてshとして定義されている。
もともとはStephen Bourneが開発したBourne shellのshがあったが、現在はbash(Bourne Again Shell)が広くデフォルトとして使われている。

3.1.2.1 ストリーム

入力ストリームと出力ストリームとがある。これらを合わせてI/Oと言う。
シェルはすべてのプロセスに対してIO用の以下の三つのファイルディスクリプタを用意している。

  • FD0: stdin(標準入力)
  • FD1: stdout(標準出力)
  • FD2: stderr(標準エラー出力)

デフォルトではstdinはキーボードに、stdout, stderrはスクリーンに接続されている。
これを変更したい場合、ストリームをリダイレクトすることでファイルに保存できたりする。
ストリームのリダイレクトには$FD>,<$FDを使用する。
例)2>はstderrをリダイレクト。1>>は(stdoutがデフォルトのFDのため?)同義。

stdout, stderrの両方をリダイレクトしたい場合は&>を使用し、ストリームの出力が不要ならば/dev/nullを使用する。

curl example.com > /tmp/content.txt 2> /tmp/curl-status
❯ head -3 /tmp/content.txt
<!doctype html>
<html>
<head>cat /tmp/curl-status
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1256  100  1256    0     0   1690      0 --:--:-- --:--:-- --:--:--  1692
特殊文字
  • & コマンドの最後に置くと、コマンドをバックグラウンドで実行する。
  • \(backslash) 長いコマンドを折り返して複数行で記述できる。
  • | あるプロセスのstdoutと次にプロセスのstdinとを接続し、データを直接受け渡せる。
curl https://example.com 2> /dev/null | wc -l
46
sifi_bordersifi_border

3.1.2.2 変数

  • 環境変数: シェル全体の設定。envで確認できる。exportで作成できる。
  • シェル変数: 現在実行中のシェルでのみ有効。bashではsetコマンドで設定できる。子プロセスには継承されない。

「set」はシェルの設定を確認、変更するコマンドです。setはbashのビルトインコマンド(シェルの組み込みコマンド)です(※1)。このためmanコマンドではなく、helpコマンドで詳細を表示できます。(引用元)

set | grep MY_VAR
MY_VAR=42export MY_GLOBAL_VAR="hoge global var"set | grep '^MY_*'
MY_GLOBAL_VAR='hoge global var'
MY_VAR=42env | grep '^MY_*'
MY_GLOBAL_VAR=hoge global var
❯ zsh # 子プロセスを起動set | grep MY_VAR
❯ env | grep '^MY_*'
MY_GLOBAL_VAR=hoge global var
❯ exitunset MY_VAR
❯ set | grep '^MY_*'
MY_GLOBAL_VAR='hoge global var'unset MY_GLOBAL_VAR
❯ set | grep '^MY_*'
主なシェル変数、環境変数
変数名 種別 意味 実行結果
EDITOR env デフォルトエディタへのpath. vim
PS1 env プロンプト文字列
PWD env working directoryのpath /home/yukari
SHELL env 現在使用しているshell /usr/bin/zsh
TERM env 現在使用しているterminal emulator xterm-256color
UID env ユーザID 1000
USER env current user name yukari
HOME POSIX current userのhome directoryのpath. /home/yukari
IFS POSIX フィールドを区切る文字のリスト。単語の展開時に用いられる。 \t\n
PATH POSIX シェルが実行可能なプログラムを探すディレクトリの一覧
HOSTNAME bash 現在のホスト名 empty
OLDPWD bash 最後にcdコマンドを実行したdirectoryのpath /home/yukari/atcoder
RANDOM bash [0,32767 (= 2^{15} - 1)]のランダムな整数値。 15677
_ bash foregroundで実行された直前のコマンドの最後の引数 USER
? bash 直前に実行したコマンドの終了ステータス 0
$ bash current process PID 11
0 bash current process name zsh
sifi_bordersifi_border

3.1.2.3 終了ステータス

シェルはコマンド実行が完了すると、終了ステータスを呼び出し元に返す。0なら正常終了、1-255なら異常終了。これを確認するにはecho $?を使用。
パイプラインで処理するときは最後に実行されたコマンドの終了ステータスしか利用できないため注意が必要。

3.1.2.4 ビルトインコマンド

シェルに組み込まれているコマンドもある。
そうでないものは外部プログラムであり、/usr/bin(ユーザコマンド)もしくは/usr/sbin(管理コマンド)にある。

which cat
/usr/bin/cat
❯ which read
read: shell built-in command

whichはPOSIXで規定されていない外部プログラムであるため、command -vの使用が提案されている。

sifi_bordersifi_border

3.1.2.5 ジョブ制御

デフォルトではコマンドを入力すると、画面とキーボードの制御を受けるフォアグラウンド実行となる。
一方、対話的に実行しないバックグラウンドジョブが存在する。

バックグラウンドでプロセスを起動するには最後に&を、フォアグラウンドのプロセスをバックグラウンドに送るにはctrl + zを押下する。

厳密にはctrl + zの場合はプロセスがサスペンドされるので注意。

sleep 5 &
[1] 2101[1]  + 2101 done       sleep 5 # バックグラウンドで(勝手に)実行され、結果が表示されるsleep 5
^Z
[1]  + 2155 suspended  sleep 5jobs
[1]  + suspended  sleep 5fg
[1]  + 2155 continued  sleep 5

ジョブ制御よりもターミナルマルチプレクサを使いなさ~い という旨で〆られている...

sifi_bordersifi_border

3.1.3 モダンなコマンド

紹介されているものを表にする。

コマンド 元のコマンド 機能
bat cat 表示、ページ、syntax highlight
envsubst - テンプレートから環境変数を展開
eza ls lsの上位互換(?)
dog dig DNS参照
fx jq json 処理
fzf ls, find, grep fuzzy finder
gping ping ping with graph
httpie curl 色を付けてくれる?
jo - json/array生成
rg find, grep ripgrep is a line-oriented search tool that recursively searches the current directory for a regex pattern.
tldr man コマンドの使用例
zoxide cd path を記憶しており、雑な入力でも良い

以下のページによくまとまっている。
https://github.com/ibraheemdev/modern-unix

sifi_bordersifi_border

brew install hogeで諸々を入れていたらcleanupが自動で走っていくつかのコマンドが壊れてしまった...
/home/linuxbrew/.linuxbrew/bin/starship: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.33 not found (required by /home/linuxbrew/.linuxbrew/bin/starship)とあり解決法を調べてみるとwsl のubuntuをversionを更新するのが早そうだったので、そうする。
これは何ら関係なく、brew link gccで直った。

sifi_bordersifi_border

3.1.4 一般的なタスク

3.1.4.1 よく使うコマンドを短くする

エイリアスやfishのアブリビエーションがある。

3.1.4.2 操作

コマンド 作用
ctrl + a カーソルを行頭に移動
ctrl + e カーソルを行末に移動
ctrl + f カーソルを1文字 forword 移動
ctrl + b カーソルを1文字 back 移動
alt + f カーソルを1単語 forward 移動
alt + b カーソルを1単語 back 移動
ctrl + d カーソル上の文字を削除
ctrl + h カーソルの左の文字を削除
ctrl + w カーソルの左の単語を削除
ctrl + k カーソルから行末まで削除
ctrl + u カーソルから行頭まで削除
ctrl + l 画面クリア
ctrl + r コマンド履歴を検索
ctrl + g 上記の検索をキャンセル

上記の操作はemacsベースらしい。.bashrcにset -o viと記載してsourceするとviベースの操作が使えるようだ。

zsh用のプラグインがあったので導入してみた。
https://github.com/jeffreytse/zsh-vi-mode