在Android kernel启动完成之后,将会启动init进程,这个进程是用户空间的第一个进程。在init进程中将会解析init.rc文件。在init.rc文件中包含有一些系统服务。这些服务可以自动启动,或者是根据某些条件启动。
在项目的开发过程中,有抓取开机log和开机报文的需求。最初的想法是把log和报文抓到U盘里面,但是分析系统启动 流程发现在开机的时候网络链接要早于U盘挂载,所以无法将开机的报文抓到U盘里。所以最终决定将开机报文分为两部分:1.开机启动过程的报文(U盘挂载之前);2.开机U盘挂载之后的报文。
由于开机的时候系统还没有完全启动,所以想要在init.rc中添加一个service,由这个service运行抓取开机的报文。启动网络之前启动这个服务。等到U盘挂载之后,暂停前一个服务,重新启动这个服务,达到抓取这两个阶段的开机报文。
脚本内容如下:
#!/system/bin/sh
if [ "start" == $1 ];
then
status=$(getprop persist.sys.tcpdump.switch)
echo ${status}
if [ "$status" != "" ];
then
if [ ${status} = "enable" ]
then
eval $(mount |grep vold | awk '{printf("usb_path=%s;",$2)}')
if [ "${usb_path}" != "" ]
then
sync
kill all tcpdump
sync
#cp -rf /data/eth0_stage1.pcap ${usb_path}/eth0_stage1.pcap &
logcat -v time > ${usb_path}/logcat.log &
tcpdump -i eth0 -w ${usb_path}/eth0_stage2.pcap &
if [ -f "/data/eth0_stage1.pcap" ]
then
cp -rf /data/eth0_stage1.pcap ${usb_path}/eth0_stage1.pcap &
fi
while 1
do
sync
sleep 1
done
else
tcpdump -i eth0 -w /data/eth0_stage1.pcap
echo "No usb devices insert"
fi
fi
fi
elif [ "stop" == $1 ];
then
sync
#setprop persist.tcpdump.switch disable
killall tcpdump
killall logcat
sync
if [ ! -f "/data/eth0_stage1.pcap" ]
then
rm /data/eth0_stage1.pcap
fi
fi
在init.rc文件中添加service,以及添加触发service的prop
service tcpdump_stop /system/bin/tcpdump.sh stop
class main
user root
oneshot
disabled
service tcpdump_start /system/bin/tcpdump.sh start
class main
user root
oneshot
disabled
on property:sys.vold.usb.mounted=1
start tcpdump_start
on property:sys.vold.usb.mounted=0
start tcpdump_stop
on property:sys.ethernet.started=1
start tcpdump_start
但是在测试过程中发现这个service第一次运行没有问题,但是U盘挂载之后再次启动service运行这个脚本的时候发现并没有运行,并且手动启动service也不行。所以需要分析一下init.c中关于service是如何启动的。
本项目是基于Android 4.4进行开发的,init.c文件路径为:system/core/init/init.c
在init.c中创建了一些目录,初始化了一些系统信息,以及加载了init.rc文件。本文主要分析与service相关的部分。
主要代码有:
/system/core/init/init.c
int main(int argc, char **argv){
···
init_parse_config_file("/init.rc");//读取rc文件,初始化相关的链表
···
for(;;) {
int nr, i, timeout = -1;
execute_one_command();//触发service,
restart_processes();
//创建socket,用来处理setprop/getprop/start/stop/restart
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;
}
//创建socket,用来监听系统进程退出的信息,设置service运行状态
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;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
//轮询状态
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
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();
}
}
}
}
···
}
具体的加载init.rc文件过程就不展开说明了,就是根据关键字查找对应的函数,然后将参数和处理函数填充对应的结构。这里面的结构主要有如下介个:
1.struct service //用来存储service相关信息
2.struct action//存放所有的命令,维护all action list /pending action list等
3.struct commands //用来存储所有支持的命令
通过命令行setprop设置属性运行service的过程如下:
1.setprop程序中创建socket,链接init进程中的socket,链接成功之后进行属性设置,
2.init进程接收到设置的命令之后调用handle_property_set_fd函数进行识别和处理
void handle_property_set_fd(){
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
INFO("PROP_MSG_SETPROP name %s \n", msg.name);
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0) {//ctl.start + service名称方式启动service,一般是framework通过jni方式启动service
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) {
INFO("deal with ctl commands \n");
handle_control_message((char*) msg.name + 4, (char*) msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {//通过setprop/getprop方式设置系统属性。
if (check_perms(msg.name, cr.uid, cr.gid, source_ctx) || strcmp(msg.name , "sys.start.adb") == 0) {
INFO("deal with property_set\n");
property_set((char*) msg.name, (char*) msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
}
int property_set(const char *name, const char *value)
{
prop_info *pi;
int ret;
size_t namelen = strlen(name);
size_t valuelen = strlen(value);
INFO("property_set \n");
if (!is_legal_property_name(name, namelen)) return -1;
if (valuelen >= PROP_VALUE_MAX) return -1;
pi = (prop_info*) __system_property_find(name);
//更新或者设置系统属性,实际上就是创建文件,在文件中写入属性值
if(pi != 0) {
/* ro.* properties may NEVER be modified once set */
if(!strncmp(name, "ro.", 3)) return -1;
__system_property_update(pi, value, valuelen);
} else {
ret = __system_property_add(name, namelen, value, valuelen);
if (ret < 0) {
ERROR("Failed to set '%s'='%s'\n", name, value);
return ret;
}
if(strcmp(name, "ro.ubootenv.varible.prefix") == 0) {
int vlen = (valuelen < 30) ? valuelen : 30;
memcpy(uboot_var_prefix, value, vlen);
uboot_var_prefix[vlen] = '.';
}
}
/* If name starts with "net." treat as a DNS property. */
if (strncmp("net.", name, strlen("net.")) == 0) {
if (strcmp("net.change", name) == 0) {
return 0;
}
/*
* The 'net.change' property is a special property used track when any
* 'net.*' property name is updated. It is _ONLY_ updated here. Its value
* contains the last updated 'net.*' property.
*/
property_set("net.change", name);
} else if (persistent_properties_loaded &&
strncmp("persist.", name, strlen("persist.")) == 0) {
/*
* Don't write properties to disk until after we have read all default properties
* to prevent them from being overwritten by default values.
*/
write_persistent_property(name, value);
} else if (strcmp("selinux.reload_policy", name) == 0 &&
strcmp("1", value) == 0) {
selinux_reload_policy();
}
//通知系统,属性已经改变。
property_changed(name, value);
return 0;
}
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled) {
INFO("property_changed: name [%s] value [%s] \n",name,value);
if (is_bootenv_varible(name)) {
update_bootenv_varible(name, value);
}
queue_property_triggers(name, value);//
}
}
//查找属性名称对应的action,然后将action中存储的value与设置的value相比较,如果不相同,就需要触发。这里面仅仅是将action加入到action_queue中,之后在init.c的主循环中execute_one_command进行处理。
void queue_property_triggers(const char *name, const char *value)
{
INFO("queue_property_triggers \n");
struct listnode *node;
struct action *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:"))) {
const char *test = act->name + strlen("property:");
int name_length = strlen(name);
if (!strncmp(name, test, name_length) &&
test[name_length] == '=' &&
(!strcmp(test + name_length + 1, value) ||
!strcmp(test + name_length + 1, "*"))) {
action_add_queue_tail(act);
}
}
}
}
/system/core/init/init.c
void execute_one_command(void)
{
int ret;
if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
cur_action = action_remove_queue_head();//从action_queue列表中取第一个
cur_command = NULL;
if (!cur_action)
return;
INFO("processing action %p (%s)\n", cur_action, cur_action->name);
cur_command = get_first_command(cur_action);
} else {
cur_command = get_next_command(cur_action, cur_command);
}
if (!cur_command)
return;
ret = cur_command->func(cur_command->nargs, cur_command->args);//调用注册的函数。我们实际上是调用start tcpdump_start
ERROR("command '%s' r=%d\n", cur_command->args[0], ret);
}
//system/core/init/init_parser.c
struct action *action_remove_queue_head(void)
{
if (list_empty(&action_queue)) {
return 0;
} else {
struct listnode *node = list_head(&action_queue);
struct action *act = node_to_item(node, struct action, qlist);
list_remove(node);
list_init(node);
return act;
}
}
上面可以知道,之前已经将prop name 对应的action加入到了action_queue中了,因此这就是查找这个action,然后调用action中的command以及注册函数,即 start函数。
system/core/init/builtins.c
int do_start(int nargs, char **args)
{
struct service *svc;
svc = service_find_by_name(args[1]);
if (svc) {
service_start(svc, NULL);
}
return 0;
}
void service_start(struct service *svc, const char *dynamic_args){
···
if (svc->flags & SVC_RUNNING) {//如果service目前是running的话就直接返回
return;
}
···
pid = fork();//创建子线程执行service脚本
···
execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);//执行service对应的保本
_exit(127);
···
svc->time_started = gettime();
svc->pid = pid;
svc->flags |= SVC_RUNNING;
···
if (properties_inited())
notify_service_state(svc->name, "running");//设置service对应的系统属性为running
}
也就是init进程中启动一个线程取运行service脚本,并且标识这个service是running状态。
在上面的分析中,在init主循环中注册了get_signal_fd。这个函数就是用来监听子线程退出消息的。在这个里面,接收到子线程退出的signal之后,清除SVC_RUNNING标志,并且设置SVC_DISABLED标志为,然后设置service对应的属性为stopped状态。
至此也就是通过setprop 设置系统属性,从而启动系统服务的整个过程。
那么测试遇到的service第一次能够启动,但是后面再次启动没有运行的问题也就很清楚了。在service启动的脚本里面tcpdump 是阻塞状态,导致脚本始终没有退出。因此这个service始终是running状态,所以第二次启动service没有再次启动。
解决方案有两个:
1.将tcpdump后台执行,保证service运行的脚本能够正常退出
2.再次启动service时先停止之前启动的service,通过stop + service的方式。