続・玩式草子 ―戯れせんとや生まれけん―

第51回Plamo Linuxの遊び方(その2)

前回に引き続き、今回もPlamo Linuxの特徴的な部分のうちシステム起動回りの処理を解説しましょう。もっともこのあたりに関しては、Plamo Linuxが従来のやり方(SysV-init)を固守しているうちに、ほとんどのディストリビューションが新しいSystemdに移行してしまった結果なので、ベテランユーザはむしろこのやり方に馴染みがあるかも知れません。⁠苦笑

さまざまな/sbin/init

Linux/Unixでは、何でもできる大きなプログラムよりも、1つのことを上手にこなす小さなプログラムを組み合わせて使うスタイルが好まれ、多数の小さなプログラム(デーモン)が協働してOSの機能を提供するようになっています。それらのデーモンを起動するのが/sbin/initの主要な仕事です。

Linux/Unixの設計では、起動されたカーネルはCPUやメモリ、周辺機器等を認識、初期化していき、最終的にはルートパーティションの/sbin/initを起動して、以後のユーザ領域での処理を引き継ぎます。

/sbin/initを起動した後、カーネルは「カーネル(特権)モード」で動作してハードウェアの管理、運用に専念し、⁠ユーザ(一般)モード」で動作する他のプログラムからは直接見えなくなります。

カーネルから起動された/sbin/initは、⁠ユーザモード」で動く全てのプロセスの祖として、デーモン類の起動等、必要な処理を順に実行していきます。この処理の仕組みをあれこれ工夫することで、さまざまな/sbin/initの実装が生まれてきました。

Plamo Linuxが採用しているsysvinitは、"SystemV(システム・ファイブ⁠⁠ Init"の名前が示すように、AT&Tが開発、販売していた"Unix SystemV"の/sbin/initの機能をFOSSとして実装したプログラムです。この/sbin/initは、システムの「ランレベル」という概念を持ち、ランレベルに応じて必要なスクリプトを起動する、という実装になっています。

Plamo Linuxの元になったSlackware Linuxでは、SystemVとは別系統のUnixであるBSD-Unixが用いていた/etc/rcと/etc/rc.localの2つのスクリプトで起動時の処理を行う、という設計になっています。この方法はシンプルなものの、新しい処理を追加するにはrc.localを手作業で修正する必要があります。

"sysvinit"では処理を行うスクリプトをファイル名の順に実行していくため、途中に時間がかかる処理があると以後の作業が遅延してしまいます。また、処理の多くは独立して実行できるものの、それらを並列実行するような仕組みは用意されていません。

このあたりを改良するために考案されたのがUpstartOpenRCInit-ngといった実装で、各処理に依存関係を定義し、依存関係にない処理は並列で実行する等、システム起動にかかる時間を短縮するための仕組みがさまざまに考案されました。

