/*****************************************************************
*version:android4.2
*author:冷雨
*嵌入式开发群:122879839
*****************************************************************/
上一部分我们分析了init.c文件开始的那部分代码,下面这部分我们要做两部分工作。第一,简单分析一下这部分属性相关的代码;第二,解析init.rc文件。我们先贴出这这部分的代码。
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
INFO("reading config file\n");
init_parse_config_file("/init.rc");
这里首先判断机器是否是以字符模式启动,将结果保存到变量is_charger中。如果不是字符模式启动则执行函数property_load_boot_defaults()。这个函数是和属性相关的操作。函数位于system/core/init/property_service.c文件中。
void property_load_boot_defaults(void)
{
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
这里又调用了另一个函数。
static void load_properties_from_file(const char *fn)
{
char *data;
unsigned sz;
data = read_file(fn, &sz);
if(data != 0) {
load_properties(data);
free(data);
}
}
在这个函数中,会读取PROP_PATH_RAMDISK_DEFAULT文件中的内容,这个文件中存储的是系统的一些默认的属性值。最后调用load_properties函数加载这些属性。注意到,我们这里传送过去的是data,也就是我们刚才读出来的数据。看一下load_properties这个函数。
static void load_properties(char *data)
{
char *key, *value, *eol, *sol, *tmp;
sol = data;
while((eol = strchr(sol, '\n'))) {
key = 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);
}
}
这个函数主要完成对传入的字符串进行解析,然后调用property_set函数。我们现在看一下,google是怎样解析这个字符串的。这样我们先找一段属性文件如下,
# begin build properties
ro.build.type=eng
ro.build.user=root
上面那三行是咱家产品的属性文件的一部分,我们用他作为例子来理解一下解析算法。首先我们先把上面的那些数据编号。我们假设他们在内存中的地址从x开始。
# b e g I n b u I l d p r o p e r t
x x+1 x+2 x+3 x+5 x+6 x+7 x+8 x+9 x+10 x+11 x+12 x+13 x+14 x+15 x+15 x+16 x+17 x+18 x+19 x+20
I e s \n r o . b u I l d . t y p e =
x+21 x+22 x+23 x+24 x+25 x+26 x+27 x+28x+29 x+30 x+31 x+32 x+33 x+34 x+35 x+36 x+37 x+38
e n g \n……
x+39 x+40 x+41 x+42……
我们假设data中存储的就是上面那三行代码,首先将data赋值给sol,进入while循环。注意到这里有一个函数strchr(sol, '\n'),这里返回给eol的就是’\n’在sol第一次出现的地址,在这里也就是x+24,然后我们将这个值赋值给key。接下来*eol++ = 0,这里将以eol为地址的数据’\n’改成0,并且eol完成了自加1,也就是说现在的字符串已经以x+24为断点截成了两段,因为0恰好代表的就是一个字符串的终点。下面看value = strchr(key, '=')这一行,这句返回的是第一个’=’出现的位置,这里value是0,因为没有找到那个’=’,所以这一次循环就直接退出了。
我们注意到现在这里sol是x+25,继续咱们刚才的分析。我们看一下执行到value = strchr(key, '=')这一行的时候,各个值的变化。key= x+25,eol= x+42, sol=x+42,value= x+38。好的,到这里我们就跟刚才的东西接上了,我们继续往下分析,下面的这行代码是*value++ = 0,完成的功能跟上面*eol++ = 0的功能一样,将以value为地址的数据置位为0,然后让value指向下一个字符。下面的while语句就是让key往后挪,直到指向第一个不为空的字符。接下来的那个while直接飘过,第二个条件达不到。接下来我们让value重复刚才key的操作,也就是让value往后挪,直到指向第一个不为空的字符。下一个while同样是第二个条件达不到。最后调用函数property_set(key, value)。我们需要注意的是这里传递进去的这两个参数,由于我们把x+38和x+42这两个位置的字符换成了0,所以这里传进去的key,和value其实是ro.build.type和eng。如果了解android的话这里的意思就是这次编译的类型是eng,啥是eng,好吧,如果你编译过android源代码的话会明白的。我们还是继续去看下面的代码吧。
int property_set(const char *name, const char *value)
{
prop_area *pa;
prop_info *pi;
int namelen = strlen(name);
int valuelen = strlen(value);
if(namelen >= PROP_NAME_MAX) return -1;
if(valuelen >= PROP_VALUE_MAX) return -1;
if(namelen < 1) 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;
pa = __system_property_area__;
update_prop_info(pi, value, valuelen);
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
} else {
pa = __system_property_area__;
if(pa->count == PA_COUNT_MAX) return -1;
pi = pa_info_array + pa->count;
pi->serial = (valuelen << 24);
memcpy(pi->name, name, namelen + 1);
memcpy(pi->value, value, valuelen + 1);
pa->toc[pa->count] =
(namelen << 24) | (((unsigned) pi) - ((unsigned) pa));
pa->count++;
pa->serial++;
__futex_wake(&pa->serial, INT32_MAX);
}
/* 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);
#ifdef HAVE_SELINUX
} else if (strcmp("selinux.reload_policy", name) == 0 &&
strcmp("1", value) == 0) {
selinux_reload_policy();
#endif
}
property_changed(name, value);
return 0;
}
下面分别使用strlen函数获得传过来的那两个参数的长度并保存在namelen和valuelen中。再下面就是一些判断逻辑,namelen不能大于PROP_NAME_MAX,valuelen 不能大于PROP_VALUE_MAX 。我们继续往下看,接下来遇到这个__system_property_find。看看他是干嘛的。
const prop_info *__system_property_find(const char *name)
{
prop_area *pa = __system_property_area__;
unsigned count = pa->count;
unsigned *toc = pa->toc;
unsigned len = strlen(name);
prop_info *pi;
while(count--) {
unsigned entry = *toc++;
if(TOC_NAME_LEN(entry) != len) continue;
pi = TOC_TO_INFO(pa, entry);
if(memcmp(name, pi->name, len)) continue;
return pi;
}
return 0;
}
一开始就涉及到其他的数据,我们先把他们补齐。
static unsigned dummy_props = 0;
prop_area *__system_property_area__ = (void*) &dummy_props;
看到这里,下面的几乎都不用再看了。那个while的初始条件都不满足。直接返回了。这里我们假设先不返回,我们看看这个函数有什么作用。从名字上判断这里是从系统中寻找以传进来的参数name为名字的一个结构体。prop_area结构体中的count用于计数,代表的是属性的数目。如果这个count不为零就代表着这里面还有属性。上面这个函数其实就是在在属性系统里面根据名字来查找是否有这样一个属性,有的话就返回这样一个prop_info结构体。当然我们这里返回的是0了。
回到property_set函数中,因为返回是0,所以这里要执行else里面的代码。在这部分代码中我们主要是将prop_info结构体放到合适位置,填充了prop_info结构体指针pi的成员,并且改变了prop_area 结构体指针pa成员变量的值,即改变属性计数。最后会调用property_changed函数去通知系统属性已经改变。
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
queue_property_triggers(name, value);
}
这里如果需要执行queue_property_triggers去做一些事情的话那个if判断必须通过,可现在我们这个property_triggers_enabled还没有达到这种地步,所以这里是不会执行的。那什么时候可以执行的,如果看一下代码的话,将property_triggers_enabled设置为1差不多在系统init的最后阶段,这是后话了……
这样我们的第一个属性就添加完毕,我们应该还记得我们这是在load_properties函数里面的一个while循环中。后面还有好多属性,就让while自己在那里慢慢添加吧。
回到init.c文件的main函数中,接下来是执行init_parse_config_file("/init.rc"),这可是重头戏。我们知道android虽然叫做操作系统可是我们通常意义上也仅仅是把android当做文件系统,它的底层实际上是linux系统。在linux的根目录下面有一个init.rc文件。这个文件里有很多重要的东西。而现在这个函数需要做的就是解析这个文件,将文件中的东西保存到内存中,方便我们以后去使用,当然在这里并不是杂乱无章的去保存。init_parse_config_file位于system/core/init/init_parser.c文件中
int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(f