本文基于Android 5.1版本SDK。
客户需求:自定义sn,统一规则编号。
Android的sn由SystemProperties.get("ro.serialno", "");
而来。
ro.serialno并不像常见的其他属性一样存在于build.prop等prop文件中,而是在/system/core/init/init.c里由ro.boot.serialno转换而来,转换的函数是export_kernel_boot_props,源码如下:
static void export_kernel_boot_props(void)
{
char tmp[PROP_VALUE_MAX];
int ret;
unsigned i;
struct {
const char *src_prop;
const char *dest_prop;
const char *def_val;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
};
for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
ret = property_get(prop_map[i].src_prop, tmp);
if (ret > 0)
property_set(prop_map[i].dest_prop, tmp);
else
property_set(prop_map[i].dest_prop, prop_map[i].def_val);
}
ret = property_get("ro.boot.console", tmp);
if (ret)
strlcpy(console, tmp, sizeof(console));
/* save a copy for init's usage during boot */
property_get("ro.bootmode", tmp);
strlcpy(bootmode, tmp, sizeof(bootmode));
/* if this was given on kernel command line, override what we read
* before (e.g. from /proc/cpuinfo), if anything */
ret = property_get("ro.boot.hardware", tmp);
if (ret)
strlcpy(hardware, tmp, sizeof(hardware));
property_set("ro.hardware", hardware);
snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
property_set("ro.revision", tmp);
/* TODO: these are obsolete. We should delete them */
if (!strcmp(bootmode,"factory"))
property_set("ro.factorytest", "1");
else if (!strcmp(bootmode,"factory2"))
property_set("ro.factorytest", "2");
else
property_set("ro.factorytest", "0");
}
重点看for循环部分,可以明显看到,ro.serialno的值是由ro.boot.serialno而来。
export_kernel_boot_props在process_kernel_cmdline中调用
static void process_kernel_cmdline(void)
{
/* don't expose the raw commandline to nonpriv processes */
chmod("/proc/cmdline", 0440);
/* first pass does the common stuff, and finds if we are in qemu.
* second pass is only necessary for qemu to export all kernel params
* as props.
*/
import_kernel_cmdline(0, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
/* now propogate the info given on command line to internal variables
* used by init as well as the current required properties
*/
export_kernel_boot_props();
}
process_kernel_cmdline中还调用了import_kernel_nv,import_kernel_nv的方法体如下:
static void import_kernel_nv(char *name, int for_emulator)
{
char *value = strchr(name, '=');
int name_len = strlen(name);
if (value == 0) return;
*value++ = 0;
if (name_len == 0) return;
if (for_emulator) {
/* in the emulator, export any kernel option with the
* ro.kernel. prefix */
char buff[PROP_NAME_MAX];
int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
if (len < (int)sizeof(buff))
property_set( buff, value );
return;
}
if (!strcmp(name,"qemu")) {
strlcpy(qemu, value, sizeof(qemu));
} else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
const char *boot_prop_name = name + 12;
char prop[PROP_NAME_MAX];
int cnt;
cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
if (cnt < PROP_NAME_MAX)
property_set(prop, value);
}
}
代码很清晰,从cmdline中将androidboot.*的属性拿出来设置给了ro.boot.*的属性。在终端里面执行cat /proc/cmdline,如下:
shell@msm8909:/ # cat /proc/cmdline
sched_enable_hmp=1 console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x3F ehci-hcd.pa
rk=3 androidboot.bootdevice=7824900.sdhci lpm_levels.sleep_disabled=1 earlyprintk androidboot.selinux=permissive androidboot.emmc=true androidbo
ot.serialno=c336a56 androidboot.baseband=msm mdss_mdp3.panel=1:dsi:0:qcom,mdss_dsi_jd9366_800p_video:1:none
可以看到androidboot.serialno=c336a56,说明sn是在bootloader中获得,通过cmdline传递到内核。
至此,sn的传递和赋值流程基本梳理清楚。
自定义sn的思路是在系统的persist分区(persist分区的内容不会因为恢复出厂设置而被清除,重新刷机会清除)里面预设一个文本文件,系统启动之后,从app里面向这个文本文件写入按规则编号的sn,然后重启系统,init进程重新读取文本里面的值来设置ro.serialno和ro.boot.serialno。
首先在init.c里面修改,屏蔽掉ro.boot.serialno赋值的逻辑,然后在export_kernel_boot_props的for循环里面,i=0的时候是对ro.serialno进行赋值,在这里增加需要的代码:fopen打开文本获取到文件指针fp->fgets从fp读取一行内容到buf里面->调用property_set将buf的值设置给ro.serialno和ro.boot.serialno。
修改之后,重新编译boot.img。
make bootimage -j8
然后重刷boot:
adb reboot bootloader
fastboot flash boot boot.img
fastboot reboot
重启进系统,dmesg | grep init查看打印,尴尬的问题来了,fp是null。看来在init进程启动的时候,persist分区还没挂载,因此访问不到persist分区。在init.c里读取文本这个路子行不通。
init.rc里面on boot之后,以root权限执行脚本,脚本中读取文本内容,然后将内容设置给ro.serialno和ro.boot.serialno。
脚本内容和init.rc增加的代码都很简单:
on boot
start setsn
service setsn /sbin/busybox sh /system/etc/setsn.sh service
oneshot
disabled
user root
group root
**********************************
**********************************
#!/system/bin/sh
SN_FILE_PATH=/persist/sn.txt
SN_FILE_VALUE=`cat $SN_FILE_PATH`
SN_BACKUP_VALUE=`getprop persist.serialno.backup`
if [[ "$SN_FILE_VALUE" != "" ]];
then
SN=$SN_FILE_VALUE
else
SN=$SN_BACKUP_VALUE
fi
setprop ro.serialno $SN
setprop ro.boot.serialno $SN
此方法实测有效。
PS:
在调试过程中发现,init进程中对ro.serialno和ro.boot.serialno只能设值一次,如果不屏蔽init.c里面设值的逻辑,那么执行脚本将不会改变sn。