Android init介绍(上)
1. 介绍
init进程是Linux系统第一个用户进程,是Android系统应用程序的根进程,即1号进程(PID为1);Android中的init文件位于/init,代码位于system/core/init目录
Linux中第一个进程为init_task,也即0号进程(PID为0),init进程由init_task进程fork而来,在kernel初始化完成后init_task便化身为idle进程
更多内核初始化init_task和init进程的信息,参考<Android 8.0 : 系统启动流程之Linux内核>
首先说明一下,笔者的代码分析基于Android 9.0
-------------------------------------------------------------------------------
| 镜像 | 内容 | 挂载点 | 加载方式 |
-------------------------------------------------------------------------------
| ramdisk.img | $(OUT)/root | / | 内核加载 |
| ramdisk-recovery.img | $(OUT)/recovery/root | / | |
| boot.img | $(OUT)/kernel + ramdisk.img | | |
| recovery.img | ramdisk-recovery.img | | |
| system.img | $(OUT)/system | /system | init加载 |
| userdata.img | $(OUT)/data | /data | init加载 |
-------------------------------------------------------------------------------
2. 云竹
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
} if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
} if (argc > 1 && !strcmp(argv[1], "subcontext")) {
InitKernelLogging(argv);
const BuiltinFunctionMap function_map;
return SubcontextMain(argc, argv, &function_map);
} if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
2.1 ueventd和watchdogd
在Android.mk中
LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \
ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd
这里是在/sbin/目录下创建init的软连接,因为ueventd和watchdogd应用的代码也位于init目录中,通过程序名称来决定运行的代码
而在system/core/rootdir/init.rc文件中
on early-init
...
start ueventd
...
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
由此可见,ueventd会在init解析rc文件的early-init阶段被执行;而watchdogd由厂商来定制是否要运行
2.2 信号处理
当编译userdebug或者eng版本时,会在Android.mk中打开REBOOT_BOOTLOADER_ON_PANIC选项,该选项打开时会注册init进程的特殊信号处理函数,当init收到SIGABRT、SIGBUS、SIGSEGV等异常信号时Android系统将进入bootloader模式
3. 第一阶段
这里简单介绍下Android Device Tree(DT)
// CASE 1: 默认路径
/proc/device-tree/firmware/android // CASE 2: 内核命令行/proc/cmdline中定义
// androidboot.android_dt_dir=/sys/bus/platform/devices/ANDR0001:00/properties/android/
/sys/bus/platform/devices/ANDR0001:00/properties/android/
DT中定义了Android中初始化中需要的一些参数, 笔者该目录内容如下
# tree /sys/bus/platform/devices/ANDR0001:00/properties/android/
.
|---compatible // "android,firmware"
|---fstab
| |---compatible // "android,fstab"
| |---product
| | |---compatible // "android,product"
| | |---dev // "/dev/block/pci/pci0000:00/0000:00:1c.0/by-name/product"
| | |---fsmgr_flags // "wait,slotselect,avb"
| | |---mnt_flags // "ro"
| | |---type // "ext4"
| |---vendor
| | |---compatible // "android,vendor"
| | |---dev // "/dev/block/pci/pci0000:00/0000:00:1c.0/by-name/vendor"
| | |---fsmgr_flags // "wait,slotselect,avb"
| | |---mnt_flags // "ro"
| | |---type // "ext4"
|---vbmeta
| |---compatible // "android,vbmeta"
| |---parts // "vbmeta,boot,system,vendor,tos,product"
3.1 挂载文件系统
首先挂载了如下文件系统
----------------------------------------------
| 设备 | 类型 | 挂载目录 |
----------------------------------------------
| tmpfs | tmpfs | /dev |
| devpts | devpts | /dev/pts |
| proc | proc | /proc |
| sysfs | sysfs | /sys |
| selinuxfs | selinuxfs | /sys/fs/selinux |
| tmpfs | tmpfs | /mnt |
----------------------------------------------
创建如下文件夹
/dev/socket: 用于Android套接字
创建如下字符设备文件
/dev/kmsg: 1/11
/dev/kmsg_debug: 1/11
/dev/random: 1/8
/dev/urandom: 1/9
3.2 日志初始化
InitKernelLogging(argv)
// FIXME: 将[标准输入/标准输出/错误输出]重定向到/dev/null
open("/sys/fs/selinux/null", O_RDWR);
dup2(fd, 0/1/2); // 设置日志输出函数, 然后获取环境变量ANDROID_LOG_TAGS并解析从而设置最小输出等级
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter)
// 将日志写入/dev/kmsg
android::base::KernelLogger
3.3 挂载分区
DoFirstStageMount()
// 检测DT的fstab配置是否支持, 如果支持则直接跳过挂载
android::init::is_android_dt_value_expected("fstab/compatible", "android,fstab")
// 获取Android DT(设备树)并获取$(DT)/fstab/compatible的值, 然后与android,fstab比较
android::init::read_android_dt_file("fstab/compatible", &dt_content) // 检测DT的vbmeta配置, 如果支持则使用FirstStageMountVBootV2, 否则使用FirstStageMountVBootV1
android::init::FirstStageMount::Create()
android::init::FirstStageMount::FirstStageMount()
// 获取$(DT)/fstab/目录定义的分区及属性, 笔者当前包含product和vendor
fs_mgr_read_fstab_dt()
read_fstab_from_dt()
fs_mgr_read_fstab_file()
/*
* 读取fstab
* - 首先尝试$(DT)/boot_devices
* - 不成功则依次尝试
* /odm/etc/fstab.$(platform)
* /vendor/etc/fstab.$(platform)
* /fstab.$(platform)
*/
fs_mgr_get_boot_devices()
// 获取$(DT)/vbmeta/parts的值, 当前为: vbmeta,boot,system,vendor,tos,product
android::init::FirstStageMountVBootV2::FirstStageMountVBootV2() // 挂载fastb中的分区列表
android::init::FirstStageMount::DoFirstStageMount()
android::init::FirstStageMount::InitDevices()
android::init::FirstStageMount::GetRequiredDevices()
android::init::FirstStageMount::InitRequiredDevices()
// 依次挂载各分区
android::init::FirstStageMount::MountPartitions()
android::init::FirstStageMount::SetUpDmVerity()
fs_mgr_do_mount_one()
3.4 AVB初始化
SetInitAvbVersionInRecovery()
// 如果不是Revery模式则直接返回
IsRecoveryMode()
// 如果不兼容vbmeta则直接返回
IsDtVbmetaCompatible()
android::init::FirstStageMountVBootV2()
// 获取vbmeta/parts对应的分区
read_android_dt_file("vbmeta/parts", &device_tree_vbmeta_parts_)
// 初始化设备
android::init::FirstStageMount::InitDevices()
// 获取AVB Handler
FsManagerAvbHandle::Open(FirstStageMountVBootV2::by_name_symlink_map_)
// 设置环境变量INIT_AVB_VERSION
setenv("INIT_AVB_VERSION", avb_handle->avb_version().c_str(), 1)
AVB(Android Verified Boot),主要用于防止系统文件本身被篡改,还包含了防止系统回滚的功能
3.5 seccomp初始化
// 此处决定是否使能全局seccomp; 如果没有使能, zygote也会使能
// 读取/proc/cmdline内容, 如果包含androidboot.seccomp=global则调用set_global_seccomp_filter
// set_global_seccomp_filter位于bionic/libc/seccomp/seccomp_policy.cpp
global_seccomp()
3.6 SELinux初始化
// 将selinux日志重定向到内核日志输出, 即/dev/kmsg
SelinuxSetupKernelLogging() SelinuxInitialize()
/*
* 加载SELinux策略
* 如果IsSplitPolicyDevice返回true, 执行LoadSplitPolicy, 否则执行LoadMonolithicPolicy
*/
android::init::LoadPolicy()
// 检测/system/etc/selinux/plat_sepolicy.cil文件是否存在
android::init::IsSplitPolicyDevice()
android::init::LoadSplitPolicy()
/*
* 依次查找以下预编译SELinux文件
* "/vendor/etc/selinux/precompiled_sepolicy"
* "/odm/etc/selinux/precompiled_sepolicy"
*/
android::init::FindPrecompiledSplitPolicy(&file)
open(file, O_RDONLY | O_CLOEXEC | O_BINARY)
selinux_android_load_policy_from_fd(fd, file)
set_selinuxmnt("/sys/fs/selinux")
mmap(NULL, , PROT_READ, MAP_PRIVATE, fd, 0)
// 将策略文件写入到 /sys/fs/selinux/load
security_load_policy(,)
// 从文件/sepolicy加载策略
android::init::LoadMonolithicPolicy()
selinux_android_load_policy()
open("/sepolicy", O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
selinux_android_load_policy_from_fd(fd, "/sepolicy") /*
* 设置selinux状态
* 如果内核selinux状态与自定义selinux状态进行比较, 最终以自定义selinux状态为准
*/
// 获取内核selinux状态
security_getenforce()
// 获取Android自定义selinux状态
android::init::IsEnforcing()
// 如果未定义ALLOW_PERMISSIVE_SELINUX, 直接返回Enforcing, 否则才调用StatusFromCmdline
// 读取/proc/cmdline内容, 如果包含androidboot.selinux=permissive则返回Permissive
android::init::StatusFromCmdline()
// 设置当前selinux状态, 其实是将0/1写入/sys/fs/selinux/enforce
security_setenforce() // 设置checkreqprot为0, 该值决定SELinux通过程序(1)还是通过内核(0)响应进行安全检查
WriteFile("/sys/fs/selinux/checkreqprot", "0") // 获取SELinux策略加载时间并设置到设置环境变量INIT_SELINUX_TOOK
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1); // 将init文件的Context恢复成file_contexts中的初始配置
selinux_android_restorecon("/init", 0)
SEAndroid整体架构图如下图所示
3.7 第二阶段准备
// 将环境变量INIT_SECOND_STAGE设置为1, 准备进入第二阶段
setenv("INIT_SECOND_STAGE", "true", 1); // 设置环境变量INIT_STARTED_AT
setenv("INIT_STARTED_AT", start_ms, 1); // 重新执行/init
execv("/init", { /init, nullptr });
4. 第二阶段
4.1 创建密钥环
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
关于密钥环,参考:
<Linux密钥保留服务入门>
<Kernel Key Retention Service>
4.2 启动准备
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
创建/dev/.booting文件,用于标识当前正在启动中,当firmware_mounts_complete被触发时删除
4.3 Property服务初始化
property_init()
// 创建目录: O/RWX、G/X、O/X
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH) // 创建序列化的System Property
CreateSerializedPropertyInfo()
// 读取System Property并加载到PropertyInfoEntry
LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts", &e)
LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts", &e)
LoadPropertyInfoFromFile(/vendor/etc/selinux/nonplat_property_contexts", &e)
// 将加载的System Property序列化
BuildTrie(e, "u:object_r:default_prop:s0", "string", &s)
// 将序列化的System Property写入文件
WriteStringToFile(s, "/dev/__properties__/property_info",,,,)
// 恢复property_info文件的Context
selinux_android_restorecon("/dev/__properties__/property_info", 0) // 初始化System Property内存区域
__system_property_area_init()
SystemProperties::AreaInit("/dev/__properties__", false)
ContextsSerialized::Initialize(true, "/dev/__properties__", false)
ContextsSerialized::InitializeProperties()
// 加载System Property默认路径
android::properties::PropertyInfoAreaFile::LoadDefaultPath()
// 将文件加载到内存
LoadPath("/dev/__properties__/property_info")
// 初始化ContextNode
ContextsSerialized::InitializeContextNodes()
// 获取ContextNode数量
android::properties::PropertyInfoAreaFile::num_contexts()
// 创建num_contexts * ContextNode大小共享内核
mmap(, , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS , -1, 0)
// 创建ContextNode
ContextNode(PropertyInfoAreaFile::context(i), "/dev/__properties__")
// 将所有property对应的Security Context写入文件
ContextNode::Open(true, false)
// 将property序列化并保持至/dev/__properties/properties_serial
ContextsSerialized::MapSerialPropertyArea(true, false)
以下是初始化完成后/dev/__properties__目录文件列表
# ls -al /dev/__properties__
drwx--x--x 2 root root 2360 2011-11-11 19:11 .
drwxr-xr-x 18 root root 4820 2011-11-11 19:11 ..
-r--r--r-- 1 root root 131072 2011-11-11 19:11 properties_serial
-r--r--r-- 1 root root 29660 2011-11-11 19:11 property_info
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:audio_prop:s0
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:bluetooth_prop:s0
...
-r--r--r-- 1 root root 131072 2019-10-01 08:00 u:object_r:system_prop:s0
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:system_radio_prop:s0
...
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:vendor_default_prop:s0
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:vendor_persist_prop:s0
...
-r--r--r-- 1 root root 131072 2011-11-11 19:11 u:object_r:wifi_prop:s0
4.4 处理设备树和命令行
/*
* DT中的属性优先级高于内核命令行的属性
*/
// 处理Android设备树中定义的属性
process_kernel_dt();
// 检测是否兼容Android, 否则直接返回
is_android_dt_value_expected("compatible", "android,firmware") // 读取/proc/cmdline, 将androidboot.name=value的键值对保存为ro.boot.name=value属性
process_kernel_cmdline();
import_kernel_cmdline(false, )
import_kernel_nv(key, value)
property_set("ro.boot." + key.substr(12), value)
4.5 RO属性相关
/*
* 将部分ro.boot.*属性转换为ro.*属性, 前者值不存在则设置默认值
* { "ro.boot.serialno", "ro.serialno", "", },
* { "ro.boot.mode", "ro.bootmode", "unknown", },
* { "ro.boot.baseband", "ro.baseband", "unknown", },
* { "ro.boot.bootloader", "ro.bootloader", "unknown", },
* { "ro.boot.hardware", "ro.hardware", "unknown", },
* { "ro.boot.revision", "ro.revision", "0", },
*/
export_kernel_boot_props() // 将第一阶段设置的环境变量设置为property属性
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
property_set("ro.boot.avb_version", getenv("INIT_AVB_VERSION")) // 清空环境变量
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
4.6 SELinux再次初始化
// 将selinux日志重定向到内核日志输出, 即/dev/kmsg
SelinuxSetupKernelLogging(); // FIXME:
SelabelInitialize();
selinux_android_file_context_handle()
selinux_android_set_sehandle() // 恢复file_contexts中的初始配置
SelinuxRestoreContext();
selinux_android_restorecon("/dev", 0);
selinux_android_restorecon("/dev/kmsg", 0);
...
4.7 EPOLL实例
// 创建EPOLL
epoll_create1(EPOLL_CLOEXEC) /*
* 创建一个相互连接的套接字对
* 接收SIGCHLD信号时往其中一个套接字写
* 另一个套接字的读则注册到EPOLL中
*/
sigchld_handler_init()
socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s)
act.sa_handler = SIGCHLD_handler;
|-> write(signal_write_fd, "1", 1)
sigaction(SIGCHLD, &act, 0);
register_epoll_handler(signal_read_fd, handle_signal);
read(signal_read_fd, buf, sizeof(buf)) <-|
ReapAnyOutstandingChildren() <-| /*
* 添加SIGTERM信号处理函数, 并注册到EPOLL中
*/
InstallSigtermHandler()
sigaddset(&mask, SIGTERM);
sigterm_signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
register_epoll_handler(sigterm_signal_fd, HandleSigtermSignal);
read(sigterm_signal_fd, &, ) <-|
HandlePowerctlMessage("shutdown,container") <-|
EPOLL的使用方法如下:
- epoll_create/epoll_create1创建一个EPOLL实例
- epoll_ctl向EPOLL实例注册/修改/删除监听事件
- epoll_wait等待事件发生
- close关闭EPOLL实例
4.8 Property相关服务
// 加载系统启动属性
property_load_boot_defaults();
load_properties_from_file("/system/etc/prop.default", NULL)
load_properties_from_file("/product/build.prop", NULL)
load_properties_from_file("/odm/default.prop", NULL)
load_properties_from_file("/vendor/default.prop", NULL)
ReadFile()
LoadProperties()
// 将property键值对设置到系统中
HandlePropertySet(key, value, ,)
// 设置persist.sys.usb.config属性值
update_sys_usb_config() /*
* OEM Lock和AVB相关, 涉及如下属性
* ro.oem_unlock_supported
* ro.boot.verifiedbootstate
* ro.boot.flash.locked
*/
export_oem_lock_status(); // 启动属性系统服务
start_property_service();
// 创建socket, 用于监听写系统属性的请求
CreateSocket("property_service")
lisent(fd, 8)
// 注册到EPOLL中
register_epoll_handler(fd, handle_property_set_fd)
HandlePropertySet(key, value)<-| // 设置sys.usb.controller属性值
set_usb_controller();
下一篇请参考<Android init介绍(下)>
Android init介绍(上)的更多相关文章
- Android init介绍(下)
上一篇请参考<Android init介绍(上)> 5. AIL 在init启动过程中,系统服务等均是通过解析rc文件来启动,而rc文件则是由Android初始化语言(Android In ...
- Android bluetooth介绍(两): android 蓝牙源架构和uart 至rfcomm过程
关键词:蓝牙blueZ UART HCI_UART H4 HCI L2CAP RFCOMM 版本号:基于android4.2先前版本 bluez内核:linux/linux3.08系统:an ...
- android Animation介绍
Animation介绍: 在Android SDK介绍了2种Animation模式: 1. Tween Animation:间动画,通过对场景里的对象不断做图像变换(平移.缩放.旋转)产生动画效果,即 ...
- android init进程分析 ueventd
转自:http://blog.csdn.net/freshui/article/details/2132299 (懒人最近想起我还有csdn好久没打理了,这个Android init躺在我的草稿箱中快 ...
- 开源框架】Android之史上最全最简单最有用的第三方开源库收集整理,有助于快速开发
[原][开源框架]Android之史上最全最简单最有用的第三方开源库收集整理,有助于快速开发,欢迎各位... 时间 2015-01-05 10:08:18 我是程序猿,我为自己代言 原文 http: ...
- Android monkey介绍
Android monkey介绍 原文地址 1 简略 monkey是android下自动化测试比较重要的的一个工具,该工具可以运行在host端或者设备(模拟器或真实设备).它会向系统发送随机事件流(即 ...
- Android init.rc文件格式解析
/***************************************************************************** * Android init.rc文件格式 ...
- [Learn Android Studio 汉化教程]第一章 : Android Studio 介绍
注:为了看上去比较清晰这里只转载了中文 原地址: [Learn Android Studio 汉化教程]第一章 : Android Studio 介绍 本章将引导您完成安装和设置开发环境,然后你就可 ...
- 菜鸟Android之路(上)
自己为什么要学android 本人作为应届毕业生,自己进入社会前做过好多梦,可是呢,现实还是打败了无邪!!面对社会的压力和残酷的竞争力自己如何生成下去??我自己对自己说:第一步 先养活自己,才能走好以 ...
随机推荐
- C#使用cmd运行命令并返回控制台输出信息
public static string RunCmd(string cmd){ cmd = cmd.Trim().TrimEnd('&') + "&exit";/ ...
- jq同一页面内容切换
$(function() { //选择标题显示 初始显示内容及样式 $('.right-content .right-item').eq(0).addClass('showcontent') $('. ...
- 引用类型前需要加ref?
方法的参数前加ref代表的是传的参数的地址,值类型前加ref的作用相当于把这个值类型当成引用类型在用,那引用类型作为参数有一种情况也需要加ref,不然得到的值会有差. 不加ref: class Pro ...
- jquery+css 点赞喜欢特效
百度盘链接 https://pan.baidu.com/s/1Nu8fiUrdffsNd6usTsUESg 密码 mps4 效果:
- redis 设置自启动
redis 设置自启动 1.创建服务(redis.conf 配置文件要注意,经过cp产生了很多个redis.conf) vim /lib/systemd/system/redis.service [U ...
- 关于nslookup以及dig命令的研究报告
我们在日常上网时都是用域名访问网路,如www.baidu.com,而在实际寻址过程中,是使用IP地址,如180.101.49.11,域名到IP地址的解析是通过DNS服务器来实现的,系统中我们可以用一些 ...
- golang开发:环境篇(六) Go运行监控Supervisord的使用
为什么要使用Supervisord 17年第一次写Go项目的时候,用Go开发项目倒没没费多大劲,很快就开发完成了.到了在测试环境部署的时候,由于不知道有 Supervisord 这个软件,着实花了些功 ...
- Unity 代码提示符和UGUI屏幕自适应
[Header]("提示字符") Canvas Scaler 屏幕自适应
- docker 命令(我使用过的)
是否安装docker: docker version 启动docker: service docker start 查看本机可用镜像: docker images 删除镜像: doc ...
- HttpClient参观记:.net core 2.2 对HttpClient到底做了神马
.net core 于 10月17日发布了 ASP.NET Core 2.2.0 -preview3,在这个版本中,我看到了一个很让我惊喜的新特性:HTTP Client Performance Im ...