Hello again, linux!

背景

1996年頃、たしか「メモンドウズ」ってキーワードの記事だった気がするが
今はgoogleっても出てこない。まさかFJか?

とにかく・・・記事ではwindows95の某システムファイルがただのtext-fileで、
explorer.exe」(だったかな?もうwin95手元にないし。)って書いてあるところを
「notepad.exe」と変わってしまった状態で再起動すると、
見慣れた画面ではなく、notepadのウィンドウが1個だけ立ち上がってくる。

notepad終了するとwin95も終了する。・・・どないせいちゅうねん???

という・・・遊び?Hack?トラブる記録?・・・をネット上の記事で見つけて試した思い出がある。

一見しょうもない遊びだが、systemの起動の仕組みの一端を実感できるという点で
まことに有意義であった。天晴。

で、ちょっとLinuxで似たような事を実験しておくと後輩に教えるときのサンプルになるかもしれないのでやってみる。

とりあえず、この前用意したqemu環境で。

linuxが最初に実行するユーザ空間プロセス

細かい話は以下の書籍を見ればいいとして:

要は、linux-2.6.28/init/main.c の init_post()の最後の方:

    817         /*
    818          * We try each of these until one succeeds.
    819          *
    820          * The Bourne shell can be used instead of init if we are
    821          * trying to recover a really broken machine.
    822          */
    823         if (execute_command) {
    824                 run_init_process(execute_command);
    825                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
    826                                         "defaults...\n", execute_command    826 );
    827         }
    828         run_init_process("/sbin/init");
    829         run_init_process("/etc/init");
    830         run_init_process("/bin/init");
    831         run_init_process("/bin/sh");
    832 
    833         panic("No init found.  Try passing init= option to kernel.");
    834 }

ここで規定の実行ファイルを順番にTryしていくわけだ。

で、ポイントは

    823         if (execute_command) {
    824                 run_init_process(execute_command);
    825                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
    826                                         "defaults...\n", execute_command    826 );
    827         }

ここで、 execute_command に、kernel command-line で
"init=" option で渡ってきたパスが入っていたりする。
あと、initramfsを使用していた場合はもう少し上のライン

    811         if (ramdisk_execute_command) {
    812                 run_init_process(ramdisk_execute_command);
    813                 printk(KERN_WARNING "Failed to execute %s\n",
    814                                 ramdisk_execute_command);
    815         }

ramdisk_execute_commandに"/init"が入ったりする。

実験1 "Hello again" メッセージを表示しかしないシステムの構築

shellから

  $ cd $BOARD_DIR
  $ mkdir initram.d
  $ cd initram.d
  $ nano -cw hello.c

として、hello.cにお約束のあのフォーマットを。

  #include 
  
  int
  main(int argc, char** argv)
  {
          printf("Hello again, linux!\n");
          return 0;
  }

然る後shellから

  $ arm-none-linux-gnueabi-gcc -static -o init hello.c

ライブラリ準備が面倒なので -static にしているのがポイント。

次いで、最低限コンソールのスペシャルファイルが無いとprintfの出力先がないので(要確認)

  $ mkdir dev
  $ sudo mknod dev/console c 5 1

こんなとこか?

  $ tree ./
  ./
  |-- dev
  |   `-- console
  |-- hello.c
  `-- init
  
  1 directory, 3 files

で、今作った最低限過ぎるrootfsをinitramfsに取り込んだkernelの製作

$ cd $KBUILD_DIR
$ make menuconfig

で、makeしてqemuで確認

  $ make
  
    .....(略).....
  
  $ qemu-system-arm -nographic -M versatileab \
  -kernel ./arch/arm/boot/zImage \
  -append "console=ttyAMA0 root=/dev/ram" -m 64

を〜、できた。

実験2 "No init found." errorメッセージを見よう

linux-2.6.28/init/main.c の init_post() のコードから、
すべてのTryに失敗すると

    833         panic("No init found.  Try passing init= option to kernel.");

このエラーメッセージが得られるはずである。

状況として、さっきのルートファイルシステム
/init が実行不能だった場合が思いつく・・・

では作業開始。

$ cd ${BOARD_DIR}/initram.d
$ chmod -x init
$ cd $KBUILD_DIR
$ make
$ qemu-system-arm -nographic -M versatileab \
  -kernel ./arch/arm/boot/zImage \
  -append "console=ttyAMA0 root=/dev/ram" -m 64

よしよし、できた。

今日のまとめ

knowhow:

  • カーネル初期化終了後、ユーザランドの最初のプロセスを呼び出す仕組みは(比較的)単純。
    • linux/init/main.c を起点にcodeを追える。
  • 規定の"init"がいるわけでなく、hello worldでもinitって名前にしてしまえば動く。
    • 独自initを作成できたり、system customize の幅は広い。

Followup:

  • 今使ってるkernelの.config等について記録しておく