【实验二】 树莓派基本开发

一、【实验目的】

  • 搭建起编译树莓派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。

八、参考资料

  1. Raspberry Pi Documentation

“【实验二】 树莓派基本开发”的一个回复

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注