网站被k后换域名 做301之外_之前发的外链怎么办,小型企业网络设计方案报告,网站开发外包 合同,钦州市网站建设当我们买了一个手机或者平板#xff0c;按下电源键的那一刻#xff0c;到进入Launcher#xff0c;选择我们想要使用的某个App进入#xff0c;这个过程中#xff0c;系统到底在做了什么事#xff0c;伙伴们有仔细的研究过吗#xff1f;可能对于Framework这块晦涩难懂的专…当我们买了一个手机或者平板按下电源键的那一刻到进入Launcher选择我们想要使用的某个App进入这个过程中系统到底在做了什么事伙伴们有仔细的研究过吗可能对于Framework这块晦涩难懂的专题比较枯燥那么从这篇文章开始将会对Framework相关的知识进行全面的剖析先从系统启动流程开始。
1 系统启动流程分析
当我们打开电源键的时候硬件执行的第一段代码就是BootLoader会做一些初始化的操作例如初始化CPU速度、内存等。然后会启动第一个进程idle进程pid 0这个进程是在内核空间初始化的。
idle进程作为系统启动的第一个进程它会创建两个进程系统创建进程都是通过fork的形式完成其中在Kernel空间会创建kthreadd进程还有一个就是在用户空间创建init进程pid 1这个进程想必我们都非常熟悉了。
像我们启动app或者系统应用都需要zygote进程来孵化进程那么zygote进程也是通过init进程来创建完成的像系统服务的创建和启动是通过system_server进程来管理而system_server进程则是由zygote进程fork创建。
所以通过下面这个图我们就能大致了解从电源按下的那一刻到应用启动的流程。 如需完整版Android 学习资料 请点击此处免费获取 接下来我们分析每个进程启动流程。
2 C/C Framework Native层
2.1 init进程启动分析
通过上面的流程图我们知道init进程是通过内核空间启动的所以我们看一下内核层的代码。
kernel_common/init/main.c
在内核层的main.c文件中有一个静态方法kernel_init这个方法会首先执行。
//
static int kernel_init(void *);
static int __ref kernel_init(void *unused)
{int ret;kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();kprobe_free_init_mem();ftrace_free_init_mem();free_initmem();mark_readonly();/** Kernel mappings are now finalized - update the userspace page-table* to finalize PTI.*/pti_finalize();system_state SYSTEM_RUNNING;numa_default_policy();rcu_end_inkernel_boot();//初始化文件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))return 0;panic(No working init found. Try passing init option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.);
}
在kernel_init方法中我们看到会调用try_to_run_init_process函数去加载一些文件例如我们需要关心的/bin/init这个文件就是在设备system/bin/init下的。 init可以看做是一个模块它与install、gzip等系统能力属于平级都是通过系统源码编译过来的一种二进制文件那么在这个文件加载的时候具体执行的是哪些代码呢这个需要我们看这个模块具体是怎么编译出来的需要看下Android.bp文件。
cc_binary {name: init_second_stage,recovery_available: true,stem: init,defaults: [init_defaults],static_libs: [libinit],srcs: [main.cpp],symlinks: [ueventd],target: {platform: {required: [init.rc,ueventd.rc,e2fsdroid,extra_free_kbytes,make_f2fs,mke2fs,sload_f2fs,],},recovery: {cflags: [-DRECOVERY],exclude_static_libs: [libxml2,],exclude_shared_libs: [libbinder,libutils,],required: [init_recovery.rc,ueventd.rc.recovery,e2fsdroid.recovery,make_f2fs.recovery,mke2fs.recovery,sload_f2fs.recovery,],},},visibility: [//packages/modules/Virtualization/microdroid],
}
当系统编译init模块的时候对应的srcs源码为main.cpp也就是说系统system/bin/下的init模块入口函数为main.cpp当kernel内核执行kernel_init函数的时候其实就会执行init模块的main.cpp。
system/core/init/main.cpp
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)__asan_set_error_report_callback(AsanReportCallback);
#endifif (!strcmp(basename(argv[0]), ueventd)) {return ueventd_main(argc, argv);}if (argc 1) {if (!strcmp(argv[1], subcontext)) {android::base::InitLogging(argv, android::base::KernelLogger);const BuiltinFunctionMap function_map;return SubcontextMain(argc, argv, function_map);}if (!strcmp(argv[1], selinux_setup)) {return SetupSelinux(argv);}if (!strcmp(argv[1], second_stage)) {return SecondStageMain(argc, argv);}}return FirstStageMain(argc, argv);
}
所有函数的入口都是main函数所以看下main函数中做了什么事。首先我们看到当第一次进来时会执行FirstStageMain这个函数如果再次进入此时就会走SecondStageMain。那么我们首先进入第一阶段看系统做了什么事。
system/core/init/ first_stage_init.cpp
这个类中我们找一些关键的代码来看一下
int FirstStageMain(int argc, char** argv) {if (REBOOT_BOOTLOADER_ON_PANIC) {//核心代码1//init如果挂掉就会重启InstallRebootSignalHandlers();}boot_clock::time_point start_time boot_clock::now();std::vectorstd::pairstd::string, int errors;
#define CHECKCALL(x) \if (x ! 0) errors.emplace_back(#x failed, errno);// Clear the umask.umask(0);//核心代码2CHECKCALL(clearenv());CHECKCALL(setenv(PATH, _PATH_DEFPATH, 1));// Get the basic filesystem setup we need put together in the initramdisk// on / and then well let the rc file figure out the rest.CHECKCALL(mount(tmpfs, /dev, tmpfs, MS_NOSUID, mode0755));CHECKCALL(mkdir(/dev/pts, 0755));CHECKCALL(mkdir(/dev/socket, 0755));CHECKCALL(mount(devpts, /dev/pts, devpts, 0, NULL));
#define MAKE_STR(x) __STRING(x)CHECKCALL(mount(proc, /proc, proc, 0, hidepid2,gid MAKE_STR(AID_READPROC)));
#undef MAKE_STR// Dont expose the raw commandline to unprivileged processes.CHECKCALL(chmod(/proc/cmdline, 0440));gid_t groups[] {AID_READPROC};CHECKCALL(setgroups(arraysize(groups), groups));CHECKCALL(mount(sysfs, /sys, sysfs, 0, NULL));CHECKCALL(mount(selinuxfs, /sys/fs/selinux, selinuxfs, 0, NULL));CHECKCALL(mknod(/dev/kmsg, S_IFCHR | 0600, makedev(1, 11)));if constexpr (WORLD_WRITABLE_KMSG) {CHECKCALL(mknod(/dev/kmsg_debug, S_IFCHR | 0622, makedev(1, 11)));}CHECKCALL(mknod(/dev/random, S_IFCHR | 0666, makedev(1, 8)));CHECKCALL(mknod(/dev/urandom, S_IFCHR | 0666, makedev(1, 9)));// This is needed for log wrapper, which gets called before ueventd runs.CHECKCALL(mknod(/dev/ptmx, S_IFCHR | 0666, makedev(5, 2)));CHECKCALL(mknod(/dev/null, S_IFCHR | 0666, makedev(1, 3)));// These below mounts are done in first stage init so that first stage mount can mount// subdirectories of /mnt/{vendor,product}/. Other mounts, not required by first stage mount,// should be done in rc files.// Mount staging areas for devices managed by vold// See storage config details at http://source.android.com/devices/storage/CHECKCALL(mount(tmpfs, /mnt, tmpfs, MS_NOEXEC | MS_NOSUID | MS_NODEV,mode0755,uid0,gid1000));// /mnt/vendor is used to mount vendor-specific partitions that can not be// part of the vendor partition, e.g. because they are mounted read-write.CHECKCALL(mkdir(/mnt/vendor, 0755));// /mnt/product is used to mount product-specific partitions that can not be// part of the product partition, e.g. because they are mounted read-write.CHECKCALL(mkdir(/mnt/product, 0755));// /apex is used to mount APEXesCHECKCALL(mount(tmpfs, /apex, tmpfs, MS_NOEXEC | MS_NOSUID | MS_NODEV,mode0755,uid0,gid0));// /debug_ramdisk is used to preserve additional files from the debug ramdiskCHECKCALL(mount(tmpfs, /debug_ramdisk, tmpfs, MS_NOEXEC | MS_NOSUID | MS_NODEV,mode0755,uid0,gid0));
#undef CHECKCALLSetStdioToDevNull(argv);// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually// talk to the outside world...//初始化日志模块InitKernelLogging(argv);//......const char* path /system/bin/init;const char* args[] {path, selinux_setup, nullptr};execv(path, const_castchar**(args));// execv() only returns if an error happened, in which case we// panic and never fall through this conditional.PLOG(FATAL) execv(\ path \) failed;return 1;
}
2.2 init进程启动总结
到此init进程的主要任务就完成了我们总结一下init进程主要干了什么事
1init进程是由内核进程idle进程fork出来的因此init进程初始化也是由kernel启动的即调用了kernel_int方法此时会从系统的system/bin文件夹下查找init二进制文件
2init二进制文件是通过Android.bp脚本编译从bp文件中可以看到init关联的srcs为main.cpp也就是system/core/init/main.cpp文件其入口为main函数
3当进入到main函数中时首先会执行FirstStageMain函数在这个函数中主要是注册signal挂载文件或者创建文件进行一些初始化操作然后再次进入到main函数中
4此时进入main函数会执行SetupSeLinux这里主要做linux的一些安全策略然后会再次执行init的main函数
5此时会执行SecondStageMain函数在这个函数中首先会初始化属性域注册到enpoll中然后解析init.rc文件随后进入while循环继续执行init.rc中的command指令。
3 Java Framework层
过了C/C源码真正到.java文件结尾的源码就是Zygote进程是由init进程fork出来的也就是说Zygote才是Java进程的鼻祖。
3.1 init.rc文件
前面我们提到了在SecondStageMain函数中会进行init.rc文件的解析那么init.rc到底是什么呢你可以理解为就是一个脚本文件只不过在脚本文件中需要系统执行指令。
system/core/rootdir/init.rc
import /init.${ro.zygote}.rc# Mount filesystems and start core system services.
on late-init//......# Now we can start zygote for devices with file based encryption trigger zygote-starton zygote-start property:ro.crypto.stateunencrypted# A/B update verifier that marks a successful boot.exec_start update_verifier_nonencryptedstart netdstart zygotestart zygote_secondary
从init.rc文件中我们可以看到当在SecondStageMain中解析init.rc文件的时候就会启动Zygote进程所以这个时候才会真正进入到了Java的进程。
从脚本中看start zygote最终会执行import进来的init.zygote.rc文件。
3.2 Zygote启动流程 所以当启动Zygote进程的时候如果是32位的操作系统那么就会解析init.zygote32.rc文件
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks对于.rc文件的语法这里简单介绍一下对于service命令具体格式为
service name pathname [args......]
name服务的名称
pathname可执行的二进制文件路径service的文件路径
args要启动service所要带的参数这里我们会看到启动Zygote服务进程会执行/system/bin/app_process二进制文件对于二进制文件是通过Android.bp来编译生成的我们看下对应的文件。
cc_binary {name: app_process,srcs: [app_main.cpp],multilib: {lib32: {suffix: 32,},lib64: {suffix: 64,},},
}我们可以看到对于app_process可执行文件其函数入口为app_main.cpp文件也就是在启动Zygote进程之后就会进入到app_main.cpp。
3.3 native启动Zygote进程总结
至此在native层的Zygote进程就已经启动完成了我们来简单总结一下当解析init.rc文件的时候init进程就会fork出zygote进程。
此时系统执行init.rc中的脚本执行start zygote时会执行import进来的init.zygote.rc脚本此时会根据系统版本决定执行32位的脚本或者64位的脚本。当执行service zygote命令时会执行系统system/bin下的二进制执行文件app_process会进入到app_main.cpp中的main函数。
此时会调用AndroidRuntime的start方法执行ZygoteInit.java.main方法在此之前会在native层创建VM虚拟机并注册JNI函数保证C和Java层之前的双向通信调用。
3.4 Java层的Zygote启动
通过前面我们知道native层启动Zygote时会调用Java层的ZygoteInit.java.main方法我们看下这个类。
public static void main(String[] argv) {ZygoteServer zygoteServer null;//......Runnable caller;try {// ......boolean startSystemServer false;String zygoteSocketName zygote;String abiList null;boolean enableLazyPreload false;// 与nativ层一致也是在根据传入的属性给一些状态位赋值for (int i 1; i argv.length; i) {if (start-system-server.equals(argv[i])) {startSystemServer true;} else if (--enable-lazy-preload.equals(argv[i])) {enableLazyPreload true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {abiList argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {zygoteSocketName argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException(Unknown command line argument: argv[i]);}}// .....// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.//核心代码 1if (!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}// Do an initial gc to clean up after startupbootTimingsTraceLog.traceBegin(PostZygoteInitGC);gcAndFinalize();bootTimingsTraceLog.traceEnd(); // PostZygoteInitGCbootTimingsTraceLog.traceEnd(); // ZygoteInitZygote.initNativeState(isPrimaryZygote);ZygoteHooks.stopZygoteNoThreadCreation();//创建 Socket对象zygoteServer new ZygoteServer(isPrimaryZygote);//核心代码 2if (startSystemServer) {Runnable r forkSystemServer(abiList, zygoteSocketName, zygoteServer);// {code r null} in the parent (zygote) process, and {code r ! null} in the// child (system_server) process.if (r ! null) {r.run();return;}}Log.i(TAG, Accepting command socket connections);// The select loop returns early in the child process after a fork and// loops forever in the zygote.caller zygoteServer.runSelectLoop(abiList);} catch (Throwable ex) {Log.e(TAG, System zygote died with fatal exception, ex);throw ex;} finally {if (zygoteServer ! null) {zygoteServer.closeServerSocket();}}// Were in the child process and have exited the select loop. Proceed to execute the// command.if (caller ! null) {caller.run();}
}
在方法的开始有一个ZygoteServer对象它其实是一个Socket用于与各个进程间通信既然使用到进程间通信了为什么不使用Binder呢
不知有没有伙伴会考虑这个问题为什么要使用Socket呢例如AMS想要创建一个进程那么就会通知Zygote来孵化出一个进程此时创建进程就需要通过fork这种形式其实相当于是做了一次进程copy那么当前进程所有线程、对象都会被copy到新的进程那么此时线程就不再拥有线程的特性而是一个对象此时在子进程中如果调用线程的方法那么是无效的还有就是如果在父进程中某个线程持有一把锁那么在子进程中想要竞争这把锁对象但是这把锁可能永远无法被释放导致死锁的情况发生。
所以在Zygote进程中如果使用Binder因其内部是多线程组成的线程池会有发生死锁的可能性通过Socket进行进程间通信也是为了避免这种情况的发生。
3.5 Java层启动Zygote进程总结
当fork出system_server进程之后Java层的Zygote进程将会进入死循环接收消息并执行简单总结一下
1当在native层创建JVM并注册JNI函数之后就会执行Zygote.java.main方法进入到Java代码中
2在main方法中首先会解析传入的参数给一些标志位赋值然后会根据标志位进行判断是否支持预加载预加载包括但不限于classes、resources目的为了快速启动进程
3在预加载完成之后如有需要那么就会创建Socket连接然后调用forkSystemServer方法fork system_server进程最终调用的还是C层的函数调用系统的fork函数
4随后会调用ZygoteServer(scoket)的runSelectLoop方法开启死循环socket服务端会接收客户端发送的消息进行处理例如AMS想要创建一个进程。