作为一名长期从事嵌入式系统开发的工程师,今天我想和大家深入探讨OpenHarmony标准系统在RK3568开发板上的内核启动全流程。这个启动过程涉及从Linux内核初始化到OpenHarmony用户态服务的完整链路,是理解整个系统运作机制的关键。
所有Linux系内核的启动都始于start_kernel()函数,这个位于init/main.c的函数是内核初始化的总控中心。在OpenHarmony的适配中,这个函数经过了一些定制化修改:
c复制asmlinkage __visible void __init __no_sanitize_address start_kernel(void)
{
//...各种子系统初始化...
arch_call_rest_init(); //系统初始化和服务启动
}
这里的关键在于arch_call_rest_init()的调用,它最终会触发rest_init()函数。这个设计采用了弱符号(weak symbol)机制,允许不同架构进行定制化实现,体现了Linux内核良好的扩展性设计。
经验提示:在实际调试时,可以在start_kernel()开始处添加early_printk()输出,这对早期启动问题的定位非常有帮助。
rest_init()是内核初始化阶段的"临门一脚",它完成了三个重要使命:
c复制noinline void __ref rest_init(void)
{
// 1. 创建kernel_init进程(PID=1)
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
// 2. 创建kthreadd进程(PID=2)
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
// 3. 启动空闲任务
cpu_startup_entry(CPUHP_ONLINE);
}
这里有几个关键点需要注意:
在实际开发中,我曾遇到过因为kthreadd创建失败导致整个系统挂起的情况。通过添加rcu_read_lock()保护进程查找操作,可以有效避免这类竞态条件问题。
kernel_init函数是连接内核与用户态的关键桥梁,它的主要执行逻辑如下:
c复制static int __ref kernel_init(void *unused)
{
// 尝试多种init程序路径
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
}
if (execute_command) {
ret = run_init_process(execute_command);
}
// 默认尝试路径
try_to_run_init_process("/sbin/init");
try_to_run_init_process("/bin/init");
try_to_run_init_process("/bin/sh");
panic("No working init found");
}
在OpenHarmony的实现中,ramdisk_execute_command被定义为"/init",指向ramdisk中的init程序。这个设计保证了即使根文件系统尚未挂载,系统也能完成基础初始化。
OpenHarmony的ramdisk镜像中包含一个特殊的init程序链接:
code复制lrwxrwxrwx 1 root root 14 Jan 3 21:26 init -> bin/init_early*
这个init_early程序是OpenHarmony用户态初始化的第一阶段,其主函数非常简单:
c复制int main(int argc, char * const argv[])
{
long long upTimeInMicroSecs = GetUptimeInMicroSeconds(NULL);
SystemPrepare(upTimeInMicroSecs); //此时依然拥有root权限
return 0;
}
SystemPrepare()函数完成了几个关键操作:
调试技巧:在这个阶段可以通过修改init_early的日志级别(INIT_INFO -> INIT_DEBUG)获取更详细的启动信息。
MountRequiredPartitions()函数负责挂载系统运行必需的分区,其核心逻辑包括:
RK3568开发板的cmdline配置示例如下:
code复制append earlycon=uart8250,mmio32,0xfe660000 root=PARTUUID=614e0000-0000-4b53-8000-1d28000054a9 rw rootwait rootfstype=ext4
这个配置指定了:
StartSecondStageInit()函数完成了从ramdisk到真实文件系统的切换:
c复制void StartSecondStageInit(long long upTimeInMicroSecs)
{
// 1. 卸载ramdisk中的非必要挂载点
// 2. 挂载新的根文件系统
// 3. 执行switch_root操作
// 4. 启动/bin/init程序
}
这个切换过程需要特别注意:
OpenHarmony的主init程序通过GN构建系统配置,关键配置如下:
gn复制ohos_executable("init") {
sources = [
"../standard/init.c",
"../standard/init_service.c",
//...其他源文件...
]
install_images = ["system", "updater"]
}
这个配置表明init程序会被安装到system和updater两个镜像中,确保系统在各种模式下都能正常启动。
init程序的主函数采用了清晰的阶段划分:
c复制int main(int argc, char * const argv[])
{
SystemInit(); // 系统初始化
SystemExecuteRcs(); // 执行rc脚本
SystemConfig(uptime); // 系统配置
SystemRun(); // 系统运行
return 0;
}
这种分阶段的设计使得系统初始化过程更加模块化,便于维护和调试。
在实际开发中,启动过程可能会在以下几个阶段卡住:
OpenHarmony提供了多级日志系统:
建议的日志级别设置:
c复制EnableInitLog(INIT_DEBUG); // 开发阶段使用DEBUG级别
通过分析启动时间戳,可以精确找出性能瓶颈:
c复制long long GetUptimeInMicroSeconds(void); // 获取精确启动时间
Linux内核使用task_struct管理进程,在init过程中涉及的关键字段包括:
c复制struct task_struct {
volatile long state; // 进程状态
pid_t pid; // 进程ID
struct files_struct *files; // 打开文件表
//...
};
理解这个结构体对调试进程创建问题很有帮助。
内核与用户态init程序通过多种方式传递参数:
在RK3568平台上,主要使用设备树和命令行参数结合的方式。
OpenHarmony的启动过程包含多重安全措施:
这些机制共同保障了系统的安全启动和运行。
如果需要将OpenHarmony移植到其他平台,需要重点关注:
建议的移植步骤:
在RK3568上的实践经验表明,存储设备的初始化时序是需要特别注意的点。