前言:

关于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传给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文件

好的,开始分析源码:

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


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.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

安卓ro.serialno产生的整个流程的更多相关文章

  1. 安卓逆向基础(001)-APK安装流程

    1.在/data/app下以报名为文件夹名新建文件夹 APK包存放在这里 以及lib文件 存放so 2./data/dalvik-cache 存放dex dex是dalvik虚拟机可执行文件 3./d ...

  2. android 启动流程 相关 杂项记录

    Android原生流程 Init进程 主要流程及分支梳理 ueventd_main()watchdogd_main()主要流程a) 公共部分 增加PATH 环境变量初始化内核日志,打开/dev/kms ...

  3. SmokeTest测试流程

    没办法了,本来是表格,但是粘贴不过来 测试目的: 用于检测该版本在基本的应用场景下,基本的功能是否满足. 测试前提: 发货版本 示例:ATV9冒烟测试测试项解读 表格获取:Google ATV hel ...

  4. 【Unity】微信支付SDK官方安卓Demo的使用问题

    Unity3d使用微信支付是属于APP内发起支付调用的情况,其本质上是在安卓项目上使用微信SDK,安卓项目开发完成后再导入到Unity中作为Unity插件使用,即Unity中C#调用安卓(Java)代 ...

  5. 安卓自定义View教程目录

    基础篇 安卓自定义View基础 - 坐标系 安卓自定义View基础 - 角度弧度 安卓自定义View基础 - 颜色 进阶篇 安卓自定义View进阶 - 分类和流程 安卓自定义View进阶 - Canv ...

  6. android 启动流程 相关2 init进程 属性服务

    Init属性服务 系统属性服务 属性梳理 来源和读取时机 来源:内核参数 ro.kernel.*   代表有qemu内核参数才会设置(在虚拟机中) ro.boot.*     1.内核设备树相关的设备 ...

  7. 【安卓开发】一个简单快递查询APP实例的实现摘要

    前言 做毕业设计涉及到安卓开发,决定好好学习安卓开发.在正式做毕业设计之前,有必要先设计和完成一个与毕业设计最终成果相关的demo或者说样例APP.最终毕业设计需要实现的功能包括通过调用PHP端API ...

  8. 【CTS】几个serialno失败项

    [问题结论] [Common]SN配置项的问题,只可以'数字与大小写字母' 将配置SN改为字母数字组合,测试全部pass [问题描述] CTS三条失败项 run cts -m CtsTelephony ...

  9. 第3章2节《MonkeyRunner源码剖析》脚本编写示例: MonkeyDevice API使用示例(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...

  10. 【转载】linux内核启动android文件系统过程分析

    主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源代码目录介绍Makefile (全局的Makefile)bioni ...

随机推荐

  1. MathNet用到的一些功能

    1.计算一元线性函数 Tuple<double, double> myLineTuple = MathNet.Numerics.Fit.Line(myXArray, myYArray); ...

  2. 防御式编程之断言assert的使用

    防御式编程的重点就是需要防御一些程序未曾预料的错误,这是一种提高软件质量的辅助性方法,断言assert就用于防御式编程,编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设.使用断言是 ...

  3. 【每日一题】【链表.next.next判空条件】141. 环形链表/NC4 判断链表中是否有环-211120/220123

    给你一个链表的头节点 head ,判断链表中是否有环. 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环. 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链 ...

  4. 深刻理解JAVA并发中的有序性问题和解决之道

    欢迎关注专栏[JAVA并发] 更多技术干活尽在个人公众号--JAVA旭阳 问题 Java并发情况下总是会遇到各种意向不到的问题,比如下面的代码: int num = 0; boolean ready ...

  5. Pycharm介绍下载指南

    Pycharm的基本介绍 PyCharm是一种Python IDE是可以帮助用户在使用Python语言开发时提高其效率的开发工具. Pycharm的官网:https://www.jetbrains.c ...

  6. 持续发烧,聊聊Dart语言的并发处理,能挑战Go不?

    前言 貌似关于Dart的文章没流量啊,就算在小编关怀上了首页,看得人还是很少的. 算了,今天持续发烧,再来写写如何使用 Dart 语言的并发操作.说起并发操作,玩 Go 的同学该笑了,这就是我们的看家 ...

  7. MySQL约束条件(主键-自增-默认值)

    目录 一:MySQL约束条件 1.什么是约束条件? 二:unsigned(去除正负号) 三:zerofill(不够位数零填充) 四:not null(非空) 1.使用约束条件(不添加会报错) 五:de ...

  8. Jmeter 之 jp@gc - Stepping Thread Group

    jp@gc - Stepping Thread Group 自定义线程组,根据业务需要进行设计用户增加间隔时间等 1.  下载jmeter-plugins-manager-1.3.jar插件放入lib ...

  9. 【爬虫+数据分析+数据可视化】python数据分析全流程《2021胡润百富榜》榜单数据!

    目录 一.爬虫 1.1 爬取目标 1.2 分析页面 1.3 爬虫代码 1.4 结果数据 二.数据分析 2.1 导入库 2.2 数据概况 2.3 可视化分析 2.3.1 财富分布 2.3.2 年龄分布 ...

  10. [随笔所想] UBC学习生活经验分享

    当时受到了很多人的帮助,在网上也查到了很多经验帖子,比如如何办理签证,如何填写表格,要准备哪些材料以及生活上要带哪些物品,等等.当时就想到等我办理好这些,也一定和大家分享,为更多的人提供一些参考. 1 ...