现在的位置: 首页 > 综合 > 正文

Android 的启动流程

2013年10月05日 ⁄ 综合 ⁄ 共 10959字 ⁄ 字号 评论关闭
文章目录

 Android 根文件系统启动过程。

在Android系统启动时,内核引导参数上一般都会设置“init=/init”, 这样的话,如果内核成功挂载了这个文件系统之后,首先运行的就是这个根目录下的init程序。

init程序源码在Android官方源码的system/core/init中,main在init.c里。我们的分析就从main开始。

init:
(1)安装SIGCHLD信号。(如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。因此需要对SIGCHLD信号做出处理,回收僵尸进程的资源,避免造成不必要的资源浪费。)
(2)对umask进行清零。
 

umask是什么?

当我们登录系统之后创建一个文件总是有一个默认权限的,那么这个权限是怎么来的呢?这就是umask干的事情。umask设置了用户创建文件的默认权限,它与chmod的效果刚好相反,umask设置的是权限“补码”,而chmod设置的是文件权限码。一般在/etc/profile、$ [HOME]/.bash_profile或$[HOME]/.profile中设置umask值。

如何计算umask值?

umask命令允许你设定文件创建时的缺省模式,对应每一类用户(文件属主、同组用户、其他用户)存在一个相应的umask值中的数字。对于文件来说,这一数字的最大值分别是6。系统不允许你在创建一个文本文件时就赋予它执行权限,必须在创建后用chmod命令增加这一权限。目录则允许设置执行权限,这样针对目录来说,umask中各个数字最大可以到7。

该命令的一般形式为:umask nnn
其中nnn为umask置000 - 777。

我们只要记住u m a s k是从权限中“拿走”相应的位即可。下表是umask值与权限的对照表:
umask 文件 目录
--------------------
0 6 7
1 6 6
2 4 5
3 4 4
4 2 3
5 2 2
6 0 1
7 0 0
--------------------

如:umask值为022,则默认目录权限为755,默认文件权限为644。

(3)为rootfs建立必要的文件夹,并挂载适当的分区。
    /dev (tmpfs)
        /dev/pts (devpts)
        /dev/socket
    /proc (proc)
    /sys  (sysfs)
 (4)创建/dev/null和/dev/kmsg节点。
 (5)解析/init.rc,将所有服务和操作信息加入链表。

脚本解析过程:
parse_config_file("/init.rc")
int parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);
    if (!data) return -1;

    parse_config(fn, data);
    DUMP();
    return 0;
}
static void parse_config(const char *fn, char *s)

    ...
    case T_NEWLINE:
        if (nargs) {
            int kw = lookup_keyword(args[0]);
            if (kw_is(kw, SECTION)) {
                state.parse_line(&state, 0, 0);
                parse_new_section(&state, kw, nargs, args);
            } else {
                state.parse_line(&state, nargs, args);
            }
            nargs = 0;
        }
   ...

parse_config会逐行对脚本进行解析,如果关键字类型为  SECTION ,那么将会执行 parse_new_section()
类型为 SECTION 的关键字有: on 和 sevice
关键字类型定义在 Parser.c (system\core\init) 文件中
Parser.c (system\core\init)
#define SECTION 0x01
#define COMMAND 0x02
#define OPTION  0x04
关键字        属性      
capability,  OPTION,  0, 0)
class,       OPTION,  0, 0)
class_start, COMMAND, 1, do_class_start)
class_stop,  COMMAND, 1, do_class_stop)
console,     OPTION,  0, 0)
critical,    OPTION,  0, 0)
disabled,    OPTION,  0, 0)
domainname,  COMMAND, 1, do_domainname)
exec,        COMMAND, 1, do_exec)
export,      COMMAND, 2, do_export)
group,       OPTION,  0, 0)
hostname,    COMMAND, 1, do_hostname)
ifup,        COMMAND, 1, do_ifup)
insmod,      COMMAND, 1, do_insmod)
import,      COMMAND, 1, do_import)
keycodes,    OPTION,  0, 0)
mkdir,       COMMAND, 1, do_mkdir)
mount,       COMMAND, 3, do_mount)
on,          SECTION, 0, 0)
oneshot,     OPTION,  0, 0)
onrestart,   OPTION,  0, 0)
restart,     COMMAND, 1, do_restart)
service,     SECTION, 0, 0)
setenv,      OPTION,  2, 0)
setkey,      COMMAND, 0, do_setkey)
setprop,     COMMAND, 2, do_setprop)
setrlimit,   COMMAND, 3, do_setrlimit)
socket,      OPTION,  0, 0)
start,       COMMAND, 1, do_start)
stop,        COMMAND, 1, do_stop)
trigger,     COMMAND, 1, do_trigger)
symlink,     COMMAND, 1, do_symlink)
sysclktz,    COMMAND, 1, do_sysclktz)
user,        OPTION,  0, 0)
write,       COMMAND, 2, do_write)
chown,       COMMAND, 2, do_chown)
chmod,       COMMAND, 2, do_chmod)
loglevel,    COMMAND, 1, do_loglevel)
device,      COMMAND, 4, do_device)

