本文介绍init进程中的action触发方式
一,什么是action
在android中,使用action来管理并执行命令。action是一个数据结构,里面包含了命令集合command,action的名字等。Android系统通过action来执行一组命令。
struct action { /* node in list of all actions */ struct listnode alist; /* node in the queue of pending actions */ struct listnode qlist; /* node in list of actions for a trigger */ struct listnode tlist; unsigned hash; const char *name; struct listnode commands; struct command *current; };
所有的action都是通过alist组成一个链表(action_list),通过qlist组成一个执行队列(action_queue),tlist暂时没有用到。action里面的所有命令,以struct command的结构存放在commands为头的链表中;current指向当前的命令。
struct command { /* list of commands in an action */ struct listnode clist; int (*func)(int nargs, char **args); int nargs; char *args[1]; };
上面的command结构体中,用clist来链接action下面的所有命令,命令包含了函数func,参数个数nargs,以及参数args。
有两个链表头,来链接action们:
static list_declare(action_list); static list_declare(action_queue);
其中第一个为action链表头,第二个为action执行链表头。从init.rc配置文件中读出来的以及代码中设置的action,首先加入到action_list链表中,然后根据启动时机(init,boot阶段等)把需要执行的action加入到action_queue中。在init的主循环中,action_queue中的命令得到执行。
二,action是如何添加的
有两种方式把action添加到action_list和action_queue链表中,第一种为init.rc中配置,在init进程启动过程中解析init.rc配置文件;另外一种为代码中调用函数添加。
首先介绍第一种方式。 init进程启动时,会读取init.rc(以及init.xxxx,rc,xxxx为设备名字)配置文件。init.rc定义了专门的格式,分为三种类型:命令(action),服务(service),导入(import),比如:
on early-init
start ueventd
service ueventd /sbin/ueventd
class core
critical
import init.test.rc
如上,以on开头的标志了action开始,early-init为action的名字,下面的start ueventd为action的命令,可以有多条命令,直到遇到其他类型的定义关键字(这里是遇到service)为止。以service开头标志了服务,service后面的为服务名字和服务可执行程序,下面的class core等,为uevented服务的属性,可以有多条属性,直到遇到其他类型为止。import导入其他的文件。
在init进程中,先把配置文件读入到内存中 ,然后调用int init_parse_config_file(const char *fn)函数来解析配置文件,把on开头的读入到action结构体中,其后面的命令读入到command结构体中,并链接到action中。然后把action结构链接到action_list表头中。
之后通过调用action_for_each_trigger(“early-init”, action_add_queue_tail)等函数把名称为early-init的action加入到action_queue中。
除了解析配置文件构建action之外,代码中也可以通过调用queue_builtin_action来添加action到action_list和action_queue中:
void queue_builtin_action(int (*func)(int nargs, char **args), char *name) { struct action *act; struct command *cmd; act = calloc(1, sizeof(*act)); act->name = name; list_init(&act->commands); cmd = calloc(1, sizeof(*cmd)); cmd->func = func; cmd->args[0] = name; list_add_tail(&act->commands, &cmd->clist); list_add_tail(&action_list, &act->alist); action_add_queue_tail(act); } void action_add_queue_tail(struct action *act) { list_add_tail(&action_queue, &act->qlist); }
函数申请了action和command的内存空间,然后链接到action_list和action_queue中。
三,action的执行
action_list里面包含了一系列的action,并且按照一定次序添加到了action_queue队列中。接下来需要从action_queue中读取action,并且依次执行里面的command。
init的主循环中,做了命令的执行动作:
for(;;) { int nr, i, timeout = -1; execute_one_command(); restart_processes(); ………. if (!action_queue_empty() || cur_action) timeout = 0; ………. nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; ………., }
如上,execute_one_command调用只执行了一个command命令,如果执行完整个action的所有命令,则把此action从action_queue中删除。事实上,init进程开始的时候,加入了很多action到队列中,因此需要反复循环调用execute_one_command才能够执行完成。
通过action_queue_empty调用来控制循环,当action_queue不为空的时候,代码设置了timeout = 0,这样会导致poll(ufds, fd_count, timeout)调用立即返回,而不是以阻塞 方式等待。所以一旦有action加入到action_queue队列中,整个for循环就会轮回多次,多次执行execute_one_command,直到action_queue为空。