ro.boot.serialno 实现流程全链路解析
ro.boot.serialno 的传递贯穿设备启动的多个层级,其核心流程可以概括为以下步骤:
Bootloader阶段 Kernel内核参数 Android Init进程 导入内核参数 属性系统初始化 属性映射 ro.serialno属性 设备特定服务 设置sys.serialno 触发属性变更
下面我们来详细看看每个阶段的关键实现。
1. Bootloader阶段:序列号起源
Bootloader(通常是LK,Little Kernel)负责获取或定义初始序列号。
-
关键代码路径:
vendor/mediatek/proprietary/bootable/bootloader/lk/app/mt_boot/mt_boot.c vendor/mediatek/proprietary/bootable/bootloader/lk/platform/mtXXXX/mtXXXX.c -
序列号来源(MTK平台可能的方式):
- 从特定存储位置读取:可能是EFUSE、PROINFO分区或安全存储
- 由制造商烧录工具写入:量产时通过工具写入独立分区
- 根据硬件信息生成:基于Wi-Fi MAC地址等硬件标识符派生
-
关键函数调用(具体函数名因平台而异):
c// 将序列号添加到内核命令行参数 snprintf(cmdline + strlen(cmdline), sizeof(cmdline) - strlen(cmdline), " androidboot.serialno=%s", serial_number);
2. Kernel阶段:参数接收
内核接收Bootloader传递的参数,但不直接处理 androidboot.serialno,只是将其保存在内核命令行中。
-
验证命令 :
bashadb shell cat /proc/cmdline | grep -o "androidboot.serialno=[^ ]*"预期输出示例:
androidboot.serialno=ABCD12345678
3. Android Init进程:属性转换
这是最关键的转换阶段,系统属性在此初始化。
-
核心代码文件:
system/core/init/init.cpp system/core/init/property_service.cpp system/core/init/util.cpp -
process_kernel_cmdline() - 处理内核命令行:
cpp// system/core/init/init.cpp int main(int argc, char** argv) { property_init(); // 初始化属性系统 process_kernel_cmdline(); // 处理内核命令行参数 // ... } // system/core/init/util.cpp static void import_kernel_cmdline(bool in_qemu, const std::function<void(const std::string&, const std::string&, bool)>& fn) { // 读取 /proc/cmdline 并解析,回调 fn 处理每个 key=value 对 } static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) { if (key == "androidboot.serialno") { property_set("ro.boot.serialno", value); // 关键转换! } // 处理其他参数... } -
export_kernel_boot_props() - 属性映射:
cpp// system/core/init/init.cpp static void export_kernel_boot_props() { struct { const char *src_prop; const char *dst_prop; const char *default_value; } prop_map[] = { { "ro.boot.serialno", "ro.serialno", "" }, // 序列号映射 // ... 其他属性映射 }; for (size_t i = 0; i < arraysize(prop_map); i++) { std::string value = GetProperty(prop_map[i].src_prop, ""); // 将 ro.boot.serialno 映射到 ro.serialno property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value); } }
4. 属性系统:只读特性
ro 属性(只读)的特殊处理机制:
- 代码路径 :
bionic/libc/system_properties/prop_area.cpp - ro属性规则 :
- 系统启动时只能设置一次
- 通过
__system_property_find检查是否已存在,存在则不可修改 - Init进程在早期拥有设置ro属性的权限
5. 应用层获取
应用通过系统API获取序列号:
-
Java层代码:
java// frameworks/base/core/java/android/os/Build.java public static final String SERIAL = getString("ro.serialno"); // vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/deviceinfo/hardwareinfo/SerialNumberPreferenceController.java String serial = Build.SERIAL; // 实际读取的是 ro.serialno -
Native层获取:
c#include <sys/system_properties.h> char serial_buf[PROP_VALUE_MAX]; __system_property_get("ro.serialno", serial_buf);
🔧 代码跟踪与Log打印方法
关键日志点
要在代码跟踪时看到相关日志,你可以在以下关键位置添加或查看日志:
| 阶段 | 日志位置/方法 | 关键日志内容 |
|---|---|---|
| Bootloader | LK代码中添加日志或查看Uboot日志 | "serialno: ABC123", "cmdline: ... androidboot.serialno=..." |
| Kernel | `adb shell dmesg | grep serialno` |
| Init进程 | `adb logcat | grep -iE "(init | property)"` |
| 属性系统 | `adb logcat | grep -i property` |
MTK平台专用日志方法
MTK提供了完整的日志抓取系统,可以获取从开机开始的完整日志:
-
进入工程模式:
- 拨号盘输入
*#*#3646633#*#*或*#*#38777#*#* - 或者使用
*20121220#
- 拨号盘输入
-
开启MTKLogger:
bash# 开启所有日志级别 adb shell setprop persist.log.tag.all LOGV # 或者通过工程模式界面开启MobileLog自动记录 -
获取完整启动日志:
- 日志路径:
/sdcard/mtklog/mobilelog/APLog_XXXX_XXXX/ - 关键文件 :
kernel_log.boot- 内核启动日志main_log.boot- Android系统启动日志properties- 系统属性信息
- 日志路径:
实用调试命令
bash
# 查看当前序列号相关属性
adb shell getprop | grep serialno
# 检查内核命令行
adb shell cat /proc/cmdline
# 监控属性变化
adb shell watch getprop | grep serialno
# 获取MTK日志文件
adb pull /sdcard/mtklog/ ./mtklog/
⚠️ 常见问题与调试技巧
-
序列号为空或unknown
- 检查bootloader是否正确传递了参数
- 确认persist分区或PROINFO分区是否有有效序列号
-
属性设置失败
- 确认只在系统启动早期设置ro属性
- 检查SELinux策略是否允许属性设置
-
MTK平台特有问题
- 某些MTK平台可能使用
ro.boot.sn而非ro.boot.serialno - 检查
vendor/mediatek/proprietary下的厂商定制代码
- 某些MTK平台可能使用
通过以上完整的代码跟踪方案和日志打印方法,你应该能够深入理解MTK平台上 ro.boot.serialno 的完整实现流程。在实际调试中,建议结合MTKLogger获取完整启动日志,这样可以清晰地看到序列号从bootloader到应用层的完整传递过程。