码迷,mamicode.com
首页 > 移动开发 > 详细

Android init进程——源码分析

时间:2014-11-07 17:12:21      阅读:300      评论:0      收藏:0      [点我收藏+]

标签:des   android   blog   http   io   ar   os   使用   sp   

概述

Android本质上是一个基于Linux内核的开源操作系统,与我现在用的Ubuntu系统类似,但是所有的Android设备都是运行在ARM处理器(ARM源自进阶精简指令集机器,源自ARM架构)上,而像Ubuntu操作系统是x86(x86是一系列的基于intel 8086 CPU计算机微处理器指令集架构)系统。不过既然Android也是基于Linux内核的系统,那么基本的启动过程也应该符合Linux的规则。下图基本描述了当你按下电源开关后Android设备的执行步骤:
bubuko.com,布布扣
一个完整的Linux系统首先会将一个Linux内核装载到内存,也就是编译Linux内核源代码生产的bzImage文件,对于为Android优化的LInux内核源代码会生成zImage文件。该文件就是Linux内核的二进制版本。由于zImage在内核空间运行,而我们平常使用的软件都是在应用空间运行,内核空间和应用空间是不能直接通过内存地址级别访问的,所以就需要建立某种通讯机制。目前Linux有很多通讯机制可以在用户空间和内核空间之间交互,例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys目录等)。Linux的重要特征之一就是一切都是以文件的形式存在,例如,一个设备通常与一个或多个设备文件对应。这些与内核空间交互的文件都在用户空间,所以在Linux内核装载完,需要首先建立这些文件所在的目录,而完成这些工作的程序就是init进程。
在分析init的核心代码之前,可以初步了解init的主要做了如下工作:
1. Android系统有很多属性,init提供了一个property_service(属性服务)来管理它们。
2. 处理配置文件的命令(主要是init.rc文件),包括处理各种Action。
3. 性能分析(使用bootchart工具)。
4. 无限循环执行command。

init进程源码

源码位于:system/core/init/目录,本文将基于Android4.4.4_r1版本进行分析。
其中init.c是init的主文件,由于init是命令行程序,所以分析init.c需要从main函数入手,我对main函数做了注释,代码如下:
int main(int argc, char **argv)
{
    int fd_count = 0;
    struct pollfd ufds[4];
    char *tmpdev;
    char* debuggable;
    char tmp[32];
    int property_set_fd_init = 0;
    int signal_fd_init = 0;
    int keychord_fd_init = 0;
    bool is_charger = false;

    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_main(argc, argv);

    if (!strcmp(basename(argv[0]), "watchdogd"))
        return watchdogd_main(argc, argv);

    /* clear the umask */
    umask(0);

    // 创建用户空间的目录,例如/dev,/proc,/sys等。
    mkdir("/dev", 0755);
    mkdir("/proc", 0755);
    mkdir("/sys", 0755);

    mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
    mkdir("/dev/pts", 0755);
    mkdir("/dev/socket", 0755);
    mount("devpts", "/dev/pts", "devpts", 0, NULL);
    mount("proc", "/proc", "proc", 0, NULL);
    mount("sysfs", "/sys", "sysfs", 0, NULL);

    // 检测/dev/.booting文件是否可读写和可创建。
    close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));

    // 将标准输入、输出、错误输出重定向到/dev/__null__。
    open_devnull_stdio();
    // 将init的日志输出设备设置为/dev/__kmsg__。
    klog_init();
    // 初始化和属性相关的资源
    property_init();

    get_hardware_name(hardware, &revision);
    
    // 处理内核命令行
    process_kernel_cmdline();

    union selinux_callback cb;
    cb.func_log = klog_write;
    selinux_set_callback(SELINUX_CB_LOG, cb);

    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    selinux_initialize();
    /* These directories were necessarily created before initial policy load
     * and therefore need their security context restored to the proper value.
     * This must happen before /dev is populated by ueventd.
     */
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

    is_charger = !strcmp(bootmode, "charger");

    INFO("property init\n");
    if (!is_charger)
        property_load_boot_defaults();

    INFO("reading config file\n");
    // 分析/init.rc文件的内容
    init_parse_config_file("/init.rc");
    
    // 解析完init.rc配置文件后,会得到一系列的Action动作。
    // init将动作的执行时间划分为四个阶段:early-init,init,early-boot,boot
    action_for_each_trigger("early-init", action_add_queue_tail);

    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");

    /* execute all the boot actions to get us started */
    action_for_each_trigger("init", action_add_queue_tail);

    /* skip mounting filesystems in charger mode */
    if (!is_charger) {
        action_for_each_trigger("early-fs", action_add_queue_tail);
        action_for_each_trigger("fs", action_add_queue_tail);
        action_for_each_trigger("post-fs", action_add_queue_tail);
        action_for_each_trigger("post-fs-data", action_add_queue_tail);
    }

    /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
     * wasn't ready immediately after wait_for_coldboot_done
     */
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    queue_builtin_action(property_service_init_action, "property_service_init");
    queue_builtin_action(signal_init_action, "signal_init");
    queue_builtin_action(check_startup_action, "check_startup");

    if (is_charger) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("early-boot", action_add_queue_tail);
        action_for_each_trigger("boot", action_add_queue_tail);
    }

        /* run all property triggers based on current state of the properties */
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");


