(一)启动进程Init

xiaoxiao2021-02-28  64

一、Init进程元认知

init进程是用户级的第一个进程(UserPlace) 在Linux中所有进程都是init的子进程,Linux中一切都是以文件的形式存在的,主要提供四大功能

建立文件所在目录挂载设备

处理Action

职责关注部分: 创建zygote进程 启动属性服务 性能分析(BootChart) 无线循环(启动其他的进程)

二、init代码部分

init进程的入口函数是main,它的代码如下所示: [-->init.c] int main(int argc, char **argv) { intdevice_fd = -1; intproperty_set_fd = -1; intsignal_recv_fd = -1; intkeychord_fd = -1; int fd_count; ints[2]; intfd; structsigaction act; chartmp[PROP_VALUE_MAX]; structpollfd ufds[4]; char*tmpdev; char*debuggable; //设置子进程退出的信号处理函数,该函数为sigchld_handler。 act.sa_handler = sigchld_handler; act.sa_flags= SA_NOCLDSTOP; act.sa_mask = 0; act.sa_restorer = NULL; sigaction(SIGCHLD, &act, 0); **......//创建一些文件夹,并挂载设备,这些是和Linux相关的,不拟做过多讨论。** mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0,NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); //重定向标准输入/输出/错误输出到/dev/_null_。 open_devnull_stdio(); /* 设置init的日志输出设备为/dev/__kmsg__,不过该文件打开后,会立即被unlink了, 这样,其他进程就无法打开这个文件读取日志信息了。 */ log_init(); //上面涉及很多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析 ****//解析init.rc配置文件**** parse_config_file("/init.rc"); ...... //下面这个函数通过读取/proc/cpuinfo得到机器的Hardware名,我的HTCG7手机为bravo。 get_hardware_name(); snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware); //解析这个和机器相关的配置文件,我的G7手机对应文件为init.bravo.rc。 parse_config_file(tmp); /* 解析完上述两个配置文件后,会得到一系列的Action(动作),下面两句代码将执行那些处于 early-init阶段的Action。init将动作执行的时间划分为四个阶段:early-init、init、 early-boot、boot。由于有些动作必须在其他动作完成后才能执行,所以就有了先后之分。哪些 动作属于哪个阶段由配置文件决定。后面会介绍配置文件的相关知识。 */ action_for_each_trigger("early-init", action_add_queue_tail); drain_action_queue(); /* 创建利用Uevent和Linux内核交互的socket。关于Uevent的知识,第9章中对 Vold进行分析时会做介绍。 */ device_fd = device_init(); **//初始化和属性相关的资源 property_init();** //初始化/dev/keychord设备,这和调试有关,本书不讨论它的用法。读者可以自行研究, //内容比较简单。 keychord_fd = open_keychord(); ...... /* INIT_IMAGE_FILE定义为”/initlogo.rle”,下面这个函数将加载这个文件作为系统的开机 画面,注意,它不是开机动画控制程序bootanimation加载的开机动画文件。 */ if(load_565rle_image(INIT_IMAGE_FILE) ) { /* 如果加载initlogo.rle文件失败(可能是没有这个文件),则会打开/dev/ty0设备,并 输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。 */ ...... } } if(qemu[0]) import_kernel_cmdline(1); ...... //调用property_set函数设置属性项,一个属性项包括属性名和属性值。 property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown"); ......//执行位于init阶段的动作 action_for_each_trigger("init", action_add_queue_tail); drain_action_queue(); **//启动属性服务** property_set_fd = start_property_service(); /* 调用socketpair函数创建两个已经connect好的socket。socketpair是Linux的系统调用, 不熟悉的读者可以利用man socketpair查询相关信息。后面就会知道它们的用处了。 */ if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) { signal_fd = s[0]; signal_recv_fd = s[1]; ...... } ...... //执行配置文件中early-boot和boot阶段的动作。 action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); drain_action_queue(); ...... //init关注来自四个方面的事情。 ufds[0].fd= device_fd;//device_fd用于监听来自内核的Uevent事件 ufds[0].events = POLLIN; ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性服务器的事件 ufds[1].events= POLLIN; //signal_recv_fd由socketpair创建,它的事件来自另外一个socket。 ufds[2].fd = signal_recv_fd; ufds[2].events = POLLIN; fd_count = 3; if(keychord_fd > 0) { //如果keychord设备初始化成功,则init也会关注来自这个设备的事件。 ufds[3].fd = keychord_fd; ufds[3].events = POLLIN; fd_count++; } ...... #if BOOTCHART ......//与Boot char相关,不做讨论了。 /* Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表, 以提供一些有价值的信息,而这些信息最大的用处就是帮助提升系统的启动速度。 */ #endif for(;;) { **//从此init将进入一个无限循环。** int nr, i, timeout = -1; for (i = 0; i < fd_count; i++) ufds[i].revents = 0; //在循环中执行动作 drain_action_queue(); restart_processes(); //重启那些已经死去的进程 ...... #if BOOTCHART ...... // Boot Chart相关 #endif //调用poll等待一些事情的发生 nr= poll(ufds, fd_count, timeout); ...... //ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。 if(ufds[2].revents == POLLIN) { //有一个子进程去世,init要处理这个事情 read(signal_recv_fd, tmp, sizeof(tmp)); while (!wait_for_one_process(0)) ; continue; } if(ufds[0].revents == POLLIN) handle_device_fd(device_fd);//处理Uevent事件 if(ufds[1].revents == POLLIN) handle_property_set_fd(property_set_fd);//处理属性服务的事件。 if(ufds[3].revents == POLLIN) handle_keychord(keychord_fd);//处理keychord事件。 } return0; }

1.1 入口函数Main

初始化(/der.proc/目录,执行init.rc 解析配置文件)void部分 Uevent 与linux交互Socketfor循环(守护进程 信号量)

1.1.1 解析配置文件init.rc 还有一个rc文件是xx.rc(xx是硬件名),这个文件只是系统的一个镜像文件,更改也没有用。如想改变则需要在boot.ing核镜像中修改。有一个函数parseconfig函数进行解析,其中有一个Section Zygote被放在一个Service中。 1.1.2 解析规则 使用的是Android Init Language 编译的脚本 分为 :

Action (一组被命名的Command序列)Command 五种语句类型Service 由init进程启动或重启的程序Options Services 修饰词 ,指定如何并何时启动Import

一个Section 的结构一般是:

on <trigger> <command> <command> service <name> <pathname> [ <argument> ]*

#是注释 ,on init 和 onboot是Action类型。 解析rc文件的详细内容: http://blog.csdn.net/yangwen123/article/details/9029959

1.1.3解析Service init中使用一个结构体来保存Service Section 相关信息(使用Socket,action结构体) 函数ParseService 搭建一个servce的架子。 Parse_line_service 将根据配置文件的内容填完service结构体 zygote 解析后的结果:service List 将解析后的Service全部连在一起。 Socketinifo -> Socket链表 onrestart commands 指向一个 Commands 链表

1.1.4 init控制service 其中sygote 就是在其中一个Service中 在rc 文件中一个service

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 660 root system onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd

class_start default ->是一个COMMAND 位于boot(引导) section 范围内。 最后执行service_start函数,并在启动前要判断可执行文件,是否存在 /system/bin/applprocress 主要做了以下几个工作:

调用fork()系统调用创建新的进程;获取属性匿名存储空间句柄,并添加为服务配置的环境变量;创建服务配置的socket,调用publish_socket函数将创建的socket句柄添加到环境变量中;该环境变量为:ANDROID_SOCKET_XXX = fd为新进程打开控制台,并设置新进程的PID,GID等;调用execve()系统调用执行新进程运行的程序;设置服务运行状态属性;该属性为:init.svc.XXX = running

1.1.5 重启Zygote onrestart是在zygote重启时用的,当内存不足时,Android系统会自动杀死一下进程来释放空间,所以当某些重要的服务被杀,同时该服务进程并未设置为oneshot,则必须重新启动该服务进程。 zygote死后父进程init 会通过Socket接收数据执行代码(Main函数中)->init.rc.main中工作如下:

static int wait_for_one_process(int block) { //block = 0 -->false pid_t pid; int status; struct service *svc; struct socketinfo *si; time_t now; struct listnode *node; struct command *cmd; /*当进程被终止时,将发送SIGCHLD信号,waitpid()函数用来回收进程所占用的资源,第一个参 数pid是指欲等待的子进程的识别码,设置为-1表示查看所有子进程是否发出SIGCHIL信号,第二 个参数status用于返回子进程的结束状态;第三个参数决定waitpid()函数是否应用阻塞处理方式。 waitpid()函数返回产生SIGCHID信号的进程pid */ while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR ); //正常情况下返回的死亡进程pid大于0,因此wait_for_one_process的返回值正常情况下为0 if (pid <= 0) return -1; INFO("waitpid returned pid %d, status = x\n", pid, status); //用于根据pid值在服务链表中查找对应的服务 svc = service_find_by_pid(pid); if (!svc) { ERROR("untracked pid %d exited\n", pid); return 0; } NOTICE("process '%s', pid %d exited\n", svc->name, pid); /* 检查服务是否设置了oneshot标志,SVC_ONESHOT表示进程仅运行一次,如果没有设置SVC_ONESHOT标志, 表示需要重启该服务进程,首先将该服务进程组下的所有子进程杀死 */ if (!(svc->flags & SVC_ONESHOT)) { kill(-pid, SIGKILL); NOTICE("process '%s' killing any children in process group\n", svc->name); } /* 删除该服务进程下的创建的所有socket */ for (si = svc->sockets; si; si = si->next) { char tmp[128]; snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); unlink(tmp); } //设置服务的pid为0 ,并清除SVC_RUNNING标志 svc->pid = 0; svc->flags &= (~SVC_RUNNING); /* 如果设置了SVC_ONESHOT标志,表示服务只能运行一次,因此设置表示位SVC_DISABLED */ if (svc->flags & SVC_ONESHOT) { svc->flags |= SVC_DISABLED; } /* 判断服务标志是否设置了SVC_DISABLED 或 SVC_RESET 对于设置了这两种标志的进程是不能重启的 */ if (svc->flags & (SVC_DISABLED | SVC_RESET) ) { //设置进程运行状态属性值为stopped notify_service_state(svc->name, "stopped"); return 0; } now = gettime(); //如果死亡的服务进程是系统关键进程,则直接重启手机 if (svc->flags & SVC_CRITICAL) { if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { ERROR("critical process '%s' exited %d times in %d minutes; " "rebooting into recovery mode\n", svc->name, CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); //手机重启 android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); return 0; } } else { svc->time_crashed = now; svc->nr_crashed = 1; } } //设置服务进程标志SVC_RESTARTING,在restart_processes()函数中会重启持有SVC_RESTARTING svc->flags |= SVC_RESTARTING; /* 运行该service下所有Execute all onrestart commands for this service. */ list_for_each(node, &svc->onrestart.commands) { cmd = node_to_item(node, struct command, clist); cmd->func(cmd->nargs, cmd->args); } //设置进程运行状态属性值为stopped notify_service_state(svc->name, "restarting"); return 0; } 找到死service杀掉zygote创建的所有子进程清除socket信息如果实质SVC_CRIICAL标志位则4分钟内不超过4次否则进入(recovery模式)

1.2属性服务(ProPerty_Service)

系统应用程序将会存储一些属性进入属性表中。即时系统重启,注册表应用程序重启,也能初始化。 property_service (属性服务机制)(socket服务) 属性文件是一些位列于不同目录,系统依次读取的配置文件。 1.启动(Socket服务) init.c文件中函数()与属性服务有关的代码 property_init(); property_set_fd=start_property_service();

1.2.1 属性服务的初始化 在前面分析main函数时涉及到一个property_init函数,该函数调用了init_property_area函数,该函数用于初始化属性内存区域,也就是system_property_area变量。 这个函数加载defaut.prop文件(属性文件) 虽然init进程创建但希望其他进程也能读到内存中的东西,于是做了以下两项工作:

共享内存中 。init.workspace 函数利用gcc的Constructor属性指明_libc_prenit函数,libc_prenit函数.libc_ini_common.c文件中->_libc_init_comment->system.propertis_init()//初始化客户端属性存储区域

1.2.2 属性服务器分析 (1)start_property_service函数,该函数在Property_service.c文件中,该文件与init.c文件中同一个目录。 函数如下:

void start_property_service(void) { int fd; // 装载不同的属性文件 load_properties_from_file(PROP_PATH_SYSTEM_BUILD); load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_properties(); // 创建socket服务(属性服务) fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); if(fd < 0) return; fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, O_NONBLOCK); // 开始服务监听 listen(fd, 8); property_set_fd = fd; }

在start_property_service 中执行了:load_persistent_properties(); // 创建socket服务(属性服务)。客户端只通过对此进行属性设置。 并定义两个宏(和系统预定属性文件路径有关) 将属性文件加载到属性空间中,四个储存属性的文件分别是:

#define PROP_PATH_RAMDISK_DEFAULT "/default.prop" #define PROP_PATH_SYSTEM_BUILD "/system/build.prop" #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"

(2)处理设置请求(在init进程for循环中): handle_property_set_fd进行处理。权限满足,调用property_set进行处理。 (3)客户端发送请求。 property_set 发送请求,由libcutils库进行提供。propertis.c文件中。

转载请注明原文地址: https://www.6miu.com/read-66390.html

最新回复(0)