您的位置:首页 > 移动开发 > Android开发

android init.c 文件分析

2015-01-28 18:19 393 查看
     Android中的内核启动后,kernel会启动第一个用户级别的进程:init,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。init始终是第一个进程。
PS:可以通过:ps  | grep init命令来查看其Pid为1。为天字一号进程,是其他进程的亲爹

本人使用的源码是mtk源码!!可能跟谷歌源码有小的差异!

int main(int argc, char **argv)
{

/* 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.
*/
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);
//在linux 跟目录创建一下目录和设备节点, 并赋予权限
*/
open_devnull_stdio();
klog_init();
property_init();

get_hardware_name(hardware, &revision);

process_kernel_cmdline();

#ifdef MTK_INIT
add_boot_event("SElinux start.");
#endif
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();

其中

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); 将 0 标准输入 定向到/dev/__null__
dup2(fd, 1);  将 1 标准输出 定向到/dev/__null__
dup2(fd, 2);  将 2 标准错误 定向到/dev/__null__
if (fd > 2) {
close(fd);  关闭这个 fd
}
return;
}
}

exit(1);
}

void klog_init(void)
{
static const char *name = "/dev/__kmsg__";

if (klog_fd >= 0) return; /* Already initialized */

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

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

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;
}
PROP_FILENAME = /dev/__properties__
 打开这个设备节点!!

在adb shell  下cat  /proc/cpuinfo结果如下:

Processor       : ARMv7 Processor rev 3 (v7l)
processor       : 0
BogoMIPS        : 1191.73

processor       : 1
BogoMIPS        : 1191.73

Features        : swp half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 3

Hardware        : MT8685
Revision        : 0000
Serial          : 0000000000000000


static char hardware[32];
static unsigned revision = 0;
void get_hardware_name(char *hardware, unsigned int *revision)
{
#ifdef MTK_INIT
char data[2048];
#else
char data[1024];
#endif
int fd, n;
char *x, *hw, *rev;

/* Hardware string was provided on kernel command line */
if (hardware[0])  开始的时候此时 hardware[0]=null;
return;

fd = open("/proc/cpuinfo", O_RDONLY);  打开设备节点能获得硬件CPU 的信息 ,通过cat /proc/cpuinfo
if (fd < 0) return;

#ifdef MTK_INIT
n = read(fd, data, 2047);
#else
n = read(fd, data, 1023);
#endif
close(fd);
if (n < 0) return;

data
= 0;
hw = strstr(data, "\nHardware"); 找到\nHardware在data中出现的地址,并把地址赋值给hw
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
= 0;
}
}

if (rev) {
x = strstr(rev, ": ");
if (x) {
*revision = strtoul(x + 2, 0, 16);
}
} //结果是 Hardware = MT8685
//Revision =0000
}

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
= 0;
close(fd);
} else {
cmdline[0] = 0;
}

ptr = cmdline;
while (ptr && *ptr) {
char *x = strchr(ptr, ' ');
if (x != 0) *x++ = 0;
import_kernel_nv(ptr, in_qemu);
ptr = x;
}
}


读取环境变量到har cmdline[1024] 中,然后将地址复制给 ptr指针,然后执行函数  

import_kernel_nv(ptr, in_qemu);
就是执行函数

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;

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


strchr() 函数

功能:查找字符串s中首次出现字符c的位置

说明:返回首次出现c的位置的指针,

返回的地址是字符串在内存中随机分配的地址再加上你所搜索的字符在字符串位置,

如果s中不存在c则返回NULL。

adb shell cat /proc/cmdline
console=ttyMT0,921600n1 vmalloc=496M slub_max_order=0 lcm=1-cpt_clap070wp03xg_lvds fps=6001 vram=29360128 bootprof.pl_t=1635 bootprof.lk_t=2768 printk.disable_uart=1 boot_reason=0 androidboot.serialno=0123456789ABCDEF hdmi_res=0


通过查看发现strlcpy(qemu, value, sizeof(qemu));没有其他操作。只有androidboot. 开始字符串有处理 ,就是设置属性,也就是
androidboot.serialno=0123456789ABCDEF
这一个属性被添加得到属性之中。通过adb shell  getprop也可以验证 

这个函数最主要函数是  property_set(prop, value); 说白了就是导入linux 的环境变量,
有关于selinux_initialize这篇文章讲的比较好selinux

void property_load_boot_defaults(void)
{
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}


是读取 default.prop文件内容。然后设置属性!!

static int get_meminfo() {
FILE *f;
char line[MAX_LINE];
unsigned long mem_total;
f = fopen("/proc/meminfo", "r");
if (!f) return errno;

NOTICE("[reading mem config file] Start\n");
while (fgets(line, MAX_LINE, f)) {
sscanf(line, "MemTotal: %ld kB", &mem_total);
}

fclose(f);

NOTICE("[reading mem config file] done %ld kB\n", mem_total);
if(mem_total <= LOW_MEM_SIZE_KB)
property_set("ro.config.low_ram","true");
else
property_set("ro.config.low_ram","false");

return 0;
}
#endif


通过打开/proc/meminfo,获得meminfo 内存的总大小,然后根据总大小小于500M设置

