Android init 分析

Android init 分析


一、AndroidInit.c执行流程

Android中的内核启动后,kernel会启动第一个用户级别的进程:init,它是一个由内核启动的用户级进程,它的进程号是1,它以一个守护进程的方式运行,主要提供以下功能:

@设备管理;

@解析启动脚本init.rc;

@执行init.rc中的基本功能;

@执行init.rc中的各种服务;
init进程对应的代码在android源码目录中的:system/core/init/中。

Android的每个目录下面都有一个非常重要的文件Android.mk,负责编译该目录下面的代码:System/core/init/android.mk

LOCAL_MODULE:= init

LOCAL_FORCE_STATIC_EXECUTABLE := true

LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)

LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED)

LOCAL_STATIC_LIBRARIES := libfs_mgr libcutils libc

ifeq ($(HAVE_SELINUX),true)

LOCAL_STATIC_LIBRARIES += libselinux

LOCAL_C_INCLUDES += external/libselinux/include

LOCAL_CFLAGS += -DHAVE_SELINUX

endif

include $(BUILD_EXECUTABLE)

# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init

SYMLINKS := \

$(TARGET_ROOT_OUT)/sbin/ueventd \

$(TARGET_ROOT_OUT)/sbin/watchdogd

上面的代码会生成一个叫init的可执行程序,它会被放在/下面,且同时会产生一个符号链接/sbin/eventd,指向/init,先看下init.rc的内容:

on early-init

start ueventd

看来init在解析脚本的时候又启动了一个自己的进程,只是进程名变成了ueventd

现在来看下system/core/init/init.c

int main(int argc, char **argv)

{

…...

if (!strcmp(basename(argv[0]),"ueventd"))

return ueventd_main(argc,argv);

根据进程名不同,程序执行路径不同。Ueventd顾名思义应该是接收uvent的守护进程,这里它的主要作用根据uevent是创建或删除/dev/xxx(xxx设备名),我们知道在linux下面创建设备节点的接口mknod,我们跟进去看看这个接口是在哪里调用的

system/core/init/ueventd.c

intueventd_main(int argc, char **argv)

{

umask(000); //umask设置了用户创建文件的默认权限.

signal(SIGCHLD,SIG_IGN);

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

ueventd_parse_config_file("/ueventd.rc");

snprintf(tmp,sizeof(tmp), "/ueventd.%s.rc", hardware);

ueventd_parse_config_file(tmp);

device_init();

//ueventd有两个脚本需要解析,ueventd.rc,ueventd.xxx.rc脚本,又见这个脚本可以让客户设置/dev/sys目录及子目录的权限.

Uevent.rc

/dev/null 0666 root root

/dev/zero 0666 root root

/dev/full 0666 root root

...

#sysfs properties

/sys/devices/virtual/input/input* enable 0660 root input

/sys/devices/virtual/input/input* poll_delay 0660 root input

/sys/devices/virtual/usb_composite/* enable 0664 root system

...

这里请注意,ueventd_parse_config_file并不创建设备节点,它的作用是提供数据库,当有设备节点生成的时候,eventd会参考这个数据库设置设备节点的权限。

system/core/init/devices.c

voiddevice_init(void)

{

device_fd= uevent_open_socket(64*1024, true);

.....

coldboot("/sys/class");

coldboot("/sys/block");

coldboot("/sys/devices");

uevent_open_socket这个函数是通过kobject_uevent的方式通知的应用层,就是往一个socket广播一个消息,只需要在应用层打开socket监听NETLINK_KOBJECT_UEVENT组的消息就可以收到了,主要是创建了socket接口获得uevent的文件描述符,然后触发/sys/class,/sys/block,/sys/devices这三个目录及其子目录下的uevent,然后接受并创建设备节点,至此设备节点才算创建。

system/core/init/ueventd.c

intueventd_main(int argc, char **argv)

{

...

ufd.events= POLLIN;

ufd.fd= get_device_fd();

while(1){

ufd.revents= 0;

nr= poll(&ufd, 1, -1);

if(nr <= 0)

continue;

if(ufd.revents == POLLIN)

handle_device_fd();

}

}

死循环,最后调用handle_device_fd来处理收到的kernel传过来的uevent消息,动态创建或删除节点。handle_device_fd会最终调用mknod创建设备节点,流程如下:

handle_device_fd->handle_device_event-> handle_device-> make_device-> mknod

对于uevent_open_socket这个函数我们现在回过头来再跟进去深入分析下,因为我们在portingandroid4.2 的时候发现这个函数调用了一个系统函数socket,但是返回了一个错误码:Protocolnot supported。我们首先检查一下bionic的系统调用号是否和kernel3.4定义的是否一致:

system/core/libcutils/uevent.c

int uevent_open_socket(intbuf_sz, bool passcred)

{

...

s = socket(PF_NETLINK,SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

if(s < 0) {

KLOG_ERROR("****uevent5","%s\n", strerror(errno));

return -1;

}

bionic/libc/include/sys/linux-syscalls.h:594:

#define __NR_socket (__NR_SYSCALL_BASE + 347)

/kernel3.4/common/arch/mvp/include/asm/unistd.h:383:

#define __NR_socket (__NR_Linux + 347)

通过检查是一致的,排除是系统调用号错误的可能。

最后发现socket函数的第二个参数SOCK_DGRAM,在bionic/libc/include/sys/socket.h#defineSOCK_DGRAM 2

kernel3.4/common/arch/mvp/include/asm/socket.h

SOCK_DGRAM = 1,

修改kernel3.4中的定义SOCK_DGRAM = 2,

Kernel3.4/common/lib/Makefile

lib-$(CONFIG_HOTPLUG) +=kobject_uevent.o

socket函数的第三个参数NETLINK_KOBJECT_UEVENT,和kernelHOTPLUG机制有关,需要配置HOTPLUG

现在我们接着前面的分析继续来看system/core/init/init.c

int main(int argc, char **argv)

{

...

# 创建一些linux根文件系统中的目录,Linux中将一个文件系统与一个存储设备关联起来的过程称为挂装(mount)。使用mount命令将一个文件系统附着到当前文件系统层次结构中(根)。在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。因此,这里就是把tmpfs文件系统加到目录/dev下面,文件系统的名称是tmpfstmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的RamfsTmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。接着创建ptssocket目录,在/dev/pts挂装devpts虚拟文件系统,在目录/proc挂装proc文件系统,在目录/sys挂装sysfs文件系统。

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/null

open_devnull_stdio();

# 读取并且解析init.rc文件

init_parse_config_file("/init.rc");

#解析完init.rc脚本文件会得到一系列的Action(动作),接着下面的代码将执行这些Actioninit将动作执行的时间划分为四个阶段:early-init,init,early-boot,boot。由于有些动作必须在其他动作完成后才能执行,所以就有了先后之分。

action_for_each_trigger("early-init",action_add_queue_tail);

#触发在init脚本文件中名字为early-initaction,并且执行其commands,其实是:on early-init

queue_builtin_action(wait_for_coldboot_done_action,"wait_for_coldboot_done");

android冷过程结束后会生成dev/.coldboot_done文件,wait_for_coldboot_done这个action会等待dev/.coldboot_done文件的生成,等待时长为5s。当然这个action不会阻塞android的冷启动过程,它会每查询一次就会休眠0.1s,直到冷启动结束。

queue_builtin_action(keychord_init_action,"keychord_init");

目前的init过程中没有service执行keychord机制。

queue_builtin_action(console_init_action,"console_init");

如果/proc/cmdline指定了控制台终端,那么优先使用这个控制台,如果没有指定,那么将使用默认控制台终端/dev/console。调用load_565rle_image函数加载开机图片

queue_builtin_action(property_service_init_action,"property_service_init");

读取/system/build.prop/system/default.prop下的属性并将其设置;创建一个服务器端UNIXDomainSocket,它的socket文件路径为/dev/socket/property_service,这个socket监听来自客户端的属性修改请求

queue_builtin_action(signal_init_action,"signal_init");

这个函数是向其中的一个socketsignal_fd写入数据,由于signal_init过程中初始化了一对已连接的socketsignal_fdsignal_recv_fd,因此此时signal_recv_fd会收到向signal_fd写入的数据,然后查询那个service终止,然后根据该service的属性来作相关的操作,是重启还是结束进行资源回收。

queue_builtin_action(check_startup_action,"check_startup");

确保property_service_init属性设置socket文件描述符和signal_initsignalsocket文件描述符,如果两个有其一不存在,那么将退出系统。

queue_builtin_action(queue_property_triggers_action,"queue_property_triggers");

根据init.rcaction指定的property值与属性中的值比较,如果相等则执行对应的command。例如:
onproperty:ro.secure=0
startconsole
如果当前ro.secure的值为0,那么启动console服务。

queue_builtin_action(bootchart_init_action,"bootchart_init");

Bootchart能够对系统的性能进行分析,并生成系统启动过程的图表,以便为你提供有价值的参考信息。综合所得的信息,你就可以进行相应的改进,从而加快你的Linux 系统启动过程。

.......

init轮询过程:以上部分将所有需要操作的action均放在了action待执行队列中,那么init进程将要进入一个死循环过程,整个android的将会运行在这个生命周期内。1.执行action待执行队列中的所有command
2.
重启所有需要重启的service
3.
注册属性设置property_set_fd,信号signal处理signal_recv_fdkeychordkeychord_fd三个文件描述符的为轮询对象。

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;

}

注册属性设置property_set_fd,信号signal处理signal_recv_fdkeychordkeychord_fd三个文件描述符的为轮询对象。nit进程将三个描述符均定义为了POLLIN事件响应,当描述符有可读数据时,对于socket描述符,有连接请求时ufds就会收到POLLIN事件。

..........

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();

}

}

上面的代码为轮询的总体体现,当有POLLIN事件发生时,相应的ufds[i].revents就会被置为POLLIN,然后执行各自的handler

1.property_set_fd
收到属性设置的socket请求之后,设置相关属性。
2.signal_recv_fd
当有子进程终止时,也就是service终止时,内核会给init发送SIGCHLD,此时调用注册的handler函数,这个handler函数是向其中的一个socketsignal_fd写入数据,由于signal_init过程中初始化了一对已连接的socketsignal_fdsignal_recv_fd,因此此时signal_recv_fd会收到向signal_fd写入的数据,然后查询那个service终止,然后根据该service的属性来作相关的操作,是重启还是结束进行资源回收。
3.keychord_fd
目前的init过程中没有service执行keychord机制。





二、Androidinit脚本语言

前面简单分析了下init.c里的操作,里面提到了解析Init.rc脚本,下面详细解析下Androidinit脚本语言的规范。

Android初始化语言主要包含以下内容:

Actions(行为)、Triggers(触发器)、Commands(命令)、Services(服务)和Options(选项)

Actions(行为):
Actions其实就是一序列的Commands(命令)。Actions都有一个trigger(触发器),它被用于决定action的执行条件。当一个符合action触发条件的事件发生时,action会被加入到执行队列的末尾,除非它已经在队列里了。队列中的每一个action都被依次提取出,而这个action中的每个command(命令)都将被依次执行。
Actions的形式如下:
on<trigger>
<command1>
<command2>
<command3>
示例:
oninit

loglevel 3

# setup the global environment

export PATH/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin

export LD_LIBRARY_PATH /vendor/lib:/system/lib

export ANDROID_BOOTLOGO 1

export ANDROID_ROOT /system

export ANDROID_ASSETS /system/app

export ANDROID_DATA /data

on boot

# adbd on at boot in emulator

on property:ro.kernel.qemu=1

start adbd
上面声明了三个actioninitboot和一个属性触发器,当init被触发时,会顺序执行后面的命令,直到onboot新的actionInit的触发是由init.c里的函数action_for_each_trigger来决定的。当属性ro.kernel.qemu1时,会触发startadbd命令。

Services(服务):
Services(服务)通常表示启动一个可执行程序,options(选项)是当前服务的附加内容,用于配合服务使用。
service <name> <pathname> [ <argument>]*
<option>
<option>
...
name:服务名
pathname:当前服务对应的程序位置
option:当前服务设置的选项

Options(选项):Options(选项)是一个Services(服务)的修正者。他们影响Services(服务)在何时,并以何种方式运行。
Critical

说明这是一个对于设备关键的服务。如果他四分钟内退出大于四次,系统将会重启并进入recovery(恢复)模式。

Disabled

说明这个服务不会同与他同trigger(触发器)下的服务自动启动。他必须被明确的按名启动。

setenv <name> <value> (设置环境变量):

在进程启动时将环境变量<name>设置为<value>

socket <name> <type> <perm> [ <user> [<group> ] ]
创建一个Uinx域的名为/dev/socket/<name>的套接字,并传递它的文件描述符给已启动的进程。<type>必须是"dgram""stream"Usergroup默认为0

user <username>
在启动这个服务前改变该服务的用户名。此时默认为root。当前,如果你的进程要求Linuxcapabilities(能力),你无法使用这个命令。即使你是root,你也必须在程序中请求capabilities(能力)。然后降到你想要的uid

group <groupname> [ <groupname>]*
在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups())。此时默认为root
oneshot
服务只启动一次,服务退出时不重启。

class<name>
指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。

onrestart
当服务重启,执行一个命令(下详)。
export<name> <value>
在全局环境变量中设在环境变量<name><value>。(这将会被所有在这命令之后运行的进程所继承)
ifup<interface>
启动网络接口<interface>
import<filename>
解析一个init配置文件,扩展当前配置。
hostname<name>
设置主机名。
chmod<octal-mode> <path>
更改文件访问权限。
chown<owner> <group> <path>
更改文件的所有者和组。
class_start<serviceclass>
启动所有指定服务类下的未运行服务。
class_stop<serviceclass>
停止指定服务类下的所有已运行的服务。
domainname<name>
设置域名。
insmod<path>
加载<path>中的模块。
mkdir<path> [mode] [owner][group]
创建一个目录<path>,可以选择性地指定modeowner以及group。如果没有指定,默认的权限为755,并属于root用户和root组。
mount<type> <device> <dir> [ <mountoption>]*
试图在目录<dir>挂载指定的设备。<device>可以是以mtd@name的形式指定一个mtd块设备。<mountoption>包括"ro""rw""remount""noatime"...
setprop <name> <value>
设置系统属性<name> <value>.
setrlimit<resource> <cur><max>
设置<resource>rlimit(资源限制)。
start<service>
启动指定服务(如果此服务还未运行)。
stop<service>
停止指定服务(如果此服务在运行中)。
symlink<target> <path>
创建一个指向<path>的软连接<target>
sysclktz<mins_west_of_gmt>
设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)
trigger<event>
触发一个事件。用于将一个action与另一个action排列。
write<path> <string> [ <string>]*
打开路径为<path>的一个文件,并写入一个或多个字符串。

Properties(属性):

Init更新一些系统属性以提供对正在发生的事件的监控能力:
init.action
此属性值为正在被执行的action的名字,如果没有则为""
init.command
此属性值为正在被执行的command的名字,如果没有则为""
init.svc.<name>
名为<name>service的状态("stopped"(停止),"running"(运行),"restarting"(重启))







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值