Android系统启动过程分析

Android系统的框架架构图如下(来自网上):

Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android相关的服务和应用。

整个的启动过程如下图所示:


以下针对Android 4.2内核代码的启动部分进行分析。

Init进程,是一个由内核启动的用户级进程。内核自行启动(已被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动用户级程序init的方式,完成引导进程。

Init进程始终是第一个进程。Init进程的对应的代码的main函数在目录~/my_android/system/core/init/init.c


整个Android系统的启动分为Linux kernel的启动和Android系统的启动。


Linux kernel启动起来后,然后就运行第一个用户程序,在Android中,就是init程序,在目录~/my_android/system/core/init/init.c,对其中的main()函数分段进行介绍

1. 首先声明一些局部变量,代码如下:

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;    ......    ......}

main函数中该段代码主要是声明了一些后续会使用的变量,其中涉及一个结构体pollfd,后续对其操作时再进行介绍


2.对传入的argv[0]进行判断,决定程序的执行分支,代码如下:

int main(int argc, char **argv){    ......    if (!strcmp(basename(argv[0]), "ueventd"))        return ueventd_main(argc, argv);    if (!strcmp(basename(argv[0]), "watchdogd"))        return watchdogd_main(argc, argv);......}

说明了这里处理kernel执行会跳转到以外,还有其他地方会调用这个main函数。
其中的argv[0]就是表示要执行的函数名称。
从这里看,应该有三个地方会执行此处的main()函数:

  • 标准的android启动代码
  • ueventd_main()代码
  • watchdogd_main()代码


3. 创建并挂载Android系统启动所需要的文件系统

int main(int argc, char **argv){    ......    /* clear the umask */    umask(0);        /* Get the basic filesystem setup we need put         * together in the initramdisk on / and then we'll         * let the rc file figure out the rest.         */    /* Don't repeat the setup of these filesystems,     * it creates double mount points with an unknown effect     * on the system.  This init file is for 2nd-init anyway.     */#ifndef NO_DEVFS_SETUP    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);        /* indicate that booting is in progress to background fw loaders, etc */    close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));        /* We must have some place other than / to create the         * device nodes for kmsg and null, otherwise we won't         * be able to remount / read-only later on.         * Now that tmpfs is mounted on /dev, we can actually         * talk to the outside world.         */......}

4.生成log设备,以及一些属性设置

int main(int argc, char **argv){    ......        /* We must have some place other than / to create the         * device nodes for kmsg and null, otherwise we won't         * be able to remount / read-only later on.         * Now that tmpfs is mounted on /dev, we can actually         * talk to the outside world.         */    open_devnull_stdio();    klog_init();#endif    property_init();    get_hardware_name(hardware, &revision);    process_kernel_cmdline();......}
其中open_devnull_stdio()的定义在~/my_android/system/core/init/util.c中,代码如下:

void open_devnull_stdio(void){    int fd;    static const char *name = "/dev/__null__";    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {        fd = open(name, O_RDWR);        unlink(name);        if (fd >= 0) {            dup2(fd, 0);            dup2(fd, 1);            dup2(fd, 2);            if (fd > 2) {                close(fd);            }            return;        }    }    exit(1);}

该函数首先创建一个设备节点"/dev/__null__"(在/dev目录下生产__null__设备节点文件),然后打开这个设备文件,并将打开的文件描述符保存在变量fd中,接着使用unlink函数删除该文件,虽然文件删除了,但是现在系统中借助这个fd还是能够找到该文件的内容的。if中的语句利用dup2函数将文件描述符fd的0,1,2信息重定向到这个fd文件描述符的文件中。即把标准输入、标准输出、标准错误输出重定向到一个设备文件中(0——标准输入,1——标准输出,2——标准错误输出)。重定向操作完成后,就关闭掉fd。示意图如下:


回到main()函数中,接着是执行klog_init()函数,其定义在:~/my_android/system/core/libcutils/klog.c中,实现代码如下:

void klog_init(void){    static const char *name = "/dev/__kmsg__";    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {        klog_fd = open(name, O_WRONLY);        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);        unlink(name);    }}
该函数和open_devnull_stdio的实现很相像,创建设备节点,打开,操作,然后删除文件。其中的fcntl(klog_fd, F_SETFD, FD_CLOEXEC); 表示当在子进程中使用exec执行其他程序时会把这个文件描述符关闭。

接着是执行main()函数中的property_init()函数,其定义在:~/my_android/system/core/init/property_service.c文件中。实现的代码如下:

void property_init(void){    init_property_area();}
此处调用了另一函数init_property_area(),其定义也在~/my_android/system/core/init/property_service.c文件中,实现代码如下:

static int init_property_area(void){    prop_area *pa;    if(pa_info_array)        return -1;    if(init_workspace(&pa_workspace, PA_SIZE))        return -1;    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);    pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);    pa = pa_workspace.data;    memset(pa, 0, PA_SIZE);    pa->magic = PROP_AREA_MAGIC;    pa->version = PROP_AREA_VERSION;        /* plug into the lib property services */    __system_property_area__ = pa;    property_area_inited = 1;    return 0;}
在该函数中,涉及了几个结构体变量,首先看一下各结构体的定义

prop_area结构体的定义如下,其定义在:

struct prop_area{nsigned volatile count;unsigned volatile serial;unsigned magic;unsigned version;unsigned reserved[4];unsigned toc[1];};

然后是pa_info_array的定义为:

static pro_info *pa_info_array;

所以在函数中,由于pa_info_array由于刚刚定义的,所以当if判断其是否为空,结果为空,所以继续往下执行。接着调用init_workspace(&pa_workspace, PA_SIZE)函数,其定义为:

static int init_workspace(workspace *w, size_t size){    void *data;    int fd;        /* dev is a tmpfs that we can use to carve a shared workspace         * out of, so let's do that...         */    fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600);    if (fd < 0)        return -1;    if (ftruncate(fd, size) < 0)        goto out;    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);    if(data == MAP_FAILED)        goto out;    close(fd);    fd = open("/dev/__properties__", O_RDONLY);    if (fd < 0)        return -1;    unlink("/dev/__properties__");    w->data = data;    w->size = size;    w->fd = fd;    return 0;out:    close(fd);    return -1;}
pa_workspace和PA_SIZE也在当前文件夹中定义,如下:

#define PA_SIZE 49152static workspace pa_workspace;

该函数中有涉及另一个结构体workplace,就定义当函数所在的文件夹,其定义为:

typedef struct {    void *data;    size_t size;    int fd;} workspace;
这个文件夹中存储了三个变量,数据、大小和文件描述符。init_workplace()这个函数里面就要初始化一个这样的结构体。

首先,打开一个设备文件/dev/__properties__", 通过ftruncate()函数调用将这个文件的大小改为size。size是通过调用函数时形参传递进来的。

然后,调用mmap()函数映射一段内存。返回映射区的地址保存在data中。

最后,将对应的data、size、fd分别给workplace结构体指针w赋值。

函数执行成功,返回0。回到init_property_area函数中。init_workplace()函数返回后进行if判断,若执行成功,返回0,所以接着往下执行。

调用fcntl()函数,关于fcntl函数的功能,

参考:http://www.cnblogs.com/andtt/articles/2178875.html和http://blog.csdn.net/ustc_dylan/article/details/6930189


pa_workspace.data表示的是一段大小为PA_SIZE的内存地址,将这个地址加上PA_INFO_START赋值给pa_info_array。PA_INFO_START定义为:

#define PA_INFO_START 1536
然后将pa_workplace.data代表的那段大小为PA_SIZE的内存通过调用memset()函数将其内存清0。
记者就是一些简单的赋值操作了,现在回到main()函数中。


执行get_hardware_name(hardware, &revision);该函数定义在:~/my_android/system/core/init/util.c文件中,实现代码如下:

void get_hardware_name(char *hardware, unsigned int *revision){    char data[1024];    int fd, n;    char *x, *hw, *rev;    /* Hardware string was provided on kernel command line */    if (hardware[0])        return;    fd = open("/proc/cpuinfo", O_RDONLY);    if (fd < 0) return;    n = read(fd, data, 1023);    close(fd);    if (n < 0) return;    data[n] = 0;    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[n++] = tolower(*x);                x++;                if (n == 31) break;            }            hardware[n] = 0;        }    }    if (rev) {        x = strstr(rev, ": ");        if (x) {            *revision = strtoul(x + 2, 0, 16);        }    }}

这个函数的实参hardware是一32个元素的字符数组,revision为一个unsigned值,也定义在函数所在的文件中,定义如下:

static char hardware[32];static unsigned revision = 0;

get_hardware_name()函数从"proc/cpuinfo"文件读取相应字符串到data中,然后通过调用strstr函数将data中"\nHardware"开始的字符保存到hw中,将“\nRevision”开始的字符保存到rev中。

strstr()函数的功能:就是在第一个参数中查找第二个参数第一次出现的地址,将地址赋值给一个字符指针,接着就可以利用这个字符指针找到从这个地址开始往后的字符。

"/proc/cpuinfo中"中的内容,可以通过adb shell登录模拟器来查看,其内容如下:


后面两个if语句对hw和rev进行处理,最终得到我们想要的数值。其中hw部分,提取Goldfish这几个字符,并将其大写转为小写。rev那部分数据转化为十六进制表示。


回到main()函数,接着执行process_kernel_cmdline();该函数和main()函数定义在同一文件夹中,实现代码如下:

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);    /* now propogate the info given on command line to internal variables     * used by init as well as the current required properties     */    export_kernel_boot_props();}
除了使用import_kernel_cmdline函数导入内核变量外,主要的功能就是调用export_kernel_boot_props函数通过属性设置内核变量,主要实现的功能是处理内核命令行,以下从细节进行分析。


首先调用chmod()函数改变"/proc/cmdline"的文件属性。

import_kernel_cmdline(0, import_kernel_nv);的定义在~/my_android/system/core/init/util.c中,实现代码如下:

void import_kernel_cmdline(int in_qemu,                           void (*import_kernel_nv)(char *name, int in_qemu)){    char cmdline[1024];    char *ptr;    int fd;    fd = open("/proc/cmdline", O_RDONLY);    if (fd >= 0) {        int n = read(fd, cmdline, 1023);        if (n < 0) n = 0;        /* get rid of trailing newline, it happens */        if (n > 0 && cmdline[n-1] == '\n') n--;        cmdline[n] = 0;        close(fd);    } else {        cmdline[0] = 0;    }    ptr = cmdline;    while (ptr && *ptr) {        char *x = strchr(ptr, ' ');        if (x != 0) *x++ = 0; //可以拆分为*x = 0; x++;        import_kernel_nv(ptr, in_qemu);        ptr = x;    }}

首先打开文件"/proc/cmdline",读取其内容到变量cmdline中,"/proc/cmdline"中内容如下:


其后对cmdline的字符数组处理非常简单。然后到while()循环,

strchr函数返回第二个变量在第一个变量中第一次出现的位置,具体用法可参考:http://blog.csdn.net/sky2098/article/details/1530433

import_kernel_nv(ptr, in_qemu)定义在~/my_android/system/core/init/init.c中,实现的代码如下:

static void import_kernel_nv(char *name, int for_emulator){    char *value = strchr(name, '=');    int name_len = strlen(name);    if (value == 0) return;    *value++ = 0;    if (name_len == 0) return;#ifdef HAVE_SELINUX    if (!strcmp(name,"selinux")) {        selinux_enabled = atoi(value);    }#endif    if (for_emulator) {        /* in the emulator, export any kernel option with the         * ro.kernel. prefix */        char buff[PROP_NAME_MAX];        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );        if (len < (int)sizeof(buff))            property_set( buff, value );        return;    }    if (!strcmp(name,"qemu")) {        strlcpy(qemu, value, sizeof(qemu));#ifdef WANTS_EMMC_BOOT    } else if (!strcmp(name,"androidboot.emmc")) {        if (!strcmp(value,"true")) {            emmc_boot = 1;        }#endif    } else if (!strcmp(name,BOARD_CHARGING_CMDLINE_NAME)) {        strlcpy(battchg_pause, value, sizeof(battchg_pause));    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {        const char *boot_prop_name = name + 12;        char prop[PROP_NAME_MAX];        int cnt;        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);        if (cnt < PROP_NAME_MAX)            property_set(prop, value);    }}


然后,返回到process_kernel_cmdline()中,由于变量qemu的定义为:

static char qemu[32];

由于没有初始化,所以if(qemu[0])判断为否,所以接着执行export_kernel_boot_props()函数,其定义在~/my_android/system/core/init/init.c中,实现的代码如下:

static void export_kernel_boot_props(void){    char tmp[PROP_VALUE_MAX];    const char *pval;    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++) {        pval = property_get(prop_map[i].src_prop);        property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);    }    pval = property_get("ro.boot.console");    if (pval)        strlcpy(console, pval, sizeof(console));    /* save a copy for init's usage during boot */    strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode));    /* if this was given on kernel command line, override what we read     * before (e.g. from /proc/cpuinfo), if anything */    pval = property_get("ro.boot.hardware");    if (pval)        strlcpy(hardware, pval, sizeof(hardware));    property_set("ro.hardware", hardware);    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);    property_set("ro.revision", tmp);    property_set("ro.emmc",emmc_boot ? "1" : "0");    property_set("ro.boot.emmc", emmc_boot ? "1" : "0");    /* 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函数的代码可以看出,该函数实际上就是来回设置一些属性值,并且利用某些属性值修改console、hardware等变量。其中hardware变量(就是一个长度为32的字符数组)在get_hardware_name函数中已经从/proc/cpuinfo文件中获得过一次值了,在export_kernel_boot_props函数中又通过ro.boot.hardware属性设置了一次值。

其中property_get(prop_map[i].src_prop)和property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);的定义在~/my_android/system/core/init/property_service.c中,实现的代码如下:

const char* property_get(const char *name){    prop_info *pi;    if(strlen(name) >= PROP_NAME_MAX) return 0;    pi = (prop_info*) __system_property_find(name);    if(pi != 0) {        return pi->value;    } else {        return 0;    }}
int property_set(const char *name, const char *value){    prop_area *pa;    prop_info *pi;    int namelen = strlen(name);    int valuelen = strlen(value);    if(namelen >= PROP_NAME_MAX) return -1;    if(valuelen >= PROP_VALUE_MAX) return -1;    if(namelen < 1) return -1;    pi = (prop_info*) __system_property_find(name);    if(pi != 0) {        /* ro.* properties may NEVER be modified once set */        if(!strncmp(name, "ro.", 3)) return -1;        pa = __system_property_area__;        update_prop_info(pi, value, valuelen);        pa->serial++;        __futex_wake(&pa->serial, INT32_MAX);    } else {        pa = __system_property_area__;        if(pa->count == PA_COUNT_MAX) return -1;        pi = pa_info_array + pa->count;        pi->serial = (valuelen << 24);        memcpy(pi->name, name, namelen + 1);        memcpy(pi->value, value, valuelen + 1);        pa->toc[pa->count] =            (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));        pa->count++;        pa->serial++;        __futex_wake(&pa->serial, INT32_MAX);    }    /* 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);#ifdef HAVE_SELINUX    } else if (strcmp("selinux.reload_policy", name) == 0 &&               strcmp("1", value) == 0) {        selinux_reload_policy();#endif    }    property_changed(name, value);    return 0;}


关于Android中的property机制,可参考http://leave001.blog.163.com/blog/static/1626912932013030101531571/

接下来的#ifdef HAVE_SELINUX……#endif,代码如下:

int main(int argc, char **argv){    ...#ifdef HAVE_SELINUX    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);    INFO("loading selinux policy\n");    if (selinux_enabled) {        if (selinux_android_load_policy() < 0) {            selinux_enabled = 0;            INFO("SELinux: Disabled due to failed policy load\n");        } else {            selinux_init_all_handles();        }    } else {        INFO("SELinux:  Disabled by command line option\n");    }    /* 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");#endif...}

是和Security-Enhanced Android相关的。



参考:

  1. http://blog.csdn.net/jiangbei_lengyu/article/details/8564144
  2. http://www.cnblogs.com/bastard/archive/2012/08/28/2660389.html
  3. http://www.cnblogs.com/nokiaguy/archive/2013/04/14/3020774.html





更多相关文章

  1. android JNI调用
  2. Android(安卓)Animation浅析
  3. Android第三章 (UI)
  4. Android(安卓)SDK下载慢的最给力解决办法
  5. Unity Android交互过坑指南
  6. Android项目结构和AndroidManifest.xml
  7. 箭头函数的基础使用
  8. NPM 和webpack 的基础使用
  9. Python技巧匿名函数、回调函数和高阶函数

随机推荐

  1. Android WebView与 JS 的交互方式
  2. 百度android定位SDK问题
  3. Android-开发搭建
  4. android之常用知识点(一)
  5. android中线程进程模型
  6. 对Android系统权限的认识
  7. android 仿QQ界面
  8. Android关于分屏的知识总结
  9. Android真响应式架构——Model层设计
  10. Android中的字符串