Skip to content

1-2. OS (Linux) の基礎知識

この節では、OSのカーネルとプロセスについて説明します。

OSとカーネル

皆さんご存知の通り、OSはコンピューター全体の動作を管理・制御し、コンピューター上で我々が書いたプログラムが動くようにしてくれるソフトウェアです。
全てのOSは、様々な部品で成り立っています。その中で最も重要な役割を担うのがカーネルです。

OSとカーネル

カーネルは、CPU・メモリの直接制御を担う、OSの中核部品です。
一般的なOSは、このカーネルの上にシェル・デーモン・コマンドなど、カーネルをより使いやすくする部品を一緒に提供しています。

実は、Linuxというのはこのカーネル部分だけを指します。Linux公式はカーネルしか提供していません。
Linuxカーネルに様々な部品を上乗せしてセットで提供しているのが、UbuntuRHELに代表されるLinuxディストリビューションです。
Linux本体とディストリビューションの違いがわかっていなかった方は、ぜひこの機会に覚えておいて下さいね!

プロセスとスレッド

ここからはLinuxに対象を絞って話を進めます。

アプリケーションを実行すると、OSの中ではプロセスという実態が生成されます。
アプリケーションが設計図、プロセスがそれを元に作られた実際の製品というイメージですね。

プロセスは1つ1つがID (プロセスID/PID) を持ち、これをもって識別されます。PIDは一回の起動の間は全てのプロセスでユニークです。
また、プロセスはいきなり何もないところから発生するわけではなく、自分をコピーしてプロセスを生成すること (fork) で増えていきます。
forkされたプロセスはフォーク元のプロセスの子プロセスとなり、紐づけがされます。

プロセスとフォーク (PIDは適当)

プロセスを増やす方法はフォークしかありません。プロセスを増やすときは、全て前のプロセスのコピーなのです。
「ではシェルで別のプログラムを起動するときはどうしてるんだ」と皆さん疑問に思ったことでしょう。
そのためにexecという処理が存在します。これは、プロセス本体/PIDを変えないまま、プロセスで実行するプログラムをまるっと入れ替える処理です。
別プログラムを子プロセスで実行したいとき、実は裏でforkしてから子プロセスでexecするという処理が行われています。
つまり、先程の「アプリケーションを実行するとプロセスになる」という説明は実は正確ではなくプロセスは器であり、器を増やしてアプリケーションを入れることで作られる、という説明の方が正確です。

forkとexec (PIDは適当)

また、プロセスからプロセスが生まれるのであれば、最初のプロセスはどこで生まれるのでしょう?
実は、Linuxが必ず最初に直接起動するプロセスが存在し、これはinitプロセスと呼ばれます。これのPIDは必ず1になります。
initプロセスは他の全てのプロセスの親であり、他のプロセスはinitプロセスをルートとする木構造を成します。

initプロセスからの木

このプロセスの木構造は、pstreeコマンドを使うことで確認することができます。

plain
systemd-+-ModemManager---3*[{ModemManager}]
        |-agetty
        |-containerd---14*[{containerd}]
        |-containerd-shim-+-php-fpm---2*[php-fpm]
        |                 `-10*[{containerd-shim}]
        |-containerd-shim-+-mysqld---34*[{mysqld}]
        |                 `-10*[{containerd-shim}]
        |-cron
        |-dbus-daemon
        |-dockerd-+-5*[docker-proxy---7*[{docker-proxy}]]
        |         |-2*[docker-proxy---6*[{docker-proxy}]]
        |         `-27*[{dockerd}]
        |-fwupd---5*[{fwupd}]
        |-mdadm
        |-multipathd---6*[{multipathd}]
        |-polkitd---3*[{polkitd}]
        |-rsyslogd---3*[{rsyslogd}]
        |-sshd
        |-systemd-journal
        |-systemd-logind
        |-systemd-network
        |-systemd-resolve
        |-systemd-timesyn---{systemd-timesyn}
        |-systemd-udevd
        |-tailscaled---13*[{tailscaled}]
        |-thermald---4*[{thermald}]
        |-udisksd---5*[{udisksd}]
        |-unattended-upgr---{unattended-upgr}
        `-upowerd---3*[{upowerd}]

また、プロセスの亜種としてスレッドというものも存在します。
プロセスの場合はフォークする時メモリの中身 (≒ 変数内のデータ) がコピーされますが、スレッドの場合は紐づいた全てのスレッド同士でデータが共有されます (レジスタなど一部共有されないデータもあります)。
プロセスよりもスレッドの方が生成・切り替えが軽量で、goroutineの裏側ではスレッドが使われていたりします。