(一)启动进程Init

系统启动的整体流程

一、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

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

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

  1. Action (一组被命名的Command序列)
  2. Command 五种语句类型
  3. Service 由init进程启动或重启的程序
  4. Options Services 修饰词 ,指定如何并何时启动
  5. 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
主要做了以下几个工作:

  1. 调用fork()系统调用创建新的进程;
  2. 获取属性匿名存储空间句柄,并添加为服务配置的环境变量;
  3. 创建服务配置的socket,调用publish_socket函数将创建的socket句柄添加到环境变量中;该环境变量为:ANDROID_SOCKET_XXX
    = fd
  4. 为新进程打开控制台,并设置新进程的PID,GID等;
  5. 调用execve()系统调用执行新进程运行的程序;
  6. 设置服务运行状态属性;该属性为: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 = %08x\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;  
}  
  1. 找到死service
  2. 杀掉zygote创建的所有子进程
  3. 清除socket信息
  4. 如果实质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进程创建但希望其他进程也能读到内存中的东西,于是做了以下两项工作:

  1. 共享内存中 。init.workspace 函数
  2. 利用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文件中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值