第二课:搭建开发环境

好,咱们正式开始动手了。这一课说白了就是“把家伙事儿备齐”。

我见过不少新手,一上来就急着写驱动代码,结果编译都过不了,最后发现是工具链没配好。嗯,磨刀不误砍柴工,咱们先把环境搭稳了再说。

2.1 配置交叉编译工具链

为什么需要交叉编译?因为你的开发机是x86架构,目标板是ARM(或者其他架构)。你想想看,x86上编译出来的程序,ARM怎么可能跑得动?

我个人习惯用 Linaro 提供的工具链,稳定且社区支持好。以 ARM Cortex-A7 为例,我常用的版本是 gcc-arm-linux-gnueabihf。

安装步骤:

  1. 下载工具链:wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
  2. 解压到 /opt 目录:sudo tar -xvf gcc-linaro-*.tar.xz -C /opt
  3. 添加环境变量:编辑 ~/.bashrc,加入 export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
  4. 验证安装:arm-linux-gnueabihf-gcc --version

重要:工具链版本必须与内核版本匹配。比如 Linux 4.19 内核,建议用 gcc 7.x 系列。版本差太多,编译时会报一些莫名其妙的错误。

我的小技巧:我习惯在 /opt 下建一个 toolchains 目录,把不同版本的工具链都放进去。这样切换项目时,改一下 PATH 就行,不用重新下载。

2.2 获取Linux内核源码

内核源码从哪里来?两种主流方式:

  • 官方主线:从 kernel.org 下载。优点是干净,缺点是可能缺少你板子的补丁。
  • 厂商BSP:比如 NXP、TI、Rockchip 提供的定制内核。我建议新手用这个,因为厂商已经帮你适配好了板级支持。

以我手头一块 i.MX6ULL 板子为例,我习惯用 NXP 的官方 BSP:

git clone -b imx_4.19.35_1.0.0 https://source.codeaurora.org/external/imx/linux-imx
cd linux-imx
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig

这里要注意 CROSS_COMPILE 后面的横线不能丢。我曾经因为少写了这个横线,编译出来的内核在板子上启动到一半就挂了,查了半天才发现是工具链没生效。

避坑指南:下载内核时,尽量用 git 而不是直接下载 tar 包。因为后续你可能需要查看 git log 来定位某个驱动的修改历史。我遇到过几次,厂商的补丁提交信息里藏着关键的调试线索。

2.3 编译最小系统

最小系统包含三样东西:bootloader、内核镜像、根文件系统。咱们这节课先搞定内核和根文件系统,bootloader 后面单独讲。

编译内核:

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

-j4 表示用4个线程并行编译。如果你的电脑是8核,可以改成 -j8。我第一次编译内核时不知道这个参数,单线程跑了快半小时,后来同事告诉我加 -j,速度直接翻倍。

编译设备树:

设备树文件(.dts)会被编译成 .dtb。比如 i.MX6ULL 的板子,对应的设备树是 imx6ull-14x14-evk.dtb。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx6ull-14x14-evk.dtb

制作根文件系统:

我推荐用 Buildroot,它比 Yocto 轻量,适合初学者。配置方法:

git clone https://git.buildroot.net/buildroot
cd buildroot
make menuconfig
# 选择 Target options -> Target Architecture -> ARM (little endian)
# 选择 Toolchain -> External toolchain -> Linaro ARM
# 选择 Filesystem images -> ext2/3/4 variant -> ext4
make -j4

编译完成后,在 output/images 目录下会生成 rootfs.ext4。嗯,这就是咱们的根文件系统镜像。

核心要点:根文件系统里必须包含 /dev、/proc、/sys 这几个目录,否则内核启动时会报 Kernel panic - not syncing: VFS: Unable to mount root fs。我第一次做的时候忘了加 /dev,板子启动后直接黑屏,还以为硬件坏了。

2.4 烧录到目标板

烧录方式取决于你的板子。常见的有:

  • SD卡烧录:适合大多数开发板。把内核、设备树、根文件系统拷贝到SD卡对应分区。
  • 网络烧录(TFTP/NFS):适合调试阶段。我更喜欢这种方式,因为不用反复插拔SD卡。
  • 串口烧录:慢,但兼容性好。适合没有网口的板子。

以SD卡烧录为例,步骤:

# 假设SD卡设备是 /dev/sdb
sudo dd if=rootfs.ext4 of=/dev/sdb2 bs=1M
sudo mount /dev/sdb1 /mnt
sudo cp arch/arm/boot/zImage /mnt/
sudo cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /mnt/
sudo umount /mnt

我的经验:烧录前先用 lsblk 确认SD卡的设备名。我见过有人把 /dev/sda(系统盘)当成SD卡,结果把自己的电脑系统给覆盖了。嗯,那个人就是我。

烧录完成后,插上SD卡,上电启动。如果看到串口输出 Welcome to Buildroot 的登录提示,恭喜你,环境搭建成功了!

2.5 验证环境

最后一步,写一个简单的驱动模块,验证整个链路是否通畅。

// hello.c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, ALSA driver world!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, ALSA driver world!\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

Makefile:

obj-m += hello.o

all:
    make -C /path/to/linux-kernel M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

clean:
    make -C /path/to/linux-kernel M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- clean

编译后得到 hello.ko,拷贝到板子上,执行 insmod hello.ko。如果 dmesg 里看到那句 "Hello",说明你的开发环境完全OK了。

注意:内核模块的编译必须使用与当前运行内核完全相同的源码树。如果你在板子上运行的是自己编译的内核,那就用同一份源码来编译模块。版本不匹配的话,insmod 会报 Invalid module format 错误。

好了,环境搭完了。下一课咱们开始真正接触 ALSA 框架,我会从最核心的数据结构讲起。到时候见。


可可精品资料库