前言
学习笔记
主题
init 进程是 Android 内核启动的第一个进程,其进程号(pid)为1,是 Android 系统所有进程的祖先,因此它肩负着系统启动的重要责任。
Android 的 init 源代码位于 system/core/init/ 目录下,伴随 Android 系统多个版本的迭代,init 源代码也几经重构。目前 Android4.4 源代码中,
init 目录编译后生成如下 Android 系统的三个文件,分别是
/init
/sbin/ueventd-->/init
/sbin/watchdogd-->/init
其中 ueventd 与 wathdogd 均是指向 /init 的软链接。(具体实现请阅读init/Android.mk)。在 Android 系统早期版本(2.2之前)只有 init
进程,Android2.2 中将创建设备驱动节点文件功能独立到 ueventd 进程完成,在 Android4.1 中则添加了 watchdogd。
进程功能:
/init主要完成三大功能:
解析init.rc初始化Android属性系统,并维护属性服务
初始化Android属性系统,并维护属性服务
处理子进程启动、停止、重启动
/ueventd用于创建设备驱动节点。
/watchdogd 是看门狗服务进程。
流程总结:
1. 若通过 ueventd 执行 init 可执行文件,则执行 ueventd_main() (ueventd.c)
2. 创建文件目录,并将它们作为虚拟文件系统的挂载点进行挂载,还有设置启动标志。
3. 改变 log 的输入输出。
4. 函数 property_init() 用户初始化属性区域,该属性区域来自设备文件 /dev/__properties__ 映射(mmap)到 init
进程空间后的虚拟地址。
5. 获得硬件平台型号名称。
6. 通过 process_kernel_cmdline() 将内核启动参数从 /proc/cmdline 中的配置项中读出,并赋值给相应的属性。
7. 若是安全 Linux(SELinux), 则加载安全策略。
8. 判断设备是否处于充电模式。
9. 遍历解析 Android 初始化语言脚本语言文件 init.rc
10. 将 trigger 为 early-init 的 action 添加到待执行队列 action_queue 中
11. 将一些内建的 action 添加到 action_list 列表中和待执行队列中
12. 将 .rc 文件中的 init 部分的 action 添加到待执行队列 action_queue 尾部
13. 在充电模式时,跳过加载文件系统的操作。在正常的非充电模式下,将 .rc 文件中的 early-fs/fs/post-fs/post-fs-data
四个部分的 section 中的 action 添加到待执行队列 action_queue 尾部
14. 将内建的 action 添加到 action_list 和 action_queue 中
property_service_init_action() 主要用于将下面三个宏定义的文件中的属性装载进来
#defind PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#defind PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#defind PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
如果下面的宏定义还有属性文件,亦进行装载(可能覆盖上面的属性值)
#define PERSISTENT_PROPERTY_DIR "/data/property"
最后他创建一个 UNIX 本地 socket,监听属性值的变动
signal_init_action 用于处理子进程(各个 .rc 中的 service 进程)的退出、重启、停止等操作
见 signal_handler.c 中的 wait_for_one_process()
check_startup_action 检查前面的启动是否顺序完成,并删除前面创建的标识文件 /dev/.booting
15. 区分充电械和非充电模式,从而执行不同操作。充电模式是将 .rc 文件中的 charger 部分的 action 添加到
待执行队列尾部,否则添加 early-boot 和 boot 中的 action
16. 将内建的 action 添加到 action_list 和 action_queue 中
queue_property_triggers_action
遍历所有的 action_list 中的所有 action,检查是否有关于
property 的 action,有则添加其到 action_queue 队列中,这意味着 .rc 文件中的关于属性改变
而触发引起的执行操作在这儿都会被触发执行一次
17. 若定义的 BOOTCHART 为真,则添加内置的 bootchart 初始化操作
bootchart_init_action 将调用 bootchart_init() 进行初始化操作,如准备记录文件,获取系统版本信息等
18. 进入无限循环,先执行上面的添加到待执行队列 action_queue 中的各个 action 中的 command,然后检查
是否有 service 需要重启的操作
19. 接着,在无限循环中的中间及后面部分,继续完成 bootchart 操作,记录统计信息,写入文件等。
20. 在无限循环的最后面,用于检查是否有属性值改变而触发 action 的 command 操作,是否有组合键 keychord
写入而启动 service 的操作,是否有 service 等子进程退出需要处理的操作。
解析内核参数
解析内核启动参数,并根据启动参数判断系统类型,将生成 ro.kernel.{name} = {value} 写入 Android 属性系统, 如当
系统以模拟器启动时,启动参数如下:
root@generic:/ # cat /proc/cmdline
qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1
会在代码中分成如下几段,以 import_kernel_nv() 处理:
qemu.gles=0
qemu=1
console=ttyS0
android.qemud=ttyS1
android.checkjni=1
ndns=1
然后根据解析结果通过 property_set() 写入 Android 属性系统中,写入如下内容:
root@generic:/ # getprop | grep ro.kernel.
[ro.kernel.android.checkjni]: [1]
[ro.kernel.android.qemud]: [ttyS1]
[ro.kernel.console]: [ttyS0]
[ro.kernel.ndns]: [1]
[ro.kernel.qemu.gles]: [0]
[ro.kernel.qemu]: [1]
函数最后通过 export_kernel_boot_props() 读取 Android 属性系统,根据 ro.boot.xxx 来设置系统属性
其代码不在详细描述。该函数用于设置几个系统属性,具体包括如下:
读取ro.boot.serialno,若存在其值写入ro.serialno,否则ro.serialno写入空。
读取ro.boot.mode,若存在其值写入ro.bootmode,否则ro.bootmode写入"unkown"
读取ro.boot.baseband,若存在其值写入ro.baseband,否则ro.baseband写入"unkown"
读取ro.boot.bootloader,若存在其值写入ro.bootloader,否则ro.bootloader写入"unkown"
读取ro.boot.console,若存在,其值写入全局缓冲区console中
读取ro.bootmode,若存在,其值保存到全局缓冲区bootmode中
读取ro.boot.hardware,若存在其值写入ro.hardware,否则将/proc/cmdline中解析出来的hardware写入ro.hardware中。
解析配置文件 init.rc 系列文件
init.rc是一个文本文件,可认为它是Android系统启动脚本。init.rc文件中定义了环境变量配置、系统进程启动,分区挂载,属性配置
等诸多内容。init.rc具有特殊的语法。init源码目录下的readme.txt中详细的描述了init启动脚本的语法规则,是试图定制 init.rc 的开发
者的必读资料。
Android启动脚本包括一组文件,包括:
init.rc
init.usb.rc
init.trace.rc
init.{hardware}.rc
init.environ.rc
init.zygote32.rc
这些文件可能分布于如下目录中:
system/core/rootdir
device/{vendor}/{hardware}/
除 init.rc 外,其他文件都由 init.rc 中以 import 语句导入,一般来说 init.rc 文件存放通用配置,而其他特定模块以及特定硬件的配置
则放置在独立的文件中,这样设计可以使 init.rc 脚本简洁,方便系统维护和升级。
一个简单的init.rc语法如下。(基于system/core/rootdir/init.rc裁剪):
# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
on early-init
start ueventd
on init
sysclktz 0
loglevel 3
mkdir /system
mkdir /data 0771 system system
write /proc/sys/kernel/panic_on_oops 1
# Load properties from /system/ + /factory after fs mount.
on load_all_props_action
load_all_props
on post-fs
# once everything is setup, no need to modify /
mount rootfs rootfs / ro remount
on boot
# basic network init
ifup lo
class_start core
class_start main
on property:vold.decrypt=trigger_reset_main
class_reset main
service ueventd /sbin/ueventd
class core
critical
on property:ro.debuggable=1
start console
service debuggerd /system/bin/debuggerd
class main
service bootanim /system/bin/bootanimation
class main
user graphics
group graphics
disabled
oneshot
为了行文方便,下文提及init.rc,通常泛指Android启动脚本。
init.rc 可以定义两类结构:Actions 与 Services
【Actions】
Actions是一组命令的集合,定义一个Actions如下,每个Actions都可以定义一个触发器(trigger),Actions格式如下:
on <trigger>
<command> //其中<command>类似于shell命令,command对应一个函数,通常执行一条动作,例如创建一个文件夹等。
<command>
<command>
触发器(Triggers):
触发器只是一段字符串罢了,PS:在android/system/core/rootdir/下执行
grep -h "^on" --include="*.rc" -r .
可以当前init启动脚本所含有的trigger,如下
on early-init
on init
on property:sys.boot_from_charger_mode=1
on load_all_props_action
on firmware_mounts_complete
on late-init
on post-fs
on post-fs-data
on boot
on nonencrypted
on property:sys.init_log_level=*
on charger
on property:vold.decrypt=trigger_reset_main
on property:vold.decrypt=trigger_load_persist_props
on property:vold.decrypt=trigger_post_fs_data
on property:vold.decrypt=trigger_restart_min_framework
on property:vold.decrypt=trigger_restart_framework
on property:vold.decrypt=trigger_shutdown_framework
on property:sys.powerctl=*
on property:sys.sysctl.extra_free_kbytes=*
on property:sys.sysctl.tcp_def_init_rwnd=*
on property:ro.debuggable=1
on property:ro.kernel.qemu=1
on boot
on post-fs-data
on property:sys.usb.config=none
on property:sys.usb.config=adb
on property:sys.usb.config=accessory
on property:sys.usb.config=accessory,adb
on property:sys.usb.config=audio_source
on property:sys.usb.config=audio_source,adb
on property:sys.usb.config=accessory,audio_source
on property:sys.usb.config=accessory,audio_source,adb
on property:persist.sys.usb.config=*
根据 trigger 的不同,可以将 Actions 大致分为两类:
(1) 普通型
这类trigger的的作用仅仅是用于给一个Actions命名,方便查找和引用。如 early-init、init、late-init、early-fs、fs、post-fs、
post-fs-data、early-boot、boot、charger等, 一般来说,这类 Actions 将在 Android 启动时执行,其 trigger 暗示了执行对应 Actions 执
行的时机。具体的执行流程将在本文最后介绍。
此外,根据 readme.txt 描述,还有其他几种 trigger,但在 init.rc 以及 init 源代码中却没有找到相关代码,如下所示,
device-added-<path> // 设备节点被添加或移除时调用,。
device-removed-<path>
service-exited-<name>
这类trigger将在某 service 退出时执行。关于什么是 service 稍后介绍。
(2) 属性型
其 trigger 为 property:<name>=<value>
其 trigger 不仅唯一标识了这个 Actions,同时也设定了这类 Actons 执行的条件,当 property <name> 的值为 <value> 时才会被执行。
Commands:
command的格式如下:
command-name <parament1> [parament2...] // <>表示必须存在的参数,[]表示可选参数
说明:readme.txt中虽然有大部分commands的介绍,但并不完整。init.rc 中所有 commands 都在 keywords.h 中定义,可使用如下命令提取。
sed -n "s/KEYWORD([,]\+,[ \t]\+COMMAND.*/\1/p" keywords.h // 增加用于补全高亮"/*
目前 Android4.4 支持的 Commands 如下:Keywords.h (s:\i841\system\core\init)
chdir <direcotory> 改变工作目录
chroot <directory> 改变当前进程的root目录
class_start <serviceclass> 如果serviceclass内所有services尚未启动,则启动它
class_stop <serviceclass> 停止serviceclass内所有services
class_reset <serviceclass> 重启serviceclass内所有services
domainname <name> 设置domain名称
enable <servicename>
exec <path> [ <argument> ]* fork后执行path所执行的程序,该语句会阻塞直到path指定的程序执行完毕。
export <name> <value> 设置全局环境变量,将会被该命令后所启动的进程继承。
hostname <name> 设置主机名
ifup <interface> 启动interface所指定的网络接口
insmod <path> 安装path所指定的内核模块
mkdir <path> [mode] [owner] [group] 创建path制定的目录,并根据可选的mode、owner、group设定参数。如果未指定可选参数,则创建的文件夹权限将会设置为0755,而owner与group都为root
mount_all
mount <type> <device> <dir> [ <mountoption] *
powerctl
restart <service>
restorecon
restorecon_recursive
rm <path> 删除path指定的文件
rmdir <path> 删除path指定的目录(目录为空才能删除)
setcon
setenforce
setkey
setprop
setrlimit
setsebool
start <service>
stop <service>
swapon_all
trigger
symlink
sysclktz
wait
write
copy
chown
chmod
loglevel
load_persist_props
load_all_props
【Actions】
这里的 service 仅仅是 init.rc 中的概念,与通常意义上的“服务”概念无关。一个 Service 对应一个可执行程序,并且可以设定该程序
的一些执行性质,如仅仅执行一次、或退出时自动重启。当service所代表的可执行程序在退出时自动重启时,该service通常意味着这是一个守护进程。
service 字段由如下格式定义:
service <name> <pathname> [ <argument> ]* // <name>字段为service的名字
// <pathname>为该service对应的二进制程序的路径,随后是该程序的参数列表
<option> // <option>是该service的属性
<option>
...
目前 Android4.4 中所支持的 option 如下所示(ps,由 sed -n "s/KEYWORD([,]\+,[ \t]\+OPTION.*/\1/p" keywords.h 命令生成) // 增加用于补全高亮"/*
文件路径: system/core/init/keywords.h
capability
class <name> 设定service的class
console
critical 说明该服务是个对于设备很关键的服务,如果4分钟内退出大于4次,则系统将重启并进入recovery恢复模式
disabled 该服务不能通过启动一类服务来启动,比如 class_start core来启动,只能以单独的名字来启动 start adbd.
group <groupname> [<groupname>]* 设定进程
keycodes 用于特殊按键启动服务,一般调试用
oneshot 该服务只启动一次,退出后不再运行
onrestart 当 service 重启时,自动执行后面的命令
seclabeli
setenv
socket 语法:socket <name> <type> <perm> <user> <group>, 创建一个名字为vold<name>,类别为stream<type> //访问权限为0660<perm> 用户为root,用户组为mount
user <username>
ioprio
为了方便管理多个 service,可为 service 设定 class 属性,具有同样 class 的多个 service 构成一个组,可以在 Actions 中通过
class_start、class_stop、class_reset等命令启动、停止、重启动。
【此外init.rc中还有其他规则】
以#号开头的行为注释行
import语句导入其他init脚本文件,
\可用于转义换行符
空格与Tab字符都可用作空白字符
Android中的实现非常的简洁。
【解析设计思路】如下:
1. 引入 section 的概念。一个 Actions、Service、import 是一个section,分别实现不同的 section 解析代码。
2. 基于行解析,行解析函数与当前所在的 section 有关,使用函数指针实现。
3. 利用空白字符(一个或多个空行)实现分词,当检测到新的一行时,识别关键词为 on、service、import,若是则认为一个 section 开
始了,同时也意味着上一个 section 终结了。import使用递归实现
4. 每个 Actions 创建一个 struct action 数据结构,每个 command 创建一个 struct command 数据结构,action 中有一个 command 的链表;
每个 service 创建一个 struct service 数据结构。
5. 创建全局 Actions 链表,将识别到 Actions 都加入全局链表中,创建全局 Service 链表,将识别到的 Service 加入到全局链表中
【数据关系图】全局数据结构组合关系类似如下图:
【actions 表】
struct action ---> ... ---> struct action
| // struct action : 代表 init.rc 中的一个 Actions
|- struct command // struct command: 代表 init.rc 中 Actions 下的命令
|- struct command // struct service: 代表 init.rc 中的一个 Service
...
【service 表】
struct service ---> .... ---> struct service
【执行思路】如下:
创建一个额外的链表充当 Actions 执行队列 【action-queue】。将一些 Actions 链表加入到执行队列中。
然后一次从队列中取出一个 Actions,依次执行其中每一条 command,当该 Actions 执行完毕后则从
actions-queue 取出下一个 Actions 继续执行。
有关 service start 的流程:
(1)init_parse_config_file("/init.rc") 读取并分析 init.rc 文件,将里面的 action 和 service 分别添加到 action_list 和 service_list
(2)调用 action_for_each_trigger,queue_builtin_action,直接或间接调用 action_add_queue_tail 将 action_list 里面存在的 action 和单
独指定的"builtin action” 添加到action queue。
(3)在 for 循环里面调用 execute_one_command 执行 action_queue 里面添加的各个 action 里面的各个 command,当然也包括 "on" section 对
应的 "boot "action。
(4)"boot "action 里面的 class start 这个 command 的执行,其实对应的就是 do_class_start 函数执行,最终 service_start() 被调用来启动各个 service。