parse_new_section()中再分别对 service 或者 on 关键字开头的内容进行解析。
    ...
    case K_service:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;
    case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
    }
    ...

对 on 关键字开头的内容进行解析
static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    ...
    act = calloc(1, sizeof(*act));
    act->name = args[1];
    list_init(&act->commands);
    list_add_tail(&action_list, &act->alist);
    ...
}

对 service 关键字开头的内容进行解析
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc;
    if (nargs < 3) {
        parse_error(state, "services must have a name and a program\n");
        return 0;
    }
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'\n", args[1]);
        return 0;
    }
    //如果服务已经存在service_list列表中将会被忽略
    svc = service_find_by_name(args[1]);
    if (svc) {
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);

        return 0;
    }
  
    nargs -= 2;
    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    if (!svc) {
        parse_error(state, "out of memory\n");
        return 0;
    }
    svc->name = args[1];
    svc->classname = "default";
    memcpy(svc->args, args + 2, sizeof(char*) * nargs);
    svc->args[nargs] = 0;
    svc->nargs = nargs;
    svc->onrestart.name = "onrestart";
    list_init(&svc->onrestart.commands);
    //添加该服务到 service_list 列表
    list_add_tail(&service_list, &svc->slist);
    return svc;
}
服务的表现形式:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...

申请一个service结构体,然后挂接到service_list链表上,name 为服务的名称 pathname 为执行的命令 argument
为命令的参数。之后的 option 用来控制这个service结构体的属性,parse_line_service 会对 service关键字后的
内容进行解析并填充到 service 结构中 ,当遇到下一个service或者on关键字的时候此service选项解析结束。

例如:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

    socket zygote stream 666
    onrestart write /sys/android_power/request_state wake
服务名称为:                           zygote
启动该服务执行的命令:                 /system/bin/app_process
命令的参数:                           -Xzygote /system/bin --zygote --start-system-server

socket zygote stream 666: 创建一个名为:/dev/socket/zygote 的 socket ,类型为:stream

当*.rc 文件解析完成以后:
action_list 列表项目如下:
on init
on boot
on property:ro.kernel.qemu=1
on property:persist.service.adb.enable=1
on property:persist.service.adb.enable=0
init.marvell.rc 文件
on early-init
on init
on early-boot
on boot

service_list 列表中的项有:
service console
service adbd
service servicemanager
service mountd
service debuggerd
service ril-daemon
service zygote
service media
service bootsound
service dbus
service hcid
service hfag
service hsag
service installd
service flash_recovery

状态服务器相关:
在init.c 的main函数中启动状态服务器。
property_set_fd = start_property_service();
状态读取函数:
Property_service.c (system\core\init)
const char* property_get(const char *name)
Properties.c (system\core\libcutils)
int property_get(const char *key, char *value, const char *default_value)
状态设置函数:
Property_service.c (system\core\init)
int property_set(const char *name, const char *value)
Properties.c (system\core\libcutils)
int property_set(const char *key, const char *value)

在终端模式下我们可以通过执行命令 setprop <key> <value>
setprop 工具源代码所在文件: Setprop.c (system\core\toolbox)
Getprop.c (system\core\toolbox):        property_get(argv[1], value, default_value);

Property_service.c (system\core\init)
中定义的状态读取和设置函数仅供init进程调用,

handle_property_set_fd(property_set_fd);
  property_set()   //Property_service.c (system\core\init)
    property_changed(name, value) //Init.c (system\core\init)
      queue_property_triggers(name, value)
      drain_action_queue()
只要属性一改变就会被触发,然后执行相应的命令: 
例如:
在init.rc 文件中有
on property:persist.service.adb.enable=1
  start adbd
on property:persist.service.adb.enable=0
  stop adbd
所以如果在终端下输入:
setprop property:persist.service.adb.enable 1或者0
那么将会开启或者关闭adbd 程序。

执行action_list 中的命令:
从action_list 中取出 act->name 为 early-init 的列表项,再调用 action_add_queue_tail(act)将其插入到
队列 action_queue 尾部。drain_action_queue() 从action_list队列中取出队列项 ,然后执行act->commands
列表中的所有命令。
所以从  ./system/core/init/init.c mian()函数的程序片段:
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
/* run all property triggers based on current state of the properties */
queue_all_property_triggers();
drain_action_queue();
可以看出,在解析完init.rc init.marvell.rc 文件后,action 命令执行顺序为:
执行act->name 为 early-init,act->commands列表中的所有命令
执行act->name 为 init,            act->commands列表中的所有命令
执行act->name 为 early-boot,act->commands列表中的所有命令
执行act->name 为 boot,            act->commands列表中的所有命令

