

安卓ro.serialno产生的整个流程 - 耳东Sir
source link: https://www.cnblogs.com/erdongsir/p/17152900.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

关于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 OK\n"); property_set("ro.boot.mode", "emmc"); break; } else if ((s3 > 0) && (s4 > 0)) { ERROR("OK,NVME DRIVERS INIT OK\n"); property_set("ro.boot.mode", "nvme"); break; } else { ERROR("OK,EMMC DRIVERS NOT READY, RERRY=%d\n", 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_init\n"); .... 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 preboot\n"); 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] = '\0'; PRINT_E("sn: %s\n", 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
来获取。
至此,大功告成
感谢阅读~
希望能帮到你~
see you~
码字不易,转载请注明原作者 ~ (from:https://erdong.work)
__EOF__
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK