前言:
关于ro.serialno
这个属性,相信大家都不陌生了,应用层的Build.getSerial()
,Build.SERIAL
等均是直接或间接的获取了这个属性值。接下来从boot到系统应用,小小的分析一下它的整个流程:
由于是APP经常使用,那我们从应用层分析到底层kernel/boot
一,framework层
好的,我们进入安卓源码目录,grep
查找一下:
xxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "SERIAL" frameworks/base/ frameworks/base/docs/html/about/versions/android-4.2.jd:364:address or the {@link android.os.Build#SERIAL} number), they will provide the same value for each frameworks/base/api/test-current.txt:28614: field public static final java.lang.String SERIAL; frameworks/base/api/system-current.txt:31035: field public static final java.lang.String SERIAL; frameworks/base/api/current.txt:28540: field public static final java.lang.String SERIAL; frameworks/base/core/java/android/os/Build.java:102: public static final String SERIAL = getString("ro.serialno"); frameworks/base/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java:61: private static final String SERIAL = "0000000012345678"; frameworks/base/tests/AccessoryDisplay/sink/src/com/android/accessorydisplay/sink/SinkActivity.java:254: sendString(conn, UsbAccessoryConstants.ACCESSORY_STRING_SERIAL, SERIAL); xxxx@server01:~/workspace/rk3128_tablet$
成功的在Build.java
找到了这个SERIAL属性,我们继续往下跟getString
这个方法大概在871行。
..... /** * Returns the version string for the radio firmware. May return * null (if, for instance, the radio is not currently on). */ public static String getRadioVersion() { return SystemProperties.get(TelephonyProperties.PROPERTY_BASEBAND_VERSION, null); } private static String getString(String property) { return SystemProperties.get(property, UNKNOWN); } private static String[] getStringList(String property, String separator) { String value = SystemProperties.get(property); if (value.isEmpty()) { return new String[0]; } else { return value.split(separator); } } .....
SystemProperties
大家应该很熟了
可以看出,getString
是传入的"ro.serialno"
这个字串去获取的属性中的值,其效果在命令行上相当于getprop ro.serialno
。
好的,framework分析到这。
二,系统层
我们从第一个程序init
开始,源码路径:
your_pro/system/core/init/init.cpp
根据关键字ro.serialno
找到了地方,大概在464行:
static void export_kernel_boot_props() { char cmdline[1024]; char* s1; char* s2; char* s3; char* s4; struct { const char *src_prop; const char *dst_prop; const char *default_value; } prop_map[] = { { "ro.boot.serialno", "ro.serialno", "", },//就是这了,根据ro.boot.serialno的值设置ro.serialno的值 { "ro.boot.mode", "ro.bootmode", "unknown", }, { "ro.boot.baseband", "ro.baseband", "unknown", }, { "ro.boot.bootloader", "ro.bootloader", "unknown", }, { "ro.boot.hardware", "ro.hardware", "unknown", }, { "ro.boot.revision", "ro.revision", "0", }, }; //if storagemedia is emmc, so we will wait emmc init finish for (int i = 0; i < EMMC_RETRY_COUNT; i++) { proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) ); s1 = strstr(cmdline, STORAGE_MEDIA); s2 = strstr(cmdline, "androidboot.mode=emmc"); s3 = strstr(cmdline, "storagemedia=nvme"); s4 = strstr(cmdline, "androidboot.mode=nvme"); if ((s1 == NULL) && (s3 == NULL)) { //storagemedia is unknow break; } if ((s1 > 0) && (s2 > 0)) { ERROR("OK,EMMC DRIVERS INIT OKn"); property_set("ro.boot.mode", "emmc"); break; } else if ((s3 > 0) && (s4 > 0)) { ERROR("OK,NVME DRIVERS INIT OKn"); property_set("ro.boot.mode", "nvme"); break; } else { ERROR("OK,EMMC DRIVERS NOT READY, RERRY=%dn", i); usleep(10000); } } for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {//这里这里 std::string value = property_get(prop_map[i].src_prop); property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value); } /* save a copy for init's usage during boot */ std::string bootmode_value = property_get("ro.bootmode"); if (!bootmode_value.empty()) strlcpy(bootmode, bootmode_value.c_str(), sizeof(bootmode)); /* if this was given on kernel command line, override what we read * before (e.g. from /proc/cpuinfo), if anything */ std::string hardware_value = property_get("ro.boot.hardware"); if (!hardware_value.empty()) strlcpy(hardware, hardware_value.c_str(), sizeof(hardware)); property_set("ro.hardware", hardware); symlink_fstab(); }
以上代码针对于ro.serialno
的大致意思就是根据ro.boot.serialno
的值设它。
但是,ro.boot.serialno
在哪还不知道呢,我们继续。
好的,分析开始
从mian开始,找到第一阶段需要执行的代码
int main(int argc, char** argv) { .... if (!is_first_stage) { // Indicate that booting is in progress to background fw loaders, etc. close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); property_init(); // If arguments are passed both on the command line and in DT, // properties set in DT always have priority over the command-line ones. process_kernel_dt(); process_kernel_cmdline();//根据函数名字就大概知道,这是处理内核cmdline的函数 //add by xzj to set ro.rk.soc read from /proc/cpuinfo if not set set_soc_if_need(); // Propagate the kernel variables to internal variables // used by init as well as the current required properties. export_kernel_boot_props();//这里就是将处理完cmdline的相关的boot属性输出,我们上面已经分析过这个函数了 } .... }
先看process_kernel_cmdline函数:
这里做了两个动作,改cmdline的权限和设置import_kernel_nv
这个回调函数
static void process_kernel_cmdline() { // Don't expose the raw commandline to unprivileged processes. chmod("/proc/cmdline", 0440); // The first pass does the common stuff, and finds if we are in qemu. // The second pass is only necessary for qemu to export all kernel params // as properties. import_kernel_cmdline(false, import_kernel_nv); if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv); }
回调函数import_kernel_nv
将传入的cmdline中的条目解析并且设置property
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) { if (key.empty()) return; if (for_emulator) { // In the emulator, export any kernel option with the "ro.kernel." prefix. property_set(android::base::StringPrintf("ro.kernel.%s", key.c_str()).c_str(), value.c_str()); return; } if (key == "qemu") { strlcpy(qemu, value.c_str(), sizeof(qemu)); } else if (android::base::StartsWith(key, "androidboot.")) { property_set(android::base::StringPrintf("ro.boot.%s", key.c_str() + 12).c_str(), value.c_str()); } }
再看看import_kernel_cmdline
做了什么动作?
这里从/proc/cmdline
读出数据,然后以空格“ ”分开数据,for循环调用传入的回调函数指针fn,也就是import_kernel_nv
函数,再将分开的数据传参入回调函数。
void import_kernel_cmdline(bool in_qemu, std::function<void(const std::string&, const std::string&, bool)> fn) { std::string cmdline; android::base::ReadFileToString("/proc/cmdline", &cmdline); for (const auto& entry : android::base::Split(android::base::Trim(cmdline), " ")) { std::vector<std::string> pieces = android::base::Split(entry, "="); if (pieces.size() == 2) { fn(pieces[0], pieces[1], in_qemu); } } }
这里小小的总结下:
从上面的步骤跟踪下来,发现整体流程是将从boot传给kernel的cmdline中的androidboot.serialno
赋给ro.boot.serialno
,然后再根据ro.boot.*相关的属性去设置export_kernel_boot_props
函数中prop_map
这个数组对应的ro. 属性。
举个栗子,此处serialno的流程就该为:
boot- > kernel cmdline -> androidboot.serialno -> ro.boot.serialno -> ro.serialno -> 然后再被prop调用
到这里,只有kernel cmdline之前的流程不知道了,具体boot是怎么将一堆东西传给/proc/cmdline的呢?
好的,安排它~
三,u-Boot层
继续进uboot目录搜索一下:
xxx@server01:~/workspace/rk3128_tablet$ grep -nrw "androidboot.serialno" u-boot/ 匹配到二进制文件 u-boot/u-boot.bin 匹配到二进制文件 u-boot/common/cmd_bootrk.o 匹配到二进制文件 u-boot/common/built-in.o 匹配到二进制文件 u-boot/uboot.img 匹配到二进制文件 u-boot/u-boot u-boot/include/fastboot.h:81:#define FASTBOOT_SERIALNO_BOOTARG "androidboot.serialno" xxx@server01:~/workspace/rk3128_tablet$
找到一个FASTBOOT_SERIALNO_BOOTARG,继续搜它,看谁用了
xtw-cl@server01:~/workspace/pnd_rk3128_tablet$ grep -nrw "FASTBOOT_SERIALNO_BOOTARG" u-boot/ u-boot/common/cmd_bootrk.c:583: if (!strstr(command_line, FASTBOOT_SERIALNO_BOOTARG)) { u-boot/common/cmd_bootrk.c:585: "%s %s=%s", command_line, FASTBOOT_SERIALNO_BOOTARG, sn); u-boot/include/fastboot.h:81:#define FASTBOOT_SERIALNO_BOOTARG "androidboot.serialno" xtw-cl@server01:~/workspace/pnd_rk3128_tablet$
找到了,u-boot/common/cmd_bootrk.c文件
好的,开始分析源码:
static void rk_commandline_setenv(const char *boot_name, rk_boot_img_hdr *hdr, bool charge) { .... snprintf(command_line, sizeof(command_line), "%s SecureBootCheckOk=%d", command_line, SecureBootCheckOK); char *sn = getenv("fbt_sn#"); if (sn != NULL) { /* append serial number if it wasn't in device_info already */ if (!strstr(command_line, FASTBOOT_SERIALNO_BOOTARG)) { snprintf(command_line, sizeof(command_line), "%s %s=%s", command_line, FASTBOOT_SERIALNO_BOOTARG, sn); } } command_line[sizeof(command_line) - 1] = 0; setenv("bootargs", command_line); #endif /* CONFIG_CMDLINE_TAG */ }
从源码可得知,androidboot.serialno的这个sn参数是通过getenv("fbt_sn#")获取到的,好的,继续搜索fbt_sn#看看是哪里设置的这个环境变量
xxx@server01:~/workspace/rk3128_tablet$ grep -nrw "fbt_sn#" u-boot/ 匹配到二进制文件 u-boot/u-boot.bin u-boot/common/cmd_bootrk.c:580: char *sn = getenv("fbt_sn#"); 匹配到二进制文件 u-boot/common/cmd_fastboot.o 匹配到二进制文件 u-boot/common/cmd_bootrk.o u-boot/common/cmd_fastboot.c:662: //setenv("fbt_sn#", serial_number); u-boot/common/cmd_fastboot.c:668: char *sn = getenv("fbt_sn#"); 匹配到二进制文件 u-boot/common/built-in.o u-boot/board/rockchip/rk33xx/rk33xx.c:226: setenv("fbt_sn#", tmp_buf); u-boot/board/rockchip/rk32xx/rk32xx.c:220: setenv("fbt_sn#", tmp_buf); 匹配到二进制文件 u-boot/board/rockchip/rk32xx/rk32xx.o 匹配到二进制文件 u-boot/board/rockchip/rk32xx/built-in.o 匹配到二进制文件 u-boot/uboot.img 匹配到二进制文件 u-boot/u-boot xxx@server01:~/workspace/rk3128_tablet$
可以得知,设setenv的有两个,但是我们生成的二进制文件是rk32xx.o,所以我们分析rk32xx.c这个源码。
#ifdef CONFIG_BOARD_LATE_INIT extern char bootloader_ver[24]; int board_late_init(void) { debug("board_late_initn"); .... char tmp_buf[32]; /* rk sn size 30bytes, zero buff */ memset(tmp_buf, 0, 32); if (rkidb_get_sn(tmp_buf)) { setenv("fbt_sn#", tmp_buf); } debug("fbt prebootn"); board_fbt_preboot(); return 0; } #endif
从上面可以看出设进fbt_sn#属性名字的tmp_buf是从rkidb_get_sn函数获取的,so继续。
顺便提一句,board_late_init会在环境初始化函数中调用,而它会被启动的更底层的汇编程序调用,这里不展开讲
搜一下这个rkidb_get_sn函数
xxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "rkidb_get_sn" u-boot/ u-boot/board/rockchip/rk33xx/rk33xx.c:225: if (rkidb_get_sn(tmp_buf)) { u-boot/board/rockchip/rk32xx/rk32xx.c:219: if (rkidb_get_sn(tmp_buf)) { 匹配到二进制文件 u-boot/board/rockchip/rk32xx/rk32xx.o 匹配到二进制文件 u-boot/board/rockchip/rk32xx/built-in.o u-boot/board/rockchip/common/rkloader/idblock.c:565:int rkidb_get_sn(char* buf) u-boot/board/rockchip/common/rkloader/idblock.su:7:idblock.c:565:5:rkidb_get_sn 16 static u-boot/board/rockchip/common/rkloader/idblock.h:252:int rkidb_get_sn(char *buf); 匹配到二进制文件 u-boot/board/rockchip/common/rkloader/idblock.o 匹配到二进制文件 u-boot/board/rockchip/common/built-in.o u-boot/u-boot.map:1468: .text.rkidb_get_sn u-boot/u-boot.map:1470: 0x0000000060008bc4 rkidb_get_sn u-boot/u-boot.map:4608: .rel.text.rkidb_get_sn u-boot/System.map:219:60008bc4 T rkidb_get_sn 匹配到二进制文件 u-boot/u-boot xxxx@server01:~/workspace/rk3128_tablet$
实现在u-boot/board/rockchip/common/rkloader/idblock.c
文件,打开它
int (char* buf) { int size; Sector3Info *pSec3; uint8 *pidbbuf = (uint8 *)gIdDataBuf; pSec3 = (Sector3Info *)(pidbbuf + IDBLOCK_SIZE * IDBLOCK_SN); size = pSec3->snSize; if (size <= 0 || size > SN_MAX_SIZE) { PRINT_E("empty serial no.n"); return false; } strncpy(buf, (char *)pSec3->sn, size); buf[size] = ' '; PRINT_E("sn: %sn", buf); return true; }
可以看出是通过ID Block去读的,通过地址偏移取值拿到的,那我们继续找寻哪里给这个gIdDataBuf赋的值。
搜索一下,根据搜索出的信息去筛选
xxxxx@server01:~/workspace/rk3128_tablet$ grep -nrw "gIdDataBuf" u-boot/ 匹配到二进制文件 u-boot/board/rockchip/common/storage/storage.o u-boot/board/rockchip/common/storage/storage.h:197:EXT uint32 gIdDataBuf[512] __attribute__((aligned(ARCH_DMA_MINALIGN))); u-boot/board/rockchip/common/SecureBoot/SecureBoot.c:133: FlashSramLoadStore(&gIdDataBuf[384], 1536, 1, 512); // idblk sn info 匹配到二进制文件 u-boot/board/rockchip/common/SecureBoot/SecureBoot.o 匹配到二进制文件 u-boot/board/rockchip/common/mediaboot/sdmmcBoot.o u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c:120: ret1 = SDM_Read(ChipSel, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf); u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c:123: if (gIdDataBuf[0] == 0xFCDC8C3B) { 匹配到二进制文件 u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c u-boot/board/rockchip/common/mediaboot/UMSBoot.c:307: __UMSReadLBA(usb_stor_curr_dev, UMS_BOOT_PART_OFFSET, gIdDataBuf, 4); u-boot/board/rockchip/common/mediaboot/UMSBoot.c:308: if (gIdDataBuf[0] == 0xFCDC8C3B) { u-boot/board/rockchip/common/mediaboot/UMSBoot.c:309: if (0 == gIdDataBuf[128+104/4]) { u-boot/board/rockchip/common/mediaboot/UMSBoot.c:313: } else if (1 == gIdDataBuf[128+104/4]) { u-boot/board/rockchip/common/mediaboot/sdhciBoot.c:53: block_mmc_read(SDHCI_EMMC_DEV_ID, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf); u-boot/board/rockchip/common/rkloader/idblock.c:30:extern uint32 gIdDataBuf[512]; u-boot/board/rockchip/common/rkloader/idblock.c:505: pdst = (uint8 *)gIdDataBuf; u-boot/board/rockchip/common/rkloader/idblock.c:512: GetIdblockDataNoRc4((char *)&gIdDataBuf[128 * 2], 512); u-boot/board/rockchip/common/rkloader/idblock.c:513: GetIdblockDataNoRc4((char *)&gIdDataBuf[128 * 3], 512); u-boot/board/rockchip/common/rkloader/idblock.c:532: if (gIdDataBuf[0] == 0xFCDC8C3B) { u-boot/board/rockchip/common/rkloader/idblock.c:533: memcpy((char *)&idb0_info, gIdDataBuf, 512); u-boot/board/rockchip/common/rkloader/idblock.c:545: uint8 *buf = (uint8 *)&gIdDataBuf[0]; u-boot/board/rockchip/common/rkloader/idblock.c:569: uint8 *pidbbuf = (uint8 *)gIdDataBuf; u-boot/board/rockchip/common/rkloader/idblock.c:588: uint8 *pidbbuf = (uint8 *)gIdDataBuf; u-boot/board/rockchip/common/rkloader/idblock.c:609: uint8 *pidbbuf = (uint8 *)gIdDataBuf; 匹配到二进制文件 u-boot/board/rockchip/common/rkloader/idblock.o 匹配到二进制文件 u-boot/board/rockchip/common/built-in.o u-boot/u-boot.map:6203: .bss.gIdDataBuf u-boot/u-boot.map:6205: 0x000000006009b5c0 gIdDataBuf u-boot/System.map:1464:6009b5c0 B gIdDataBuf 匹配到二进制文件 u-boot/u-boot xxxxx@server01:~/workspace/rk3128_tablet$
我们这里的目的是需要知道哪里给gIdDataBuf其赋值,所以我们直接查看有编译到产出.o文件的并且有可能是直接给它赋值的文件及函数位置。
文件位置:u-boot/board/rockchip/common/mediaboot/sdmmcBoot.c
从名字就可以大概看出,这是操作sdmmc
的,也就是eMMC
或SD
卡的地方,好的继续看函数。
uint32 SdmmcInit(uint32 ChipSel) { int32 ret1 = SDM_SUCCESS; uint32 ioctlParam[5] = {0, 0, 0, 0, 0}; ..... ret1 = SdmmcReinit(ChipSel); if (ret1 == SDM_SUCCESS) { /* 卡能识别 */ #ifdef EMMC_NOT_USED_BOOT_PART ioctlParam[0] = ChipSel; ..... /* id blk data */ ret1 = SDM_Read(ChipSel, SD_CARD_BOOT_PART_OFFSET, 4, gIdDataBuf);//这里就是加载eMMC中id block数据的地方 #ifdef RK_SDCARD_BOOT_EN if (ChipSel == 0) { if (gIdDataBuf[0] == 0xFCDC8C3B) { gSdCardInfoTbl[ChipSel].FwPartOffset = SD_CARD_FW_PART_OFFSET; if (0 == gIdDataBuf[128 + 104 / 4]) { /* sd卡升级 */ gsdboot_mode = SDMMC_SDCARD_UPDATE; PRINT_E("SDCard Update.n"); } else if (1 == gIdDataBuf[128 + 104 / 4]) { /* sd 卡运行 */ gsdboot_mode = SDMMC_SDCARD_BOOT; PRINT_E("SDCard Boot.n"); } } else { ..... return ERROR; }
好的,从上面可以看出,gIdDataBuf
里是存在eMMC上某个地方的数据,通过SDM_Read
去读取加载的。
其实到这里,已经非常明确了,但是秉着一探到底的原则,我们继续往前~
看看SdmmcInit
是哪里调用的?
经过grep跟踪大法一顿操作,加上分析,发现SdmmcInit
是以方法结构体的方式存在于u-boot/board/rockchip/common/storage/storage.c
文件中,具体如下:
#ifdef RK_SDMMC_BOOT_EN static MEM_FUN_T emmcFunOp = { 2, BOOT_FROM_EMMC, 0, SdmmcInit, SdmmcReadID, SdmmcBootReadPBA, SdmmcBootWritePBA, SdmmcBootReadLBA, SdmmcBootWriteLBA, SdmmcBootErase, SdmmcReadFlashInfo, SdmmcCheckIdBlock, NULL, NULL, NULL, SdmmcGetCapacity, SdmmcSysDataLoad, SdmmcSysDataStore, SdmmcBootEraseData, }; #endif
然后又被包含在了一个结构体指针数组里:
static MEM_FUN_T *memFunTab[] = { #ifdef RK_UMS_BOOT_EN &UMSFunOp, #endif #ifdef RK_SDCARD_BOOT_EN &sd0FunOp, #endif #if defined(RK_SDMMC_BOOT_EN) || defined(RK_SDHCI_BOOT_EN) &emmcFunOp, #endif #ifdef RK_FLASH_BOOT_EN &NandFunOp, #endif #ifdef CONFIG_RK_NVME_BOOT_EN &nvmeFunOp, #endif };
最后被StorageInit调用:
#define MAX_MEM_DEV (sizeof(memFunTab)/sizeof(MEM_FUN_T *)) int32 StorageInit(void) { uint32 memdev; memset((uint8*)&g_FlashInfo, 0, sizeof(g_FlashInfo)); for(memdev=0; memdev<MAX_MEM_DEV; memdev++) { gpMemFun = memFunTab[memdev]; if(memFunTab[memdev]->Init(memFunTab[memdev]->id) == 0) { memFunTab[memdev]->Valid = 1; StorageReadFlashInfo((uint8*)&g_FlashInfo); vendor_storage_init(); return 0; } } /* if all media init error, usding null function */ gpMemFun = &nullFunOp; return -1; }
然后被在RK的板级逻辑u-boot/board/rockchip/rk32xx/rk32xx.c
中的board_storage_init
调用
int board_storage_init(void) { int ret = 0; if (StorageInit() == 0) { printf("storage init OK!n"); ret = 0; } else { printf("storage init fail!n"); ret = -1; } return ret; }
而board_storage_init
又在u-boot/arch/arm/lib/board.c
uboot启动阶段被调用:
/************************************************************************ * * This is the next part if the initialization sequence: we are now * running from RAM and have a "normal" C environment, i. e. global * data can be written, BSS has been cleared, the stack size in not * that critical any more, etc. * ************************************************************************ */ void board_init_r(gd_t *id, ulong dest_addr) { ulong malloc_start; #if !defined(CONFIG_SYS_NO_FLASH) ulong flash_size; #endif ..... #ifdef CONFIG_ROCKCHIP board_storage_init();//这里调用的 #endif ..... #ifdef CONFIG_BOARD_LATE_INIT board_late_init(); #endif ..... /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop(); } /* NOTREACHED - no way out of command loop except booting */ }
然后来到uboot最靠前的汇编s文件u-boot/arch/arm/lib/crt0.S
里,调用了board_init_r
这个C函数:
/* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ ldr r0, =__bss_start /* this is auto-relocated! */ ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ clbss_l:cmp r0, r1 /* while not at end of BSS */ strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l bl coloured_LED_init bl red_led_on /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */ ldr pc, =board_init_r /* this is auto-relocated! */ /* we should not return here. */ #endif ENDPROC(_main)
四,总结
uboot在启动时,从eMMC某块区域读取了一定字节大小的数据,根据芯片厂商定义的偏移地址取出一组sn号,然后再用这串sn号以“androidboot.serialno=”前缀设进cmdline参数里,在启动kernel时传入,然后kernel将收到的cmdline数据写入到/proc/cmdline
里,接着启动系统的第一个程序init程序,init程序从/proc/cmdline
读出对应的“androidboot.serialno“数据以“ro.boot.serialno”名字设置属性,然后drmservice用init程序设置的"ro.boot.serialno"属性来设置“ro.serialno,最后系统通过getprop ro.serialno
来获取,APP通过Build.getSerial()
,Build.SERIAL
来获取。
至此,大功告成
end
感谢阅读~
希望能帮到你~
see you~
码字不易,转载请注明原作者 ~ (from:https://erdong.work)