- 在VisualStudio中部署GDAL库的C++版本(包括SQLite、PROJ等依赖)
- Android开机流程介绍
- STM32CubeMX教程31USB_DEVICE-HID外设_模拟键盘或鼠标
- 深入浅出Java多线程(五):线程间通信
从2014年Android4.0开始接触机器人,开发过App应用软件,研究过Framework层框架结构、也梳理过Native层的系统流程,但是对于Hal层,以及底下的kernel方向,知之甚少。 本着一份对于底层知识的渴望,也为方便以后debug问题,故开始尝试分析下Android的开机流程.
如下是Google官网提供的平台架构图,很直观地攘括了Android的层级,关于各个层级的结构,有想了解可以参考:https://developer.android.google.cn/guide/platform?hl=zh-cn 。
ps: Android系统为什么要有Hal层?
如下是从某大佬的文章里摘取的图片,很详细地描述了Android系统启动过程:Boot Loader引导开机 -> Kernel -> Native -> Framework -> App 。
zImage是一般情况下默认的压缩内核映像文件,压缩vmlinux,加上一段解压启动代码得到,只能从0X0地址运行.
RAMDISK(initrd) 是一个小的分区像,在引导时内核以只读方式挂载它。它只保护/int和一些置文件,它用于初始化和挂载其它的文件系统镜像。 ramdisk.img被包含Google android SDK中(SSDK ROOT/toolslibimages/ramdiskimg) , 也可以编生成(SSDK ROOT/outarget/productSPRODUT NAME/ramdisk.img) 。这是一个gzip压缩的CPIO文件.
rc文件,是用Android Init Language编写的特殊文件。用这种语法编写的文件,统一用".rc"后缀。所有rc文件,不会被编译/链接。它是配置文件,不是程序,是一种用于android init的配置文件。真正加载rc文件,并进行解析,做事情的是 Init进程.
当长按电源开机的时候,引导芯片开始从固化在ROM的预设代码开始执行,然后将加载引导程序到RAM中.
BootLoader又称为引导程序,它在运行操作系统之前运行的一段程序,是运行的第一个程序。主要的功能有检查RAM、初始化一些硬件外设等功能,它最终的目的是启动操作系统.
Kernel初始化可以分成三部分:zImage解压缩、kernel的汇编启动阶段、Kernel的C启动阶段 。
idle进程的启动是用汇编语言编写(感兴趣可以去研究下),对应的启动如下:
@bsp\kernel\kernel5.4\arch\arm\kernel\head-common.S
ldmia r4, {r0, r1, r2, r3}
str r9, [r0] @ Save processor ID
str r7, [r1] @ Save machine type
str r8, [r2] @ Save atags pointer
cmp r3, #0
strne r10, [r3] @ Save control register values
mov lr, #0
b start_kernel //启动start_kernel函数
其中,语句b start_kernel,b 是跳转的意思,即跳转到start_kernel函数,对应的实现在bsp/kernel/kernel5.4/init/main.c,至此idle进程被启动.
start_kernel()函数是内核初始化C语言部分的主体。这个函数完成系统底层基本机制,包括处理器、存储管理系统、进程管理系统、中断机制、定时机制等的初始化工作.
@bsp\kernel\kernel5.4\init\main.c
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();//打印了驱动加载的第一行log
... //初始化一系列系统底层机制
pr_notice("%s", linux_banner);//打印内核版本信息
...
pr_notice("Kernel command line: %s\n", saved_command_line);//打印从uboot传递过来的command_line字符串
...
/* Do the rest non-__init'ed, we're now alive */
arch_call_rest_init();//创建init进程、kthread进程、idle进程
prevent_tail_call_optimization();
}
(1)kernel内核启动阶段,smp_setup_processor_id()函数会打印的第一条log(文中log可以在 [目录5.1开机log] 下载查看),如下:
[ 0.000000] c0 Booting Linux on physical CPU 0x0
(2)接着会打印内核的一些信息,版本,作者,编译器版本,日期等,如下:
[ 0.000000]c0 Linux version 5.4.147+ (lzq@cz-PowerEdge-R730) (Android (7284624, based on r416183b) clang version 12.0.5 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee), LLD 12.0.5 (/buildbot/src/android/llvm-toolchain/out/llvm-project/lld c935d99d7cf2016289302412d708641d52d2f7ee)) #5 SMP PREEMPT Thu Sep 28 15:17:40 CST 2023
(3)打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的(proc/cmdline) 。
[ 0.000000]c0 Kernel command line: earlycon=sprd_serial_ex,0x508d0000,115200n8 console=ttySE0,921600n8 loglevel=7 init=/init root=/dev/ram0 rw vmalloc=360M printk.devkmsg=on androidboot.boot_devices=soc/soc:ap-ahb/20600000.sdio initcall_debug=1 swiotlb=1 androidboot.selinux=permissive androidboot.hardware=sl8541e_1h10_32b androidboot.dtbo_idx=0 lcd_id=ID40396 lcd_name=lcd_st7123_truly_mipi_hd lcd_base=9e000000 lcd_size=1440x720 logo_bpix=24 androidboot.ddrsize=1024M androidboot.ddrsize.range=[1024,2048) sysdump_magic=80001000 sysdump_re_flag=1 androidboot.wdten=0 androidboot.dswdten=disable modem=shutdown ltemode=lcsfb rfboard.id=-1 rfhw.id=0 crystal=2 32k.less=1 androidboot.pmic.chipid=2721 modemboot.method=emmcboot cpcmdline=end androidboot.verifiedbootstate=orange androidboot.flash.locked=0 androidboot.serialno=LE210210001326000028 buildvariant=userdebug androidboot.vbmeta.device=PARTUUID=1.0 androidboot.vbmeta.avb_version=1.1 androidboot.vbmeta.device_state=unlocked androidboot.
创建了Linux系统中两个重要的进程init和kthreadd,并且将当前进程设置为idle进程:
@bsp\kernel\kernel5.4\init\main.c
void __init __weak arch_call_rest_init(void)
{
rest_init();
}
noinline void __ref rest_init(void)
{
...
pid = kernel_thread(kernel_init, NULL, CLONE_FS);//创建init进程
...
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd进程
...
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);//设置当前进程为idle进程
}
Linux下有三个特殊的进程,idle(swapper)进程(PID = 0),init进程(PID = 1)和看threadd(PID = 2):
(1)idle(swapper)进程由系统自动创建,运行在内核态。idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换,常常被称为交换进程。 (2)init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init进程,并最终转变为用户空间的init进程,是系统中所有其他用户进程的祖先进程。 (3)kthreadd进程是idle通过kernel_thread创建,并始终运行在内核空间 ,负责所有内核线程的调度和管理.
这个函数是Linux内核为非引导CPU初始化和进入空闲循环的入口函数,负责在系统没有任务需要执行时,让CPU进入空闲状态.
@bsp\kernel\kernel5.4\kernel\sched\idle.c
void cpu_startup_entry(enum cpuhp_state state)
{
arch_cpu_idle_prepare();
cpuhp_online_idle(state);
while (1)
do_idle();
}
由上一节可知,Init进程由0号idle进程启动,kernel_init()为其入口函数,该函数主要通过三种方式启动init程序.
static int __ref kernel_init(void *unused)
{
...
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);//Step 1. 根据ramdisk_execute_command的值来启动init程序
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
if (execute_command) {
ret = run_init_process(execute_command);//Step 2. 根据execute_command的值来启动init程序
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh")) //Step 3.根据系统默认位置来启动init程序
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");//Step 4.异常重启
}
(1)ramdisk_execute_command是一个全局的char指针变量,值为“/init”,也就是根目录下的init程序。该值通过uboot传递过来,具体可参考上一节的command_line。(如未配置该值,即会将该值默认设置为"/init",该项目是从该处启动init进程) (2)execute_command也是一个全局的char指针变量,值通过uboot传递; (3)在前面两种情况都不满足的情况下,从系统默认位置加载init程序; (4)如上三种情况都不执行的话,进入panic函数,则设备会异常重启; (5)可以通过如下log确认系统启动的是哪个init程序,该log由run_init_process函数打印 。
[ 1.000614]c1 Run /init as init process
我们知道init程序是引用的根目录下的init,即/init,但是其对应的软链接指向:system/bin/init,相关信息如下
编译makefile文件如下:
@system\core\init\Android.bp
phony {
name: "init",
required: [
"init_second_stage",
],
}
cc_binary {
name: "init_second_stage",
recovery_available: true,
stem: "init",//最终生成的二进制文件名
defaults: ["init_defaults"],
static_libs: ["libinit"],
...
srcs: ["main.cpp"],
...
}
init程序的入口函数,根据不同的入参,响应init不同阶段、处理不同业务逻辑.
@system\core\init\main.cpp
int main(int argc, char** argv) {
...
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);//初始化uevent事件
}
if (argc > 1) {
...
if (!strcmp(argv[1], "selinux_setup")) {
android::mboot::mdb("SELinux Setup ...");
return SetupSelinux(argv);//selinux权限
}
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);//第二阶段
}
}
return FirstStageMain(argc, argv);//第一阶段
}
由于在kernel阶段启动init程序时,未配置参数,故首先会引用FirstStageMain函数。另外,根据log可以看到,其引用顺序如下:
[ 1.004958]c1 init: init first stage started!
[ 2.547025]c0 init: Opening SELinux policy
[ 3.226672]c0 init: init second stage started!
[ 3.715657]c1 ueventd: ueventd started!
第一阶段,该阶段所挂载的文件系统都属于ramdisk,运行在虚拟内存上.
@system\core\init\first_stage_mount.cpp
int FirstStageMain(int argc, char** argv) {
...
//Step 1. 创建&挂载最基本的文件系统
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));//将/dev设置为tmpfs并挂载,设置0755权限,tmpfs是在内存上建立的文件系统(Filesystem)
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mkdir("/dev/dm-user", 0755));
...
//Step 2. 初始化log系统并打印
SetStdioToDevNull(argv);
InitKernelLogging(argv);
...
LOG(INFO) << "init first stage started!";
...
//Step 3. 加载内核驱动模块
if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
module_count)) {
...
}
...
//Step 4.挂载分区(system、vendor、product等分区)
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
...
//Step 5.初始化Android的安全框架Android Verified Boot
SetInitAvbVersionInRecovery();
...
//Step 6.执行下一个阶段
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
...
execv(path, const_cast<char**>(args));//exec系列函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID,即重新回到main.cpp
...
}
Step 1. 创建&挂载最基本的文件系统,创建了如下五类文件系统
文件系统 | 挂载路径 | 描述 |
---|---|---|
tmpfs | /dev | 一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。 |
devpts | /dev/pts | 为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。 |
proc | /proc | 一个虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。 |
sysfs | /sys | 与proc文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。 |
selinuxfs | /sys/fs/selinux | 用于支持SELinux的文件系统,SELinux提供了一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。 |
相关挂载情况如下:
Step 2. 结合dev/kmsg节点,初始化log系统,并在第一阶段开始时,打印第一条log 。
[ 1.004958]c1 init: init first stage started!
Step 3. 加载内核驱动模块,第一阶段加载的内核模块如下:
[ 1.012653]c1 init: Loaded kernel module /lib/modules/sprd_wdt.ko
[ 1.020197]c0 init: Loaded kernel module /lib/modules/sc2721-regulator.ko
[ 1.022427]c0 init: Loaded kernel module /lib/modules/nvmem-sc27xx-efuse.ko
[ 1.026126]c0 init: Loaded kernel module /lib/modules/spool.ko
[ 1.033668]c0 init: Loaded kernel module /lib/modules/sipx.ko
[ 1.047553]c0 init: Loaded kernel module /lib/modules/seth.ko
[ 1.048526]c0 init: Loaded kernel module /lib/modules/usb_f_vser.ko
...
Step 4. 挂载分区。创建/first_stage_ramdisk并挂载,然后将根目录切换到/first_stage_ramdisk,并挂载system、vendor 、product等系统分区,挂载信息如上。 Step 5. 初始化Android的安全框架Android Verified Boot,用于防止系统文件本身被篡改、防止系统回滚,以免回滚系统利用以前的漏洞。 Step 6. 启动下一个阶段SetupSelinux.
该阶段主要是初始化Selinux权限相关业务,同时在业务流程最后一步时,重新执行system/bin/init程序,再次启动下一个阶段SecondStageMain.
@system\core\init\selinux.cpp
int SetupSelinux(char** argv) {
...
LOG(INFO) << "Opening SELinux policy";
...
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
...
return 1;
}
该阶段log打印如下:
[ 2.547025]c0 init: Opening SELinux policy
第二阶段,涉及到文件系统挂载、属性服务等系统相关业务,其中最主要的一点去解析rc文件(创建目录,修改权限,挂载分区,启动服务进程等),以期让开机流程进入下一阶段.
@system\core\init\init.cpp
int SecondStageMain(int argc, char** argv) {
//Step 1.初始化log
SetStdioToDevNull(argv);
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
...
//Step 2.属性服务初始化,读取默认属性配置
PropertyInit();
...
//Step 3.挂载其他文件系统,如/apex
MountExtraFilesystems();
...
//Step 4.启动属性服务
StartPropertyService(&property_fd);
...
//Step 5.加载开机rc文件
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
...
//Step 6.设置进程优先级,主进程不能退出
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
...
}
return 0;
}
Step 1. 初始化log,该阶段log打印如下:
[ 3.226672]c0 init: init second stage started!
Step 2. 属性服务初始化,读取默认属性配置。获取system/build.prop、vendor/build.prop、/odm/build.prop、/product/build.prop等其他build.prop属性,并加载到properties map结构中,然后通过MMAP映射到全局内存中,供所有进程调用; Step 3. 挂载其他文件系统,如/apex。挂载第二阶段相关的文件系统; Step 4. 启动属性服务。创建socket,处理客户端发来的请求,决定是更新属性值还是新增属性值; Step 5. 加载开机rc文件,rc文件的加载顺序如下; 。
[ 3.481952]c0 init: Parsing file /system/etc/init/hw/init.rc...
[ 3.489693]c0 init: Parsing file /init.environ.rc...
[ 3.490110]c0 init: Parsing file /system/etc/init/hw/init.usb.rc...
[ 3.491867]c0 init: Parsing file /init.sl8541e_1h10_32b.rc...
[ 3.493283]c0 init: Parsing file /vendor/etc/init/hw/init.sl8541e_1h10_32b.rc...
[ 3.494372]c0 init: Parsing file /vendor/etc/init/hw/init.sl8541e_1h10_32b.usb.rc...
[ 3.509664]c0 init: Parsing file /vendor/etc/init/hw/init.ram.rc...
...
解析init.rc会把一条条命令映射到内存中,然后依次启动,启动顺序如下:
on on early-init:在初始化早期阶段触发
on init:在初始化阶段触发
on late-init:在初始化晚期阶段触发
on boot/charger:当系统启动/充电时触发
on property:当属性值满足条件时触发
Step 6. 设置进程优先级,主进程不能销毁和退出.
(1)rc文件编译 init.rc文件是一个配置文件,最终会被打包到该目录:/system/etc/init/hw/ 。
@system\core\rootdir\Android.bp
prebuilt_etc {
name: "init.rc",
src: "init.rc",
sub_dir: "init/hw",
required: [
"fsverity_init",
"platform-bootclasspath",
],
}
(2)init.rc 由上一节我们知道,当init程序启动到第二阶段时候,会去加载rc文件,且最开始加载的rc文件是:system/etc/init/hw/init.rc 。
@system\core\rootdir\init.rc
...
import /system/etc/init/hw/init.${ro.zygote}.rc//导入zygote的rc文件,ro.zygote属性可根据系统读取
...
# Mount filesystems and start core system services.
on late-init
...
# Now we can start zygote for devices with file based encryption
trigger zygote-start //在开机初始化晚期阶段,触发zygote启动
...
# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device's init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
(3)init.zygote32.rc 启动 app_process,并改名为zygote,使用:-Xzygote、/system/bin、–zygote和–start-system-server等参数,同时重启了audioserver、cameraserver、media等服务.
@system\core\rootdir\init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
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
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
(4)zygote启动log 在解析完rc文件后,会启动对应的服务,zygote启动log如下:
[ 7.916749]c3 init: starting service 'zygote'...
(1)zygote编译文件 。
@frameworks\base\cmds\app_process\Android.bp
cc_binary {
name: "app_process",
srcs: ["app_main.cpp"],
...
}
(2)app_process启动 zygote是一个名为zygote的app_process进程,解析rc文件参数,启动ZygoteInit.
int main(int argc, char* const argv[])
{
...
while (i < argc) {//读取rc文件传进来的参数
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
}
...
}
...
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//启动ZygoteInit
}
...
}
(3)AndroidRuntime 通过反射去启动ZygoteInit的main函数,相关引用如下:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
ALOGD(">>>>>> START %s uid %d <<<<<<\n",
className != NULL ? className : "(unknown)", getuid());
...
//通过反射启动函数
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
...
}
当前流程会打印如下log:
01-01 08:00:08.538 387 387 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<
(1)ZygoteInit 。
@frameworks\base\core\java\com\android\internal\os\ZygoteInit.java
public static void main(String[] argv) {
...
try {
//Step 1. 预加载资源文件
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
}
...
//Step 2.注册Zygote的socket监听接口
zygoteServer = new ZygoteServer(isPrimaryZygote);
//Step 3.创建system_server进程
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
//Step 4.主线程loop消息循环
caller = zygoteServer.runSelectLoop(abiList);
}
...
}
Step 1. 提前加载类,加载系统资源(如一些公共的库、SDK等),这样当程序被fork处理后,应用的进程内已经包含了这些系统资源,大大节省了应用的启动时间。 Step 2. 注册Zygote的socket监听接口,用来接收启动应用程序的消息; Step 3. frok出SystemServer进程; Step 4. 主线程loop消息循环 。
(2)forkSystemServer fork出SystemServer进程,并且去调用SystemServer进程的main函数 。
@frameworks\base\core\java\com\android\internal\os\ZygoteInit.java
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
...
/* Hardcoded command line to start the system server */
String[] args = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
+ "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
"com.android.server.SystemServer",
};//启动SystemServer相关参数
...
try {
...
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids,
parsedArgs.mRuntimeFlags,
null,
parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);//fork出SystemServer进程
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {
...
return handleSystemServerProcess(parsedArgs);//通过反射调用SystemServer进程的main函数
}
return null;
}
(3)handleSystemServerProcess 此函数为调用SystemServer进程,最终会通过反射调用SystemServer进程的main函数,其方法调用栈如下: handleSystemServerProcess()->ZygoteInit.zygoteInit()->RuntimeInit.applicationInit()->RuntimeInit.findStaticMain() 。
@frameworks\base\core\java\com\android\internal\os\RuntimeInit.java
protected static Runnable findStaticMain(String className, String[] argv,
ClassLoader classLoader) {
Class<?> cl;
try {
cl = Class.forName(className, true, classLoader);//反射调用类:com.android.server.SystemServer
}
...
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });//反射调用方法:main
}
...
return new MethodAndArgsCaller(m, argv);
}
Android系统在启动的时候,在启动两个重要的进程,一个是Zygote进程,另一个是由zygote进程fork出来的system_server进程。SystemSever负责启动系统的各项服务,Android系统中Java世界的核心Service都在这里启动.
由上一章节我们知道,SystemServer的入口函数是main方法,如下:
@frameworks\base\services\java\com\android\server\SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
...
Slog.i(TAG, "Entered the Android system server!");
...
// Start services.
try {
t.traceBegin("StartServices");
startBootstrapServices(t);//引导服务
startCoreServices(t);//核心服务
startOtherServices(t);//其他服务
}
...
// Loop forever.
Looper.loop();//主线程循环队列
throw new RuntimeException("Main thread loop unexpectedly exited");
}
SystemServer进程成功启动,会打印如下log:
01-01 08:00:21.430 1001 1001 I SystemServer: Entered the Android system server!
在一系列的java服务中,可以分为三类:系统boot级别服务、核心服务、其他服务,其对应如下:
类型 | 服务 | 备注 |
startBootstrapServices | ActivityManagerService | 简称AMS,管理Android四大组件的生命周期 |
PowerManagerService | 电源管理服务 | |
PackageManagerService | 简称PMS,用于APK的安装、卸载、权限验证等 | |
UserManagerService | 用户创建、删除、查询等 | |
... | ... | |
startCoreServices | BatteryService | 对设备电池状态进行监控 |
UsageStatsService | 收集App的使用频率等信息 | |
... | ... | |
startOtherServices | CameraService | 管理设备相机功能 |
BluetoothService | 管理蓝牙服务 | |
WindowManagerService | 简称WMS,管理窗口服务 | |
FingerPrintService | 管理指纹服务 | |
... | ... |
一般情况下,Android原生的软体会包含两个home进程,一个是Settings进程的Fallbackhome,一个是Launcher进程.
(1)FallbackHome编译 Fallbackhome位于Settings进程内,随系统启动时被拉起.
@packages\apps\Settings\Android.bp
android_app {
name: "Settings",
defaults: ["platform_app_defaults"],
platform_apis: true,
certificate: "platform",
system_ext_specific: true,
privileged: true,
...
}
(2)Launcher进程编译 Launcher进程主要用于显示App的界面信息。随着Android版本的迭代、以及层出不穷的产品,目前Android的Launcher版本较多,如Home、Launcher2、Launcher3、Launcher3QuickStep,要根据自身项目的配置,找到对应的Launcher应用.
@packages\apps\Launcher3\Android.mk
include $(CLEAR_VARS)
...
LOCAL_PACKAGE_NAME := Launcher3QuickStepGo
LOCAL_PRIVILEGED_MODULE := true
LOCAL_SYSTEM_EXT_MODULE := true
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep
...
FallbackHome是系统由未解密到解密过程的一个过度界面,只要用户把系统解锁过一次后,FallbackHome收到解锁广播就会退出,而WMS检测到当前Acitivity栈是空的,进而启动真正的Launcher。由于FallbackHome没有界面,所以可能会出现一个问题,home进程切换时会出现空白界面,接下来才是显示Launcher的一个图标界面.
@packages\apps\Settings\src\com\android\settings\FallbackHome.java
protected void onCreate(Bundle savedInstanceState) {
...
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));//注册ACTION_USER_UNLOCKED广播
maybeFinish();
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
maybeFinish();//接收ACTION_USER_UNLOCKED广播
}
};
private void maybeFinish() {
if (getSystemService(UserManager.class).isUserUnlocked()) {
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME);
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);//查询home包名信息,此处一般是返回Launcher的信息
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
mHandler.sendEmptyMessageDelayed(0, 500);//间隔500ms轮询
} else {
Log.d(TAG, "User unlocked and real home found; let's go!");
getSystemService(PowerManager.class).userActivity(
SystemClock.uptimeMillis(), false);
finish();//结束当前Activity,启动Launcher应用
}
}
}
(1) 启动home进程 刚开机时,SystemSever进程会启动WMS服务,如果WMS未检测到Activity栈有任务时,会启动一个默认的home进程,此进程即FallbackHome.
@frameworks\base\services\core\java\com\android\server\wm\RootWindowContainer.java
void startHomeOnEmptyDisplays(String reason) {
forAllTaskDisplayAreas(taskDisplayArea -> {
if (taskDisplayArea.topRunningActivity() == null) {
startHomeOnTaskDisplayArea(mCurrentUser, reason, taskDisplayArea,
false /* allowInstrumenting */, false /* fromHomeKey */);
}
});
}
(2) ACTION_USER_UNLOCKED广播发送 系统解锁时会发送该广播,相关代码如下:
@frameworks\base\services\core\java\com\android\server\am\UserController.java
void finishUserUnlocked(final UserState uss) {
...
if (!mInjector.getUserManager().isPreCreated(userId)) {
// Dispatch unlocked to external apps
final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
unlockedIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), userId);
}
...
}
串口+logcat:https://download.csdn.net/download/u013320490/88800008 。
本文章涉猎Android多个层级,旨在梳理整体流程,对Android设备的启动有一个感性的认识,能够达到一定逻辑自洽。 Android的每个模块、每个进程、每行代码都有其深度,没有细细揣测与推敲,是有点"亵渎"了,他日必对其中感兴趣模块加以研究,respect! 。
最后此篇关于Android开机流程介绍的文章就讲到这里了,如果你想了解更多关于Android开机流程介绍的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
晚上在 QQ 上看到昵称为“乱码”的好友回答了搜搜问问里一个问题: 在VBS中有办法定义字节数组么? 在VBS中有办法定义字节数组么?就是字节子类型数组(VarType是8209的那种)注意不是V
例如,员工管理应用程序可能包括一个EmPloyee 类。然后可以用这个类来创建和维护特定实例,比如Gonn和Sally。 根据预定义的类创建对象常称为类的实例化(class insta
在自然语言中,我们理解抽象的概念是,一个物体的一种大的描述,这种描述对某类物体来说是共有的特性。那么在PHP中也是一样的,我们把一个类进行抽象,可以指明类的一般行为,这个类应该是一个模板,它指示它的
DBA_2PC_PENDING Oracle会自动处理分布事务,保证分布事务的一致性,所有站点全部提交或全部回滚。一般情况下,处理过程在很短的时间内完成,根本无法察觉到。但是,如果在commit或
目录 计算过程 投影分量计算 假设你有一家理发店,已经记录了过去一年中所有顾客的头发长度和发型偏好的数据。现在你想从这些数据中提取一些主要的信息,比如顾客最常
Object.defineProperty函数会直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象。 一、简单使用 const obj = {} Object.defineP
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
SPL官网 http://www.scudata.com.cn/ 介绍 业务逻辑经常包含较复杂的流程和计算,同时涉及数据库的读写。由于授权麻烦、影响数据库安全、无法迁移、技术要求高、编写困难等原因,很
一 点睛 Thrift 是一歀基于 CS 架构的 RPC 框架,最初由 Facebook 研发,2008 年转入 Apache 组织。开发人员可以使用 Thrift 提供的 IDL(接口定义语言)来定
数据库应用程序与主应用程序分开存在,并存储数据集合。 每个数据库都使用一个或多个API来创建,访问,管理,搜索和复制其包含的数据。 数据库还使用非关系数据源,例如对象或文件。 然而,数据库证明是大数
介绍 Ant是一个 Apache 基金会下的跨平台的基于 Java 语言开发的构件工具。在我们详细了解 Apache Ant 之前, 让我们来讲解为什么构建工具是需要最先了解的。 构建工具的需求
我现在正在尝试学习ocaml,并希望从一个小程序开始,生成所有位组合: [“0”,“0”,“0”] [“0”,“0”,“1”] [“0”,“1”,“0”] ... 等等 我的想法是下面的代码: let
我正在做我的介绍 C 类(class)作业,我的任务是执行以下任务...... 为一个函数编写代码,该函数通过值接收两个参数(a 和 b)并通过引用具有另外两个参数(c 和 d)。所有参数都是双倍的。
我希望提供有关我网站内容的快速演示,以及如何在用户访问我的页面后立即以正确的方式使用它们。我希望使用顶部的弹出式窗口进行演示。 我的意思是小信息框,一个接一个地通知用户各个步骤。任何人都可以帮助我如何
与C、Java等语言一样,JavaScript中可以用&&、||、!三个逻辑判断符来对boolean值进行逻辑判断。与C、Java不同的是,JavaScript中逻辑与(&&
JavaScript中,==与===操作符均可用于判断两个值是否相等;不同之处在于,如果进行判断的两个值类型不一致,===操作符会直接返回false,而==操作符则会在类型转换后再进行判断。详细的判
JavaScript中,object转换为boolean的操作非常简单:所有的object转换成boolean后均为true;即使是new Boolean(false)这样的object在转换为bo
在android开发中,当不满足触发条件就按返回键的时候,就要对此进行检测。尤其是当前Activity需要往前一个Activity传送消息时。即Activity1跳转到Activity3如果采用的是
背景 当要求系统启动一个应用程序时,系统会先查找当前命令是否是内部命令,若不是,则在当前目录下查找,如果仍没有找到,则在系统变量 Path 指定的路径去查找。JDK(Java Developmen
概述 想做一个微信的公众平台,阅读了微信官方给的网址接入的示例代码,发现有个问题好像一直都是半知半解的,就是在类里边直接使用$_GET。仔细查了下关于这方面的知识,发现PHP中这部分的基础知识掌握
我是一名优秀的程序员,十分优秀!