#if BOOTCHART
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
    // 进入无限循环,建立init的子进程(init是所有进程的父进程)
    for(;;) {
        int nr, i, timeout = -1;

        execute_one_command();
        restart_processes();

        if (!property_set_fd_init && get_property_set_fd() > 0) {
            ufds[fd_count].fd = get_property_set_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            property_set_fd_init = 1;
        }
        if (!signal_fd_init && get_signal_fd() > 0) {
            ufds[fd_count].fd = get_signal_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            signal_fd_init = 1;
        }
        if (!keychord_fd_init && get_keychord_fd() > 0) {
            ufds[fd_count].fd = get_keychord_fd();
            ufds[fd_count].events = POLLIN;
            ufds[fd_count].revents = 0;
            fd_count++;
            keychord_fd_init = 1;
        }

        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (!action_queue_empty() || cur_action)
            timeout = 0;

#if BOOTCHART
        if (bootchart_count > 0) {
            if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
                timeout = BOOTCHART_POLLING_MS;
            if (bootchart_step() < 0 || --bootchart_count == 0) {
                bootchart_finish();
                bootchart_count = 0;
            }
        }
#endif

        nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;

        for (i = 0; i < fd_count; i++) {
            if (ufds[i].revents == POLLIN) {
                if (ufds[i].fd == get_property_set_fd())
                    handle_property_set_fd();
                else if (ufds[i].fd == get_keychord_fd())
                    handle_keychord();
                else if (ufds[i].fd == get_signal_fd())
                    handle_signal();
            }
        }
    }

    return 0;
}
我们可以看到init.c的main函数还是非常复杂的,但是我们不需要每条语句都很清楚(没这个必要,而且这样搞难度太大)。我们通常只需要了解init的主线就可以了。从上述main函数分析,可以看出,init实际上就分为两个部分:
1. 初始化(包括建立/dev、/proc等目录、初始化属性、执行init.rc等初始化文件中的action等。
2. 使用for循环无限循环建立子进程。

初始化

我们先来分析初始化部分的代码。main函数最开始完成的建立目录的工作比较简单,没什么可分析的,就是调用了一些普通的API(mkdir)建立了一些目录。我们重点分析接下来执行的2个函数:
1. get_hardware_name,主要源码如下:
void get_hardware_name(char *hardware, unsigned int *revision)
{
    char data[1024];
    int fd, n;
    char *x, *hw, *rev;

    // 如果hardware已经有值,说明hardware通过内核命令行提供,直接返回
    if (hardware[0])
        return;
    
    // 已只读方式打开/proc/cpuinfo文件
    fd = open("/proc/cpuinfo", O_RDONLY);
    if (fd < 0) return;
    
    // 读取/proc/cpuinfo文件的内容
    n = read(fd, data, 1023);
    close(fd);
    if (n < 0) return;

    data[n] = 0;
    // 从/proc/cpuinfo文件中读取Hardware第一次出现的位置
    hw = strstr(data, "\nHardware");
    rev = strstr(data, "\nRevision");

    if (hw) {
        x = strstr(hw, ": ");
        if (x) {
            x += 2;
            n = 0;
            while (*x && *x != '\n') {
                if (!isspace(*x))
                    // 将Hardware字段的值都转换为小写,并更新hareware参数的值
                    // hardware也就是在init.c文件中定义的hardware数组
                    hardware[n++] = tolower(*x);
                x++;
                if (n == 31) break;
            }
            hardware[n] = 0;
        }
    }

    if (rev) {
        x = strstr(rev, ": ");
        if (x) {
            *revision = strtoul(x + 2, 0, 16);
        }
    }
}
从get_hardware_name方法的代码可知,该方法主要用于确定hardware和version变量的值。获取hardware的来源是Linux命令行或者/proc/cpuinfo文件的内容。我们可以直接从手机adb模式看一下/proc/cpuinfo的内容,如下图所示:
bubuko.com,布布扣
从上图可以看出,设备的Hadrware的名称为:MT6592。这里多介绍一点,init.rc文件中的开始部分用了很多import语句导入了其他配置文件,例如,import /init.usb.rc。但是有一处import语句它使用的是一个变量,import /init.${ro.hardware}.rc,这个ro.hardware就是上问介绍的方法获得的hardware的值。

2. process_kernel_cmdline,函数源码如下:
static void process_kernel_cmdline(void)
{
    /* don't expose the raw commandline to nonpriv processes */
    chmod("/proc/cmdline", 0440);

    /* first pass does the common stuff, and finds if we are in qemu.
     * second pass is only necessary for qemu to export all kernel params
     * as props.
     */
    import_kernel_cmdline(0, import_kernel_nv);
    if (qemu[0])
        import_kernel_cmdline(1, import_kernel_nv);

    // 用属性值设置内核变量
    export_kernel_boot_props();
}
在process_kernel_cmdline函数中,除了使用import_kernel_cmdline函数导入内核变量外,主要的功能就是调用export_kernel_boot_props函数通过属性设置内核变量。我们来看一下export_kernel_boot_props函数的源码:
static void export_kernel_boot_props(void)
{
    char tmp[PROP_VALUE_MAX];
    int ret;
    unsigned i;
    struct {
        const char *src_prop;
        const char *dest_prop;
        const char *def_val;
    } prop_map[] = {
        { "ro.boot.serialno", "ro.serialno", "", },
        { "ro.boot.mode", "ro.bootmode", "unknown", },
        { "ro.boot.baseband", "ro.baseband", "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
    };
    
    // 通过内核的属性设置应用层配置文件的属性
    for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
        ret = property_get(prop_map[i].src_prop, tmp);
        if (ret > 0)
            property_set(prop_map[i].dest_prop, tmp);
        else
            property_set(prop_map[i].dest_prop, prop_map[i].def_val);
    }
    
    // 根据ro.boot.console属性的值设置console变量
    ret = property_get("ro.boot.console", tmp);
    if (ret)
        strlcpy(console, tmp, sizeof(console));

    /* save a copy for init's usage during boot */
    property_get("ro.bootmode", tmp);
    strlcpy(bootmode, tmp, sizeof(bootmode));

    // 获取ro.boot.hardware的值,保存在tmp字符串数组中
    ret = property_get("ro.boot.hardware", tmp);
    if (ret)
        strlcpy(hardware, tmp, sizeof(hardware));
    // 利用hardware的值设置ro.hardware属性。
    // 这个属性就是前面提到的初始化文件名的属性
    property_set("ro.hardware", hardware);

    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);

    /* TODO: these are obsolete. We should delete them */
    if (!strcmp(bootmode,"factory"))
        property_set("ro.factorytest", "1");
    else if (!strcmp(bootmode,"factory2"))
        property_set("ro.factorytest", "2");
    else
        property_set("ro.factorytest", "0");
}
从export_kernel_boot_props函数的代码可以看出,该函数实际上就是来回设置一些属性值。
介绍完前面的2个函数,那么问题来了,前面多次提到属性或属性服务,那么Android系统到底有哪些属性?属性服务又是如何工作的?看接下来的代码讲解。

