天天看點

Android init service啟動流程分析

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