1.首先init()进程在启动的过程对属性服务做了如下操作:
1)property_init(); //创建属性存储空间
2)property_load_boot_defaults(); //从 “/default.prop”中加载默认属性
3)queue_builtin_action(property_service_init_action, “property_service_init”); //初始化属性服务,创建名为“property_service”的socket,该socket文件存放在“/dev/socket/”目录下。然后绑定socket,开始监听。
4)queue_builtin_action(queue_property_triggers_action, “queue_property_triggers”); //首先将“queue_property_triggers”插入到执行action队列,这个action对应的执行函数位“queue_property_triggers_action”
2.文件系统-知识补充:
1)tmpfs文件系统
tmpfs是一种虚拟的内存文件系统,它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有文件均为临时性文件,tmpfs是一个独立的文件系统,不是快设备,只要挂载即可使用。它驻留在RAM中,大小自动变化,断电后tmpfs的内容就会消失。
2)devpts文件系统
devpts文件系统为伪终端(pty-pseudo-terminal slave)提供了一个标准接口,它的标准挂接点是/dev/pts。伪终端是成对的逻辑终端设备(即master主和slave从设备,对master的操作会反应到slave上)
3)proc文件系统
proc是一个非常重要的文件系统,它可以看做是内核内部数据的接口,通过它可以获取系统的信息,同时也能在运行时修改特定的参数,只需要在对应的文件中添加新的值即可,如果修改失败,则只能重新启动设备。
4)sysfs文件系统
与proc文件系统相似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂载到/sys目录下。sysfs文件系统是linux2.6内核引入,它把连接到系统上的设备和总线组织为一个分级的文件,使得他们可以在用户空间存取。
在init进程开始时,创建了三个文件夹,分别为/dev,/proc和/sys。之后又创建了/dev/pts和/dev/socket两个文件夹,分别用于伪终端和用于与服务通信的socket。以上四中文件系统均被挂载到制定的目录上。
3.下面针对上面四点逐一解析;
1)创建属性存储空间
void property_init(void)
{
init_property_area();
}
init_property_area()函数如下:
static int init_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;
return 0;
}
其中property_area_inited标记属性存储区域是否初始化,__system_property_area_init()函数如下,该函数位于“bionic/libc/bionic/system_properties.cpp”中:
int __system_property_area_init()
{
return map_prop_area_rw();
}
map_prop_area_rw()函数如下:
static int map_prop_area_rw()
{
/* dev is a tmpfs that we can use to carve a shared workspace
* out of, so let's do that...
*/
const int fd = open(property_filename,
O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
if (fd < 0) {
if (errno == EACCES) {
/* for consistency with the case where the process has already
* mapped the page in and segfaults when trying to write to it
*/
abort();
}
return -1;
}
// TODO: Is this really required ? Does android run on any kernels that
// don't support O_CLOEXEC ?
const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret < 0) {
close(fd);
return -1;
}
if (ftruncate(fd, PA_SIZE) < 0) {
close(fd);
return -1;
}
pa_size = PA_SIZE;
pa_data_size = pa_size - sizeof(prop_area);
compat_mode = false;
void *const memory_area = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (memory_area == MAP_FAILED) {
close(fd);
return -1;
}
prop_area *pa = new(memory_area) prop_area(PROP_AREA_MAGIC, PROP_AREA_VERSION);
/* plug into the lib property services */
__system_property_area__ = pa;
close(fd);
return 0;
}
由于dev是tmpfs系统,所以可以用来开辟一块共享存储空间。map_prop_area_rw()函数首先创建属性文件“/dev/_properties_”,大小PA_SIZE为128*1024,然后将该文件的对象映射进内存。由于属性是存放在一个混合的线索/二进制树结构(prop_bt)中,所以在内存区域中new了一个prop_area的头节点,并赋给了变量”_system_property_area_“.
之后,init_workspace()函数初始化工作空间,其中workspace的结构体如下:
typedef struct {
size_t size;
int fd;
} workspace;
此时,fd为之间创建的属性文件的open操作的句柄。size为初始化指定大小。最后将“property_area_inited”置为1,至此,属性存储空间开辟完成。
4.从 “/default.prop”中加载默认属性
void property_load_boot_defaults(void)
{
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
其中PROP_PATH_RAMDISK_DEFAULT文件为”/default.prop”
static void load_properties_from_file(const char *fn, const char *filter)
{
char *data;
unsigned sz;
data = read_file(fn, &sz);
if(data != 0) {
load_properties(data, filter);
free(data);
}
}
read_file()即我们平时普通的读文件操作。load_properties函数采用递归的方式解析读取内容中的key-value对。次函数不予解释。
5.初始化属性服务
queue_builtin_action(property_service_init_action, "property_service_init");
这里,queue_builtin_action的第一个参数是一个函数,第二个参数是action的名称以及命令队列中的名称。
static int property_service_init_action(int nargs, char **args)
{
/* read any property files on system or data and
* fire up the property service. This must happen
* after the ro.foo properties are set above so
* that /data/local.prop cannot interfere with them.
*/
start_property_service();
if (get_property_set_fd() < 0) {
ERROR("start_property_service() failed\n");
exit(1);
}
return 0;
}
如注释所说,读取系统中所有的属性文件,启动属性服务。
void start_property_service(void)
{
int fd;
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL);
if(fd < 0) return;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
property_set_fd = fd;
}
创建与属性服务通信的socket,创建函数如下:
/*
* create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
* ("/dev/socket") as dictated in init.rc. This socket is inherited by the
* daemon. We communicate the file descriptor's value via the environment
* variable ANDROID_SOCKET_ENV_PREFIX ("ANDROID_SOCKET_foo").
*/
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
gid_t gid, const char *socketcon)
{
struct sockaddr_un addr;
int fd, ret;
char *filecon;
if (socketcon)
setsockcreatecon(socketcon);
fd = socket(PF_UNIX, type, 0);
if (fd < 0) {
ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
return -1;
}
if (socketcon)
setsockcreatecon(NULL);
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) {
ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
goto out_close;
}
filecon = NULL;
if (sehandle) {
ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
if (ret == 0)
setfscreatecon(filecon);
}
ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
if (ret) {
ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno));
goto out_unlink;
}
setfscreatecon(NULL);
freecon(filecon);
chown(addr.sun_path, uid, gid);
chmod(addr.sun_path, perm);
INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
addr.sun_path, perm, uid, gid);
return fd;
out_unlink:
unlink(addr.sun_path);
out_close:
close(fd);
return -1;
}
在“/dev/socket”目录下创建一个Unix domain socket(同一台主机进程间通信,不需要网络协议,打包拆包等操作,只是将应用层数据从一个进程拷贝到另一个进程)Unix Domain Socket有SOCK_DGRAM或SOCK_STREAM两种工作模式,类似于UDP和TCP。
UNIX Domain socket与网络socket类似,可以与网络socket对比应用。不同如下:
1.address family为AF_UNIX(PF_UNIX的宏)
2.因为应用与IPC,所以Unix Domain socket不需要IP和端口,取而代之的是文件路径表示网络地址
用socket函数创建套接字之后,要绑定该socket和路径(此处有待斟酌),最后监听该端口
6.将“queue_property_triggers”插入到执行action队列
static int queue_property_triggers_action(int nargs, char **args)
{
queue_all_property_triggers();
/* enable property triggers */
property_triggers_enabled = 1;
return 0;
}
queue_all_property_triggers()函数如下:
void queue_all_property_triggers()
{
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:"))) {
/* parse property name and value
syntax is property:<name>= */
const char* name = act->name + strlen("property:");
const char* equals = strchr(name, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - name;
if (length > PROP_NAME_MAX) {
ERROR("property name too long in trigger %s", act->name);
} else {
int ret;
memcpy(prop_name, name, length);
prop_name[length] = 0;
/* does the property exist, and match the trigger value? */
ret = property_get(prop_name, value);
if (ret > 0 && (!strcmp(equals + 1, value) ||
!strcmp(equals + 1, "*"))) {
action_add_queue_tail(act);
}
}
}
}
}
}
该函数主要将解析init.rc文件得到的action列表中的property_action插入到action队列的末尾。
以上即为init进程中属性服务的解释,写这篇博客的目的主要是刚开始学习android源代码,找到方法才是最重要的。并以此鼓励自己。