第四章:嵌入式Linux内核编译——获取源码、配置与烧录

好,到了这一章,我们终于要跟内核源码打交道了。说实话,很多初学者觉得编译内核是个很玄乎的事情,其实说白了,就是三步:拿到源码、告诉内核你要什么、然后编译成能跑的东西。我在带团队的时候,经常跟新人说,内核编译就像做饭——你得先有菜(源码),然后决定口味(配置),最后开火(编译)。

4.1 获取内核源码

获取源码的方式有好几种,我个人的习惯是直接从kernel.org下载主线版本。但做嵌入式开发,你最好用芯片厂商提供的BSP内核。为什么?因为主线内核可能没有你板子的驱动支持。

常用的获取方式:

  • 官网下载:从 https://www.kernel.org 获取稳定版本
  • 芯片厂商BSP:比如NXP、Rockchip、Allwinner提供的定制内核
  • Git仓库克隆git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

我的经验:如果你用的是开发板,比如树莓派或BeagleBone,直接去官方GitHub拉取他们的内核分支。我踩过坑——用主线内核去编译BeagleBone的驱动,结果GPIO死活点不亮灯,折腾了两天才发现是设备树不匹配。

下载完成后,解压或者进入你的内核目录:

tar -xvf linux-5.10.100.tar.xz
cd linux-5.10.100

4.2 配置内核选项

配置内核,说白了就是告诉内核:你要支持哪些硬件、哪些文件系统、哪些网络协议。嗯,这里要注意,配置不对,编译出来的内核要么太大,要么缺驱动。

常用的配置命令:

  • make menuconfig —— 基于ncurses的图形界面,我最常用
  • make defconfig —— 使用默认配置,适合快速验证
  • make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig —— 交叉编译时指定架构

举个例子,你要添加摄像头驱动支持(V4L2),在menuconfig里这样操作:

  1. 进入 Device Drivers
  2. 选择 Multimedia support
  3. 勾选 Video for LinuxV4L2 sub-device userspace API
  4. Media drivers 里选择你的摄像头芯片型号

关键点:配置时,Y 表示编译进内核,M 表示编译成模块(.ko文件),N 表示不编译。我建议把不常用的驱动编成模块,这样内核体积小,启动快。

我曾经犯过一个低级错误:把USB摄像头驱动编成了模块,但文件系统里没有加载模块的脚本,结果摄像头插上去完全没反应。后来我改成编译进内核,问题就解决了。

4.3 编译内核

配置完成后,就是编译了。交叉编译的命令格式:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules

这里解释一下:

  • ARCH=arm 指定目标架构
  • CROSS_COMPILE=arm-linux-gnueabihf- 指定交叉编译工具链前缀
  • -j4 表示用4个线程并行编译,加快速度
  • zImage 是压缩后的内核镜像
  • modules 编译所有标记为M的驱动模块

编译完成后,你会得到这些文件:

文件 说明
arch/arm/boot/zImage 内核镜像
arch/arm/boot/dts/你的板子.dtb 设备树文件
drivers/.../*.ko 内核模块

注意:编译时间取决于你的机器性能。我有一台老笔记本,编译一次要40分钟。后来我换了台i7的机器,8分钟搞定。如果你用虚拟机,记得多分点CPU和内存。

4.4 烧录到开发板

编译好了,怎么把内核放到板子上?常见的方式有几种:

  • TFTP网络加载:适合调试阶段,不用反复烧写
  • SD卡烧录:把内核放到SD卡分区,U-Boot读取
  • NAND/NOR Flash烧写:量产时用

我以SD卡方式为例,假设你的开发板是ARM架构:

# 挂载SD卡第一个分区(通常是FAT格式,存放内核和设备树)
sudo mount /dev/sdb1 /mnt/boot

# 复制内核和设备树
sudo cp arch/arm/boot/zImage /mnt/boot/
sudo cp arch/arm/boot/dts/你的板子.dtb /mnt/boot/

# 挂载第二个分区(根文件系统)
sudo mount /dev/sdb2 /mnt/rootfs

# 安装内核模块
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=/mnt/rootfs

# 卸载
sudo umount /mnt/boot /mnt/rootfs

然后插回开发板,上电启动。如果一切正常,你会看到内核启动日志,最后出现登录提示符。

调试技巧:如果启动卡住了,可以在U-Boot里设置 bootargs 加上 earlyprintk 参数,这样内核在早期初始化阶段就会输出调试信息。我曾经靠这个参数找到了一个时钟配置错误的问题。

4.5 常见问题与避坑

编译内核时,你可能会遇到这些问题:

  • 缺少ncurses库:运行menuconfig时报错,安装 libncurses-dev 即可
  • 交叉编译工具链找不到:检查环境变量 PATH 是否包含工具链路径
  • 设备树编译错误:检查dts文件语法,可以用 make dtbs 单独编译
  • 内核启动时panic:多半是根文件系统挂载失败,检查 root= 参数

我记得有一次,编译出来的内核有3MB,但我的板子Flash只有2MB空间。后来我通过 make menuconfig 去掉了很多不需要的驱动和调试选项,内核缩小到1.2MB,顺利烧录。

好了,这一章的内容就到这里。你只要跟着步骤走,把内核编译出来并烧到板子上,摄像头驱动开发的环境就算搭好了。下一章我们会开始写第一个字符设备驱动,敬请期待。