本文基於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賦值
自定義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。