• Android源码学习---init


    init,是linux系统中用户空间的第一个进程,也是Android系统中用户空间的第一个进程。

    位于/system/core/init目录下。

    分析init

    1. int main(int argc, char **argv)
    2. {
    3. //设置子进程退出的信号处理函数 sigchld_handler
    4. act.sa_handler = sigchld_handler;
    5. //解析init.rc文件
    6. parse_config_file("/init.rc");
    7. //通过这个函数读取/proc/cpuinfo得到机器的HardWare名,
    8. get_hardware_name();
    9. snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
    10. //解析这个和机器相关的配置文件
    11. parse_config_file(tmp);
    12. //执行各个阶段的工作 共分为4个阶段
    13. /**
    14. * ① early-init阶段完成的动作
    15. */
    16. action_for_each_trigger("early-init", action_add_queue_tail);
    17. drain_action_queue();
    18. //初始化和属性相关的资源
    19. property_init();
    20. /**INIT_IMAGE_FILE定义为“initlogo.rle”
    21. * 这个函数将加载这个文件作为系统开机的画面
    22. * 如果加载文件initlogo.rle失败就会打开/dev/tty0设备,将输出“ANDROID”的字样作为开机画面
    23. */
    24. if( load_565rle_image(INIT_IMAGE_FILE) ) {
    25. fd = open("/dev/tty0", O_WRONLY);
    26. if (fd >= 0) {
    27. const char *msg;
    28. msg = "\n"
    29. "\n"
    30. "\n"
    31. "\n"
    32. "\n"
    33. "\n"
    34. "\n" // console is 40 cols x 30 lines
    35. "\n"
    36. "\n"
    37. "\n"
    38. "\n"
    39. "\n"
    40. "\n"
    41. "\n"
    42. " A N D R O I D ";
    43. write(fd, msg, strlen(msg));
    44. close(fd);
    45. }
    46. }
    47. /**
    48. * ②init
    49. */
    50. action_for_each_trigger("init", action_add_queue_tail);
    51. drain_action_queue();
    52. //启动属性服务
    53. property_set_fd = start_property_service();
    54. /**
    55. * ③early-boot ④boot
    56. */
    57. action_for_each_trigger("early-boot", action_add_queue_tail);
    58. action_for_each_trigger("boot", action_add_queue_tail);
    59. drain_action_queue();
    60. //boot char是一个小工具 对系统性能进行分析 帮助提升系统的启动速度
    61. #if BOOTCHART
    62. bootchart_count = bootchart_init();
    63. if (bootchart_count < 0) {
    64. ……
    65. #endif
    66. /**
    67. * 进入init循环
    68. */
    69. for(;;) {
    70. int nr, i, timeout = -1;
    71. for (i = 0; i < fd_count; i++)
    72. ufds[i].revents = 0;
    73. drain_action_queue();
    74. //重启死亡的进程
    75. restart_processes();
    76. //调用poll等待一些事情的发生
    77. nr = poll(ufds, fd_count, timeout);

    总结:init工作主要先初始化两个配置文件,然后执行四个阶段的动作,接下来调用property_init相关的属性资源,并通过property_start_service启动属性服务,然后init将进入一个无限循环,并等待一些事情的发生。

    解析配置文件

    init会解析两个配置文件一个是系统配置文件init_rc,另一个是与硬件平台相关的配置文件,解析调用的是parse_config_file函数,然后看看这个函数里做了什么?

     /system/core/init下的parser.c文件

    1. int parse_config_file(const char *fn)
    2. {
    3. parse_config(fn, data);
    4. return 0;
    5. }
    6. static void parse_config(const char *fn, char *s)
    7. {
    8. struct parse_state state;
    9. char *args[SVC_MAXARGS];
    10. int nargs;
    11. nargs = 0;
    12. state.filename = fn;
    13. state.line = 1;
    14. state.ptr = s;
    15. state.nexttoken = 0;
    16. //设置解析函数 不同的内容用不同的解析函数
    17. state.parse_line = parse_line_no_op;
    18. for (;;) {
    19. switch (next_token(&state)) {
    20. case T_EOF:
    21. state.parse_line(&state, 0, 0);
    22. return;
    23. case T_NEWLINE:
    24. if (nargs) {
    25. //得到关键字类型
    26. int kw = lookup_keyword(args[0]);
    27. //判断关键字是不是SECTION
    28. if (kw_is(kw, SECTION)) {
    29. state.parse_line(&state, 0, 0);
    30. //解析这个SECTION
    31. parse_new_section(&state, kw, nargs, args);
    32. } else {
    33. state.parse_line(&state, nargs, args);
    34. }
    35. nargs = 0;
    36. }
    37. break;
    38. case T_TEXT:
    39. if (nargs < SVC_MAXARGS) {
    40. args[nargs++] = state.text;
    41. }
    42. break;
    43. }
    44. }
    45. }

    实际上,parse_config首先会找到配置文件中的section,然后根据不同的section使用不同的解析函数去解析。section是什么?在parse.h里包含了keywords.h脚本文件

     

    第一次包含keywords.h时声明了一些函数,定义了一个枚举值是K_class等关键字

     第二次包含keywords.h后,得到了一个keyword_info的结构体数组,以前面对应的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名称、处理函数、函数参数个数以及属性。根据keywords.h中的定义,on和service就可以表示成section。

     

    init.rc的解析:

    1 一个section的内容的从这个标识section的关键字开始,到下一个标识section的关键字结束。

    2 init.rc中出现的名字为boot和init的section就是前面介绍的四个动作执行阶段中的boot和init,也就是说,在boot这个阶段执行的动作都是由boot这个section定义的。

    然后再来解析init.rc文件

     

    总结:

    1. 一个section的内容是从这个标识section的关键字开始,直到下一个标识section的地方结束。on和service都是section开始的标识。

    2. 这里出现了名为boot和init的section,这就是前面四个动作中执行的init和boot,init和boot阶段执行的动作都是在这里定义的

    Zygote对应的是service,zygote的section是这样的

    1. service zygote /system/bin/app_process -Xzygote /system/bin =zygote \
    2. --start system server
    3. socket zygote stream 666
    4. onrestart write
    5. onrestart write
    6. onrestart restart media

     解析section的入口函数是parse_new_section,在这里,解析service时用到了parse_service和parse_line_service这两个函数,

    先来看Service结构体,是什么样子的?

    1. struct service {
    2. struct listnode slist;//将结构体连接成一个双向链表
    3. const char *name; //service的名字 这里就是zygote
    4. const char *classname; //service所属的class的名字
    5. unsigned flags;//service属性
    6. pid_t pid;//进程号
    7. time_t time_started; //上一次启动时间
    8. time_t time_crashed; //上一次死亡的时间
    9. int nr_crashed; //死亡的次数
    10. uid_t uid;
    11. gid_t gid;
    12. gid_t supp_gids[NR_SVC_SUPP_GIDS];
    13. size_t nr_supp_gids;
    14. struct socketinfo *sockets; //用来描述socket信息的结构体
    15. //service一般运行在一个单独的线程中,envvars表示创建这个进程需要的环境变量
    16. struct svcenvinfo *envvars;
    17. struct action onrestart; /* Actions to execute on restart. */
    18. /* 与keywords相关的信息 */
    19. int *keycodes;
    20. int nkeycodes;
    21. int keychord_id;
    22. //IO优先级
    23. int ioprio_class;
    24. int ioprio_pri;
    25. int nargs; //参数个数
    26. char *args[1];
    27. }

    现在已经了解了service中的结构体,再来看zygote中的三个onrestart是什么,先看action结构体:

    1. struct action {
    2. /*一个action结构体可以存放在三个双向链表中 */
    3. struct listnode alist; //存放所有的action
    4. struct listnode qlist;//链接等待执行的action
    5. struct listnode tlist; //链接那些待某些条件满足后需要执行的action
    6. unsigned hash;
    7. const char *name;
    8. /*对应的是command列表 zygote有三个onrestart就会创建三个command结构体*/
    9. struct listnode commands;
    10. struct command *current;
    11. };

    接下来,在parse_service和parse_line_service里,parse_service搭建了一个service的架子,parse_line_service里:

    1. kw = lookup_keyword(args[0]);//根据关键字进行处理
    2. switch (kw) {
    3. case K_capability:
    4. break;
    5. case K_class:
    6. if (nargs != 2) {
    7. parse_error(state, "class option requires a classname\n");
    8. } else {
    9. svc->classname = args[1];
    10. }
    11. break;
    12. case K_oneshot: //service的属性
    13. svc->flags |= SVC_ONESHOT;/*有五个属性 SVC_ONESHOT代表退出后不需要重启*/
    14. //SVC_DISABLED 不随class自动启动;SVC_RUNNING 正在运行;SVC_RESTARING:等待重启;
    15. //SVC_CONSOLE 该service需要使用控制台;SVC_CRITICAL 如果在规定时间内,该service不断重启则系统会重启进入恢复模式。
    16. ......
    17. List_add_tail(&svc->onrestart.commands, &cmd->clist);// 把新建的command加入到双向链表中
    18. ......

    由此可见,parse_line_service是根据配置文件的内容填充service结构体。

    总结:

    service_list解析后将service连接到一起,是一个双向链表的形式。

    socket_info也是一个双向链表。

    onrestart通过command指向一个command链表,zygote有三个command。

    init控制service

    Init.rc目录是system/core/rootdir/init,rc

    在init.rc的解析中,包含了这样的内容:

     class_start标识一个COMMAND,对应的处理函数是do_class_start,位于boot section里,在init的main函数里,boot阶段是这样的,

    1. //将boot的section节的的COMMAND加入到执行队列
    2. action_for_each_trigger("boot", action_add_queue_tail);
    3. //执行队列里的命令,class是一个COMMAND,所以他对应的do_class_start会被执行
    4. drain_action_queue();

    do_class_start函数位于system/core/init/builtins.c中,

    1. int do_class_start(int nargs, char **args)
    2. {
    3. /*args是do_class_start的参数,init.rc中只有一个参数,就是default,下面这个函数将从service_list中找到classname为default的service,然后调用service_start_if_not_disabled*/
    4. //zygote的classname 就是default
    5. service_for_each_class(args[1], service_start_if_not_disabled);
    6. return 0;
    7. }

    service_start_if_not_disabled里调用了service_start函数,代码分析如下:

    1 启动zygote,Zygote是由fork和execve共同创建的

    1. void service_start(struct service *svc, const char *dynamic_args)
    2. {
    3. ...
    4. if (svc->flags & SVC_RUNNING) {
    5. return; //service已经在运行,不用处理
    6. }
    7. /*service一般运行在另外一个进程中,这个进程也是init的子进程,*/
    8. if (stat(svc->args[0], &s) != 0) {
    9. ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
    10. svc->flags |= SVC_DISABLED;
    11. return;
    12. }
    13. //调用fork创建子进程
    14. pid = fork();
    15. //Pid为0的时候,表明运行在子线程中
    16. if (pid == 0) {//pid为0 表明运行在子进程中
    17. ......
    18. //添加环境变量信息 socket等
    19. if (!dynamic_args) {
    20. if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
    21. ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
    22. }
    23. }
    24. }
    25. //父进程的init的处理
    26. svc->time_started = gettime();
    27. svc->pid = pid;
    28. svc->flags |= SVC_RUNNING;
    29. ......
    30. }

    2 重启zygote

    onrestart方法顾名思义是在zygote重启的时候调用,zygote死亡后,父进程init:

    1. static void sigchld_handler(int s)
    2. { //子进程退出后 init这个信号函数会被调用
    3. write(signal_fd, &s, 1); // 往signal_fd写数据
    4. }

    signal_fd就是在init中通过socketpair创建的两个当中的一个,会往这个socket里发送数据另一个socket就一定能收到,这样就会导致init从poll函数中返回,在init.c中,

    1. for(;;) {
    2. int nr, i, timeout = -1;
    3. for (i = 0; i < fd_count; i++)
    4. ufds[i].revents = 0;
    5. drain_action_queue();//poll函数返回后 会有下一轮的循环
    6. restart_processes(); // 重启所有flag标识为SVC_RESTARTING的service
    7. }

    那么zygote在哪里重启呢?答案是init.c的main函数里.

    1. static int wait_for_one_process(int block)
    2. {
    3. ......
    4. svc = service_find_by_pid(pid);//找到死掉的service
    5. //杀掉zygote创建的所有子进程,这就是zygote死后Java世界崩溃的原因
    6. if (!(svc->flags & SVC_ONESHOT)) {
    7. kill(-pid, SIGKILL);
    8. NOTICE("process '%s' killing any children in process group\n", svc->name);
    9. }
    10. //如果设置了SVC_CRITICAL模式,则四分钟内重启的次数不能超过四次否则机器
    11. //会在重启后进入recovery模式
    12. if (svc->flags & SVC_CRITICAL) {
    13. if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
    14. if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
    15. ERROR("critical process '%s' exited %d times in %d minutes; "
    16. "rebooting into recovery mode\n", svc->name,
    17. CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
    18. sync();
    19. __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
    20. LINUX_REBOOT_CMD_RESTART2, "recovery");
    21. return 0;
    22. }
    23. } else {
    24. svc->time_crashed = now;
    25. svc->nr_crashed = 1;
    26. }
    27. //设置标识为SVC_RESTARTING,然后执行service onrestart中的COMMAND
    28. svc->flags |= SVC_RESTARTING;
    29. }

    属性服务

    Windows平台有个叫注册表的东西,可以存储一些类似key/value的键值对,包含了系统的属性,即使系统在重启后也能根据注册表中的属性进行初始化工作,Android平台这种类似的机制叫做属性服务---property service 如下是该手机的部分属性

     属性服务的初始化

    init.c中有个函数初始化属性服务函数 property_init(),这里先调用init_property_area()函数进行初始化属性存储区域,

    1. static int init_property_area(void)
    2. {
    3. prop_area *pa;
    4. if(pa_info_array)
    5. return -1;
    6. /*initworkspace是一块内存 PA_SIZE是存储空间的总大小 a_workspace是workspace的结构体*/
    7. if(init_workspace(&pa_workspace, PA_SIZE))
    8. return -1;
    9. fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
    10. pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);
    11. pa = pa_workspace.data;
    12. memset(pa, 0, PA_SIZE);
    13. pa->magic = PROP_AREA_MAGIC;
    14. pa->version = PROP_AREA_VERSION;
    15. /*这个变量由bionic libc库输出 */
    16. __system_property_area__ = pa;
    17. return 0;
    18. }

    为什么在最后要给变量赋值呢?属性虽然由init创建,但是Android希望其他进程也能读取到这块内存里的东西所以做了以下工作:

    1. 把属性区域建共享内存上,共享内存是可以跨进程的。
    2. 利用gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic libc这个库被加载时,就会自动调用_libc_prenit,这个函数内部就将完成共享内存到本地进程的映射工作

    客户端进程获取存储空间

    在bionic/libc/bionic/libc_init_common.c的libc_init_dynamic.c函数中,constructor属性指示加载器加载该库后,首先调用_libc_prenit函数,然后调用libc_init_common函数,最后一行就是初始化客户端的属性存储区域。

    1. int __system_properties_init(void)
    2. {
    3. ...
    4. //zygote中添加的环境变量 在这里取出
    5. env = getenv("ANDROID_PROPERTY_WORKSPACE");
    6. /*映射init创建的那块内存到本地进程空间,本地进程就可以使用这个共享内存了 指定了PROT_READ属性是只读不允许设置*/
    7. pa = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);
    8. }

     

    属性服务器的分析

    init进程会启动一个属性服务器来与与客户端交互设置客户端属性,属性服务器由start_property_service函数启动

    1. int start_property_service(void)
    2. {
    3. int fd;
    4. //加载属性文件,解析出来设置到属性空间中
    5. load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    6. load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
    7. load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
    8. //创建socket用于IPC通信
    9. fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
    10. if(fd < 0) return -1;
    11. fcntl(fd, F_SETFD, FD_CLOEXEC);
    12. fcntl(fd, F_SETFL, O_NONBLOCK);
    13. listen(fd, 8);
    14. return fd;
    15. }

    属性服务器接受到客户端请求后会在init进程中,调用handle_property_set_fd进行处理。检查客户端是否有足够的权限,有则调用property_set(位于properties_service.c下)函数进行相关处理。

    那么客户端是如何设置属性的?

    客户端调用property_set(位于properties.c下)进行处理,在libcutils库下,

    1. static int send_prop_msg(prop_msg *msg)
    2. { //建立和属性服务器的socket链接
    3. s = socket_local_client(PROP_SERVICE_NAME,
    4. ANDROID_SOCKET_NAMESPACE_RESERVED,
    5. SOCK_STREAM);
    6. //通过socket发出去
    7. while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {
    8. if((errno == EINTR) || (errno == EAGAIN)) continue;
    9. break;
    10. }
    11. }
    12. int property_set(const char *key, const char *value)
    13. { ......
    14. msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP
    15. return send_prop_msg(&msg);......
    16. }

    至此,讲解了init如何解析配置文件,解析zygote,以及属性服务。

  • 相关阅读:
    TypeScript 与组合式 API
    基于Python实现的黑白棋强化学习模型
    ARM 版 OpenEuler 22.03 部署 KubeSphere v3.4.0 不完全指南
    Github每日精选(第44期):磁盘工具diskusage
    【蓝桥杯集训100题】scratch指令移动 蓝桥杯scratch比赛专项预测编程题 集训模拟练习题第14题
    选择共享wifi项目哪个公司好?!
    数据结构之手写HashMap
    (最新版2022版)剑指offer之搜索算法题解
    负载均衡 - F5
    分布式事务Seata
  • 原文地址:https://blog.csdn.net/m0_56366502/article/details/127955264