关键的几个命令:
class_start default   启动所有service 关键字定义的服务。
class_start 在act->name为boot的 act->commands列表中,所以当 class_start 被触发后,实际
上调用的是函数 do_class_start()
int do_class_start(int nargs, char **args)
{
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    service_for_each_class(args[1], service_start_if_not_disabled);
    return 0;
}
void service_for_each_class(const char *classname,
                            void (*func)(struct service *svc))
{
    struct listnode *node;
    struct service *svc;
    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        if (!strcmp(svc->classname, classname)) {
            func(svc);
        }
    }
}

 (6)从/proc/cmdline中提取信息内核启动参数,并保存到全局变量。
(7)先从上一步获得的全局变量中获取信息硬件信息和版本号,如果没有则从/proc/cpuinfo中提取,并保存到全局变量。
(8)根据硬件信息选择一个/init.(硬件).rc,并解析,将服务和操作信息加入链表。
    在G1的ramdisk根目录下有两个/init.(硬件).rc:init.goldfish.rc和init.trout.rc,init程序会根据上一步获得的硬件信息选择一个解析。
(9)执行链表中带有“early-init”触发的的命令。
(10)遍历/sys文件夹,是内核产生设备添加事件(为了自动产生设备节点)。
(11)初始化属性系统,并导入初始化属性文件。
(12)从属性系统中得到ro.debuggable,若为1,則初始化keychord监听。
(13)打開console,如果cmdline中沒有指定console則打開默認的/dev/console
(14)开机文字("A N D R I O D")
Android 系统启动后,init.c中main()调用load_565rle_image()函数读取/initlogo.rle(一张565 rle压缩的位图),如果读取成功,则在/dev/graphics/fb0显示Logo图片;如果读取失败,则将/dev/tty0设为TEXT模式,并打开/dev/tty0,输出文本“A N D R I O D”字样。
定义加载图片文件名称
#define INIT_IMAGE_FILE "/initlogo.rle"
int load_565rle_image( char *file_name );
#endif
init.c中main()加载/initlogo.rle文件。

 

if( load_565rle_image(INIT_IMAGE_FILE) ) {
    fd = open("/dev/tty0", O_WRONLY);
    if (fd >= 0) {
        const char *msg;
            msg = "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"  // console is 40 cols x 30 lines
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "             A N D R O I D ";
        write(fd, msg, strlen(msg));
        close(fd);
    }
    }

 

相关代码: 
/system/core/init/init.c 
    
/system/core/init/init.h 
/system/core/init/init.rc 
/system/core/init/logo.c 
*.rle文件的制作步骤: 
a. 使用GIMP或者Advanced Batch Converter软件,将图象转换为RAW格式; 
b. 使用android自带的rgb2565工具,将RAW格式文件转换为RLE格式(如:rgb2565 -rle < initlogo.raw > initlogo.rle)。

 

 

 

(15)判斷cmdline 中的參數,并设置属性系统中的参数:
         1、 如果 bootmode為
         - factory,設置ro.factorytest值為1
         - factory2,設置ro.factorytest值為2
         - 其他的設ro.factorytest值為0
       2、如果有serialno参数,則設置ro.serialno,否則為""
       3、如果有bootmod参数,則設置ro.bootmod,否則為"unknown"
       4、如果有baseband参数,則設置ro.baseband,否則為"unknown"
       5、如果有carrier参数,則設置ro.carrier,否則為"unknown"
       6、如果有bootloader参数,則設置ro.bootloader,否則為"unknown"
       7、通过全局变量(前面从/proc/cpuinfo中提取的)設置ro.hardware和ro.version。
(16)執行所有触发标识为init的action。
(17)開始property服務,讀取一些property文件,這一動作必須在前面那些ro.foo設置后做,以便/data/local.prop不能干預到他們。
      - /system/build.prop
      - /system/default.prop
      - /data/local.prop
      - 在讀取默認的property后讀取presistent propertie,在/data/property中
(18)為sigchld handler創建信號機制
(19)確認所有初始化工作完成:
    device_fd(device init 完成)
    property_set_fd(property server start 完成)
    signal_recv_fd (信號機制建立)
(20) 執行所有触发标识为early-boot的action
(21) 執行所有触发标识为boot的action
(22)基于當前property狀態,執行所有触发标识为property的action
(23)注冊輪詢事件:
      - device_fd
      - property_set_fd
      -signal_recv_fd
      -如果有keychord,則注冊keychord_fd
(24)如果支持BOOTCHART,則初始化BOOTCHART
(25)進入主進程循環:
      - 重置輪詢事件的接受狀態,revents為0
      - 查詢action隊列,并执行。
      - 重啟需要重啟的服务
      - 輪詢注冊的事件
          - 如果signal_recv_fd的revents為POLLIN,則得到一個信號,獲取并處理
          - 如果device_fd的revents為POLLIN,調用handle_device_fd
          - 如果property_fd的revents為POLLIN,調用handle_property_set_fd
          - 如果keychord_fd的revents為POLLIN,調用handle_keychord

抱歉!评论已关闭.