深入剖析Android中init进程实现的C语言源码
概述
init是一个进程,确切的说,它是Linux系统中用户空间的第一个进程。由于Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程。init的进程号是1。作为天字第一号进程,init有很多重要的工作:
- init提供propertyservice(属性服务)来管理Android系统的属性。
- init负责创建系统中的关键进程,包括zygote。
以往的文章一上来就介绍init的源码,但是我这里先从这两个主要工作开始。搞清楚这两个主要工作是如何实现的,我们再回头来看init的源码。
这篇文章主要是介绍init进程的属性服务。
跟init属性服务相关的源码目录如下:
system/core/init/ bionic/libc/bionic/ system/core/libcutils/
属性服务
在windows平台上有一个叫做注册表的东西,它可以存储一些类似key/value的键值对。一般而言,系统或者某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或应用程序重启,它还能根据之前在注册表中设置的属性值,进行相应的初始化工作。
Android系统也提供了类似的机制,称之为属性服务(propertyservice)。应用程序可以通过这个服务查询或者设置属性。我们可以通过如下命令,获取手机中属性键值对。
adbshellgetprop
例如红米Note手机的属性值如下:
[ro.product.device]:[lcsh92_wet_jb9] [ro.product.locale.language]:[zh] [ro.product.locale.region]:[CN] [ro.product.manufacturer]:[Xiaomi]
在system/core/init/init.c文件的main函数中,跟属性服务的相关代码如下:
property_init(); queue_builtin_action(property_service_init_action,"property_service_init");
接下来,我们分别看一下这两处代码的具体实现。
属性服务初始化
创建存储空间
首先,我们先来看一下property_init函数的源码(/system/core/init/property_service.c):
voidproperty_init(void)
{
init_property_area();
}
property_init函数中只是简单的调用了init_property_area方法,接下来我们看一下这个方法的具体实现:
staticintproperty_area_inited=0;
staticworkspacepa_workspace;
staticintinit_property_area(void)
{
//属性空间是否已经初始化
if(property_area_inited)
return-1;
if(__system_property_area_init())
return-1;
if(init_workspace(&pa_workspace,0))
return-1;
fcntl(pa_workspace.fd,F_SETFD,FD_CLOEXEC);
property_area_inited=1;
return0;
}
从init_property_area函数,我们可以看出,函数首先判断属性内存区域是否已经初始化过,如果已经初始化,则返回-1。如果没有初始化,我们接下来会发现有两个关键函数__system_property_area_init和init_workspace应该是跟内存区域初始化相关。那我们分别分析一下这两个函数具体实现。
__system_property_area_init
__system_property_area_init函数位于/bionic/libc/bionic/system_properties.c文件中,具体代码实现如下:
structprop_area{
unsignedbytes_used;
unsignedvolatileserial;
unsignedmagic;
unsignedversion;
unsignedreserved[28];
chardata[0];
};
typedefstructprop_areaprop_area;
prop_area*__system_property_area__=NULL;
#definePROP_FILENAME"/dev/__properties__"
staticcharproperty_filename[PATH_MAX]=PROP_FILENAME;
#definePA_SIZE(128*1024)
staticintmap_prop_area_rw()
{
prop_area*pa;
intfd;
intret;
/**
*O_RDWR==>读写
*O_CREAT==>若不存在,则创建
*O_NOFOLLOW==>如果filename是软链接,则打开失败
*O_EXCL==>如果使用O_CREAT是文件存在,则可返回错误信息
*/
fd=open(property_filename,O_RDWR|O_CREAT|O_NOFOLLOW|O_CLOEXEC|O_EXCL,0444);
if(fd<0){
if(errno==EACCES){
abort();
}
return-1;
}
ret=fcntl(fd,F_SETFD,FD_CLOEXEC);
if(ret<0)
gotoout;
if(ftruncate(fd,PA_SIZE)<0)
gotoout;
pa_size=PA_SIZE;
pa_data_size=pa_size-sizeof(prop_area);
compat_mode=false;
//mmap映射文件实现共享内存
pa=mmap(NULL,pa_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(pa==MAP_FAILED)
gotoout;
/*初始化内存地址中所有值为0*/
memset(pa,0,pa_size);
pa->magic=PROP_AREA_MAGIC;
pa->version=PROP_AREA_VERSION;
pa->bytes_used=sizeof(prop_bt);
__system_property_area__=pa;
close(fd);
return0;
out:
close(fd);
return-1;
}
int__system_property_area_init()
{
returnmap_prop_area_rw();
}
代码比较好理解,主要内容是利用mmap映射property_filename创建了一个共享内存区域,并将共享内存的首地址赋值给全局变量__system_property_area__。
关于mmap映射文件实现共享内存IPC通信机制,可以参考这篇文章:mmap实现IPC通信机制
init_workspace
接下来,我们来看一下init_workspace函数的源码(/system/core/init/property_service.c):
typedefstruct{
void*data;
size_tsize;
intfd;
}workspace;
staticintinit_workspace(workspace*w,size_tsize)
{
void*data;
intfd=open(PROP_FILENAME,O_RDONLY|O_NOFOLLOW);
if(fd<0)
return-1;
w->size=size;
w->fd=fd;
return0;
}
客户端进程访问属性内存区域
虽然属性内存区域是init进程创建的,但是Android系统希望其他进程也能够读取这块内存区域里的内容。为了做到这一点,init进程在属性区域初始化过程中做了如下两项工作:
把属性内存区域创建在共享内存上,而共享内存是可以跨进程的。这一点,在上述代码中是通过mmap映射/dev/__properties__文件实现的。pa_workspace变量中的fd成员也保存了映射文件的句柄。
如何让其他进程知道这个共享内存句柄呢?Android先将文件映射句柄赋值给__system_property_area__变量,这个变量属于bionic_lic库中的输出的一个变量,然后利用了gcc的constructor属性,这个属性指明了一个__lib_prenit函数,当bionic_lic库被加载时,将自动调用__libc_prenit,这个函数内部完成共享内存到本地进程的映射工作。
只讲原理是不行的,我们直接来看一下__lib_prenit函数代码的相关实现:
void__attribute__((constructor))__libc_prenit(void);
void__libc_prenit(void)
{
//...
__libc_init_common(elfdata);//调用这个函数
//...
}
__libc_init_common函数为:
void__libc_init_common(uintptr_t*elfdata)
{
//...
__system_properties_init();//初始化客户端的属性存储区域
}
__system_properties_init函数有回到了我们熟悉的/bionic/libc/bionic/system_properties.c文件:
staticintget_fd_from_env(void)
{
char*env=getenv("ANDROID_PROPERTY_WORKSPACE");
if(!env){
return-1;
}
returnatoi(env);
}
staticintmap_prop_area()
{
boolformFile=true;
intresult=-1;
intfd;
intret;
fd=open(property_filename,O_RDONLY|O_NOFOLLOW|O_CLOEXEC);
if(fd>=0){
/*Foroldkernelsthatdon'tsupportO_CLOEXEC*/
ret=fcntl(fd,F_SETFD,FD_CLOEXEC);
if(ret<0)
gotocleanup;
}
if((fd<0)&&(error==ENOENT)){
fd=get_fd_from_env();
fromFile=false;
}
if(fd<0){
return-1;
}
structstatfd_stat;
if(fstat(fd,&fd_stat)<0){
gotocleanup;
}
if((fd_stat.st_uid!=0)
||(fd_stat.st_gid!=0)
||(fd_stat.st_mode&(S_IWGRP|S_IWOTH)!=0)
||(fd_stat.st_size<sizeof(prop_area))){
gotocleanup;
}
pa_size=fd_stat.st_size;
pa_data_size=pa_size-sizeof(prop_area);
/*
*映射init创建的属性内存到本地进程空间,这样本地进程就可以使用这块共享内存了。
*注意:映射时制定了PROT_READ属性,所以客户端进程只能读属性,不能设置属性。
*/
prop_area*pa=mmap(NULL,pa_size,PROT_READ,MAP_SHARED,fd,0);
if(pa==MAP_FAILED){
gotocleanup;
}
if((pa->magic!=PROP_AREA_MAGIC)||(pa->version!=PROP_AREA_VERSION&&pa->version!=PROP_AREA_VERSION_COMPAT)){
munmap(pa,pa_size);
gotocleanup;
}
if(pa->version==PROP_AREA_VERSION_COMPAT){
compat_mode=true;
}
result=0;
__system_property_area__=pa;
cleanup:
if(fromFile){
close(fd);
}
returnresult;
}
int__system_properties_init()
{
returnmap_prop_area();
}
通过对源码的阅读,可以发现,客户端通过mmap映射,可以读取属性内存的内容,但是没有权限设置属性。那客户端是如何设置属性的呢?这就涉及到下面要将的属性服务器了。
属性服务器的分析
init进程会启动一个属性服务器,而客户端只能通过与属性服务器的交互来设置属性。
启动属性服务器
先来看一下属性服务器的内容,它由property_service_init_action函数启动,源码如下(/system/core/init/init.c&&property_service.c):
staticintproperty_service_init_action(intnargs,char**args)
{
start_property_service();
return0;
}
staticvoidload_override_properties()
{
#ifdefALLOW_LOCAL_PROP_OVERRIDE
chardebuggable[PROP_VALUE_MAX];
intret;
ret=property_get("ro.debuggable",debuggable);
if(ret&&(strcmp(debuggable,"1")==0)){
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
}
#endif
}
staticvoidload_properties(char*data)
{
char*key,*value,*eol,*sol,*tmp;
sol=data;
while((eol=strchr(sol,'\n'))){
key=sol;
//赋值下一行的指针给sol
*eol++=0;
sol=eol;
value=strchr(key,'=');
if(value==0)continue;
*value++=0;
while(isspace(*key))key++;
if(*key=='#')continue;
tmp=value-2;
while((tmp>key)&&isspace(*tmp))*tmp--=0;
while(isspace(*value))value++;
tmp=eol-2;
while((tmp>value)&&isspace(*tmp))*tmp--=0;
property_set(key,value);
}
}
intcreate_socket(constchar*name,inttype,mode_tperm,uid_tuid,gid_tgid)
{
structsockaddr_unaddr;
intfd,ret;
char*secon;
fd=socket(PF_UNIX,type,0);
if(fd<0){
ERROR("Failedtoopensocket'%s':%s\n",name,strerror(errno));
return-1;
}
memset(&addr,0,sizeof(addr));
addr.sun_family=AF_UNIX;
snprintf(addr.sun_path,sizeof(addr.sun_path),ANDROID_SOCKET_DIR"/%s",name);
ret=unlink(addr.sun_path);
if(ret!=0&&errno!=ENOENT){
gotoout_close;
}
ret=bind(fd,(structsockaddr*)&addr,sizeof(addr));
if(ret){
gotoout_unlink;
}
chown(addr.sun_path,uid,gid);
chmod(addr.sun_path,perm);
returnfd;
out_unlink:
unlink(addr.sun_path);
out_close:
close(fd);
return-1;
}
#definePROP_PATH_SYSTEM_BUILD"/system/build.prop"
#definePROP_PATH_SYSTEM_DEFAULT"/system/default.prop"
#definePROP_PATH_LOCAL_OVERRIDE"/data/local.prop"
#definePROP_PATH_FACTORY"/factory/factory.prop"
voidstart_property_service(void)
{
intfd;
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_override_properties();
/*Readpersistentpropertiesafteralldefaultvalueshavebeenloaded.*/
load_persistent_properties();
fd=create_socket(PROP_SERVICE_NAME,SOCK_STREAM,0666,0,0);
if(fd<0)return;
fcntl(fd,F_SETFD,FD_CLOEXEC);
fcntl(fd,F_SETFL,O_NONBLOCK);
listen(fd,8);
property_set_fd=fd;
}
从上述代码可以看到,init进程除了会预写入指定文件(例如:system/build.prop)属性外,还会创建一个UNIXDomainSocket,用于接受客户端的请求,构建属性。那这个socket请求是再哪里被处理的呢?
答案是:在init中的for循环处已经进行了相关处理。
服务端处理设置属性请求
接收属性设置请求的地方是在init进程中,相关代码如下所示:
intmain(intargc,char**argv)
{
//...省略不相关代码
for(;;){
//...
for(i=0;i<fd_count;i++){
if(ufds[i].fd==get_property_set_fd())
handle_property_set_fd();
}
}
}
从上述代码可以看出,当属性服务器收到客户端请求时,init进程会调用handle_property_set_fd函数进行处理,函数位置是:system/core/init/property_service.c,我们来看一下这个函数的实现源码:
voidhandle_property_set_fd()
{
prop_msgmsg;
ints;
intr;
intres;
structucredcr;
structsockaddr_unaddr;
socklen_taddr_size=sizeof(addr);
socklen_tcr_size=sizeof(cr);
char*source_ctx=NULL;
//接收TCP连接
if((s=accept(property_set_fd,(structsockaddr*)&addr,&addr_size))<0){
return;
}
//接收客户端请求数据
r=TEMP_FAILURE_RETRY(recv(s,&msg,sizeof(msg),0));
if(r!=sizeof(prop_msg)){
ERROR("sys_prop:mis-matchmsgsizereceived:%dexpected:%derrno:%d\n",r,sizeof(prop_msg),errno);
close(s);
return;
}
switch(msg.cmd){
casePROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1]=0;
msg.value[PROP_VALUE_MAX-1]=0;
if(memcmp(msg.name,"ctl.",4)==0){
close(s);
if(check_control_perms(msg.value,cr.uid,cr.gid,source_ctx)){
handle_control_message((char*)msg.name+4,(char*)msg.value);
}else{
ERROR("sys_prop:Unableto%sservicectl[%s]uid:%dgid:%dpid:%d\n",msg.name+4,msg.value,cr.uid,cr.gid,cr.pid);
}
}else{
if(check_perms(msg.name,cr.uid,cr.gid,source_ctx)){
property_set((char*)msg.name,(char*)msg.value);
}
close(s);
}
break;
default:
close(s);
break;
}
}
当客户端的权限满足要求时,init就调用property_set进行相关处理。property_set源码实现如下:
intproperty_set(constchar*name,constchar*value)
{
prop_info*pi;
intret;
size_tnamelen=strlen(name);
size_tvaluelen=strlen(value);
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开头的属性被设置后,不允许再被修改
if(!strncmp(name,"ro.",3))return-1;
__system_property_update(pi,value,valuelen);
}else{
ret=__system_property_add(name,namelen,value,valuelen);
}
//有一些特殊的属性需要特殊处理,例如net.和persist.开头的属性
if(strncmp("net.",name,strlen("net."))==0){
if(strcmp("net.change",name)==0){
return0;
}
property_set("net.change",name);
}elseif(persistent_properties_loaded&&strncmp("persist.",name,strlen("persist."))==0){
write_persistent_property(name,value);
}
property_changed(name,value);
return0;
}
属性服务器端的工作基本到这里就完成了。最后,我们来看一下客户端是如何发送设置属性的socket请求。
客户端发送请求
客户端设置属性时是调用了property_set(“sys.istest”,“true”)方法。从上述分析可知,该方法实现跟服务器端的property_set方法不同,该方法一定是发送了socket请求,该方法源码位置为:/system/core/libcutils/properties.c:
intproperty_set(constchar*key,constchar*value)
{
return__system_property_set(key,value);
}
可以看到,property_set调用了__system_property_set方法,这个方法位于:/bionic/libc/bionic/system_properties.c文件中:
structprop_msg
{
unsignedcmd;
charname[PROP_NAME_MAX];
charvalue[PROP_VALUE_MAX];
};
typedefstructprop_msgprop_msg;
staticintsend_prop_msg(prop_msg*msg)
{
structpollfdpollfds[1];
structsockaddr_unaddr;
socklen_talen;
size_tnamelen;
ints;
intr;
intresult=-1;
s=socket(AF_LOCAL,SOCK_STREAM,0);
if(s<0){
returnresult;
}
memset(&addr,0,sizeof(addr));
namelen=strlen(property_service_socket);
strlcpy(addr.sun_path,property_service_socket,sizeof(addr.sun_path));
addr.sun_family=AF_LOCAL;
alen=namelen+offsetof(structsockaddr_un,sun_path)+1;
if(TEMP_FAILURE_RETRY(connect(s,(structsockaddr*)&addr,alen))<0){
close(s);
returnresult;
}
r=TEMP_FAILURE_RETRY(send(s,msg,sizeof(prop_msg),0));
close(s);
returnresult;
}
int__system_property_set(constchar*key,constchar*value)
{
interr;
prop_msgmsg;
if(key==0)return-1;
if(value==0)value="";
if(strlen(key)>=PROP_NAME_MAX)return-1;
if(strlen(value)>=PROP_VALUE_MAX)return-1;
memset(&msg,0,sizeof(msg));
msg.cmd=PROP_MSG_SETPROP;
strlcpy(msg.name,key,sizeof(msg.name));
strlcpy(msg.value,value,sizeof(msg.value));
err=send_prop_msg(&msg);
if(err<0){
returnerr;
}
return0;
}