はじめに
Androidってkernelは完全にJavaでラップされていて、開発者が触るレイヤーはJavaの領域だけなのかななんて思っていました。
しかし実はLinux部分もガツガツ触って開発したりするんですね。
中々触る機会はなさそうですが、起動周りに触れたので記録に残しておこうと思います。
Androidの基本構造: Architecture
こんな感じに一番下にLinux kernelがあり、その上にHAL(Hardware abstraction layer)、System services、Binder IPC、Application frameworkと階層が続いています。Application frameworkがJava向けのAPI達で、javaから下位層を扱うの階層をちょっとずつ切ってる感じ。Andoridアプリが触るのはjava層より上の世界ですね。
根っこはLinux。組込の人にはなじみがある…けどちょっと違う
Linux kernel上でAndroidの思想通りに開発してもらえるよう、googleさんがAPI公開や開発ルールを定義しながらOSを公開してるって印象を受けました。
Linux kernel自体も例えばUbuntuやCent OSと同じ動きをするわけではないので、例えば起動の仕組みが独特だったり、udevのような仕組みが使えなかったりします。
udev、フォルダはあるからガンガン使ったろうと思ったのにびくともしなくてビビった(笑)
Androidと起動: init.rcとService
Android ApplicationだとActivity, Service, Applicationそれぞれでライフサイクルが違って、Activityなら最初にonCreateが呼ばれて~といった取り決めがあると思いますが、それ以前のboot後はどう動いているかという話です。
initプロセスから起動されるのですが、起動シーケンスの制御をどうするかというと、rcスクリプトというものを使います。
rcスクリプトの記述仕様はこちら。
以下の2種類の要素で構成されます。
- Actions
- Services
1. Actions
何かのイベントトリガーでの動作を書くことが出来ます。ここで使える動作はこちらのCommandsに記載されているものだけ。
chmodやchownのようなfilesystem構成を作るためのものや、2. Servicesを呼び出すコマンドがあったりします。
記述はこんな感じでon XXX
の後にコマンドをつらつらと書く感じ。実際のinit.rc等を参考にしてください。
#on XXXで定義
on early-init
#こんな感じでコマンドを羅列
chown root system /dev/memcg/memory.pressure_level
chmod 0040 /dev/memcg/memory.pressure_level
#start サービス名で指定したサービスが起動されます。
start ueventd
2. Services
initスクリプトからコマンドをSerivceとして起動することが出来ます。こちらもinit.rcより抜粋。
こんな感じでservice サービス名 コマンド
と記載することで、on XXXのstartトリガーでサービスの起動が出来ます。
サービスの権限や終了後再起動するかといった設定をserviceの下に記載していきます。
service flash_recovery /system/bin/install-recovery.sh
class main
oneshot
serviceを定義する際に重要になるのがSELinux。Android OSはSELinuxを使用していて、正しくSELinuxのルールを定義していなければserviceが起動できません。
正直めんどい
AndroidとSELinux
AndroidのserviceにはSELinuxのルールを適切に定義して上げないと起動できないようになっています。
このSELinuxルールが中々曲者で、ただ自前サービスに合わせて好きに定義すればいいわけではなく、Androidのポリシーに従って正しく定義してあげないといけない。
そうでないとneverallow rulesってのに引っかかり、ビルドすら通らないようになっています。つらい
後は、system配下とvendor配下のルールで出来ることが違ったりするので、やりたいことや構成によってどの配下に置くかを意識しないといけない。これは大変すね
やることは以下。
- SELinuxのpolicy ruleを定義する。
- 実行するコマンドファイルににfile_contextで権限を設定
1についてはこちらのExample policyを見るのが分かりやすいと思います。
typeでpolicyの名前+exec_typeを定義し、allow policy名 許可するルール
をつらつらと書いていきます。
許可するルールの取得方法はこちらに記載されていますね。要約すると
- Reading denials
2.dmesg | grep avc:
で拒否されたログを取得する。 - Switching to permissive
3. ログ取る時はadb shell setenforce 0
等でpermissibeモードにしてね - Using audit2allow
3. ログの結果にselinux/policycoreutils/audit2allowコマンドをかますとルールが取れるよ!
ってところでしょうか。
詳細は参考のURLを参照ください。
2についてはこんな感じでファイルと権限について書かれたfileがあるので、そちらに定義を加えます。
#############################
# Vendor files
#
/vendor(/.*)? u:object_r:system_file:s0
/vendor/bin/gpsd u:object_r:gpsd_exec:s0
参考
-
init.rcによる起動周り
Android起動周りのノウハウ:起動や構造の概要
Android Init Language:Initスクリプトの仕様 -
SELinux
AndroidとSELinux:概要をつかむのに非常に助けられました。
Customizing SELinux:Android側のポリシーがガンガン変わるようで、結局公式が一番役に立ちました。
Policy Compatibility:互換性と設計思想の話
Validating SELinux:SELinuxルール作成方法
Implementing SELinux:実装概要の話