Android起動時にドライバモジュールをロードする

前回は、USB WiFi子機向けのモジュールをコンパイルし、手動でAndroidにインストールしセットアップする事で、無線LANを利用出来るところまで行った。
今回は、インストールとセットアップをAndroid起動時に自動的に行う方法について書く。
Androidの起動時のプロセスは特殊であり、自動化まで結構手こずった。

Androidの起動プロセス

起動プロセスに関しては、京都マイクロコンピュータさんのブログが詳しい。今回は、このブログを参考にしながら処理の流れを確認するつもりで追っていく。

Linuxカーネルの起動プロセス

以下の説明で使用するLinuxカーネルは、Androidで使用している2.6.32を前提とする。

カーネルは、システム起動の最後のにinit/main.c L.517のstart_kernel()関数をコールする(詳細は、O’REILLY 詳解LinuxカーネルのAppendix Aを参照)。

ここから順を辿っていき、
start_kernel() -> rest_init() -> kernel_init() -> init_post()
という順に関数がコールされていき、init_post()内で、initプログラムを実行している(※kernel_init()は、rest_ini()からkernel_thread()関数により新しいスレッドが生成されコールされている)。

initプログラムは、init_post()関数内で、

        if (ramdisk_execute_command) {
                run_init_process(ramdisk_execute_command);
                printk(KERN_WARNING "Failed to execute %s\n",
                                ramdisk_execute_command);
        }

        /*
         * We try each of these until one succeeds.
         *
         * The Bourne shell can be used instead of init if we are
         * trying to recover a really broken machine.
         */
        if (execute_command) {
                run_init_process(execute_command);
                printk(KERN_WARNING "Failed to execute %s.  Attempting "
                                        "defaults...\n", execute_command);
        }
        run_init_process("/sbin/init");
        run_init_process("/etc/init");
        run_init_process("/bin/init");
        run_init_process("/bin/sh");

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

という様に、特定の場所にinitプログラムの実行を順に試みている。いずれにもinitプログラムが配置されていない場合は、カーネルパニックが発生する。
Androidの場合、固定値で指定している"/sbin/init"、"/etc/init"、"/bin/init"、"/bin/sh"は存在しない。
その代わりにinitプログラムは、/initとして配置されている。このプログラムが実行されなければならないが、これを可能にしているのがexecute_command変数である。
このexecute_command変数に、任意のinitプログラムパスが格納されていれば、そのプログラムを実行する。

execute_command変数は、たどっていくと、__setup("init=", init_setup)が元になっている様子。
ここで、init_setupは関数名で、正式なシグネチャは、static int __init init_setup(char *str)。引数のstr変数の値がexecute_command変数に代入される。__setup(str, fn)はマクロであり、どうやら第一引数strで指定したカーネルパラメータの値を第二引数fnの関数の引数にしている様子(確証はない)。

Android OS用のSDカードを作成する際、作成したbootパーティション内に配置したboot.srcというbootスクリプト内では、"init=/init"とパラメータ設定を行っていた。
この値"/init"が__setup()マクロによりexecute_command変数に渡っていたとすると、このプログラムがinitとして実行されていることになる。
従って、次は/initプログラムの中身に注目しなければならない。

Androidの起動プロセス

以下の説明で使用するAndroidは、Rowboatを前提とする。

京都マイクロコンピュータさんのブログにもあるように、Androidのinitプログラムは、system/core/init/内にあり、system/core/init/init.cファイル内にmain()関数がある。
このmain()関数内で、"/init.rc"が読み込まれ、このファイル中に記載されている内容に従ってAndroidの初期化が行われる。
init.rcの書き方に関しては、system/core/init/readme.txtに記載されている。どうやらここに何らかの方法で処理を書き加えれば、任意の初期化が出来るようになりそう。

具体的な方法をWeb上で探していたところ、仙石浩明さんのブログに、

service flash_recovery /system/etc/install-recovery.sh
    oneshot

というinit.rc内の記述により、"/system/etc/install-recovery.sh"が起動時に一度だけ実行されることが出来ると書かれている。
念のためreadme.txtを確認してみると、46行目に、

Services

              • -

Services are programs which init launches and (optionally) restarts
when they exit. Services take the form of:

service [ ]*

と記載されている。確かに初期化処理用に用いるステートメントの様だ。

初期化スクリプトを追加する

以上の調査を踏まえて、ドライバモジュールを起動時にインストールするためのスクリプトを追加する。

ドライバモジュールインストール用スクリプトの作成

前回の記事の最後に手動で行ったインストール手続きを以下の様にホストPC上でスクリプト化する。
スクリプトファイル名は、"init.opam3.sh"とする:

#!/system/bin/sh

# enable wireless lan
setprop net.dns1 192.168.11.1
insmod /system/lib/modules/rt2870sta.ko
netcfg ra0 up
netcfg ra0 dhcp

作成したスクリプトを、ADB経由でBeagleBoardに転送する:

> ./adb -s 20100720 push ~/workspace/android_init/init.omap3.sh /system/etc/init.omap3.sh

転送後、ADB経由でAndroidにリモートシェルで入り、転送したシェルスクリプトのパーミッションの変更を行う:

adb> chmod 777 /system/etc/init.omap3.sh
init.rcの修正

上記で作成したinit.omap3.shを起動時に実行するように、init.rcに追記する。
まず、BeagleBoard上のinit.rcをホストPCに持ってくる:

> ./adb -s 20100720 pull /init.rc ~/workspace/android_init/init.rc

取得したinit.rcに以下の追記を行う:

--- init.rc.org    2011-06-19 12:20:09.000000000 +0900
+++ init.rc    2011-06-17 18:24:56.000000000 +0900
@@ -388,6 +388,9 @@
 service flash_recovery /system/etc/install-recovery.sh
     oneshot
 
+service boot_script /system/etc/init.omap3.sh
+    oneshot
+
 service racoon /system/bin/racoon
     socket racoon stream 600 system system
     # racoon will setuid to vpn after getting necessary resources.

先に作成したinit.omap3.shシェルスクリプトを"boot_script"というサービス名で起動するステートメントを追加している。
追加後、BeagleBoardにファイルを転送して更新する:

> ./adb -s 20100720 push ~/workspace/android_init/init.rc /init.rc

最後に、ADB経由でAndroidを再起動する:

> ./adb -s 20100720 reboot-bootloader

この再起動以降は、起動時に自動的にドライバモジュールがインストールされるようになり、何もしなくても起動直後から無線LANにアクセス出来るようになる。

参考文献

Linuxの起動プロセスを詳しく知りたい場合に必見の本:

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

  • 作者: Daniel P. Bovet,Marco Cesati,高橋浩和,杉田由美子,清水正明,高杉昌督,平松雅巳,安井隆宏
  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2007/02/26
  • メディア: 大型本
  • 購入: 9人 クリック: 269回
  • この商品を含むブログ (73件) を見る