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语言

两个内核的初始化流程,大体上都分三步:

  1. 汇编阶段:关中断、设栈指针、清BSS段
  2. 过渡阶段:跳转到C语言入口
  3. C语言阶段:板级初始化、驱动加载、调度器启动

但细节上,差别很大。咱们一个一个看。

4.2.1 Linux内核的初始化流程

Linux的启动流程,我总结成一句话:从汇编到C,从单核到多核,从内核态到用户态

具体来说:

  • stext:设置页表、使能MMU、跳转到 start_kernel
  • start_kernel:这是C语言入口,在 init/main.c 里。它负责:
    • 打印内核版本信息
    • 初始化中断、定时器、调度器
    • 创建init进程(PID=1)
  • 最后调用 rest_init,启动第一个用户态进程

我记得有一次调试,发现内核在 start_kernel 里卡住了。查了半天,结果是 setup_arch 里某个板级初始化函数没返回。嗯,这种问题最头疼,因为那时候连打印都还没完全好。

4.2.2 LiteOS-a的初始化流程

LiteOS-a就轻量多了。它的启动流程:

  • reset_vector:关中断、设栈、清BSS,然后跳转到 main
  • main:在 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的路径更长、更复杂。

内核启动流程对比:Linux vs LiteOS-a Linux 内核 Bootloader (U-Boot) stext (汇编入口) 使能MMU、设置页表 start_kernel (C入口) 初始化子系统 创建init进程 用户态启动完成 LiteOS-a 内核 ROM / BootROM reset_vector (汇编入口) 关中断、设栈、清BSS main (C入口) 硬件初始化 创建第一个任务 任务调度开始 入口差异 C入口差异 启动终点差异

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