起動処理の高速化に加えて、/sbin/initの「カーネルから直接起動される一番最初のプロセス(pid=1⁠⁠」という立場を活かし、ユーザやネットワーク、周辺機器の管理、システムのログ取りといった機能も組み込もう、という実装も現われました。それが現在主流となっているSystemdで、DebianやFedora、Arch Linuxといった主要ディストリビューションが/sbin/initとして採用しています。

一方、Systemdは「あまりに多くの機能を取り込みすぎている」という批判もあり、DebianからSystemdを外した"Devuan"が開発されたりしています。

sysvinitの設定ファイル

前節で紹介したように、最近では"Systemd"が/sbin/initのデフォルトとなり、Plamo Linuxが使い続けている"sysvinit"については解説等を目にすることも少なくなりました。そこでこの機会に"sysvinit"が利用する設定ファイルの概要を紹介しておくことにします。

/etc/inittab

前節で触れたように、"sysvinit"にはシステムの動作モードを意味する「ランレベル」という概念があります。通常、ランレベルは0から6に分かれていて、

  • ランレベル0は全てのデーモン類を停止してシステムを終了するシャットダウンモード
  • 1はrootユーザのログインのみを許可するシングルユーザーモード
  • 2は複数ユーザがログインできるものの、NFS等のネットワークサービスは停止したモード、
  • 3が全ての機能が利用できる通常モード
  • 4は独自設定可能な予備用モードでデフォルトは3と同じ、
  • 5は3に加えてあらかじめウィンドウシステムも起動してGUIログインを可能にするモード
  • 6が全てのサービスを終了してリブートするモード

とされています。

この「6つのランレベル」はLinux Standard Baseの定義に準じてはいるものの、元々が大型コンピュータを複数のユーザが同時接続して使っていた時代の遺物なので、最近ではよりシンプルな分け方が好まれています。

"sysvinit"の/sbin/initは/etc/inittabという設定ファイルを見て、起動後のシステムをどのモードにするかを判断します。Plamo Linuxの/etc/inittabのランレベル設定部は下記のようになっています。なお、1行目に記載しているように、/etc/inittabをはじめとするPlamo Linuxの"sysvinit"用設定ファイルの多くはLFS(Linux From Scratch)が公開しているものを流用しています。

$ cat -n /etc/inittab 
   1  # /etc/inittab derived from LFS 20170713
   2  # Begin /etc/inittab
   3  
   4  id:3:initdefault:
   5  
   6  si::sysinit:/etc/rc.d/init.d/rc S
   7  
   8  l0:0:wait:/etc/rc.d/init.d/rc 0
   9  l1:S1:wait:/etc/rc.d/init.d/rc 1
  10  l2:2:wait:/etc/rc.d/init.d/rc 2
  11  l3:3:wait:/etc/rc.d/init.d/rc 3
  12  l4:4:wait:/etc/rc.d/init.d/rc 4
  13  l5:5:wait:/etc/rc.d/init.d/rc 5
  14  l6:6:wait:/etc/rc.d/init.d/rc 6
  15
  16  ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
  17
  18  su:S016:once:/sbin/sulogin

4行目の"id:3:initdefault:"がデフォルトのランレベルの設定で、通常はランレベル3(通常モード)で起動することを指示します。

6行目の"si::sysinit:..." 行はシステム起動(sysinit)時に実行する処理を意味し、":"区切りの4つめのコラム、"/etc/rc.d/init.d/rc S"コマンドが実行されます。

8行目から14行目は各ランレベルの処理で、それぞれ"/etc/rc./init.d/rc"コマンドを"0"から"6"の引数を指定して実行します。

この設定の場合、カーネルから起動された/sbin/initは、まず"/etc/rc.d/init.d/rc S"を実行し、その後ランレベル3に移行するために"/etc/rc.d/init.d/rc 3"を実行します。

ランレベル3は通常モード、すなわちコンソール画面でユーザ名とパスワードを入力してログインするモードです。あらかじめXを起動してGUI画面でログインしたい場合は4行目を"id:5:initdefault"のようにします。また、起動時にgrubのメニュー画面からカーネルパラメータから"1"や"5"等、ランレベルを指定することもできます。

linux /boot/vmlinuz-6.1.47-plamo64 root=XXXXX ro net.ifnames=0 net.ifnames=0 quiet 5

16行目の"ca:12345:..."の行は"Ctrl"+"Alt"+"Del"を同時押しした際の処理で、MS-DOSやWindowsに習って/sbin/shutdownが走るようになっています。18行目の"su:S016:.."行は、ランレベル1(シングルユーザモード)に入る際、/sbin/suloginを実行してrootのパスワードを確認する処理です。

物理的に保護されていないマシンでは、リセットボタンや電源OFFで強制的にシステムを落した上で、再起動時に上述のカーネルパラメータを使ってランレベル1で起動することができます。その際、この行を指定しておくと/sbin/suloginが動作してrootのパスワードを検証できます。

18行目から先の部分はコンソールからのログイン用にgettyを起動する処理で、今回の話題との関連は薄いため省略します。

/etc/rc.d/rc[0-6].d/

さて、それでは実際に各ランレベルでどのような処理が行われるのかを見てみましょう。

前述のように、/sbin/initは/etc/inittabの設定に基づき、/etc/rc.d/init.d/rcコマンドを"1"や"3"といった引数で実行します。この"rc"コマンドはシェルスクリプトで、与えられた引数に基づき、/etc/rc.d/rc1.d/や/etc/rc.d/rc3.d/にあるシェルスクリプトを実行していきます。

実際に/etc/rc.d/rc3.d/にあるのは、"K"か"S"で始まり2ケタの数字の後にコマンド名が続くシンボリックリンクです。

$ ls /etc/rc.d/rc3.d/
K05lightdm@   S21smartd@      S25netfs@    S32httpd@            S36bluetooth-autoconnect@
S10rsyslogd@  S21unbound@     S25random@   S32php-fpm@          S40crond@
S10vboxadd@   S22nsd@         S26ntpd@     S32proftpd@          S45samba@

これらのリンクが指しているのは/etc/rc.d/init.d/にある"lightdm"や"rsyslogd"というスクリプトです。

$ ls -l /etc/rc.d/rc3.d/
lrwxrwxrwx 1 root root 17  6月  9日  17:50 K05lightdm -> ../init.d/lightdm*
lrwxrwxrwx 1 root root 18  6月  9日  17:46 S10rsyslogd -> ../init.d/rsyslogd*
lrwxrwxrwx 1 root root 24  8月  9日  15:54 S10vboxadd -> /etc/rc.d/init.d/vboxadd*
lrwxrwxrwx 1 root root 15  6月  9日  17:51 S18acpid -> ../init.d/acpid*
  ...

さて、それではこの"/etc/rc.d/init.d/rsyslogd"が何をしているのかを見てみます。処理自体はシンプルで、下記のように33行目で引数("${1}")を調べ、引数が"start"ならば34行目からの起動処理、"stop"ならば41行目からの終了処理、"restart"ならば47行目からの終了/起動を連続実行する処理、それ以外ならば54行目からの使用例の表示、となります。

$ cat -n /etc/rc.d/init.d/rsyslogd 
   1  #!/bin/sh
   2  ########################################################################
   ...
  33  case "${1}" in
  34     start)
  35        log_info_msg "Starting rsyslogd..."
  36        parms='-i /run/rsyslogd.pid'
  37        start_daemon /sbin/rsyslogd $parms
  38        evaluate_retval
  39        ;;
  40  
  41     stop)
  42        log_info_msg "Stopping..."
  43        killproc -p $pid /sbin/rsyslogd
  44        evaluate_retval
  45        ;;
  46  
  47     restart)
  48        ${0} stop
  49        sleep 1
  50        ${0} start
  51        ;;
  52  
  53     *)
  54        echo "Usage: ${0} {start|stop|restart}"
  55        exit 1
  56        ;;
  57  esac
  58  
  59  exit 0

