• 安卓ro.serialno产生的整个流程


    前言:

    关于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 pieces = android::base::Split(entry, "=");
            if (pieces.size() == 2) {
                fn(pieces[0], pieces[1], in_qemu);
            }
        }
    }

    这里小小的总结下:

    从上面的步骤跟踪下来,发现整体流程是将从boot传给kernelcmdline中的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文件

    好的,开始分析源码:

    从源码可得知,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的,也就是eMMCSD卡的地方,好的继续看函数。

    好的,从上面可以看出,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	{
    		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.cuboot启动阶段被调用:

    
    /************************************************************************
     *
     * 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”名字设置属性,然后drmserviceinit程序设置的"ro.boot.serialno"属性来设置“ro.serialno,最后系统通过getprop ro.serialno来获取,APP通过Build.getSerial()Build.SERIAL来获取。

    至此,大功告成

    end

    感谢阅读~

    希望能帮到你~

    see you~

    码字不易,转载请注明原作者 ~ (from:https://erdong.work


    __EOF__

  • 本文作者: 耳东Sir
  • 本文链接: https://www.cnblogs.com/erdongsir/p/17152900.html
  • 关于博主: I am a good person
  • 版权声明: 欢迎转载,但转载请注明来源~
  • 声援博主: up这个重庆崽儿还得行~
  • 相关阅读:
    1030 Travel Plan(Dijksta +DFS)
    xml特殊字符问题
    【2022】【论文笔记】基于激光直写氧化石墨烯纸的超薄THz偏转——
    [JavaScript]_[初级]_[不使用JQuery原生Ajax提交表单文件并监听进度]
    Day48——前端知识CSS
    2022“杭电杯” 中国大学生算法设计超级联赛(8)5 7题解
    95---Python 直角坐标系下绘制双曲螺旋线
    MVCC 底层实现原理
    如何纯注解整合Spring SpringMVC Mybatis
    通过图片id请求后端服务,将图片渲染到页面,渲染不成功?
  • 原文地址:https://www.cnblogs.com/erdongsir/p/17152900.html