Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲SecondStageMain阶段
(若分析有误敬请指教)
一. Android系统启动基本介绍
在基于Android13的系统启动流程分析(三)之FirstStageMain阶段已经讲解过android系统启动的基本介绍了,这里不再单独介绍了
二. SecondStageMain源码分析
我们先看是怎么进入该阶段的,仍然是由用户空间层main.cpp调用,先简单的说一下第二阶段主要是干什么的:
- 设置init进程优先级并创建
/dev/.booting
设备块代表init正在初始化执行中 - 初始化属性服务,也就是会读取property_contexts文件内容以及读取build.prop内容通过MMAP映射到全局内存中,也就是对所有进程共享该资源
- 启动属性服务并创建socket_service,等待新链接去更新或新增属性值
- 挂载/apex,vendor_overlay等其他分区
- 检查设备是否被unlock解锁
- 持续监控/proc/mounts设备文件,解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等。将解析内容生成实体类追加到要挂载的mounts_中并进行挂载
- 将根目录下所有的目录设置为全局共享,例如对/data设置为根目录下的全局共享
- 解析init.rc以及其他import了的rc文件,主要解析rc中的:service,on(action),Import,而zygote进程正是从解析rc文件中创建的,然后根据zygote(本质上就是一个socket),通过JNI调用到上层代码,再fork出systemServer.java
- 让init进程无限循环,因为主进程不能退出,退出即代表发生异常
- 处理sm(ServiceList)中服务超时重启相关(init.rc中的service),若rc中启动的服务启动超时则会让其服务重新启动
...if(argc >1){...if(!strcmp(argv[1],"second_stage")){returnSecondStageMain(argc, argv);//第二阶段执行}}returnFirstStageMain(argc, argv);//第一阶段执行}
1. int SecondStageMain(int argc, char** argv)分析
位于
/system/core/init/init.cpp
,直接上代码,代码注释中分步骤来分析
intSecondStageMain(int argc,char** argv){if(REBOOT_BOOTLOADER_ON_PANIC){// 针对产生异常的进程进行信号处理,确保子进程能重启,如果主进程pid=1发生异常则触发crash// 已经在上个文章分析过该函数InstallRebootSignalHandlers();}...// 初始化kernel log,所有的kernel log均输出在/dev/kmsg设备节点上SetStdioToDevNull(argv);InitKernelLogging(argv);LOG(INFO)<<"init second stage started!";...// Init不应该因为依赖于任何其他进程而崩溃,因此我们忽略主进程的信号管道信息// 但我们不想忽略子进程的SIGPIPE(信号管道),因此我们为信号处理程序设置了一个no op函数// SIGPIPE信号产生的场景举例// ① 初始时,C、S连接建立,若某一时刻,C端进程宕机或者被KILL而终止(终止的C端进程将会关闭打开的文件描述符,即向S端发送FIN段),S端收到FIN后,响应ACK// ② 假设此时,S端仍然向C端发送数据:当第一次写数据后,S端将会收到RST分节; 当收到RST分节后,第二次写数据后,S端将收到SIGPIPE信号(S端进程被终止){structsigaction action ={.sa_flags = SA_RESTART};
action.sa_handler =[](int){};// sigaction是一个函数,可以用来查询或设置信号处理方式sigaction(SIGPIPE,&action,nullptr);}// MIN_OOM_SCORE_ADJUST = -1000;// MAX_OOM_SCORE_ADJUST = 1000;// 设置进程的优先级,例如APK优先级是AMS计算出来并下发到/proc/1/oom_score_adj// 统一由init进程设置/proc/**/oom_score_adj为-1000优先级if(auto result =WriteFile("/proc/1/oom_score_adj",StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));!result.ok()){LOG(ERROR)<<"Unable to write "<< DEFAULT_OOM_SCORE_ADJUST
<<" to /proc/1/oom_score_adj: "<< result.error();}...// 创建 /dev/.booting 文件,就是个标记,表示booting进行中// is_booting()函数会依靠空文件".booting"来判断是否进程处于初始化中,初始化结束后,这个文件会被删除close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC,0000));// 当设备解锁时,允许adb root// 如果设备unlocked(解锁了),则会修改selinux规则,放大用户权限,这一点在第一阶段已经完成了// 并设置了INIT_FORCE_DEBUGGABLE环境变量,这里只是根据环境变量获取第一阶段的内容constchar* force_debuggable_env =getenv("INIT_FORCE_DEBUGGABLE");bool load_debug_prop =false;if(force_debuggable_env &&AvbHandle::IsDeviceUnlocked()){
load_debug_prop ="true"s == force_debuggable_env;}unsetenv("INIT_FORCE_DEBUGGABLE");// 如果设备未unlock,则卸载关于debug版本的/debug_ramdisk// 让属性值读取/ramdisk而不是/debug_ramdisk,因为非unlock,不需要debug ramdiskif(!load_debug_prop){// setup 1UmountDebugRamdisk();}// 初始化属性服务// 获取system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,等其他build.prop并加载到properties map结构中// 然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值// 注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖// setup 2PropertyInit();// Umount second stage resources after property service has read the .prop files.// 在属性服务读取.prop文件后,将卸载/second_stage_resources,因为已经用不到了,已经将属性值加载到内存当中了UmountSecondStageRes();// Umount the debug ramdisk after property service has read the .prop files when it means to.// 若是debug版本,已经获取了属性值过后,也将卸载/debug_ramdiskif(load_debug_prop){UmountDebugRamdisk();}// 挂载第二阶段(该阶段)的文件系统,第一阶段已经挂载了很多基本的文件系统了以及重要的分区// 挂载/apex:简单点说apex为了解决性能而产生的机制,APK可以通过内置升级,但系统升级可是个大问题// setup 3MountExtraFilesystems();...// 之前初始化了属性服务,这里将开始属性服务,其实它就是一个socket// 创建socket,处理客户端发来的请求,决定是更新属性值还是新增属性值// setup 4StartPropertyService(&property_fd);...// 根据ro.vndk.version 版本号,将/system/vendor_overlay和/product/vendor_overlay挂载在vendor上// 也就是会覆盖vendor分区内容// setup 5fs_mgr_vendor_overlay_mount_all();// 根据ro.oem_unlock_supported属性值来决定是否可以对设备进行unlock(解锁)// 若ro.oem_unlock_supported:「1」则代表 设备支持刷写unlock,若不支持该值为0// 如果设备支持刷写解锁,ro.boot.verifiedbootstate则会为orange,根据orange状态,把androidboot.flash.locked设置为1// 如果设备不支持刷新解锁,ro.boot.verifiedbootstate则会为green,根据orange状态,把androidboot.flash.locked设置为0// androidboot.flash.locked在系统启动完成后会形成属性值// (或 /firmware/android/flash.locked DT 属性)设置为“1”(如果已锁定)或“0”(如果已解锁)来指示锁定状态。export_oem_lock_status();// 持续监控/proc/mounts 节点(fopen("/proc/mounts", "re")),主要是解析该文件// 解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等(空格分割/dev/block/dm-33 /mnt/pass_through/0/emulated ext4 rw)// 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息// setup 6
MountHandler mount_handler(&epoll);...// 将根目录下所有的目录设置为全局共享// 将根目录/{分区}类型设置为共享,以便默认情况下所有进程都可以看到任何装载事件(例如/data)if(!SetupMountNamespaces()){PLOG(FATAL)<<"SetupMountNamespaces failed";}...// setup 7// 创建ActionManager对象和ServiceList对象
ActionManager& am =ActionManager::GetInstance();
ServiceList& sm =ServiceList::GetInstance();// 加载rc文件,保存到action manager和service list中// rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析// 主要解析rc中的:service,on,Import,包含了zygote.rc,路径:/system/bin/app_process64// 在文件系统挂载的第一阶段,system/vendor分区已经成功挂载,而其它分区的挂载则通过rc来挂载// 后面主要分析一下on early-init和on init和zygoteLoadBootScripts(am, sm);...// setup 8// 构建action和触发器(on early-init),放到event_queue,等待执行函数
am.QueueBuiltinAction(SetupCgroupsAction,"SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction,"SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction,"TestPerfEventSelinux");
am.QueueEventTrigger("early-init");// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action,"wait_for_coldboot_done");// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(SetMmapRndBitsAction,"SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction([&epoll,&keychords](const BuiltinArguments& args)-> Result<void>{for(constauto& svc :ServiceList::GetInstance()){
keychords.Register(svc->keycodes());}
keychords.Start(&epoll, HandleKeychord);return{};},"KeychordInit");// Trigger all the boot actions to get us started.// 构建action和触发器(on init),放到event_queue,等待执行函数
am.QueueEventTrigger("init");// Don't mount filesystems or start core system services in charger mode.// 如果是充电模式则不需要挂载文件系统和不要启动核心服务
std::string bootmode =GetProperty("ro.bootmode","");if(bootmode =="charger"){
am.QueueEventTrigger("charger");}else{
am.QueueEventTrigger("late-init");}// Run all property triggers based on current state of the properties.// 运行所有属性触发器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action,"queue_property_triggers");// Restore prio before main loop// 设置进程优先级,主进程不能被销毁和退出,循环处理rc中的服务相关setpriority(PRIO_PROCESS,0,0);while(true){// By default, sleep until something happens.auto epoll_timeout = std::optional<std::chrono::milliseconds>{};auto shutdown_command = shutdown_state.CheckShutdown();if(shutdown_command){LOG(INFO)<<"Got shutdown_command '"<<*shutdown_command
<<"' Calling HandlePowerctlMessage()";HandlePowerctlMessage(*shutdown_command);
shutdown_state.set_do_shutdown(false);}if(!(prop_waiter_state.MightBeWaiting()||Service::is_exec_service_running())){// 执行队列中的action// 队列中依次执行每个action中携带command对应的执行函数
am.ExecuteOneCommand();}if(!IsShuttingDown()){// 处理sm(ServiceList)中服务超时重启相关(init.rc中的service)auto next_process_action_time =HandleProcessActions();if(next_process_action_time){
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(*next_process_action_time - boot_clock::now());if(*epoll_timeout <0ms) epoll_timeout =0ms;}}...return0;}
2. SecondStageMain(int argc, char** argv)----->setup 1步骤
先贴一下setup 1的代码块,主要分析
UmountDebugRamdisk
函数
if(!load_debug_prop){// setup 1UmountDebugRamdisk();}staticvoidUmountDebugRamdisk(){if(umount("/debug_ramdisk")!=0){PLOG(ERROR)<<"Failed to umount /debug_ramdisk";}}
如果设备未unlock,则卸载关于debug版本的/debug_ramdisk,让属性值读取/ramdisk而不是/debug_ramdisk
3. SecondStageMain(int argc, char** argv)----->setup 2步骤
该步骤主要作用是初始化属性值服务,这里只是一个初始化的动作
// setup 2PropertyInit();
- 初始化属性服务,获取
system/build.prop
,vendor/build.prop
,/odm/build.prop
,/product/build.prop
,等其他build.prop并加载到properties map结构中,然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值,注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖 PropertyInit
位于/system/core/init/property_service.cpp
voidPropertyInit(){...// 建立属性服务设备文件(linux思想,万物皆文件系统)mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);// 创建序列化过后的propertyInfo实体,主要就是读取property_contexts文件CreateSerializedPropertyInfo();// 这里主要步骤是:通过mmap映射,将文件(/dev/__properties__/{..})映射进内存(初始化属性内存映射文件)// 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享if(__system_property_area_init()){LOG(FATAL)<<"Failed to initialize property area";}// 加载/dev/__properties__/property_info,此文件是序列化过的,无法直接查看内容if(!property_info_area.LoadDefaultPath()){LOG(FATAL)<<"Failed to load serialized property info file";}// 读取/proc/device-tree/firmware/android/目录下的文件,生成ro.boot.xxx属性值// 这三个函数主要就是生成ro.boot.xx属性值,这里不详细研究ProcessKernelDt();ProcessKernelCmdline();ProcessBootconfig();// 初始化ro.xx,将ro.boot.xx的属性值复制给ro.xxx// { "ro.boot.serialno", "ro.serialno", UNSET, },// { "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", },ExportKernelBootProps();// 读取{system/vendor/odm/product}/build.prop等...// 将build.prop通过MMAP映射到全局内存中,供所有进程访问 PropertyLoadBootDefaults();
调用
__system_property_area_init
通过mmap映射,将文件(/dev/properties/{包含了上下文和propertys_info实体:保存了property_contexts文件内容})映射进内存(初始化属性内存映射文件), 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享
- CreateSerializedPropertyInfo:创建序列化过后的属性值信息(既然序列化了,那肯定是要跨进程通信) (1).读取{system_ext,vendor,product,odm,system}_property_contexts属性值安全上下文并赋值给:property_infos (2).property_infos属于容器类型,读取不同的property_contexts将会追加到末尾,而不是覆盖原本内容 (3).将property_infos实体序列化,使其可以跨进程传递消息 (4).将property_infos实体写入/dev/properties/property_info驱动节点中
- PropertyLoadBootDefaults 这里会将属性值全部写入build.prop里,分为:system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,注意是有优先级顺序的,按先后顺序覆盖,获取build.prop分别是直接从指定文件里获取和从指定分区中获取,这两个方式作用都一样,只不过第二种需要区分出分区里是否存在
{partition}/{etc}/build.prop
,有的分区是不存在{partition}/etc/build.prop
这个文件,而是直接存在于{partition}/build.prop
一切都写到了注释里,继续分析比较重要的2个函数
CreateSerializedPropertyInfo
和
PropertyLoadBootDefaults
3.1 CreateSerializedPropertyInfo
voidCreateSerializedPropertyInfo(){auto property_infos = std::vector<PropertyInfoEntry>();//判断文件是否存在,并判断文件是否可写(属性服务的安全上下文,之前有提过设备节点,服务,属性值都要遵守selinux规则)if(access("/system/etc/selinux/plat_property_contexts", R_OK)!=-1){// 加载property_contexts文件,该文件内容都是配置的属性值上下文,属于selinux相关知识// 通过ParsePropertyInfoFile解析该文件,得到property_infosif(!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",&property_infos)){return;}// 如果这里system_ext/vendor/product没有挂载上(例如在恢复的情况下,vendor分区将不会安装),则无法继续加载该上下文,该分区会在第一阶段挂载// 从下面的代码可以看出来,property_infos的是容器类型vector<PropertyInfoEntry>()// 所以这里并没有优先级也没有以哪个property_contexts为准,而是根据是否存在对应的分区而append加载// 也就是在对应后面追加内容,而不是覆盖:property_infos->emplace_back(property_info_entry);if(access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK)!=-1){LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",&property_infos);}if(!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",&property_infos)){// Fallback to nonplat_* if vendor_* doesn't exist.LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",&property_infos);}if(access("/product/etc/selinux/product_property_contexts", R_OK)!=-1){LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",&property_infos);}if(access("/odm/etc/selinux/odm_property_contexts", R_OK)!=-1){LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts",&property_infos);}// 若/system/etc/selinux/plat_property_contexts无法读取,则else}else{// 由于system下的安全上下文未创建,则可能是system出现异常未挂载上,或者供应商修改过plat_property_contexts// 一般供应商都是复写,而不会直接更改文件名称// 若找不到该文件,则加载根目录下的这些属性值安全上下文if(!LoadPropertyInfoFromFile("/plat_property_contexts",&property_infos)){return;}LoadPropertyInfoFromFile("/system_ext_property_contexts",&property_infos);if(!LoadPropertyInfoFromFile("/vendor_property_contexts",&property_infos)){// Fallback to nonplat_* if vendor_* doesn't exist.LoadPropertyInfoFromFile("/nonplat_property_contexts",&property_infos);}LoadPropertyInfoFromFile("/product_property_contexts",&property_infos);LoadPropertyInfoFromFile("/odm_property_contexts",&property_infos);}// 序列化property_infos实体,使其可以跨进程传递auto serialized_contexts = std::string();auto error = std::string();if(!BuildTrie(property_infos,"u:object_r:default_prop:s0","string",&serialized_contexts,&error)){LOG(ERROR)<<"Unable to serialize property contexts: "<< error;return;}// 将property_infos写入/dev/__properties__/property_info设备文件中constexprstaticconstchar kPropertyInfosPath[]="/dev/__properties__/property_info";if(!WriteStringToFile(serialized_contexts, kPropertyInfosPath,0444,0,0,false)){PLOG(ERROR)<<"Unable to write serialized property infos to file";}selinux_android_restorecon(kPropertyInfosPath,0);}
以上的步骤都是对property_infos变量进行赋值,数据就是property_contexts文件内容,而property_infos是属于vector<**PropertyInfoEntry**>()容器类型,而每次加载property_infos时都是调用的
property_infos->emplace_back(property_info_entry);
在容器后面追加数据,不会覆盖原有的数据
读取property_contexts文件内容,将内容传递给property_infos实体
3.2 PropertyLoadBootDefaults
voidPropertyLoadBootDefaults(){
std::map<std::string, std::string> properties;// 如果是恢复模式则加载/prop.defaultif(IsRecoveryMode()){load_properties_from_file("/prop.default",nullptr,&properties);}// 这里还没执行,只是一个未执行的代码块,从分区里读取build.prop文件constauto load_properties_from_partition =[&properties](const std::string& partition,int support_legacy_path_until){// 加载{system_ext,product等分区}/etc/build.prop文件// 以后代码上获取的属性值就是从该文件中获取的auto path ="/"+ partition +"/etc/build.prop";if(load_properties_from_file(path.c_str(),nullptr,&properties)){return;}...}// 获取第一阶段生成的second_stage_resources/system/etc/ramdisk/build.prop// 并追加到properties中(这里是map结构,注意会覆盖内容)LoadPropertiesFromSecondStageRes(&properties);// 先读取的/system/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)load_properties_from_file("/system/build.prop",nullptr,&properties);// 获取/system_ext分区下的build.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)load_properties_from_partition("system_ext",/* support_legacy_path_until */30);// 继续读取的/vendor/default.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)load_properties_from_file("/vendor/default.prop",nullptr,&properties);// }// 继续读取的/vendor/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)load_properties_from_file("/vendor/build.prop",nullptr,&properties);// 继续读取的/vendor_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)load_properties_from_file("/vendor_dlkm/etc/build.prop",nullptr,&properties);// 继续读取的/odm_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)load_properties_from_file("/odm_dlkm/etc/build.prop",nullptr,&properties);// 获取/{odm,product}.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)load_properties_from_partition("odm",/* support_legacy_path_until */28);load_properties_from_partition("product",/* support_legacy_path_until */30);// 因为propertiess是map结构,如果key一样则会覆盖内容,所以以上代码的顺序不能调换,优先级最高的是/product下的build.prop// /system->/system_ext->/vendor->/omd->/product// 如果"/debug_ramdisk/adb_debug.prop"存在,说明设备已经unlock过了,则加载unlock过后的属性值,例如ro.debugger=1,则是开启了调试模式if(access(kDebugRamdiskProp, R_OK)==0){LOG(INFO)<<"Loading "<< kDebugRamdiskProp;load_properties_from_file(kDebugRamdiskProp,nullptr,&properties);}// 将从build.prop,default.prop获取的properties,循环设置属性值// 这里是把.prop文件里的属性值通过mmap映射到内存中,使得所有进程可以访问(全局)for(constauto&[name, value]: properties){
std::string error;// 如果是ro则是只读,只能设置一次,再次设置会无效,如果存在相同的key,则会调用update更新if(PropertySet(name, value,&error)!= PROP_SUCCESS){LOG(ERROR)<<"Could not set '"<< name <<"' to '"<< value
<<"' while loading .prop files"<< error;}}...// 设置persist.sys.usb.config属性值来决定是否开启调试模式(adb或none)update_sys_usb_config();}
以上代码主要做的一个动作:读取各个分区里的build.prop或直接从指定目录下读取build.prop并调用
PropertySet
设置到全局内存中,让所有进程访问
主要的两个函数:
load_properties_from_partition
和
load_properties_from_file
,分别从分区里读取和从指定文件读取,为什么这么做呢?因为有的分区下是没有/etc目录的,无法直接指定文件位置,所以通过调用
"/" + partition + "/etc/build.prop"
来读取,若不存在该文件则直接return。
注意这里读取了{system,system_ext,vendor,vendor_dlkm,odm_dlkm,odm,product}/build.prop,由于properties属于map结构,如果key相同是会覆盖原有的值,所以这里是有优先级排序的:
- /system/build.prop
- /system_ext/{etc}/build.prop
- /vendor/default.prop
- /vendor/build.prop
- /vendor_dlkm/etc/build.prop
- /odm_dlkm/etc/build.prop
- /odm/{etc}/build.prop
- /product/{etc}/build.prop
如果在system中自定义了属性值,又在product自定义了一样的属性值,那么是以product为准
这里只是init初始化过程,读取build.prop并解析出来每一行属性值并调用
PropertySet
设置到全局内存中
继续分析一下
PropertySet
函数,该函数比较简单
staticuint32_tPropertySet(const std::string& name,const std::string& value, std::string* error){...// 找到该属性值
prop_info* pi =(prop_info*)__system_property_find(name.c_str());if(pi !=nullptr){// 如果是以ro开头则代表只读,禁止写入,返回errorif(StartsWith(name,"ro.")){*error ="Read-only property was already set";return PROP_ERROR_READ_ONLY_PROPERTY;}// 若存在该属性值且非ro 则更新属性值__system_property_update(pi, value.c_str(), valuelen);}else{// 若找不到该属性值则新增int rc =__system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);if(rc <0){*error ="__system_property_add failed";return PROP_ERROR_SET_FAILED;}}// 如果是以persist.开头,则会全部写进/data/property/persistent_properties// 属于一个缓存机制// std::string persistent_property_filename = "/data/property/persistent_properties";if(persistent_properties_loaded &&StartsWith(name,"persist.")){WritePersistentProperty(name, value);}...return PROP_SUCCESS;}
以上代码就是若存在属性值则更新,若不存在则新增。如果是ro开头的属性值则代表只读,只能初始化的时候给默认值,后续不允许修改值,如果是persist开头的则会全部缓存进
/data/property/persistent_properties
,persist开头的属性值是可改的,如果用户修改过了persist开头的属性值相当于修改了
/data/property/persistent_properties
里的属性值,那么重启后仍然生效,并不会还原默认值。这样即不影响属性值的原子性(原有的属性值),又给了开发者/用户操作的空间,如果是刷机或恢复出厂则会还原
如果是系统开发者自定义了属性值,但是发现默认定义的时候属性值无法写入,则可能是property_contexts安全上下文影响,可以直接修改这里的代码,
__system_property_add
强行调用该方法即可
4. SecondStageMain(int argc, char** argv)----->setup 3步骤
setup 4MountExtraFilesystems();staticvoidMountExtraFilesystems(){#defineCHECKCALL(x)\if((x)!=0)PLOG(FATAL)<< #x " failed.";// /apex is used to mount APEXesCHECKCALL(mount("tmpfs","/apex","tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));// /linkerconfig is used to keep generated linker configurationCHECKCALL(mount("tmpfs","/linkerconfig","tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,"mode=0755,uid=0,gid=0"));#undefCHECKCALL}
可以看到
MountExtraFilesystems
主要就挂载了/apex和/linkerconfig并归属于tmpfs文件系统(运行在内存的文件系统,运行速度较快),这里主要探讨一下APEX.
应用程序可以通过更新APK来升级,供应商客制化系统后可以通过OTA进行系统升级,而针对google 开发者来修复原生的系统bug该如何更新呢?那就是通过google发布的安全patch和更新manline以及apex来解决
- 简单点说apex为了解决性能而产生的机制,APK可以通过内置升级,但系统升级可是个大问题
- apex可以将系统内部的各个功能打包成模块,然后针对这些模块单独升级
- apk是应用程序的载体,对应用开发者而言,可以apk方式对应用功能进行升级
- apex是系统功能的载体,对系统开发者(目前看主要是谷歌)而言,可以apex方式对系统功能进行升级
- 一般是google开发者通过playstore发布,然后供我们下载更新,而对应ODM第三方供应商,则需要通过OTA升级
- apex相当于对系统功能进行了更细粒度的划分,可以独立升级这些功能,可以把apex看成是一个一个的系统升级包
5. SecondStageMain(int argc, char** argv)----->setup 4步骤
// setup 4StartPropertyService(&property_fd);
在setup2中初始化了property,获取了build.prop和property_contexts并设置为内存中全局共享
voidStartPropertyService(int* epoll_socket){// 在init阶段version=1,这里已经升级到2了InitPropertySet("ro.property_service.version","2");// 创建sockets,套接字,可以用于网络通信,也可以用于本机内的进程通信// socketpair()函数用于创建一对无名的,相互连接的套接字// 如果函数创建成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1int sockets[2];// 参数1:表示协议族AF_UNIX// 参数2:表示协议,SOCK_SEQPACKET提供连续可靠的数据包连接// SOCK_CLOEXEC:当文件描述符设置了O_CLOEXEC属性后,在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭// 而SOCK_DGRAM是基于UDP的// 参数3:表示类型,只能为0// 参数4:套节字柄,该两个句柄作用相同,均能进行读写双向操作if(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC,0, sockets)!=0){PLOG(FATAL)<<"Failed to socketpair() between property_service and init";}...// PROP_SERVICE_NAME:/dev/socket/property_service,创建socketif(auto result =CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,false,0666,0,0,{});
result.ok()){
property_set_fd =*result;}else{LOG(FATAL)<<"start_property_service socket creation failed: "<< result.error();}// 监听socket:/dev/socket/property_service,最大连接:8listen(property_set_fd,8);// 开启线程处理socketauto new_thread = std::thread{PropertyServiceThread};
property_service_thread.swap(new_thread);}
这里就是创建了socket,而属性值服务的本质就是一个socket(/dev/socket/property_service),最大连接为:8,持续等待连接,连接成功后决定是更新还是新增属性值,仍然是调用的
PropertySet
,继续分析一下
PropertyServiceThread
,该函数里调用了
handle_property_set_fd
函数,来看看具体实现
staticvoidhandle_property_set_fd(){// 设置超时:2sstaticconstexpruint32_t kDefaultSocketTimeout =2000;/* ms */// 可以设置四个参数,所以用的是accept4,而当启用了SOCK_CLOEXEC参数后,进程在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭int s =accept4(property_set_fd,nullptr,nullptr, SOCK_CLOEXEC);if(s ==-1){return;}...switch(cmd){case PROP_MSG_SETPROP:{char prop_name[PROP_NAME_MAX];char prop_value[PROP_VALUE_MAX];...
prop_name[PROP_NAME_MAX-1]=0;
prop_value[PROP_VALUE_MAX-1]=0;...// 当收到客户端发来的请求,去更新或新增属性值时会调用HandlePropertySet去处理constauto& cr = socket.cred();
std::string error;uint32_t result =HandlePropertySet(prop_name, prop_value, source_context, cr,nullptr,&error);if(result != PROP_SUCCESS){LOG(ERROR)<<"Unable to set property '"<< prop_name <<"' from uid:"<< cr.uid
<<" gid:"<< cr.gid <<" pid:"<< cr.pid <<": "<< error;}break;}...}}
可以看到设置了连接超时时间为2000ms,执行
case PROP_MSG_SETPROP
,获取属性值的名称和值,调用
HandlePropertySet
里的
PropertySet
进行更新属性值或新增。
PropertySet
已经分析过了
至此属性值相关就分析完毕了,简单的来说就是读取build.prop属性值共享到全局内存中让所有进程获取,然后创建属性值服务(socket),持续监听客户端(哪个进程去调用更新属性值就是当前客户端)发来的请求,最大只能同时受理8个来自客户端的请求,若有客户端请求则去更新属性值或新增属性值
6. SecondStageMain(int argc, char** argv)----->setup 5步骤
// setup 5fs_mgr_vendor_overlay_mount_all();
这里的代码只要是针对如果有vendor_overlay分区,则覆盖/vendor分区,主要看供应商的客制化
const std::vector<const std::string> kVendorOverlaySourceDirs ={"/system/vendor_overlay/","/product/vendor_overlay/",};boolfs_mgr_vendor_overlay_mount_all(){...// 获取 "/system/vendor_overlay/","/product/vendor_overlay/"下的所有子目录// 将vendor_overlay挂载到vendor上,若存在该覆盖分区则会覆盖之前的vendor分区constauto vendor_overlay_dirs =fs_mgr_get_vendor_overlay_dirs(vndk_version);if(vendor_overlay_dirs.empty())returntrue;if(fs_mgr_overlayfs_valid()== OverlayfsValidResult::kNotSupported){
LINFO <<"vendor overlay: kernel does not support overlayfs";returnfalse;}// Mount each directory in /(system|product)/vendor_overlay/<ver> on /vendor// 挂载vendor_overlay到/vendor分区上auto ret =true;for(constauto& vendor_overlay_dir : vendor_overlay_dirs){if(!fs_mgr_vendor_overlay_mount(vendor_overlay_dir)){
ret =false;}}return ret;}
7. SecondStageMain(int argc, char** argv)----->setup 6步骤
// setup 6
MountHandler mount_handler(&epoll);
该函数主要功能:持续监控
/proc/mounts
节点
(fopen("/proc/mounts", "re"))
,主要是解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等, 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息,挂载mounts信息里的分区
位置:/system/core/init/mount_handler.cpp
MountHandler::MountHandler(Epoll* epoll):epoll_(epoll),fp_(fopen("/proc/mounts","re"), fclose){if(!fp_)PLOG(FATAL)<<"Could not open /proc/mounts";auto result = epoll->RegisterHandler(fileno(fp_.get()),[this](){this->MountHandlerFunction();}, EPOLLERR | EPOLLPRI);if(!result.ok())LOG(FATAL)<< result.error();}
继续看一下
MountHandlerFunction
函数
voidMountHandler::MountHandlerFunction(){rewind(fp_.get());
std::vector<MountHandlerEntry> touched;auto untouched = mounts_;//容器类型char* buf =nullptr;
size_t len =0;// 循环读取文件内容中的每一行while(getline(&buf,&len, fp_.get())!=-1){auto buf_string = std::string(buf);// /proc/mounts文件下存在一系列代码// 若读取到/0/emulated则跳过if(buf_string.find("/emulated")!= std::string::npos){continue;}// 根据读取的文件内容,来解析分区以及device path,type等auto entry =ParseMount(buf_string);auto match = untouched.find(entry);// 若这一行解析到底了仍然没有匹配的信息,则这一条记录追加到touched中// entry:举例--->对文件内容/dev/block/dm-33 /data_mirror/data_ce/null ext4 解析过后的实体if(match == untouched.end()){
touched.emplace_back(std::move(entry));}else{// 若找到了匹配的信息则移除
untouched.erase(match);}}free(buf);// 将匹配到的entry进行移除,并记录Mount属性值for(auto& entry : untouched){SetMountProperty(entry,false);
mounts_.erase(entry);}// 将未匹配到的entry追加到mounts_,并记录Mount属性值for(auto& entry : touched){SetMountProperty(entry,true);// emplace是更具有性能的 更新或追加
mounts_.emplace(std::move(entry));}}
- 读取/proc/mounts,解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等
- 若解析到/0/emulated则跳过,不处理
- 根据mounts_,当entry(解析后的内容)可以在其中找到则移除
- 若entry未在mounts_找到,则追加到mounts_中
- 相当于移除旧的entry,将新的entry追加到mounts_
再看看是如何解析文件内容的
auto entry = ParseMount(buf_string)
,解析文件内容
MountHandlerEntry ParseMount(const std::string& line){auto fields = android::base::Split(line," ");while(fields.size()<3) fields.emplace_back("");if(fields[0]=="/dev/root"){auto& dm = dm::DeviceMapper::Instance();
std::string path;// 根据名称获取system分区目录路径,若根据名称找不到则直接获取根目录/// 若找到根目录则继续找/system,若找到则拿到device path// 例如/system就是挂载在/dev/block/dm-1上,那么获取的就是这个玩意// /dev/block/dm-3 /vendor ext4 ro,seclabel,relatime 0 0if(dm.GetDmDevicePathByName("system",&path)|| dm.GetDmDevicePathByName("vroot",&path)){
fields[0]= path;}elseif(android::fs_mgr::Fstab fstab; android::fs_mgr::ReadDefaultFstab(&fstab)){auto entry =GetEntryForMountPoint(&fstab,"/");if(entry ||(entry =GetEntryForMountPoint(&fstab,"/system"))){
fields[0]= entry->blk_device;}}}// 获取所有/dev目录下的device// readlink 是Linux系统中的一个常用命令,主要用来找出符号链接所指向的位置// 也就是找到devcie path:/dev/block/dm-33if(android::base::StartsWith(fields[0],"/dev/")){if(std::string link; android::base::Readlink(fields[0],&link)){
fields[0]= link;}}// fields0:/dev/block/dm-33(blk_device)// fields1:挂载在device上的分区/文件路径:/data_mirror/cur_profiles(mount_point)// fields2:该分区的type类型,例如可能是ext4(fs_type)returnMountHandlerEntry(fields[0], fields[1], fields[2]);}
让我们再来看看这个文件内容
可以看出来第一列是分区挂载的位置,第二列是哪个分区,第三列属于分区格式
8. SecondStageMain(int argc, char** argv)----->setup 7步骤
// setup 7// 创建ActionManager对象和ServiceList对象
ActionManager& am =ActionManager::GetInstance();
ServiceList& sm =ServiceList::GetInstance();// 解析rc文件LoadBootScripts(am, sm);
- 加载rc文件,保存到action manager和service list中
- rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
- 主要解析rc中的:service,on,Import,包含了zygote.rc,路径:/system/bin/app_process64
- 在文件系统挂载的第一阶段,system/vendor分区已经成功挂载,而其它分区的挂载则通过rc来挂载
- rc中action的执行顺序:
on early-init
,on init
,on late-init
,on property
staticvoidLoadBootScripts(ActionManager& action_manager, ServiceList& service_list){// 创建解析器,只解析init.rc文件中的service,on,Import类型// action 使用 ActionParser,而 service 使用 ServiceParser 解析
Parser parser =CreateParser(action_manager, service_list);// 获取ro.boot.init_rc属性值,此时该属性值应该是空的
std::string bootscript =GetProperty("ro.boot.init_rc","");if(bootscript.empty()){// 解析/system/core/rootdir/init.rc// 这里的路径就是将/system/core/rootdir/init.rc 拷贝到out目录下
parser.ParseConfig("/system/etc/init/hw/init.rc");...}else{
parser.ParseConfig(bootscript);}}
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list){
Parser parser;
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list,GetSubcontext(), std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager,GetSubcontext()));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));return parser;}
可以看到只需要解析init.rc文件中的service,on,import类型
9. SecondStageMain(int argc, char** argv)----->setup 8步骤
// setup 8// 构建action和触发器(on early-init),放到event_queue,等待执行函数...
am.QueueEventTrigger("early-init");// 构建action和触发器(on init),放到event_queue,等待执行函数
am.QueueEventTrigger("init");// 如果是充电模式则不需要挂载文件系统和不要启动核心服务
std::string bootmode =GetProperty("ro.bootmode","");if(bootmode =="charger"){
am.QueueEventTrigger("charger");}else{
am.QueueEventTrigger("late-init");}// 运行所有属性触发器(action),例如 on property
am.QueueBuiltinAction(queue_property_triggers_action,"queue_property_triggers");
把action加入队列中按顺序依次执行,继续一下rc文件中做了什么动作, rc中action的执行顺序:
on early-init
,
on init
,
on late-init
,
on nonencrypted(启动zygote)
,
on property
9.1. on early-init
on early-init
...
mkdir /acct/uid
# 挂载linkerconfig(动态链接器)mount none /linkerconfig/bootstrap /linkerconfig bind rec
# 启动ueventd(位于/system/bin/ueventd),ueventd是init启动的第一个进程
start ueventd
# memory.pressure_level used by lmkdchown root system /dev/memcg/memory.pressure_level
chmod 0040 /dev/memcg/memory.pressure_level
# app mem cgroups, used by activity manager, lmkd and zygotemkdir /dev/memcg/apps/ 0755 system system
mkdir /dev/memcg/system 0550 system system
mkdir /dev/net 0755 root root
symlink ../tun /dev/net/tun
...
# 挂载tracefs,可以通过指定方式到处trace日志,分析CPU和内存相关等问题mount tracefs tracefs /sys/kernel/tracing gid=3012# create sys dirctory# 创建/sys目录并指定权限mkdir /dev/sys 0755 system system
mkdir /dev/sys/fs 0755 system system
mkdir /dev/sys/block 0755 system system
可以看到针对
lmkd(Low Memory Killer Daemon)
以及app 创建用户组,创建目录,挂载tracefs:可以通过指定方式到处trace日志,分析CPU和内存相关等问题。
第一个启动的核心服务是:
start ueventd
,位于/system/bin/ueventd,ueventd是init启动的第一个服务进程
9.2. on init
on init
...
chmod 0775 /dev/cpuset/system-background
chmod 0664 /dev/cpuset/foreground/tasks
chmod 0664 /dev/cpuset/background/tasks
chmod 0664 /dev/cpuset/system-background/tasks
chmod 0664 /dev/cpuset/top-app/tasks
chmod 0664 /dev/cpuset/restricted/tasks
chmod 0664 /dev/cpuset/tasks
chmod 0664 /dev/cpuset/camera-daemon/tasks
# 挂载bpfmount bpf bpf /sys/fs/bpf nodev noexec nosuid
mkdir /dev/fscklogs 0770 root system
...
# 允许system组读写电源状态chown system system /sys/power/state
chown system system /sys/power/wakeup_count
chmod 0660 /sys/power/state
...
# 在运行其他进程之前需要先启动log服务,说明init中启动的服务,第一个启动的进程是ueventd
start logd
# 启用 Low Memory Killer Daemon(lmkd)# 1.基于Memory的CGroup进行进程的回收;2.作为frameworks与kernel的沟通桥梁传递参数与信息# Start lmkd before any other services run so that it can register themchown root system /sys/module/lowmemorykiller/parameters/adj
chmod 0664 /sys/module/lowmemorykiller/parameters/adj
chown root system /sys/module/lowmemorykiller/parameters/minfree
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
start lmkd
# Start essential services.# 启用ServiceManager,管理各个服务,非常重要
start servicemanager
start hwservicemanager
start vndservicemanager
可以看到第二个启动的核心服务是:
start logd
,日志系统。
第三个核心服务是:
start lmkd
,Low Memory Killer Daemon
作用:基于Memory的CGroup进行进程的回收,作为frameworks与kernel的沟通桥梁传递参数与信息。
接着启动了:
servicemanager
,
hwservicemanager
,
vndservicemanager
,这些都属于核心服务
若核心服务未启动成功,那么其他服务将无法启动,系统将无法启动,其他服务必须依赖核心服务
9.3. on late-init
# 装载文件系统并启动核心系统服务
on late-init
trigger early-fs
# 触发on fs和on post-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load_persist_props_action
trigger load_bpf_programs
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger boot
可以看到调用顺序为:启动系统on late-init会先执行,然后继续触发on fs,on post-fs ,on late-fs,on zygote-start ,on boot等,在调用
on zygote-start
后会解析zygote服务并指定class 名称,然后加入服务管理队列,后续等待调用
on nonencrypted
来启动zygote服务
9.4. on nonencrypted
on nonencrypted
class_start main
class_start late_start
目录:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
# class : 给服务指定一个类属
class main
priority -20# user 在执行此服务之前先切换用户名。当前默认为root.
user root
# 切换组名
group root readproc reserved_disk
# 在/dev/socket/下创建一个socket,并传递创建的文件描述符fd给服务进程# 其中type必须为dgram或stream,seqpacket.用户名和组名默认为0
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
# oneshot : 当此服务退出时不会自动重启.# disabled:服务不会自动运行,必须显式地通过服务器来启动# 据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。
critical window=${zygote.critical_window.minute:-off}target=zygote-fatal
可以看到通过class_start main来启动主函数main,位于:frameworks/base/cmds/app_process/app_main.cpp
intmain(int argc,char*const argv[]){...if(zygote){
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);}elseif(className){
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);}else{fprintf(stderr,"Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}}
这里启动了zygote,并且携带参数启动了systemServer,关于zygote这里就不再详细分析了
二. 附录
rc文件中的command以及触发器,action等,对应的关系如下:
staticconst BuiltinFunctionMap builtin_functions ={{"bootchart",{1,1,{false, do_bootchart}}},{"chmod",{2,2,{true, do_chmod}}},{"chown",{2,3,{true, do_chown}}},{"class_reset",{1,1,{false, do_class_reset}}},{"class_reset_post_data",{1,1,{false, do_class_reset_post_data}}},{"class_restart",{1,1,{false, do_class_restart}}},{"class_start",{1,1,{false, do_class_start}}},{"class_start_post_data",{1,1,{false, do_class_start_post_data}}},{"class_stop",{1,1,{false, do_class_stop}}},{"copy",{2,2,{true, do_copy}}},{"copy_per_line",{2,2,{true, do_copy_per_line}}},{"domainname",{1,1,{true, do_domainname}}},{"enable",{1,1,{false, do_enable}}},{"exec",{1, kMax,{false, do_exec}}},{"exec_background",{1, kMax,{false, do_exec_background}}},{"exec_start",{1,1,{false, do_exec_start}}},{"export",{2,2,{false, do_export}}},{"hostname",{1,1,{true, do_hostname}}},{"ifup",{1,1,{true, do_ifup}}},{"init_user0",{0,0,{false, do_init_user0}}},{"insmod",{1, kMax,{true, do_insmod}}},{"installkey",{1,1,{false, do_installkey}}},{"interface_restart",{1,1,{false, do_interface_restart}}},{"interface_start",{1,1,{false, do_interface_start}}},{"interface_stop",{1,1,{false, do_interface_stop}}},{"load_exports",{1,1,{false, do_load_exports}}},{"load_persist_props",{0,0,{false, do_load_persist_props}}},{"load_system_props",{0,0,{false, do_load_system_props}}},{"loglevel",{1,1,{false, do_loglevel}}},{"mark_post_data",{0,0,{false, do_mark_post_data}}},{"mkdir",{1,6,{true, do_mkdir}}},// TODO: Do mount operations in vendor_init.// mount_all is currently too complex to run in vendor_init as it queues action triggers,// imports rc scripts, etc. It should be simplified and run in vendor_init context.// mount and umount are run in the same context as mount_all for symmetry.{"mount_all",{0, kMax,{false, do_mount_all}}},{"mount",{3, kMax,{false, do_mount}}},{"perform_apex_config",{0,0,{false, do_perform_apex_config}}},{"umount",{1,1,{false, do_umount}}},{"umount_all",{0,1,{false, do_umount_all}}},{"update_linker_config",{0,0,{false, do_update_linker_config}}},{"readahead",{1,2,{true, do_readahead}}},{"remount_userdata",{0,0,{false, do_remount_userdata}}},{"restart",{1,1,{false, do_restart}}},{"restorecon",{1, kMax,{true, do_restorecon}}},{"restorecon_recursive",{1, kMax,{true, do_restorecon_recursive}}},{"rm",{1,1,{true, do_rm}}},{"rmdir",{1,1,{true, do_rmdir}}},{"setprop",{2,2,{true, do_setprop}}},{"setrlimit",{3,3,{false, do_setrlimit}}},{"start",{1,1,{false, do_start}}},{"stop",{1,1,{false, do_stop}}},{"swapon_all",{0,1,{false, do_swapon_all}}},{"enter_default_mount_ns",{0,0,{false, do_enter_default_mount_ns}}},{"symlink",{2,2,{true, do_symlink}}},{"sysclktz",{1,1,{false, do_sysclktz}}},{"trigger",{1,1,{false, do_trigger}}},{"verity_update_state",{0,0,{false, do_verity_update_state}}},{"wait",{1,2,{true, do_wait}}},{"wait_for_prop",{2,2,{false, do_wait_for_prop}}},{"write",{2,2,{true, do_write}}},};
三. 总结
至此第二阶段就分析完毕了,一句话来总结:
优先保证init进程的存活率(拉高优先级),处理设备是否unlock,决定是否卸载一些分区以及调整selinux规则和权限,创建并启动属性服务(实质上就是把属性值映射到全局内存中供所有进程访问,然后在创建socket等待进程来连接 实现更新和新增属性值),继续决定是否把vendor_overlay覆盖到/vendor分区中,然后持续监控/proc/mounts,如果有分区信息加入到该文件中则挂载此分区,接着解析rc文件(创建目录,修改权限,挂载分区,启动服务进程等),根据调用顺序启动核心服务(adbd,ueventd等)以及主服务(zygote)和其他服务
版权归原作者 长安故里. 所有, 如有侵权,请联系我们删除。