このスクリプトに限らず、/etc/rc.d/init.d/にある全てのスクリプトはstartstopの引数を取り、"start"で対象となるデーモン(この例では/sbin/rsyslogd)を起動、"stop"でそのデーモンを終了するようになっています。

"start"と"stop"以外にも、この例のように"stop"と"start"を連続して行う"restart"や 設定ファイルを読み直す"reload"、動作状況を表示する"status"といった引数を受け付けるスクリプトもあります。また、デーモンの起動/終了以外にも、ファイルシステムをマウント/アンマウントしたり、ネットワークの初期化/終了を担当するスクリプトもあります。

一方、シンボリックリンク経由でこのスクリプトを呼び出す"/etc/rc.d/init.d/rc"は、指定されたディレクトリにあるシンボリックリンクの先頭が"K"ならば"stop"引数、"S"ならば"start"引数で起動します。

すなわち、/etc/rc.d/rc3.d/以下のシンボリックリンクは"K"で始まるものがそのデーモンやサービスの終了、"S"で始まるものが起動を意味し、S10rsyslogd, S18acpid, S19iptablesといった2ケタの数字は起動/終了の実行順を示す、というわけです。

これら/etc/rc.d/init.d/以下のスクリプトとそれらを呼び出すための/etc/rc.d/rc[0-6].d/以下のシンボリックリンクはそれぞれのパッケージが用意することになっていて、"sysvinit"の側はどのようなパッケージがインストールされていても、指定されたディレクトリにあるスクリプトを順に実行すればいいようになっています。