property_set("ro.config.low_ram","true")


大于500M

property_set("ro.config.low_ram","false");


我们都知道正常android启动分为NORMAL,RECOVERY ,FACTORY 等等,MTK定义比他更多的模式

#define MT_NORMAL_BOOT 0
#define MT_META_BOOT 1
#define MT_RECOVERY_BOOT 2
#define MT_SW_REBOOT 3
#define MT_FACTORY_BOOT 4
#define MT_ADVMETA_BOOT 5
#define MT_ATE_FACTORY_BOOT 6
#define MT_ALARM_BOOT 7
#define MT_UNKNOWN_BOOT 8
#if defined (MTK_KERNEL_POWER_OFF_CHARGING_SUPPORT)
#define MT_KERNEL_POWER_OFF_CHARGING_BOOT 8
#define MT_LOW_POWER_OFF_CHARGING_BOOT 9
#undef MT_UNKNOWN_BOOT
#define MT_UNKNOWN_BOOT 10
根据不同启动的模式,加载不同的启动rc脚本,进入到不同的环境(fac ,alarm,或者 rmeta),然后执行不同功能,比如我现在格式化我的设备。
mt_boot_mode = get_boot_mode();
根据 mt_boot_mode=MT_FACTORY_BOOT就会进入FACTORY模式,加载它相关的rc文件。

#ifdef MTK_INIT
/* NEW FEATURE: multi-boot mode */
mt_boot_mode = get_boot_mode();
if ( (mt_boot_mode == MT_FACTORY_BOOT) || (mt_boot_mode == MT_ATE_FACTORY_BOOT) ) {
INFO("Factory Mode Booting.....\n");
property_set("sys.mtk.no.factoryimage","1");
init_parse_config_file("/factory_init.rc");
init_parse_config_file("/factory_init.project.rc");
}
else if ( mt_boot_mode == MT_META_BOOT ) {
INFO("META Mode Booting.....\n");
get_mobile_log_config();
init_parse_config_file("/meta_init.rc");
init_parse_config_file("/meta_init.project.rc");
}
#if defined (MTK_KERNEL_POWER_OFF_CHARGING_SUPPORT)
else if (mt_boot_mode == MT_KERNEL_POWER_OFF_CHARGING_BOOT || mt_boot_mode == MT_LOW_POWER_OFF_CHARGING_BOOT)
{
printf("KERNEL Power Off Charging Booting.....\n");
init_parse_config_file("/init.charging.rc");
}
#endif
else {
if ( mt_boot_mode == MT_ADVMETA_BOOT ) {
INFO("ADVMETA Mode Booting.....\n");
property_set("sys.advmetamode","1");
}
else {
property_set("sys.advmetamode","0");
}
#endif // MTK_INIT
init_parse_config_file("/init.rc");
#ifdef MTK_INIT
init_parse_config_file("/init.project.rc");
#ifdef MTK_MLC_NAND_SUPPORT
printf("reading init.fon.merge.rc file\n");
init_parse_config_file("/init.fon.merge.rc");
#else
printf("reading init.fon.rc file\n");
init_parse_config_file("/init.fon.rc");
#endif
}
#endif

#ifdef MTK_INIT
#ifdef MTK_SHARED_SDCARD
if(mt_boot_mode != MT_RECOVERY_BOOT && mt_boot_mode != MT_UNKNOWN_BOOT) {
#ifdef MTK_2SDCARD_SWAP
init_parse_config_file("/init.ssd_nomuser.rc");
#else
init_parse_config_file("/init.ssd.rc");
#endif
} else {
init_parse_config_file("/init.no_ssd.rc");
}
#else
init_parse_config_file("/init.no_ssd.rc");
#endif
if(mt_boot_mode != MT_RECOVERY_BOOT && mt_boot_mode != MT_UNKNOWN_BOOT) {
#ifdef MTK_RAWFS_NAND_SUPPORT
printf("Parsing init.protect.rawfs.rc ...\n");
init_parse_config_file("/init.protect.rawfs.rc");
#else
printf("Parsing init.protect.rc ...\n");
init_parse_config_file("/init.protect.rc");
#endif
}

if ( (mt_boot_mode == MT_FACTORY_BOOT) || (mt_boot_mode == MT_ATE_FACTORY_BOOT) ) {
NOTICE("No need modem.rc for factory mode\n");
}
#if defined (MTK_KERNEL_POWER_OFF_CHARGING_SUPPORT)
else if ( (mt_boot_mode == MT_KERNEL_POWER_OFF_CHARGING_BOOT) || (mt_boot_mode == MT_LOW_POWER_OFF_CHARGING_BOOT) ) {
NOTICE("No need modem.rc for power off charging mode\n");
}
#endif
else if ( (mt_boot_mode == MT_META_BOOT) || (mt_boot_mode == MT_ADVMETA_BOOT)) {
init_parse_config_file("/meta_init.modem.rc");
}else {
init_parse_config_file("/init.modem.rc");
}
#endif // MTK_INIT


MTK设备进入recovery模式不是走这个流程的!!要注意。

