4、内核启动流程:LiteOS-a与Linux内核的启动入口、初始化流程对比
好,咱们今天聊聊内核启动。说实话,很多做应用开发的兄弟,可能一辈子都碰不到这块。但如果你要做系统优化、做底层驱动,内核怎么起来的,这关必须过。
我当年刚接触鸿蒙时,第一反应就是:这玩意儿跟Linux到底啥关系?后来一查,发现LiteOS-a和Linux内核,虽然都是内核,但启动方式差别挺大。今天我就把这两个家伙的启动流程掰开揉碎,给你讲清楚。
4.1 启动入口:谁先被CPU执行?
先问个问题:CPU上电后,第一行代码在哪?
嗯,这得看架构。ARM Cortex-A系列,上电后从复位向量表开始。这个向量表,通常放在ROM里,或者由BootROM决定。
对于Linux内核,它的启动入口是 stext(ARM64下是 _text)。这个符号定义在 arch/arm/kernel/head.S 里。说白了,就是汇编代码,负责最基础的CPU初始化。
对于LiteOS-a,它的入口更直接——reset_vector。你可以在 arch/arm/armv7-a/src/startup.S 找到它。我刚开始看的时候,觉得这名字起得真直白,就是复位向量嘛。
核心区别:
- Linux:入口是
stext,需要Bootloader(如U-Boot)跳转过来 - LiteOS-a:入口是
reset_vector,可以直接从ROM跳转,也可以由Bootloader引导
为什么会这样?因为Linux内核假设自己运行在EL1(异常级别1),而LiteOS-a可以运行在EL1甚至EL3。我有个项目,把LiteOS-a直接跑在TrustZone里,入口就是 reset_vector,省掉了Bootloader那层。
4.2 初始化流程:从汇编到C语言
两个内核的初始化流程,大体上都分三步:
- 汇编阶段:关中断、设栈指针、清BSS段
- 过渡阶段:跳转到C语言入口
- C语言阶段:板级初始化、驱动加载、调度器启动
但细节上,差别很大。咱们一个一个看。
4.2.1 Linux内核的初始化流程
Linux的启动流程,我总结成一句话:从汇编到C,从单核到多核,从内核态到用户态。
具体来说:
stext:设置页表、使能MMU、跳转到start_kernelstart_kernel:这是C语言入口,在init/main.c里。它负责:- 打印内核版本信息
- 初始化中断、定时器、调度器
- 创建init进程(PID=1)
- 最后调用
rest_init,启动第一个用户态进程
我记得有一次调试,发现内核在 start_kernel 里卡住了。查了半天,结果是 setup_arch 里某个板级初始化函数没返回。嗯,这种问题最头疼,因为那时候连打印都还没完全好。
4.2.2 LiteOS-a的初始化流程
LiteOS-a就轻量多了。它的启动流程:
reset_vector:关中断、设栈、清BSS,然后跳转到mainmain:在kernel/base/init/main.c里。它做这几件事:- 初始化硬件(时钟、串口、中断控制器)
- 初始化内核组件(任务管理、内存管理、IPC)
- 创建第一个任务(通常是
OsMainTask) - 启动调度器,开始跑任务
我的经验:LiteOS-a从复位到第一个任务跑起来,大概只需要几百微秒。Linux嘛,至少几十毫秒。所以如果你做的是对启动时间敏感的设备,比如车载摄像头、工业控制器,LiteOS-a是更好的选择。
4.3 关键差异对比表
我整理了一张表,把核心差异列出来。你一看就明白:
| 对比项 | Linux内核 | LiteOS-a |
|---|---|---|
| 入口符号 | stext / _text |
reset_vector |
| 汇编文件位置 | arch/arm/kernel/head.S |
arch/arm/armv7-a/src/startup.S |
| C语言入口 | start_kernel |
main |
| MMU使能时机 | 汇编阶段就使能 | 可延迟到C语言阶段 |
| 多核启动 | 通过PSCI协议,从primary核唤醒secondary核 | 通常单核启动,多核支持较简单 |
| 启动到用户态 | 需要init进程、文件系统 | 直接跑任务,不需要文件系统 |
| 典型启动时间 | 几百毫秒到几秒 | 几百微秒到几毫秒 |
注意:LiteOS-a虽然启动快,但功能也少。它没有进程隔离、没有虚拟内存(默认)、没有复杂的文件系统。你想想看,如果你需要跑一个完整的Linux应用,那还是得上Linux内核。LiteOS-a更适合裸机风格的实时任务。
4.4 核心流程图:启动路径对比
下面这张SVG图,我画了两个内核的启动路径。左边是Linux,右边是LiteOS-a。你一眼就能看出,Linux的路径更长、更复杂。
4.5 避坑指南:我踩过的几个坑
最后,分享几个实战中容易出问题的地方。这些坑,我当年都踩过。
坑1:BSS段没清零
我曾经在移植LiteOS-a到新板子时,发现全局变量初始值不对。查了半天,原来是startup.S里忘了清BSS段。嗯,这个错误很低级,但很容易犯。特别是当你从别的项目复制启动代码时,一定要检查BSS清零那几行。
坑2:栈指针设置太小
Linux内核的栈通常8KB起步,LiteOS-a的任务栈可能只有1KB。我有个项目,任务栈设了512字节,结果一调用printf就栈溢出。你想想看,printf内部用了不少临时变量。所以,栈大小一定要留够余量。
坑3:中断向量表没重定位
这个坑比较隐蔽。有些芯片,复位后中断向量表默认在ROM里。如果你把内核加载到RAM里运行,但向量表没重定位到RAM,那中断一来就跳飞了。我调试这个问题花了整整两天,最后用JTAG单步才发现。
好了,关于LiteOS-a和Linux内核的启动流程对比,今天就聊到这儿。这两个内核,一个轻快、一个厚重,各有各的用武之地。你只要搞清楚它们的启动路径,后面做优化、做移植,心里就有底了。
公众号:蓝海资料掘金营,微信deep3321