initをぶっ壊そうぜ!

※以下の内容はOSを起動不可にする危険性のある操作を行います。 実験台PC、もしくはエミュレータなどの使い捨て環境でお試しください。

 

 Linuxオープンソースなんだけど、相変わらずその中身はよく分からないことが多い。 ソース読めとか、デバッガ使えとかプログラムを解析する方法はいろいろとあるようだが、結局、知識がないと分からないものでもあったりする。

 

今回のターゲットは、起動プロセスの1つ init だ。 init とは /sbin/init のこと。 これは、カーネルが起動した直後、最初に実行されるプログラムである。 こいつをどうするのか? 簡単なプログラム、たとえば、「Hello,world!」で置き換えよう! そう、Hello,world! なんかで置き換えてしまうのだから、もうOSは起動できないぜ! ただ、これをやるくらいなら特に手順を示さなくてもできるだろう。 C言語でプログラム書いて、rootになって /sbin/init を置き換えればいいだけだ。

 

しかし、今後、init をいじくりまわすことになると、プログラム一回、実行するたびにOSを再インストールするというのは非常に面倒くさい。 せめて、もっと便利にならないか?

 

カーネル起動時に実行する init を指定する

実は、Linuxカーネル、最初のプロセスとして /sbin/init 以外を実行するように指定することもできる。 ところで、カーネルの起動パラメータってどうやって指定するのか? それは、通常、ブートローダの仕事だろう。 つまり、GRUBの起動メニューに、「通常起動」のほかに「自分で作った init で起動」という項目を追加すればよいのだ。

 

GRUB は、古いバージョンの GRUB Legacy と GRUB2 があるが、Vine Linux 6.5 では、まだ古いほうの GRUB だと思うので、以下の方法は Ubuntu などには当てはまらないかもしれない。

 

Vine Linux では、/boot/grub/menu.lst が GRUB のOS起動メニュー画面の設定ファイルだ。 これを編集して、カーネルパラメータを書き換える。

 

/boot/grub/menu.lst の内容

# menu.lst generated by anaconda
#
# Note that you do not have to rerun grub after making changes to this file
# NOTICE:  You have a /boot partition.  This means that
#          all kernel and initrd paths are relative to /boot/, eg.
#          root (hd0,0)
#          kernel /vmlinuz-version ro root=/dev/VolGroup00/LogVol00
#          initrd /initrd-version.img
#boot=/dev/sda
default=0
timeout=5

title Vine Linux (Current kernel)
	root (hd0,0)
	kernel /vmlinuz ro root=/dev/VolGroup00/LogVol00 resume=swap:/dev/VolGroup00/LogVol01 vga=0x314
	initrd /initrd.img

title Vine Linux (Previous kernel)
	root (hd0,0)
	kernel /vmlinuz.old ro root=/dev/VolGroup00/LogVol00 resume=swap:/dev/VolGroup00/LogVol01 vga=0x314
	initrd /initrd.old.img

 

簡単に説明すると、title で始まっている行がメニュー項目の文字列で、それ以下に起動コマンドが続く。 kernel というコマンドが起動するカーネルを指定する行になっている。 /vmlinuz というのがカーネルの本体のファイルらしい。 そのあとの文字列がカーネルの起動パラメータになっている。 他のパラメータに、どんな意味があるのかはわからないので、とりあえず、メニュー項目の1つをコピーして、それを書き換えることにする。

 

最初のプログラム init を指定するには、パラメータに 'init=' というものを追加すればいい。 もし、/mybin/init という場所に自作の init があるとすれば、以下のようになる。 (※黄色の部分が変更・追加したところ)

title myinit
	root (hd0,0)
	kernel /vmlinuz init=/mybin/init ro root=/dev/VolGroup00/LogVol00 resume=swap:/dev/VolGroup00/LogVol01 vga=0x314
	initrd /initrd.img

上記のコマンド群をファイルの一番最後にでも追加すれば、自作 init をメニュー画面で選択してシステムを起動できるようになるはず。

 

なお、GRUBドキュメンテーションGNU GRUBのホームページ(

https://www.gnu.org/software/grub/grub-documentation.html

)に、カーネルパラメータについては、カーネルソースツリー(パッケージをダウンロードする、もしくは、/usr/srcを探す)の Documentation/kernel-parameters.txt に書いてある。

 

 シャットダウンと再起動

自作の init が Hello,world! を表示したあと、どうなるのか? 通常であれば、init はシステムのサービスを起動して待機するはずなのだが、Hello,world! の場合だと、もう何もやることがない。 もし、ここで一般のプログラムのように普通に終了してしまうと、カーネルは「カーネルパニック」というエラーを引き起こして止まってしまう。 この状態で電源を強制終了しても、いまどきのPCなら特に問題にならないだろうが、それはあくまで非常用の手段だから、あまり多用したくはない。 もっとスマートにシャットダウン、もしくは再起動を行うにはCライブラリ関数の reboot() を使用する。 「man 2 reboot」 コマンドでマニュアルを調べてみよう。

 

reboot()を実行するにはスーパーユーザ権限が必要になるが、init はちょうどスーパーユーザ権限で動くプログラムなので実行可能だ。

 

reboot() の形式は次のとおり。

#include <unistd.h>

#include <sys/reboot.h>

 

int reboot(int cmd);

 マニュアルの方は、ちょっと注意して読まないといけない。 システムコールとライブラリ関数の形式が異なっているようだ。 でも、それ以外は簡単な関数なので、一度、理解すれば問題ないだろう。

 

PCの再起動は次のコード。

reboot(RB_AUTOBOOT);

 

PCのシャットダウンは次のコード。

reboot(0x4321fedc);

 (※定数名ではなく値です)

---- ■追記 ----

シャットダウンの定数名は RB_POWER_OFF

って書けばいいらしい。 sysvinit-2.88dsf のソース見て知った。

sys/reboot.h の中身は見てなかった。。。

sysvinit: http://savannah.nongnu.org/projects/sysvinit

----------------

 

 Hello,world! を表示した後、再起動を行うプログラムは以下のとおり。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/reboot.h>

 

int main(int argc, char *argv[])
{
    printf("\n\nHello,world!\n\n");

 

    sleep(20);

 

    reboot(RB_AUTOBOOT);

 

    // 到達しない

    return 0;
}

 

 

この他、fgets()など標準入力から入力もできるようなので、独自コマンドを入力してシャットダウンを指示するようにするのもいいかもしれない。