void action_for_each_trigger(const char *trigger,
void (*func)(struct action *act))
{
struct listnode *node;
struct action *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strcmp(act->name, trigger)) {
func(act);
}
}
}
触发在init脚本文件中名字为early-init的action,并且执行其commands,其实是: on early-init

init进程解析 脚本分为四个阶段 early_init ,init early_boot 和boot ,不同阶段做不同的事情,有些阶段必须等上个阶段做完事情才能执行,所以才有先后之分。

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


例如函数queue_builtin_action来向init进程中的一个待执行action队列增加了一个名称等于“console_init”的action。这个action对应的执行函数为console_init_action,它就是用来显示第二个开机画面的。

queue_builtin_action中也会执行action_add_queue_tail;和接下来调用的action_for_each_trigger一样;
action_for_each_trigger("init", action_add_queue_tail);


执行 on init 有关action 动作!!

在这个for循环里会用execute_one_command();去执行队列中的代码,可以看到会去执行上面已经提到的这几个。

queue_builtin_action(property_init_action, "property_init");

queue_builtin_action(set_init_properties_action, "set_init_properties");

queue_builtin_action(property_service_init_action, "property_service_init");

queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");

其实是依次执行

property_init_action

set_init_properties_action

property_service_init_action

queue_property_triggers_action


有关  static void parse_config(const char *fn, char *s)分析这篇博客分析相当不错!!点击打开链接

所有的init都是由 动作(Actions),命令(Commands,服务(Services),选项(Options)组成

其中 这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。

可以使用反斜杠转义符在 Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。

如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是 说,可以用反斜杠将多行代码连接成一行代码。

 AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或 Services确定一个Section。

 而所有的Commands和Options只能属于最近定义的Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。

 Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。

下面来看看Actions、Services、Commands和Options分别应如何设置。

Actions的语法格式如下:

on <trigger>  

   <command>  

   <command>  

   <command>

      也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action。

    on boot  

        ifup lo  

        hostname localhost  

        domainname localdomain  

其中 ifup hostname,domainname属于 command

那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。

1.  boot

   这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger

2.  <name>=<value>

   当属性<name>被设置成<value>时被触发。例如,

on property:vold.decrypt=trigger_reset_main

    class_reset main

3.  device-added-<path>

    当设备节点被添加时触发

4.  device-removed-<path>

   当设备节点被移除时添加

5. service-exited-<name>

   会在一个特定的服务退出时触发

Actions后需要跟若干个命令,这些命令如下:

1.  exec <path> [<argument> ]*

  创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。

    2.  export <name> <value>

在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)

3.  ifup <interface>

   启动网络接口

4.  import <filename>

   指定要解析的其他配置文件。常被用于当前配置文件的扩展

5.  hostname <name>

   设置主机名

6.  chdir <directory>

   改变工作目录

7.  chmod <octal-mode><path>

   改变文件的访问权限

8.  chown <owner><group> <path>

   更改文件的所有者和组

9.  chroot <directory>

  改变处理根目录

10.  class_start<serviceclass>

   启动所有指定服务类下的未运行服务。

11  class_stop<serviceclass>

  停止指定服务类下的所有已运行的服务。

12.  domainname <name>

   设置域名

13.  insmod <path>

   加载<path>指定的驱动模块

14.  mkdir <path> [mode][owner] [group]

   创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。

15. mount <type> <device> <dir> [<mountoption> ]*

   试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re

16.  setkey

   保留,暂时未用

17.  setprop <name><value>

   将系统属性<name>的值设为<value>。

18. setrlimit <resource> <cur> <max>

   设置<resource>的rlimit (资源限制)

19.  start <service>

   启动指定服务(如果此服务还未运行)。

20.stop<service>

   停止指定服务(如果此服务在运行中)。

21. symlink <target> <path>

   创建一个指向<path>的软连接<target>。

22. sysclktz <mins_west_of_gmt>

   设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)

23.  trigger <event>

  触发一个事件。用于Action排队

24.  wait <path> [<timeout> ]

等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。

25. write <path> <string> [ <string> ]*

向<path>指定的文件写入一个或多个字符串。  

Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:

    service <name> <pathname> [ <argument> ]*  

          <option>  

          <option>  

例如,下面是一个标准的Service用法

复制代码

    service servicemanager /system/bin/servicemanager  

        class core  

        user system  

        group system  

        critical  

        onrestart restart zygote  

        onrestart restart media  

        onrestart restart surfaceflinger  

        onrestart restart drm  

复制代码

Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:

1.  critical

表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。

2. disabled

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

3.  setenv <name><value>

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

4.  socket <name><type> <perm> [ <user> [ <group> ] ]

   Create a unix domain socketnamed /dev/socket/<name> and pass

   its fd to the launchedprocess.  <type> must be"dgram", "stream" or "seqpacket".

   User and group default to0.

   创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。

5.  user <username>

在启动这个服务前改变该服务的用户名。此时默认为 root。

6.  group <groupname> [<groupname> ]*

在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。

7.  oneshot 属于 option

   服务退出时不重启。

8.  class <name> 属于 option

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

9. onrestart

    当服务重启,执行一个命令(下详)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: