内核移植及分析

去哪下载源码

移植Linux内核也分为2种境界:

  • 1.芯片原厂为自己的demo板修改Linux内核的官方源码;
  • 2.自制开发板时,为自己的板子修改原厂demo板或者别的参考开发板的Linux内核源码

我们如果不是在芯片原厂上班,那么就不需要去Linux内核官方仓库下载源码,直接找到自己使用的芯片厂商所提供的Linux内核就行了,后续我们的移植都是对其demo板子的Linux内核源码的修改

移植流程

Linux内核的移植不像uboot的移植一样需要改很多源码,直接把原厂的Linux内核编译了操作系统就能直接跑了。但是此时大多数硬件设备是没法用的,因为自己的开发板的硬件原理图和原厂的demo板的不同,所以自己一般要针对板子修改设备树,编写驱动,这也是驱动开发工程师做的工作,也就是说驱动开发本质上就是Linux内核开发!

移植通常包括以下步骤:

image-20240914171217353

1.在arch/arm/configs目录下添加自己的开发板的配置文件imx_myboard_defconfig

2.在arch/arm/boot/dts目录下添加自己开发板的设备树文件imx6ull-myboard.dts

3.修改设备树的Makefilearch/arm/boot/dts/Makefile,加入自己的设备树文件

具体流程参考:

i.MX6ULL嵌入式Linux开发3-Kernel移植 - 知乎 (zhihu.com)

内核启动流程

1.硬件初始化(体系结构相关)

  • 执行主体:arch/arm64/kernel/head.S中的stext,它是由内核链接脚本指定的内核入口,是一段汇编代码
  • 功能:
    • 保存启动参数:解析Bootloader(如U-Boot)传递的参数(如设备树地址、命令行参数cmdline)。
    • 底层硬件初始化:
      • 关闭中断,设置CPU为安全状态(如ARM的SVC模式)。
      • 初始化MMU(设置页表、启用虚拟内存)。
      • 配置缓存和TLB(部分架构)。
    • 设备树处理:定位设备树(DTB),初步解析内存布局和关键外设(如串口用于早期打印)。
    • 跳转到C环境:最终调用start_kernel()(位于init/main.c),进入通用初始化阶段。

2.通用内核初始化

  • 执行主体:init/main.c中的start_kernel()函数,里面一共调用了数十个函数进行初始化
  • 功能:
    • 初始化关键子系统:
      • 内存管理(mm_init()):建立物理内存映射,初始化伙伴系统
      • 调度器(sched_init()):初始化进程调度(如CFS)
      • 中断/异常(trap_init()irq_init()):设置中断向量表(如ARM的vector_swi
      • 定时器(time_init()):初始化系统时钟(如ARM的PIT或HPET)
    • 驱动模型与设备树:
      • driver_init():初始化设备模型(sysfsdevtmpfs)。
      • 扫描设备树节点,匹配并加载驱动(如串口驱动用于控制台输出)
    • 用户空间启动:
      • rest_init():创建内核线程kernel_init(PID 1),最终执行用户空间的init程序。
      • 若使用initramfs,会先解压并挂载为临时根文件系统。

精简回答

嵌入式Linux内核启动分为两部分:

  1. 汇编阶段(head.S):初始化CPU/MMU,解析设备树,跳转到start_kernel()
  2. C语言阶段(start_kernel()):初始化内存、调度、中断等核心子系统,加载驱动,最后通过rest_init()启动用户空间的init进程