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