一、【实验目的】
- 搭建起编译树莓派kernel代码的环境
- 能够成功编译树莓派kernel代码
- 能够烧写kernel镜像
二、【实验准备】
2.1 硬件准备
- 一台安装有Ubuntu 18.04 PC机
- 一个树莓派(本实验使用的是rapsberry pi 3B)
- 一张至少8G以上的Micro SD卡
- Micro SD卡读卡器
- 一根Micro USB线
- 树莓派电源(5V)
- 一根HDMI线
- 一个带有HDMI的显示器
2.2 软件准备
无
三、Ubuntu环境搭建
在我们的实验中,Ubuntu电脑主要用于代码的下载编译和烧写工作。在使用之前,需要安装一些必备的软件。安装命令如下:
sudo apt install git bc bison flex libssl-dev make
sudo apt install crossbuild-essential-armhf
四、树莓派内核代码下载
树莓派在github上提供了多个kernel版本,目前已经支持到最新的5.10版本。使用下面的命令默认下载的就是适用于树莓派的最新kernel版本:
git clone --depth=1 https://github.com/raspberrypi/linux
五、代码编译
代码位于Ubuntu电脑上,Ubuntu电脑是x86架构的计算机系统,而树莓派是基于Arm 32位的CPU,两者属于不同的架构。如果要想在Ubuntu上编译出能够在树莓派上运行的程序,需要使用交叉编译的方法。
实际操作中,怎么理解交叉编译呢?
在Ubuntu机器上,我们编译C程序一般是"gcc -o hello hello.c",这里gcc就是编译器。使用which命令查看gcc
which gcc /usr/bin/gcc
而执行
ls -l /usr/bin/gcc
会发现/usr/bin/gcc
是指向/usr/bin/gcc-7
,进一步执行ls -l /usr/bin/gcc-7
会发现/usr/bin/gcc-7
最终指向/usr/bin/x86_64-linux-gnu-gcc-7
。也就是说,Ubuntu上执行gcc实际上使用的是/usr/bin/x86_64-linux-gnu-gcc-7
适用于x86_64
架构的gcc编译器。
显然,直接用它编译出来的程序是无法在树莓派上运行的。那我们如果想在Ubuntu上编译适用于树莓派的程序,应该如何编译呢?其实只要安装一个交叉编译器就可以了。第三步的crossbuild-essential-armhf
就是所需的交叉编译器。
编译树莓派3B的代码具体方法如下:
cd $RASBERRY_PI_SRC_ROOT // 进入第四步下载的代码根目录
KERNEL=kernel7
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=./out bcm2709_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=./out zImage modules dtbs -j16
树莓派的代码是由Linux kernel延申而来,也使用了Makefile去管理代码,因此需要使用make命令编译代码。上述命令的各字段的含义是:
ARCH=arm
表示编译运行于arm
32位平台的代码。CROSS_COMPILE=arm-linux-gnueabihf-
表示使用的编译器是arm-linux-gnueabihf-gcc
。O=./out
表示编译结果和临时文件都放到当前目录下的out目录下。bcm2709_defconfig
表示树莓派3B使用这个config文件zImage modules dtbs
表示从代码中需要编译出kernel镜像zImage
,编译出所有配置的模块,编译出设备树-j16
表示编译时所使用的线程数
如果不出其他问题的话,执行完上述命令,就可以完成树莓派的kernel编译了。
六、kernel烧写
在使用raspberry pi imager烧写完sd卡之后,将这个sd卡插入ubuntu电脑执行以下命令:
$ sudo fdisk -l /dev/sdd
...
...
Device Boot Start End Sectors Size Id Type
/dev/sdd1 8192 532479 524288 256M c W95 FAT32 (LBA)
/dev/sdd2 532480 60776447 60243968 28.7G 83 Linux
$ df -Th /dev/sdd1 /dev/sdd2
Filesystem Type Size Used Avail Use% Mounted on
/dev/sdd1 vfat 253M 48M 205M 19% /media/user/boot
/dev/sdd2 ext4 29G 3.0G 25G 12% /media/user/rootfs
注意,每个人的sd卡分区名称(
/dev/sdd1
,/dev/sdd2
)可能不完全相同,请根据实际的sd卡大小,来判断那个分区是SD卡的块设备节点。比如有些人的sd卡插入Ubuntu之后,分区名字可能是/dev/sdb1
和/dev/sdb2
。
上述结果可以看出来,烧录器将sd卡分成了两个分区,第一个是/dev/sdd1
,是vfat
格式的文件系统,它是树莓派的boot分区,我们编译的kernel镜像就位于这个分区中。第二个/dev/sdd2
,是ext4
格式的文件系统,它是树莓派的rootfs分区,上层的一些应用程序和库就位于该分区内。
给树莓派刷入在第五步中编译出来的kernel镜像,实际上就是将编译结果复制到sd卡上的对应分区中,具体方法分为以下几个步骤。
6.1 挂载分区
将树莓派的sd卡插入Ubuntu机器,执行以下命令将sd卡的两个分区挂载到Ubuntu电脑上。
mkdir mnt
mkdir mnt/fat32 // 创建vfat分区的挂载目录
mkdir mnt/ext4 // 创建ext分区的挂载目录
sudo mount /dev/sdd1 mnt/fat32 // 将/dev/sdd1分区挂载到mnt/fat32目录下
sudo mount /dev/sdb2 mnt/ext4 // 将/dev/sdd2分区挂载到mnt/ext4目录下
6.2 复制kernel镜像
前面提到了,kernel编译的结果分为kernel image,modules和dtb。烧写镜像实际上就是把编译生成的这些文件复制到sd卡中。具体需要复制的文件和命令如下:
sudo cp /home/user/temp/mnt/fat32/$KERNEL.img /home/user/temp/mnt/fat32/$KERNEL-backup.img
sudo cp $RASBERRY_PI_SRC_ROOT/out/arch/arm/boot/zImage /home/user/temp/mnt/fat32/$KERNEL.img
sudo cp $RASBERRY_PI_SRC_ROOT/out/arch/arm/boot/dts/*.dtb /home/user/temp/mnt/fat32/
sudo cp $RASBERRY_PI_SRC_ROOT/out/arch/arm/boot/dts/overlays/*.dtb* /home/user/temp/mnt/fat32/overlays/
sudo cp $RASBERRY_PI_SRC_ROOT/out/arch/arm/boot/dts/overlays/README /home/user/temp/mnt/fat32/overlays/
sudo umount /home/user/temp/mnt/fat32
sudo umount /home/user/temp/mnt/ext4
注意。上述的$KERNEL
对于树莓派3B来讲就是kernel7
。$RASBERRY_PI_SRC_ROOT
需要替换成本地树莓派代码的真实路径。复制完成之后,就可以将sd卡插入树莓派直接开机了。
七、启动验证
树莓派启动后,打开终端,在终端中执行dmesg
查看kernel log:
$ dmesg
...
Linux version 5.10.60-v7+ (user@hostname) (arm-linux-gnueabihf-gcc (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0, GNU ld (GNU Binutils for Ubuntu) 2.30) #1 SMP Sat Oct 23 12:06:52 CST 2021
...
如果烧录正常的话,上述log中的user
应该是Ubuntu机器的用户名,hostname
应该是Ubuntu的hostname。
good!!!