前言:

关于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. TKE 超级节点,Serverless 落地的最佳形态

    陈冰心,腾讯云产品经理,负责超级节点迭代与客户拓展,专注于 TKE Serverless 产品演进. 背景 让人又爱又恨的 Serverless Serverless 炙手可热,被称为云原生未来发展的 ...

  2. 社论 22.10.14 区间在线去重k小

    浅谈区间在线去重k小 关于讨论 https://www.luogu.com.cn/discuss/509205 本文将描述一种分块做法以及讨论中提出的各种 \(O(n \ \text{polylog} ...

  3. 【Java EE】Day02 MySQL概念、软件、语句

    〇.总结 1. 一.数据库的基本概念 1.概念 用于存储和管理数据的仓库 特点: 持久化存储,本质是文件系统 方便存储和管理数据 使用统一方式(MySQL)操作 常见的数据库软件: MySQL:Ora ...

  4. vba + ado +sql 连接数据库的常用操作方式

    vba + ado +sql 连接Access.MySQL.Oracle Private Sub Connection_DBA() '********************************* ...

  5. 如何使用Abstract类?抽象类的威力

    简介: 今天我想谈谈如何使用抽象类,以及抽象类真正的威力.本文将结合具体业务来说明如何使用抽象类. 业务简述: 本人目前只接触过PMS(物业管理系统),公司主要业务的是美国的租房业务.由于美国租房和中 ...

  6. css样式表,选择器,伪类选择器

    CSS定义 CSS:Cascading Style Sheet(层叠样式表) 选择器 { 属性名: 属性值; } CSS样式表 (1)三种样式表使用 ·1> 内联样式 <div style ...

  7. Django框架:13、csrf跨站请求伪造、auth认证模块及相关用法

    Django框架 目录 Django框架 一.csrf跨站请求伪造 1.简介 2.csrf校验策略 form表单csrf策略 ajax请求csrf策略 3.csrf相关装饰器 FBV添加装饰器方式 C ...

  8. [机器学习] 特征选择笔记4-使用SelectFromModel特征选择

    特征选择 代码下载 本文主要介绍sklearn中进行特征选择的方法. sklearn.feature_selection模块中的类可用于样本集的特征选择/降维,以提高估计量的准确性得分或提高其在超高维 ...

  9. OI是什么?

    从OI谈起 提到OI,也许很多人并不清楚这是怎么一回事.对于在学校就学习过数学.物理.化学和生物的同学们来说,"国际五项学科奥林匹克竞赛"中的这四门是相当熟悉了(相对OI来说).而 ...

  10. 将 Timer 对象化

    Timer这玩意儿很常用,却又很烦人.烦人之处有四: 1.         如果将其设到HWND上,则 a)         必须手工维护Timer的ID,小心翼翼地保证这些ID不重复,可能有人(比如 ...