在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的方式。