編輯:關於Android編程
目錄 init解析配置文件 關鍵字定義 kw_is 解析 K_import K_on command執行 K_service service service結構體 parse_service parse_line_service init控制service
在解析service服務是如何啟動之前,讓我們先來學習一下init進程是如何解析init.rc等配置文件的。
init進程解析配置文件的代碼如下(/system/core/init/init.c&&/system/core/init/init_parser.c):
init_parse_config_file("/init.rc");
void *read_file(const char *fn, unsigned *_sz)
{
char *data;
int sz;
int fd;
struct stat sb;
data = 0;
fd = open(fn, O_RDONLY);
if (fd < 0) return 0;
// 獲取init.rc文件字符總數
sz = lseek(fd, 0, SEEK_END);
if (sz < 0) goto oops;
if (lseek(fd, 0, SEEK_SET) != 0) goto oops;
// 多余兩個字符存儲'\n'和'\0'
data = (char*)malloc(sz + 2);
if (data == 0) goto oops;
// 讀取init.rc文件內容到data數組中
if (read(fd, data, sz) != sz) goto oops;
close(fd);
data[sz] = '\n';
data[sz + 1] = 0;
if(_sz) *_sz = sz;
return data;
oops:
close(fd);
if (data != 0) free(data);
return 0;
}
int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
讀完init.rc文件內容到data數組中後,將調用parse_config函數進行解析,具體函數源碼如下:
struct parse_state
{
char *ptr;
char *text;
int line;
int nexttoken;
void *context;
void (*parse_line)(struct parse_state *state, int nargs, char **args);
const char *filename;
void *priv;
};
#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2
int next_token(struct parse_state *state)
{
char *x = state->ptr;
char *s;
if (state->nexttoken) {
int t = state->nexttoken;
state->nexttoken = 0;
return t;
}
for (;;) {
switch(*x) {
case 0:
state->ptr = x;
return T_EOF;
case '\n':
x++;
state->ptr = x;
return T_NEWLINE;
case ' ':
case '\t':
case '\r':
x++;
continue;
case '#':
while (*x && (*x != '\n')) x ++;
if (*x == '\n') {
state->ptr = x + 1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
default:
goto text;
}
}
text:
state->text = s = x;
}
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
struct listnode import_list;
struct listnode *node;
char *args[INIT_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;
for(;;) {
switch(next_token(&state)) {
case T_EOF:
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE:
state.line ++;
if (nargs) {
int kw = lookup_keyword(args[0]);
if (ks_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;
}
break;
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs ++] = state.text;
}
break;
}
}
parser_done:
// 解析import關鍵字導入的rc文件
list_for_each(node, &import_list) {
struct import *import = node_to_item(node, struct import, list);
int ret;
INFO("importing '%s'", import->filename);
ret = init_parse_config_file(import->filename);
if (ret) {
ERROR("could not import file '%s' from '%s'\n", import->filename, fn);
}
}
}
上面就是parse_config函數的實現。函數通過調用next_token函數來解析data數組內容,當有新的一行有效數據時,則把有效數據存入到args字符指針數組中,然後在T_NEWLINE分支中進行真正的解析,這也是解析過程中最復雜的地方。讓我們來深入的學習一下T_NEWLINE分支的解析過程。
我們先來看一下loopup_keyword函數的具體實現,源碼位於/system/core/init/init_parser.c文件中,源碼內容如下:
int lookup_keyword(const char *s)
{
switch(*s ++) {
case 'c':
if (!strcmp(s, "opy")) return K_copy;
if (!strcmp(s, "apability")) return K_capability;
if (!strcmp(s, "hdir")) return K_chdir;
if (!strcmp(s, "hroot")) return K_chroot;
if (!strcmp(s, "lass")) return K_class;
if (!strcmp(s, "lass_start")) return K_class_start;
if (!strcmp(s, "lass_stop")) return K_class_stop;
if (!strcmp(s, "lass_reset")) return K_class_reset;
if (!strcmp(s, "onsole")) return K_console;
if (!strcmp(s, "hown")) return K_chown;
if (!strcmp(s, "hmod")) return K_chmod;
if (!strcmp(s, "ritical")) return K_critical;
break;
case 'd':
if (!strcmp(s, "isabled")) return K_disabled;
if (!strcmp(s, "omainname")) return K_domainname;
break;
case 'e':
if (!strcmp(s, "xec")) return K_exec;
if (!strcmp(s, "xport")) return K_export;
break;
case 'g':
if (!strcmp(s, "roup")) return K_group;
break;
case 'h':
if (!strcmp(s, "ostname")) return K_hostname;
break;
case 'i':
if (!strcmp(s, "oprio")) return K_ioprio;
if (!strcmp(s, "fup")) return K_ifup;
if (!strcmp(s, "nsmod")) return K_insmod;
if (!strcmp(s, "mport")) return K_import;
break;
case 'k':
if (!strcmp(s, "eycodes")) return K_keycodes;
break;
case 'l':
if (!strcmp(s, "oglevel")) return K_loglevel;
if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
break;
case 'm':
if (!strcmp(s, "kdir")) return K_mkdir;
if (!strcmp(s, "ount_all")) return K_mount_all;
if (!strcmp(s, "ount")) return K_mount;
break;
case 'o':
if (!strcmp(s, "n")) return K_on;
if (!strcmp(s, "neshot")) return K_oneshot;
if (!strcmp(s, "nrestart")) return K_onrestart;
break;
case 'p':
if (!strcmp(s, "owerctl")) return K_powerctl;
break;
case 'r':
if (!strcmp(s, "estart")) return K_restart;
if (!strcmp(s, "estorecon")) return K_restorecon;
if (!strcmp(s, "mdir")) return K_rmdir;
if (!strcmp(s, "m")) return K_rm;
break;
case 's':
if (!strcmp(s, "eclabel")) return K_seclabel;
if (!strcmp(s, "ervice")) return K_service;
if (!strcmp(s, "etcon")) return K_setcon;
if (!strcmp(s, "etenforce")) return K_setenforce;
if (!strcmp(s, "etenv")) return K_setenv;
if (!strcmp(s, "etkey")) return K_setkey;
if (!strcmp(s, "etprop")) return K_setprop;
if (!strcmp(s, "etrlimit")) return K_setrlimit;
if (!strcmp(s, "etsebool")) return K_setsebool;
if (!strcmp(s, "ocket")) return K_socket;
if (!strcmp(s, "tart")) return K_start;
if (!strcmp(s, "top")) return K_stop;
if (!strcmp(s, "wapon_all")) return K_swapon_all;
if (!strcmp(s, "ymlink")) return K_symlink;
if (!strcmp(s, "ysclktz")) return K_sysclktz;
break;
case 't':
if (!strcmp(s, "rigger")) return K_trigger;
break;
case 'u':
if (!strcmp(s, "ser")) return K_user;
break;
case 'w':
if (!strcmp(s, "rite")) return K_write;
if (!strcmp(s, "ait")) return K_wait;
break;
}
return K_UNKNOWN;
}
lookup_keyword函數源碼是非常簡單的,羅列了一堆strcmp語句。而且源碼在匹配case ‘p’的時候有一個明顯的問題,大家感興趣的可以自行查看Android源碼。
接下來,判斷關鍵字類型是不是SECTION,我們繼續來看一下kw_is的源碼。
kw_is函數的源碼實現如下:
#define SECTION 0x01
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
從上述代碼中我們還不能搞清楚道理什麼類型算是SECTION類型,這裡直接告訴大家:只有當關鍵字為on、service和import的時候,關鍵字才被認為是SECTION
雖然關鍵字的類型很多,但是大部分關鍵字對應的解析函數都是parse_line_no_op。而parse_line_no_op的實現卻是空的,源碼如下:
void parse_line_no_op(struct parse_state *state, int nargs, char **args)
{
}
為什麼會這樣呢?
是因為,仔細分析最初的init_parse_config_file函數要解析的init.rc文件,雖然init.rc的內容很多,但是其中只有三種有效模式:
第一種trigger觸發
on xxxx
......
第二種service
service servicename
......
第三種import
import *.rc
而這三種模式的關鍵字都是屬於SECTION類型的,所以我們需要看一下parse_new_section函數裡對這三種關鍵字的解析函數指針做了賦值。
parse_new_section函數的源碼內容如下:
void parse_new_section(struct parse_state *state, int kw, int nargs, char **args)
{
printf("[ %s %s]\n", args[0], nargs > 1 ? args[1] : "");
switch(kw) {
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;
case K_import:
parse_import(state, nargs, args);
break;
}
state->parse_line = parse_line_no_op;
}
果然,針對這三種關鍵字,其解析函數parse_line也被賦予了不同的值。我們挨個分析一下。
import模式主要是導入新的rc配置文件,所以我們看一下parse_import函數的具體實現:
struct import {
struct listnode list;
const char *filename;
};
void list_add_tail(struct listnode *head, struct listnode *item)
{
item->next = head;
item->prev = head->prev;
head->prev->next = item;
head->prev = item;
}
void parse_import(struct parse_state *state, int nargs, char **args)
{
struct listnode *import_list = state->priv;
struct import *import;
char conf_file[PATH_MAX];
int ret;
if (nargs != 2) {
ERROR("single argument needed for import\n");
return;
}
ret = expand_props(conf_file, args[1], sizeof(conf_file));
if (ret) {
ERROR("error while handing import on line '%d' in '%s'\n", state->line, state->filename);
return;
}
import = calloc(1, sizeof(struct import));
import->filename = strdup(conf_file);
list_add_tail(import_list, &import->list);
INFO("found import '%s', adding to import list", import->filename);
}
代碼還是比較簡單的,一句話概括就是將import導入的rc文件鏈接到state->priv鏈表中。state->priv鏈表鏈接了所有的import文件,最後會在parse_config函數的parser_done標簽中進行處理。
K_on關鍵字代表了trigger模式,跟trigger模式相關的函數只要是parse_action和parse_line_action。
action結構體源碼如下(/system/core/init/init.h):
struct action {
/* node in list of all actions */
struct listnode alist;
/* node in the queue of pending actions */
struct listnode qlist;
/* node in the list of actions for a trigger */
struct listnode tlist;
unsigned hash;
const char *name;
// command鏈表,以zygote為例,它有4個onrestart option,所以它對應會創建4個command結構體
struct listnode commands;
struct command *current;
};
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
int nargs;
char *args[1];
};
接下來,看一下parse_action和parse_line_action函數的具體實現。
void list_init(struct listnode *node)
{
node->next = node;
node->prev = node;
}
void list_add_tail(struct listnode *head, struct listnode *item)
{
item->next = head;
item->prev = head->prev;
head->prev->next = item;
head->prev = item;
}
static void *parse_action(struct parse_state *state, int nargs, char **args)
{
struct action *act;
if (nargs < 2) {
parse_error(state, "actions must have a trigger\n");
return 0;
}
if (nargs > 2) {
parse_error(state, "actions may not have extra parameters\n");
return 0;
}
act = calloc(1, sizeof(*act));
act->name = args[1];
list_init(&act->commands);
list_init(&act->qlist);
list_add_tail(&action_list, &act->alist);
return act;
}
static void parse_line_action(struct parse_state *state, int nargs, char **args)
{
struct command *cmd;
struct action *act = state->context;
int (*func)(int nargs, char **args);
int kw, n;
if (nargs == 0) {
return;
}
kw = loopup_keyword(args[0]);
if (!kw_is(kw, COMMAND)) {
parse_error(state, "invalid command '%s'\n", args[0]);
return;
}
n = kw_nargs(kw);
cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
cmd->func = kw_func(kw);
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char*) * nargs);
list_add_tail(&act->commands, &cmd->clist);
}
在parse_line_action構造command結構體的時候,有一個關鍵函數kw_func,它返回函數指針決定了command的具體實現。我們來看一下kw_func的函數源碼:
#define kw_nargs(kw) (keyword_info[kw].nargs)
#define kw_func(kw) (keyword_info[kw].func)
struct {
const char *name;
int (*func)(int nargs, char **args);
unsigned char nargs;
unsigned char flags;
} keyword_info[KEYWORK_COUNT] = {
[K_UNKNOWN] = {"unknown", 0, 0, 0},
#include "keywords.h"
};
這keyword_info結構體的初始化是很神奇的,希望大家也能感受到它的精妙之處。可以看到,代碼裡初始化了一個成員K_UNKNOWN之後,後面跟了一句#include “keywords.h”。這就是神奇的地方,因為代碼編譯過程中,include引用的內容會在當前位置進行展開,也就是說keyword_info其它成員的初始化是在”keywords.h”文件中完成的。我們來看一下keywords.h文件源碼():
#ifndef KEYWORD
int do_chroot(int nargs, char **args);
int do_chdir(int nargs, char **args);
int do_class_start(int nargs, char **args);
int do_class_stop(int nargs, char **args);
int do_class_reset(int nargs, char **args);
int do_domainname(int nargs, char **args);
int do_exec(int nargs, char **args);
int do_export(int nargs, char **args);
int do_hostname(int nargs, char **args);
int do_ifup(int nargs, char **args);
int do_insmod(int nargs, char **args);
int do_mkdir(int nargs, char **args);
int do_mount_all(int nargs, char **args);
int do_mount(int nargs, char **args);
int do_powerctl(int nargs, char **args);
int do_restart(int nargs, char **args);
int do_restorecon(int nargs, char **args);
int do_rm(int nargs, char **args);
int do_rmdir(int nargs, char **args);
int do_setcon(int nargs, char **args);
int do_setenforce(int nargs, char **args);
int do_setkey(int nargs, char **args);
int do_setprop(int nargs, char **args);
int do_setrlimit(int nargs, char **args);
int do_setsebool(int nargs, char **args);
int do_start(int nargs, char **args);
int do_stop(int nargs, char **args);
int do_swapon_all(int nargs, char **args);
int do_trigger(int nargs, char **args);
int do_symlink(int nargs, char **args);
int do_sysclktz(int nargs, char **args);
int do_write(int nargs, char **args);
int do_copy(int nargs, char **args);
int do_chown(int nargs, char **args);
int do_chmod(int nargs, char **args);
int do_loglevel(int nargs, char **args);
int do_load_persist_props(int nargs, char **args);
int do_wait(int nargs, char **args);
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
K_UNKNOWN,
#endif
KEYWORD(capability, OPTION, 0, 0)
KEYWORD(chdir, COMMAND, 1, do_chdir)
KEYWORD(chroot, COMMAND, 1, do_chroot)
KEYWORD(class, OPTION, 0, 0)
KEYWORD(class_start, COMMAND, 1, do_class_start)
KEYWORD(class_stop, COMMAND, 1, do_class_stop)
KEYWORD(class_reset, COMMAND, 1, do_class_reset)
KEYWORD(console, OPTION, 0, 0)
KEYWORD(critical, OPTION, 0, 0)
KEYWORD(disabled, OPTION, 0, 0)
KEYWORD(domainname, COMMAND, 1, do_domainname)
KEYWORD(exec, COMMAND, 1, do_exec)
KEYWORD(export, COMMAND, 2, do_export)
KEYWORD(group, OPTION, 0, 0)
KEYWORD(hostname, COMMAND, 1, do_hostname)
KEYWORD(ifup, COMMAND, 1, do_ifup)
KEYWORD(insmod, COMMAND, 1, do_insmod)
KEYWORD(import, SECTION, 1, 0)
KEYWORD(keycodes, OPTION, 0, 0)
KEYWORD(mkdir, COMMAND, 1, do_mkdir)
KEYWORD(mount_all, COMMAND, 1, do_mount_all)
KEYWORD(mount, COMMAND, 3, do_mount)
KEYWORD(on, SECTION, 0, 0)
KEYWORD(oneshot, OPTION, 0, 0)
KEYWORD(onrestart, OPTION, 0, 0)
KEYWORD(powerctl, COMMAND, 1, do_powerctl)
KEYWORD(restart, COMMAND, 1, do_restart)
KEYWORD(restorecon, COMMAND, 1, do_restorecon)
KEYWORD(rm, COMMAND, 1, do_rm)
KEYWORD(rmdir, COMMAND, 1, do_rmdir)
KEYWORD(seclabel, OPTION, 0, 0)
KEYWORD(service, SECTION, 0, 0)
KEYWORD(setcon, COMMAND, 1, do_setcon)
KEYWORD(setenforce, COMMAND, 1, do_setenforce)
KEYWORD(setenv, OPTION, 2, 0)
KEYWORD(setkey, COMMAND, 0, do_setkey)
KEYWORD(setprop, COMMAND, 2, do_setprop)
KEYWORD(setrlimit, COMMAND, 3, do_setrlimit)
KEYWORD(setsebool, COMMAND, 2, do_setsebool)
KEYWORD(socket, OPTION, 0, 0)
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
KEYWORD(swapon_all, COMMAND, 1, do_swapon_all)
KEYWORD(trigger, COMMAND, 1, do_trigger)
KEYWORD(symlink, COMMAND, 1, do_symlink)
KEYWORD(sysclktz, COMMAND, 1, do_sysclktz)
KEYWORD(user, OPTION, 0, 0)
KEYWORD(wait, COMMAND, 1, do_wait)
KEYWORD(write, COMMAND, 2, do_write)
KEYWORD(copy, COMMAND, 2, do_copy)
KEYWORD(chown, COMMAND, 2, do_chown)
KEYWORD(chmod, COMMAND, 2, do_chmod)
KEYWORD(loglevel, COMMAND, 1, do_loglevel)
KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
KEYWORD(ioprio, OPTION, 0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif
因為KEYWORD已經在init_parser.c文件中定義了,所以從#ifndef KEYWORD到#endif之間的代碼是不需要care的。KEYWORD的定義如下(/system/core/init/init_parser.c):
#define KEYWORD(symbol, flags, nargs, func) \
[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
這裡結合KEYWORD(class_start, COMMAND, 1, do_class_start)來講解一下這個宏的作用。
預處理運算符: #(單井號)——字符串化運算符。##(雙井號)——連接運算符。 K_##symbol == K_class_start。#symbol == class_start KEYWORD(class_start, COMMAND, 1, do_class_start) == [K_class_start] = {class_start, do_class_start, 2, COMMAND}經過上述分析,我們經已經清楚了keyword_info數組的初始化過程。同時,也知道了相應的cmd的func函數指針指向的具體函數。func函數的具體實現在/system/core/init/builtins.c文件裡,感興趣的同學可以自行閱讀源碼。
那最終這些command是如何執行的呢?
因為on ×××是基於某個事件觸發,init.rc中列舉的事件包括:
on early-init on init on fs on post-fs on post-fs-data on boot on nonencrypted on charger on property:key=value那init.rc這些trigger是在什麼時間點觸發呢?其實這些trigger的觸發順序是由init.rc裡面添加它們到action queue的順序決定的。相關代碼如下:
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);
}
}
}
// func是函數指針,具體實現函數為action_add_queue_tail
void action_add_queue_tail(struct action *act)
{
if (list_empty(&act->qlist)) {
list_add_tail(&action_queue, &act->qlist);
}
}
action_for_each_trigger("early-init", action_add_queue_tail);
action_for_each_trigger("init", action_add_queue_tail);
if (!is_charger) {
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
在init.c中以合適的順序添加到action queue裡面之後,在init.c main函數的最後會依次從action queue中取出這些action,順序執行。
for(;;) {
int nr, i, timeout = -1;
execute_one_command();
}
下文會重點介紹service的解析,這裡就略過了。
上述重點在於SECTION類型中的K_on和K_import關鍵字解析。接下來,我們要看一下K_service關鍵字的解析實現了,以zygote service為例。
zygote對應的service section內容是(/system/core/rootdir/init.rc):
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
解析service時,用到了parse_service和parse_line_service這兩個函數,在分別介紹它們之前,我們先來學習一下service的數據結構。
init進程中使用了一個叫做service的結構體來保存與service section相關的信息,結構體的定義如下(system/core/init/init.h):
struct service {
/* list of all service */
struct listnode slist;
const char *name; /* service的名字,比如"zygote" */
const char *classname; /* service所屬的class的名字,默認是"default" */
unsigned flags; /* service的屬性 */
pid_t pid; /* 進程號 */
time_t time_started; /* time of last start */
time_t time_crashed; /* first crash within inspection window */
int nr_crashed; /* number of times crashed within window */
uid_t uid;
gid_t gid;
gid_t supp_gids[NR_SVC_SUPP_GIDS];
size_t nr_supp_gids;
char *seclabel;
struct socketinfo *sockets;
// service一般運行在一個獨立的進程中,envvars用來描述創建這個進程時所需的環境變量信息
struct svcenvinfo *envvars;
struct action onrestart;
int *keycodes;
int nkeycodes;
int keychord_id;
int ioprio_class;
int ioprio_pri;
int nargs; // 參數個數
char *args[1]; // 用於存儲參數
}
service一共有5個屬性,分別為:
SVC_DISABLED:不隨class自動啟動。 SVC_ONESHOT:退出後不需要重啟,也就是說這個service只啟動一次就可以了。 SVC_RUNNING:正在運行,這是service的狀態。 SVC_CONSOLE:該service需要使用控制台。 SVC_CRITICAL:如果在規定的時間內該service不斷重啟,則系統會重啟並進入恢復模式。service的結構體雖然成員很多,但是比較好理解。但是,例如像zygote這種service,其中的onrestart該怎麼表示呢?其實是用了action這個結構體。
了解了這些基本的結構體之後,我們接下來看一下parse_service和parse_line_service的具體實現。
parse_service的源碼位置是:/system/core/init/init_parser.c,源碼如下:
static int valid_name(const char *name)
{
if (strlen(name) > 16) {
return 0;
}
while (*name) {
if (!isalnum(*name) && (*name != '_') && (*name != '-')) {
return 0;
}
name++;
}
return 1;
}
#define list_for_each(node, list) \
for(node = (list)->next; node != (list); node = node->next)
struct service *service_find_by_name(const char *name)
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (!strcmp(svc->name, name)) {
return svc;
}
}
return 0;
}
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;
}
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);
list_add_tail(&service_list, &svc->slist);
}
parse_service函數只是搭建了一個service的架子,具體的內容尚需由後面的解析函數來填充。接下來看一下另外一個解析函數parse_line_service。
parse_line_service的源碼位置:/system/core/init/init_parser.c,源碼內容如下:
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc = state->context;
struct command *cmd;
int i, kw, kw_nargs;
if (nargs == 0) {
return;
}
svc->ioprio_class = IoSchedClass_NONE;
kw = loopup_keyword(args[0]);
switch(kw) {
case K_capability:
break;
case K_class:
if (nargs != 2) {
parse_error(state, "class option requires a classname\n");
} else {
svc->classname = args[1];
}
break;
case K_console:
svc->flags |= SVC_CONSOLE;
break;
case K_disabled:
svc->flags |= SVC_DISABLED;
svc->flags |= SVC_RC_DISABLED;
break;
case K_ioprio:
if (nargs != 3) {
parse_error(state, "opprio optin usage: ioprio
parse_line_service將根據配置文件的內容填充service結構體,那麼,zygote解析完後的結果為:
TODO:需要畫圖
了解了service的數據結構和組裝,接下來,我們需要了解init是如何控制service的。
init.rc中有這樣一條命令:
class_start default
class_start main
class_start core
class_start標識一個COMMAND,對應的處理函數為do_class_start,它位於on boot的范圍內。當init進程執行到下面幾句話時,do_class_start就會被執行了。
action_for_each_trigger("boot", action_add_queue_tail);
下面我們來看一下do_class_start函數,函數位置:/system/core/init/builtins.c:
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;
}
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
}
}
zygote這個service的classname值是main,flags標志位不是disable,所以會調用service_start來啟動zygote。
service_start的函數實現如下(/system/core/init/init.c):
static const char *ENV[32];
int add_environment(const char *key, const char *val)
{
int n;
for (n = 0; n < 31; n ++) {
if (!ENV[n]) {
size_t len = strlen(key) + strlen(val) + 2;
char *entry = malloc(len);
snprintf(entry, len, "%s=%s", key, val);
ENV[n] = entry;
return 0;
}
}
}
#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR "/dev/socket"
static void publish_socket(const char *name, int fd)
{
char key[64] = ANDROID_SOCKET_ENV_PREFIX;
char val[64];
strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, name, sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
snprintf(val, sizeof(val), "%d", fd);
add_environment(key, val);
/* make sure we don't close-on-exec */
fcntl(fd, F_SETFD, 0);
}
void notify_service_state(const char *name, const char *state)
{
char pname[PROP_NAME_MAX];
int len = strlen(name);
if ((len + 10) > PROP_NAME_MAX) {
return;
}
snprintf(pname, sizeof(pname), "init.svc.%s", name);
property_set(pname, state);
}
void service_start(struct service *svc, const char *dynamic_args)
{
struct stat s;
pid_t pid;
int needs_console;
int n;
char *scon = NULL;
int rc;
svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART));
svc->time_started = 0;
if (svc->flags & SVC_RUNNING) {
return;
}
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
if (needs_console && (!have_console)) {
ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED;
return;
}
/**
* service一般運行於另外一個進程中,這個進程也是init的子進程
* 所以,啟動service前需要判斷對應的可執行文件是否存在
*/
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
NOTICE("starting '%s'\n", svc->name);
pid = fork();
if (pid == 0) {
// pid為0,表示運行在子線程中
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
if (properties_inited()) {
// 得到屬性存儲空間的信息並加到環境變量中
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
// 添加環境變量信息
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
// 根據socketinfo創建socket
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET);
int s = create_socket(si->name, socket_type, si->perm, si->uid, si->gid);
if (s >= 0) {
publish_socket(si->name, s);
}
}
}
// ......省略設置uid,gid代碼
if (!dynamic_args) {
if (execve(svc->args[0], (char **) svc->args, (char **) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
} else {
// ......省略動態參數的execve執行代碼
}
if (pid < 0) {
ERROR("failed to start '%s'\n", svc->name);
svc->pid = 0;
return;
}
svc->time_started = gettime();
svc->pid = pid;
svc->flags != SVC_RUNNING;
/**
* 每一個service都有一個屬性
* 例如:zygote的屬性為init.svc.zygote,設置當前屬性值為running
* /
if (properties_inited()) {
notify_service_state(svc->name, "running");
}
}
通過上述代碼,我們可以知道,原來service的啟動是通過fork和execve共同實現的。
首先肯定需要引用select2.js:點擊打開鏈接,點擊鏈接,自己新建一個select2.js把鏈接中的源碼復制進去;然後這裡面需要修改的是這個方法:function m
自定義View一直是橫在Android開發者面前的一道坎。一、View和ViewGroup的關系從View和ViewGroup的關系來看,ViewGroup繼承View。
下拉刷新對於一個app來說是必不可少的一個功能,在早期大多數使用的是chrisbanes的PullToRefresh,或是修改自該框架的其他庫。而到現在已經有了更多的選擇
顧名思義,AndroidEventBus ( github鏈接 : https://github.com/bboyfeiyu/AndroidEventBus