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 49152
static 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.htmlhttp://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属性设置了一次值。

接下来的#ifdef HAVE_SELINUX……#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

Android 4.2启动代码分析(一)的更多相关文章

  1. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  2. Linux内核启动代码分析二之开发板相关驱动程序加载分析

    Linux内核启动代码分析二之开发板相关驱动程序加载分析 1 从linux开始启动的函数start_kernel开始分析,该函数位于linux-2.6.22/init/main.c  start_ke ...

  3. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...

  4. Cortex-M0(NXP LPC11C14)启动代码分析

    作者:刘老师,华清远见嵌入式学院讲师. 启动代码的一般作用 1.堆和栈的初始化: 2.向量表定义: 3.地址重映射及中断向量表的转移: 4.初始化有特殊要求的断口: 5.处理器模式: 6.进入C应用程 ...

  5. ARM Linux启动代码分析

    前言 在学习.分析之前首先要弄明白一个问题:为什么要分析启动代码? 因为启动代码绝大部分都是用汇编语言写的,对于没学过或者不熟悉汇编语言的同学确实有一定难度,但是如果你想真正深入地学习Linux,那么 ...

  6. STM32启动代码分析

    STM32启动文件简单分析(STM32F10x.s适用范围)定时器, 型号, 名字在<<STM32不完全手册里面>>,我们所有的例程都采用了一个叫STM32F10x.s的启动文 ...

  7. S3C6410的启动代码分析&nbsp;一

    本文开始第一篇,启动代码的编写,注意,仅仅是启动代码,并不是bootloader,因为只有boot,没有loader. 第一要明确:CPU上电之后,会从某个固定地址执行指令.ARM结构的CPU从地址0 ...

  8. STM32启动代码分析及其汇编学习-ARM

    STM32 启动代码 Author By YuCloud 边看启动文件边学汇编 汇编 see ARM: Assembler User Guide see: https://blog.csdn.net/ ...

  9. android recovery 主系统代码分析

    阅读完上一篇文章: http://blog.csdn.net/andyhuabing/article/details/9226569 我们已经清楚了如何进入正常模式和Recovery模式已有深刻理解了 ...

随机推荐

  1. iOS7中group类型tableview的section间距设置

    1.如果是首行,检查是否设置了headerView. 2.其他设置tableView . sectionFooterHeight  = 1.0.  这个距离的计算是header的高度加上footer的 ...

  2. Visual Studio Code和Docker开发asp.net core和mysql应用

    Visual Studio Code和Docker开发asp.net core和mysql应用 .net猿遇到了小鲸鱼,觉得越来越兴奋.本来.net猿只是在透过家里那田子窗看外面的世界,但是看着海峡对 ...

  3. Mybatis基础入门 I

    作为ORM的重要框架,MyBatis是iBatis的升级版.Mybatis完全将SQL语句交给编程人员掌控,这点和Hibernate的设计理念不同(至于Hibernate的理念,待我先学习学习). 下 ...

  4. JMX rmi的一些问题

    http://hi.baidu.com/84zhu/item/79bcd5de734f1318d68ed015 http://1985wanggang.blog.163.com/blog/static ...

  5. OpenStreetMap(OSM) features

    目录 1 Primary features 1.1 Aerialway 1.2 Aeroway 1.3 Amenity 1.3.1 Sustenance 1.3.2 Education 1.3.3 T ...

  6. libc++abi.dylib handler threw exception

    在iOS开发时,有时候遇到libc++abi.dylib handler threw exception这样的异常,  虽然在断点出加上了All Exceptions,也断到相应的代码了,但是没打印对 ...

  7. 设计模式六大原则——迪米特法则(LoD)

    1.背景 在图书馆借书.刚開始的时候,直接跑到对应的楼层去,到里面去转,去找要借的书,在里面溜达半天才干找到:后来知道图书馆有一个电脑查询处.然后直接在电脑上输入想要借的书,电脑就会显示你想要借的书的 ...

  8. AngularJs(五)从Controller控制器谈谈$scope作用域

    大纲 用于简单示例和简单应用的controller 应用 多个controller应用的作用域问题 controller继承作用域问题 Controller的创建 AngularJs controll ...

  9. C#中各种计时器

    1.使用 Stopwatch 类 (System.Diagnostics.Stopwatch) Stopwatch 实例可以测量一个时间间隔的运行时间,也可以测量多个时间间隔的总运行时间.在典型的 S ...

  10. CSS3 border属性的妙用

    .ribbon { background: #45c9c8; position: absolute; width: 75px; height: 25px; line-height: 25px; top ...