高通安卓中的androidboot.mode参数及其对系统流程的控制原理
高通安卓androidboot.mode参数控制系统流程原理
参考:https://blog.****.net/guofeizhi/article/details/106644773
背景
在做出厂功能测试的时候,看到之前做开发时进入ffbm模式以后的cmdline中对应的字段为androidboot.mode=ffbm-01
;而现在项目中的cmdline对应的是androidboot.mode=ffbm-02
。而且界面上也不太一样,一种是C/C++实现的;一种是直接可以在界面上点击的QMMI。
现在我也找到了答案:
FFBM对应ffbm-00或ffbm-00
// system/core/fs_mgr/fs_mgr.cpp
int fs_mgr_mount_all(struct fstab *fstab, int mount_mode)
{
int i = 0;
int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
int error_count = 0;
int mret = -1;
int mount_errno = 0;
int attempted_idx = -1;
FsManagerAvbUniquePtr avb_handle(nullptr);
char propbuf[PROPERTY_VALUE_MAX];
bool is_ffbm = false;
if (!fstab) {
return FS_MGR_MNTALL_FAIL;
}
/**get boot mode*/
property_get("ro.bootmode", propbuf, "");
if ((strncmp(propbuf, "ffbm-00", 7) == 0) || (strncmp(propbuf, "ffbm-01", 7) == 0))
is_ffbm = true;
// ..
}
QMMI对应ffbm-02
// vendor/qcom/proprietary/commonsys/fastmmi/qmmi/src/com/qualcomm/qti/qmmi/framework/QmmiReceiver.java
public class QmmiReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String bootmode = Utils.getSystemProperties("ro.bootmode", "00");
LogUtils.logi("bootmode:" + bootmode);
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED) && bootmode.equals("ffbm-02")) {
LogUtils.logi("receive boot complete");
startMainActivity(context);
} else if (intent.getAction().equals("android.provider.Telephony.SECRET_CODE")) {
LogUtils.logi("receive SECRET_CODE ");
startMainActivity(context);
}
}
// ...
}
流程解析
BootLoader针对ffbm的处理
以uefi为例。
判断进入FFBM模式
路径:bootable/bootloader/edk2/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c
EFI_STATUS EFIAPI __attribute__ ( (no_sanitize ("safe-stack")))
LinuxLoaderEntry (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
// ...
Status = GetKeyPress (&KeyPressed);
DEBUG ((EFI_D_ERROR, "reading key status: %r \n", Status));
DEBUG ((EFI_D_ERROR, "reading key status: %d \n", KeyPressed));
if (Status == EFI_SUCCESS) {
if (KeyPressed == SCAN_DOWN ||KeyPressed == SCAN_DELETE)
BootIntoFastboot = TRUE;
if (KeyPressed == SCAN_UP)
BootIntoRecovery = TRUE;
if (KeyPressed == SCAN_ESC)
RebootDevice (EMERGENCY_DLOAD);
if (KeyPressed == SCAN_HOME) //POWER+VOL UP will generate SCAN_HOME, detect this key, it will enter ffbm mode
{
DEBUG ((EFI_D_ERROR, "go to ffbm mode\n"));
SetFFBMCommand(); // 设置 androidboot.mode=ffbm-xxx
}
} else if (Status == EFI_DEVICE_ERROR) {
DEBUG ((EFI_D_ERROR, "Error reading key status: %r\n", Status));
goto stack_guard_update_default;
}
// ...
}
在cmdline中传递启动模式
BootLoader把对应的启动模式通过 command line
的方式传送给内核。
在uefi中解释这个过程需要一定的UEFI基础,因此不详细展开,但是实际上基于的就是检查SetFFBMCommand
的调用。
其中涉及到了针对分区的写操作。
// bootable/bootloader/edk2/QcomModulePkg/Library/BootLib/Recovery.c
EFI_STATUS SetFFBMCommand(VOID)
{
EFI_STATUS Status = EFI_SUCCESS;
CHAR8 FfbmPageBuffer[FFBM_MODE_BUF_SIZE] = "";
EFI_GUID Ptype = gEfiMiscPartitionGuid; // 杂项设备分区
MemCardType CardType = UNKNOWN;
CardType = CheckRootDeviceType ();
if (CardType == NAND) {
Status = GetNandMiscPartiGuid (&Ptype);
if (Status != EFI_SUCCESS) {
return Status;
}
}
AsciiSPrint (FfbmPageBuffer, sizeof (FfbmPageBuffer), "ffbm-02");
WriteToPartition (&Ptype, FfbmPageBuffer, sizeof (FfbmPageBuffer));
return Status;
}
内核
路径:system/core/init/init.cpp
内核解析cmdline,并设置对应的系统属性。
int main(int argc, char** argv) {
// ...
// 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(); // 解析命令行
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
// ...
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// ...
}
解析command line
路径:
system/core/init/util.cpp
system/core/init/init.cpp
// system/core/init/util.cpp
void import_kernel_cmdline(bool in_qemu,
const 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);
}
}
}
// system/core/init/init.cpp
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("ro.kernel." + key, value);
return;
}
// 实际上的cmdline : androidboot.mode=ffbm-02
if (key == "qemu") {
strlcpy(qemu, value.c_str(), sizeof(qemu));
} else if (android::base::StartsWith(key, "androidboot.")) {
// 写配置,相当于:ro.boot.mode = ffbm-02
property_set("ro.boot." + key.substr(12), value);
}
}
// system/core/init/init.cpp
static void process_kernel_cmdline() {
// 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);
}
设置对应的系统属性
将解析cmdline写的配置导出(实际上就是换个名字)。
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", "", },
{ "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", },
};
for (size_t i = 0; i < arraysize(prop_map); i++) {
std::string value = GetProperty(prop_map[i].src_prop, "");
property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
}
}
最终:
[ro.boot.mode]: [ffbm-00]
[ro.bootmode]: [ffbm-00]
此后,关于启动模式的处理都以ro.bootmode
为准。
安卓系统启动相关进程
当ro.bootmode
设置了以后,对应的rc解析就会根据这些内容进行处理。
路径:device/qcom/common/rootdir/etc/init.qcom.factory.rc
on property:vendor.sys.boot_mode=ffbm
write ${persist.vendor.mmi.misc_dev_path} "ffbm-01"
on property:vendor.sys.boot_mode=qmmi
write ${persist.vendor.mmi.misc_dev_path} "ffbm-02"
on property:vendor.sys.boot_mode=normal
write ${persist.vendor.mmi.misc_dev_path} "normal"
# Creating a scratch storage on /data for factory testing.
on factory-fs && property:ro.bootmode=ffbm-00
mount tmpfs tmpfs /data
on factory-fs && property:ro.bootmode=ffbm-01
mount tmpfs tmpfs /data
# aligned the usb port with system standard, otherwise if only diag be added
# Then in QMMI mode, the whole Andoid be booted, but due to the ro.bootmode is
# not normal/unknow, then when it apply the default funcs, it will turn to MTP
# which cause the diag/Wwan/modem port all be lost in qmmi mode. Details:
# UsbDeviceManager.java---->getDefaultFunctions and trySetEnabledFunctions
on property:persist.vendor.usb.config=*
setprop persist.sys.usb.ffbm-02.func ${persist.vendor.usb.config}
on mmi && property:ro.bootmode=ffbm-00
# ========================================================
# This is FFBM only settings.
# ========================================================
#mkdir for factory data files.
mkdir /mnt/vendor/persist/FTM_AP 0750 system system
start fastmmi
# start qcom-post-boot to set the misc partition path property value
start qcom-post-boot
start mmi_diag
on mmi && property:ro.bootmode=ffbm-01
# ========================================================
# This is FFBM only settings.
# ========================================================
#mkdir for factory data files.
mkdir /mnt/vendor/persist/FTM_AP 0750 system system
start fastmmi
## start qcom-post-boot to set the misc partition path property value
start qcom-post-boot
start mmi_diag
on property:persist.vendor.usb.config=* && property:ro.bootmode=ffbm-00
setprop sys.usb.config ${persist.vendor.usb.config}
on property:persist.vendor.usb.config=* && property:ro.bootmode=ffbm-01
setprop sys.usb.config ${persist.vendor.usb.config}
on property:persist.vendor.usb.config=* && property:ro.bootmode=ffbm-02
setprop sys.usb.config ${persist.vendor.usb.config}
#### 做最小系统的启动
on ffbm
trigger early-fs
trigger factory-fs
trigger fs
trigger post-fs
# Mount fstab in init.{$device}.rc by mount_all with '--late' parameter
# to only mount entries with 'latemount'. This is needed if '--early' is
# specified in the previous mount_all command on the fs stage.
# With /system mounted and properties form /system + /factory available,
# some services can be started.
trigger late-fs
# Now we can mount /data. File encryption requires keymaster to decrypt
# /data, which in turn can only be loaded when system properties are present.
trigger post-fs-data
# Now we can start zygote for devices with file based encryption
trigger zygote-start
# Load persist properties and override properties (if enabled) from /data.
trigger load_persist_props_action
# Remove a file to wake up anything waiting for firmware.
trigger firmware_mounts_complete
trigger early-boot
trigger boot
trigger mmi
原文地址:https://www.cnblogs.com/schips/p/how_qualcomm_android_run_ffbm_procedure.html
推荐阅读
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。
-
高通安卓中的androidboot.mode参数及其对系统流程的控制原理