属性服务

我们知道,在Windows平台上有一个叫注册表的东西,注册表可以存储一些类似 key/value 的键值对。一般而言,系统或某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或者应用程序重启,它还能根据之前在注册表中设置的属性,进行相应的初始化工作。Amdroid平台也提供了类似的机制,称之为属性服务(property service)。
init在启动的过程中会启动属性服务(Socket服务),并且在内存中建立一块存储区域,用来存储这些属性。init.c代码中跟属性服务有关的代码有下面两行:
property_init();
start_property_service();

属性服务初始化

创建存储空间。先看一下property_init函数,源码位置:system/core/init/property_service.c,源码如下:
void property_init(void)
{
    init_property_area();
}
可以看到。property_init函数中,只是简单的调用了init_property_area函数,那我们接下来看一下init_property_area函数是如何工作的,源码如下:
static int init_property_area(void)
{
    if (property_area_inited)
        return -1;

    if(__system_property_area_init())
        return -1;

    if(init_workspace(&pa_workspace, 0))
        return -1;

    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

    property_area_inited = 1;
    return 0;
}
从init_property_area函数,我们可以看出,函数首先判断内存区域是否已经初始化过,如果已经初始化,则直接返回-1。如果没有初始化,我们接下来会发现有两个关键函数__system_property_area_init和init_workspace应该是真正的跟内存区域初始化有关。分别分析一下这两个函数。
__system_property_area_init函数位于bionic/libc/bionic/system_properties.c文件中,其源码如下:
int __system_property_area_init()
{
    return map_prop_area_rw();
}
这里__system_property_area_init也是简单的调用了map_prop_area_rw方法,我们只能继续跟踪了(蛋疼的嵌套啊,不过map_prop_area_rw确实是真正的内存分配函数)。map_prop_area_rw源码如下:
static int map_prop_area_rw()
{
    prop_area *pa;
    int fd;
    int ret;

    /* dev is a tmpfs that we can use to carve a shared workspace
     * out of, so let's do that...
     * O_RDWR ==> 读写
     * O_CREAT ==> 若不存在则创建
     * O_NOFOLLOW ==> 如果filename是软链接,则打开失败
     * O_EXCL ==> 如果使用O_CREAT时文件存在,则可返回错误信息
     */
    fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |
            O_EXCL, 0444);
    if (fd < 0) {
        if (errno == EACCES) {
            /* for consistency with the case where the process has already
             * mapped the page in and segfaults when trying to write to it
             */
            abort();
        }
        return -1;
    }

    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0)
        goto out;

    if (ftruncate(fd, PA_SIZE) < 0)
        goto out;

    pa_size = PA_SIZE;
    pa_data_size = pa_size - sizeof(prop_area);
    compat_mode = false;
    // mmap映射文件实现共享内存
    pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(pa == MAP_FAILED)
        goto out;

    memset(pa, 0, pa_size);
    pa->magic = PROP_AREA_MAGIC;
    pa->version = PROP_AREA_VERSION;
    /* reserve root node */
    pa->bytes_used = sizeof(prop_bt);

    /* plug into the lib property services */
    __system_property_area__ = pa;

    close(fd);
    return 0;

