过去的这一年多里,做过VR,AR,现在又回到了Android客户端,开始做AI相关的应用,Android方面的知识也开始重新捡了起来,所以在这儿撸一遍Android的底层源码,巩固加强一下在自己Android方面的技能。
在撸Android的源码之前,我们先要有两件事要准备,第一件事是有一份Android的源码,我一般是通过线上(http://androidxref.com/)查看源码的,这个网站上有从2.3到8.0的所有源码,第二件事就是鸟瞰一遍Android的系统架构。
这是一张官方给出的系统架构图,通过上图我们可以知道,Android底层是Linux内核,通过Android RunTime和一些Libraries的支撑,运行Framework层,我们的app应用便是在FrameWork层上开发。Android的源码是庞大而复杂的,为了能体系的看完Android源码,我开始从最底层一点点的寻迹向上的过一遍,抛开Linux源码,我们第一步需要了解的是Linxu内核是如何启动Android系统的
我们先对启动流程有一个大致的了解,如下
所以要了解Android底层是如何启动的,我们就得先从Init进程开始,下面就来讲讲这个进程
通过ps |grep 指令,我们可以看到init进程的进程号为1,它是Linux内核启动的第一个用户级进程,下面我们就来看看init进程的源码
init进程的源码位于/system/core/init/init.cpp,这是一个C++文件,该代码所做的主要有六件事情
对init所做的事情有大致的了解后我们直接来看看main函数的源码
int main(int argc, char** argv) {
//————————————————————————————第1部分——————————————————————————————————————————————
/*
/ueventd主要是负责建立Anroid中设备节点文件、权限设定等一些列工作。服务通过使用uevent,监控驱动发送的消息,做进一步处理
/设备节点文件是设备驱动的逻辑文件,应用程序使用设备节点文件来访问驱动程序,我们添加设备,如U盘等,都会响应到ueventd进程,关于ueventd进程,之后在细说,在这儿大概了解,并不影响主体流程
*/
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//————————————————————————————第2部分——————————————————————————————————————————————
/*
/Watchdog是一个查错进程,时时去检查系统是否出错,如果出错会杀掉zygote进程,让系统重启
*/
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
//————————————————————————————第3部分——————————————————————————————————————————————
// 设置允许当前进程创建文件或者目录最大可操作的权限
umask(0);
add_environment("PATH", _PATH_DEFPATH);
//————————————————————————————第4部分——————————————————————————————————————————————
/* 挂载了tmpfs,devpts,proc,sysfs这4类文件系统
/①tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
/②devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
/③proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
*/
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
//————————————————————————————第5部分——————————————————————————————————————————————
// 屏蔽标准的输入输出,并且初始化android自己的log系统
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
//————————————————————————————第6部分——————————————————————————————————————————————
/*下面这段代码主要是初始化Android系统所需的属性,属性包括设备名字,蓝牙名字,编译信息,网络dns地址,以及其他的一些基本信息等,初始化的方式就是创建一个tmfs文件系统文件/dev/properties
/property_init初始化属性域。在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。
/process_kernel_dt初始化DT(Device Tree)的属性集,即访问“/proc/device-tree/firmware/android ”目录,先看compatible文件内容是否是“android,firmware”,然后这个目录下每个文件名作为属性,文件里面的内容作为属性值。
/process_kernel_cmdline处理内核命令行,读取“/proc/cmdline”文件中的内容,然后调用import_kernel_nv()函数设置系统属性,此处只将“/proc/cmdline”中前缀为“androidboot.”的值设置到属性中。DT属性集优先级高于此处设置的属性。
/export_kernel_boot_props设置内核启动的变量,根据系统已有的属性设置,如有值则保持不变,否则设置成初始值。
*/
if (!is_first_stage) {
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
property_init();
process_kernel_dt();
process_kernel_cmdline();
export_kernel_boot_props();
}
//————————————————————————————第7部分——————————————————————————————————————————————
/* 创建SELINUX(SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统)
/ restorecon命令用来恢复SELinux文件属性即恢复文件的安全上下文
*/
selinux_initialize(is_first_stage);
if (is_first_stage) {
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon("/property_contexts");
restorecon_recursive("/sys");
//————————————————————————————第8部分——————————————————————————————————————————————
/*epoll一个fd,这个fd是一个通信套接字,当init初始化完毕后,为成为一个不中断for循环进程,即守护进程,这个fd便不断的监听信号,我们将这个fd看成是socket会更容易理解一点
/ epoll是Linux内核为处理大批量文件描述符而作了改进的poll,对epoll不清楚的可以查查相关资料
/*/
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
//————————————————————————————第9部分——————————————————————————————————————————————
/*
/在Android中,当一个进程退出(exit())时,会向它的父进程发送一个SIGCHLD信号。父进程收到该信号后,会释放分配给该子进程的系统资源;并且父进程需要调用wait()或waitpid()等待子进程结束。如果父进程没有做这种处理,且父进程初始化时也没有调用signal(SIGCHLD, SIG_IGN)来显示忽略对SIGCHLD的处理,这时子进程将一直保持当前的退出状态,不会完全退出。这样的子进程不能被调度,所做的只是在进程列表中占据一个位置,保存了该进程的PID、终止状态、CPU使用时间等信息
/所以这个方法是初始化SIGCHLD信号处理,SIGCHLD也是通过上面创建的fd来接收处理的,具体的初始化我们先略过,之后再详说
*/
signal_handler_init();
//————————————————————————————第10部分——————————————————————————————————————————————
//加载Android系统根目录的default.prop文件,该文件中包含了一些重要属性,在前面的代码中已经进行过属性初始化工作,这儿把文件中的属性值解析并发布到系统中
property_load_boot_defaults();
export_oem_lock_status();
start_property_service();
//————————————————————————————第11部分——————————————————————————————————————————————
//从这里起,便开始了对init.rc文件的解析和触发工作,init.rc是初始化工作的配置文件,以Android Init Language作为语法
/关于init.rc的具体情况,可以参考http://qiangbo.space/2017-01-28/AndroidAnatomy_Init/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io这篇博客
/我们在这里只需要知道zygote的启动便是配置在init.rc文件中的,zygote、audioserver、cameraserver、media、netd、wificond等底层server都会在这儿被解析出来,并将解析的数据通过socket传输,后面的for循环中会处理解析出来的action,关于init.rc的解析和触发,之后在详讲
//这里将Action的function_map_替换为BuiltinFunctionMap
//下文将通过BuiltinFuntionMap的map方法,获取keyword对应的处理函数
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
//构造出解析文件用的parser对象
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service",std::make_unique());
parser.AddSectionParser("on", std::make_unique());
parser.AddSectionParser("import", std::make_unique());
//开始实际的解析过程
parser.ParseConfig("/init.rc");
// 获取ActionManager对象,需要通过am对命令执行顺序进行控制
ActionManager& am = ActionManager::GetInstance();
// 添加触发器early-init,执行on early-init内容
am.QueueEventTrigger("early-init");
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
// 添加触发器init,执行on init内容,主要包括创建/挂在一些目录,以及symlink等
am.QueueEventTrigger("init");
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
std::string bootmode = property_get("ro.bootmode");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
//————————————————————————————第12部分——————————————————————————————————————————————
/*处理添加到运行队列的事件,当Init进程处理完,它就会进入一个循环化身为守护进程,处理socket接收到的服务
/当while循环不断调用ExecuteOneCommand函数时,将按照trigger表的顺序,依次取出action链表中与trigger匹配的action。 每次均执行一个action中的一个command对应函数(一个action可能携带多个command)。 当一个action所有的command均执行完毕后,再执行下一个action。 当一个trigger对应的action均执行完毕后,再执行下一个trigger对应action。
*/
while (true) {
if (!waiting_for_exec) {
//执行该command对应的处理函数
am.ExecuteOneCommand();
//restart_processes()去重启在service_list列表中带有所有带有SVC_RESTARTING标志(该标志是在wait_for_one_process()处理中设置的),最终会调用service_start()函数去重新启动一个退出的服务
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (am.HasMoreCommands()) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
通过查看init进程的源码,我们对init的初始化过程有了一个大致的了解,这个时候,zygote这个service 已经被启动了,我们先看一下zygote的配置文件,伴随zygote一起启动的还有audioserver,cameraserver,media等底层server
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
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
现在我们可以来看看zygote了,它的源码位于/frameworks/base/cmds/app_process/app_main.cpp,这个文件的main方法大致如下
int main(int argc, char* const argv[]){
……
//AppRuntime继承自AndroidRuntime
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
……
//如果init.rc配置中有--Zygote,参数为com.android.internal.os.ZygoteInit
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (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.");
return 10;
}
}
可以看到,这个main方法主要做的就两件事
所以接下来我们接着分析AppRuntime的源码,在分析AppRuntime前,先大致了解一下zygote的启动时序流程,如下图
现在再来看看AppRuntime,也就是AndroidRuntime的源码,它的源码位于/frameworks/base/core/jni/AndroidRuntime.cpp,我们来看看它的start方法
void AndroidRuntime::start(const char* className, const Vector& options, bool zygote){
……
//1调用startVm方法来启动虚拟机
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
//2调用startReg来注册Android JNI函数
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
……
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
//3调用CallStaticVoidMethod方法来调用ZygoteInit类的main方法
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 {
//通过JNI调用Java函数,进入Java世界
env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
}
……
}
启动虚拟机和注册Android JNI函数的详细过程之后在分析,这里我们只需要知道大致过程便可,由于这儿已经注册了JNI函数,之后的java代码只需要通过JNI的方法就可以调用这些Native方法,在第三步中,它会根据输入的参数com.android.internal.os.ZygoteInit和RuntimeInit,通过反射以及JNI,找到相应的类,并调用类的main方法,ZygoteInit类在这个时候变调用,这个时候便进入了java层。
接下来在看看ZygoteInit.java这个类,它的源码位于/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
这个类主要做的事情有以下几件
①调用registerZygoteSocket函数创建了一个socket接口,用来和ActivityManagerService通讯
②调用startSystemServer函数来启动SystemServer组件
③调用runSelectLoopMode函数进入一个无限循环在前面创建的socket接口上等待ActivityManagerService请求创建新的应用程序进程,其过程如下
我们来看看main方法里面的源码
public static void main(String argv[]) {
//Android7.0在zygoteInit中对线程的使用做了限制,不让在zygote中新建线程
//主要是由于担心用户新建线程提高预加载速度 ,但是可能没做好同步工作, 当有的应用需要预加载的资源,但是多线程情况下还没有加载,发生问题
ZygoteHooks.startZygoteNoThreadCreation();
try {
……
// 1、注册监听
registerZygoteSocket(socketName);
// 2、预加载一些关键的类
preload();
……
// 3、启动system_server进程
if (startSystemServer) {
startSystemServer(abiList, socketName);
}
//4监听其他进程发出来的请求
runSelectLoop(abiList);
//关闭socket
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}
至此Android的启动流程也就分析完成了,我们回过头再来看看Android的启动流程图,心中对各种流程的执行顺序也就清晰了