MTK】运行时间可切换配置机制
最编程
2024-05-05 17:32:32
...
- 编译阶段以某种方式将不同硬件的配置信息写入Image
- 配置信息一般包含Property列表/APK/...
-
具体流程请参考下一章节的介绍
- LK(little kernel)通过 eFuse/GPIO等硬件讯息获取当前应该是用哪套配置,并通过Command Line的方式传给init.
- 这个步骤需要客户参考 DCC3209223 并根据自己的硬件设计自行实现
-
单个Package的时候,无需此步骤,此步骤仅用于需要"切换"的场景。
- init process增加一段逻辑,加载当前配置对应的预先放在Image里的 Property 列表.
-
具体流程参考:/system/core/init/property_service.cpp 里的 LoadRscRoProps LoadRscRwProps
-
具体流程参考:/system/core/init/property_service.cpp 里的 LoadRscRoProps LoadRscRwProps
- PMS 增加一段逻辑,安装当前配置对应的预先放在Image里的 APK.
- 具体流程参考:/vendor/mediatek/proprietary/frameworks/base/services/core/java/com/mediatek/server/pm/PmsExtImpl.java 搜索 rsc 相关的code
3. 配置/编译流程:
RSC相关的配置文件,在Project中的位置如下图,主要是由Project的 RuntimeSwitchConfig.mk,以及各个RSC Package自己的RuntimeSwitch.mk组成。
大部分RSC的RuntimeSwitch.mk都使用了Include其他.mk的方式用来减少配置的工作量。
Project从MTK Release的时候,只有配置default一个RSC(其内容通常为空),此时您可以把它任意换成某个RSC,当不涉及到切换的时候,是可以直接使用的,它可以被视为一套优先级很高的配置,会覆盖掉device.mk的配置。而当您要配置2个及以上的RSC的时候,就需要如上节所述,自行客制化LK里面切换的Code了。
DeviceTree部分的配置会稍微复杂,大多数Feature并不需要配置它,如果有需求的时候,可以参考Single Image的文档. DCC3209223
当完成编译后,会在不同的Image的etc/rsc/目录下生成几套不同的配置包,如下图,
然后Init和PMS以及各个模块会根据您从LK传入的rsc name,来选择不同目录进行加载/安装。
4. 客制化示例:
如果需要切换Property/APK,一般情况只需要在LK实现这个Function即可,该函数是个 Weak function,被LK的platform.c调用。
#include <rsc.h> #include <mt_gpio.h> void rsc_init(void) { /* Please customize here base on your HW design, usually call driver function to get some HW info, like eFuse value or GPIO value, then decide to use which RSC names. Example: here we check a MTK HW GPIO as an example */ if (mt_get_gpio_in(GPIO155) == 0) { cmdline_append(RSC_CMDLINE"rsc01"); } else { cmdline_append(RSC_CMDLINE"default"); } }
如果需要切换Device Tree Overlay 还需要额外实现这个Weak Function。
/* Implement this function only if you need to switch between different device tree overlay */ int rsc_get_dtbo_index(void) { if (mt_get_gpio_in(GPIO155) == 0) { return 1; //Index 1 dtbo for Special HW, map to rsc01 } return 0; //Index 0 dtbo for default }
推荐阅读
-
MTK】运行时间可切换配置机制
-
【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 方法成对出现,以确保计数正确。