out:
    close(fd);
    return -1;
}
代码还是比较好理解的,这块代码主要作用是建立了一块共享内存。
接下来,我们继续看一下init_workspace的源码:
typedef struct {
    size_t size;
    int fd;
} workspace;

static int init_workspace(workspace *w, size_t size)
{
    void *data;
    int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
    if (fd < 0)
        return -1;

    w->size = size;
    w->fd = fd;
    return 0;
}

static workspace pa_workspace;
其实,上面两个函数最关键的一点在于 __system_property_area__ = pa的赋值。__system_property_area__是bionic_libc库中输出的一个变量,为什么这里要给它赋值呢?
是因为,虽然属性区域是init进程创建的,但是Android系统希望其他进程也能够读取这块内存里的东西。为了做到这一点,因此init进程做了如下两项工作:
  1. 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,在上述代码中是通过mmap映射普通文件实现的。init_workspace也保持了映射文件的句柄。
  2. 如果让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个__lib_prenit函数,当bionic_libc库被加载时,将自动调用这个libc_prenit,这个函数内部将完成共享内存到本地进程的映射工作。
关于使用mmap映射普通文件实现共享内存IPC通信机制,可以参考我之前转载的这篇文章:http://blog.csdn.net/wzy_1988/article/details/40858765 。

属性服务器的分析

init进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性。先来看一下属性服务器的内容,它由start_property_service函数启动,代码如下所示:
void start_property_service(void)
{
    int fd;

    /*
     * 加载属性文件,其实就是解析这些文件中的属性,然后把属性设置到共享内存中去。
     * Android系统一共提供了四个存储属性的文件,它们分别是:
     * #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"
     */
    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,用于IPC通信。
    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;
}
主要的宏我已经在代码中通过注释标记出来了。load_properties_from_file函数主要是从文件中读取property属性,并通过property_set函数写入到共享内存中,这里了解一下property_set函数源码:
int property_set(const char *name, const char *value)
{
    prop_info *pi;
    int ret;

    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);

    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) return -1;

    pi = (prop_info*) __system_property_find(name);

    if(pi != 0) {
        // 如果属性以"ro."开头,表示是只读属性,不能设置,所以直接返回-1
        if(!strncmp(name, "ro.", 3)) return -1;

        __system_property_update(pi, value, valuelen);
    } else {
        ret = __system_property_add(name, namelen, value, valuelen);
        if (ret < 0) {
            ERROR("Failed to set '%s'='%s'\n", name, value);
            return ret;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
       /*
        * The 'net.change' property is a special property used track when any
        * 'net.*' property name is updated. It is _ONLY_ updated here. Its value
        * contains the last updated 'net.*' property.
        */
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
        write_persistent_property(name, value);
    } else if (strcmp("selinux.reload_policy", name) == 0 &&
               strcmp("1", value) == 0) {
        selinux_reload_policy();
    }
    property_changed(name, value);
    return 0;
}
客户端可以通过发送socket请求,来设置property。

参考链接

1. 《深入理解Android卷I》
2.   http://blog.csdn.net/nokiaguy/article/details/8800962

Android init进程——源码分析

标签:des   android   blog   http   io   ar   os   使用   sp   

原文地址:http://blog.csdn.net/wzy_1988/article/details/40781253

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!