パッケージを作る際には、⁠このデーモンはどのランレベルの際に起動されるべきか」⁠どのデーモンよりも先、あるいは後に起動する必要があるか」等を考慮してシンボリックリンクを用意する必要があります。

なお、"/etc/rc.d/init.d/rc"は、指定されたディレクトリのシンボリックリンクを起動する際、まず"K"で始まるリンクを"stop"引数で実行した上で"S"で始まるリンクを"start"引数で実行します。こうすることでtelinit等でランレベルを切り替えた際、そのレベルでは不要なデーモンやサービスを停止した上で、必要なもののみを改めて起動する、という仕組みになっています。

あるデーモンの機能が不要になった場合、削除せずに「とりあえず停止しておきたい」という場合は、/etc/rc.d/init.d/以下にある起動用スクリプトの実行パーミッションを落せば自動起動を停止できます。

実のところ、Plamo Linuxではデフォルトでインストールするパッケージの中にも、セキュリティ等への考慮から自動起動しないサービスがいくつかあります。

$ ls /etc/rc.d/init.d/
acpid*                 dhcpd         modules*              random*          sshd*
atd*                   dovecot       mountfs*              rc*              swap*
autofs                 eudev*        mountvirtfs*          rc_initpkg*      sysctl*
avahi*                 eudev_retry*  netfs*                reboot*          template*
bluetooth              gpm           network*              rngd*            unbound
...

上記スクリプトのうち、autofsbluetooth, dhcpdといった実行属性が付いていない("*"が付かない)スクリプトは/etc/rc.d/rc[0-6].d/のシンボリックリンクから呼び出されても実行されません。

Plamo Linuxの起動画面で黄色の"[ WARN ]"と表示されるのが、実行属性が付いていないスクリプトを呼び出した例です。パッケージをインストールしたのに必要なデーモンが動いていないような場合、この/etc/rc.d/init.d/以下の起動スクリプトの属性をチェックしてみてください。


さて、今回は"sysvinit"について紹介しましたが、読者の方はこの実装を見てどのように感じられたでしょう?

Linuxの元となったUnixには「Unix哲学(Unix Philosophy⁠⁠」という考え方があって、⁠小さいことは美しい」⁠1つのことをうまくやるプログラムを書け」⁠協調して動くプログラムを書け」⁠ソフトウェアを梃子てことして使え」等々、さまざまな経験則が伝えられています。

筆者の目には、シンプルなシェルスクリプトとシンボリックリンクの組み合わせだけで必要な機能を実現していく"sysvinit"の実装は、まさにこの「Unix哲学」の結晶のように見える一方、システム管理に必要な機能を積極的に取り込んいく"Systemd"には真逆の設計思想を感じます。

もちろん、⁠Unix哲学」が生まれたころのコンピュータ環境は現代とは比べものにならないくらい非力でしたし、LinuxもUnixを元に生まれたとは言え、すでにUnixの枠には収まらない規模に成長しています。その意味では新しい「Linux哲学」があってもいいし、"Systemd"のようなLinuxを前提した実装も必然なのかなぁ……と考えているところです。

おすすめ記事

記事・ニュース一覧