树莓派(Raspberry Pi 4 Model B)编译64位内核Kernel(64位系统)

9,054 阅读8分钟

树莓派系统默认安装的是ARM32位的系统,但是从树莓派3开始是支持ARM64位系统的,官方既然不给64位系统, 那只好我们自己来编译了。

网上其实有很多树莓派3B的编译64位内核,我都试了一遍,全部启动不了,也不是说写得不对,只不过好像不适用于我,搞了我三四天,很愁人;所以我下面的步骤也不保证每个人都可以成功,因为每个人的情况都不一样,造成的问题也不一样,所以出现问题只能靠自己百度或者Google了。

而且有很多笔记只是把内核编译为64位,但是文件系统还是官方的32位,并不能称为真正的64位系统,所以我后面也把根文件系统也构建为64位的了。

笔记在记载的时候难免有遗漏的,如果有哪个步骤不太对,欢迎留言,及时修改。

如果不想这么麻烦,想直接使用64位的系统,可以下载已经开源的64位系统:Debian-Pi-Aarch64,这个是第三方的,不是树莓派官方提供的。

编译环境

通过vm虚拟机安装的虚拟环境Linux,如何安装网上一搜就有了,按照步骤安装即可。

编译机:Ubuntu 18.04-desktop-amd64

想要编译64位的内核,只能在64位的机器上来编译。

交叉编译器

通过交叉编译器生成64位的内核(Kernel)

定义:交叉编译器(英语:Cross compiler)是指一个在某个系统平台下可以产生另一个系统平台的可执行文件的编译器

这句话我的理解形象一点解释就是:

  • Ubuntu 18.04-amd64:定制服装加工工厂(某个系统平台)

  • 交叉编译器:制作衣服的机器(编译器)

  • linux源码(kernel):服装材料(可执行文件的源码)

  • 树莓派4B:人(另一个平台)

有了以上,现在我要给人做一件衣服,那么我需要找一个专门给人做衣服的工厂,把服装材料给工厂后,制作衣服的机器做出来的衣服,人就可以穿了。

每个人理解的都不一样,只要记住上面专业的定义就好了,自己怎么理解按自己的来就可以了。

编译内核(kernel)

就如上面定义的一样,那四样我们都需要先获取到才可以制作;Ubuntu肯定先要安装好;树莓派当然也要准备好。

可以用普通用户,尽量不要用root用户来执行,等需要用到root用户时,再切换到root用户来执行

编译内核前准备

  • 获取交叉编译器并配置

Linaro Toolchain

下载上面链接中的 gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu.tar.xz,有可能更新了,日期和版本会变化。 或者用命令下载

$ sudo wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu.tar.xz

创建一个工作目录,后续的操作都在工作目录中操作

$ mkdir ~/build && cd ~/build
$ sudo apt-get install lrzsz
# 把下载好的文件上传并解压
$ rz
$ sudo tar -xvf  gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu.tar.xz -C /usr/src/
$ cd /usr/src/ && sudo mv gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu aarch64-linux-gnu
$ sudo vi /etc/profile
or
$ sudo vi ~/.bashrc
# 末尾添加以下内容
export ARCH_HOME=/usr/src/aarch64-linux-gnu
export PATH=$PATH:$ARCH_HOME/bin
$ source /etc/profile
or
$ source ~/.bashrc

Note:如果是在普通用户下,还需要切换到root下再配置一遍,普通用户也需要配置

  • 获取Raspberry Kernel源码

raspberry现在默认的就是4.19版本,不过更高的版本也是有的,看自己下载哪个更改一下版本就可以了,Raspberry官方的github下载超级慢,我是把Raspberry Linux迁移到了国内码云上,这样下载起来就很快了;有区别的就是官方更新,不会更新到我的码云的仓库上,想要最新的代码,可以先把官方的fork到自己的github,再迁移到自己的码云,把链接更换成自己的就可以了,下面的命令选择一个下载就可以了

$ cd ~/build
$ sudo apt-get install git
# 官方的github地址
$ git clone --depth=1 --branch rpi-4.19.y https://github.com/raspberrypi/linux
# 码云的地址
$ git clone --depth=1 --branch rpi-4.19.y https://gitee.com/nzwxl/linux
  • 安装编译环境所需的依赖
$ sudo apt-get install git bison flex libssl-dev zip libncurses-dev make

libncurses-dev依赖是支持后面menuconfig的

内核(kernel)编译开始

# 如果源码文件不叫linux,可以mv 改成linux或者你想要的的名字
$ cd linux
# 在编译之前可以先进行清除命令,以保证清洁的环境,如果在编译的环节出错或者操作失误,可以运行此命令重新开始。
$ make distclean
# 编译.config,ARCH要配置成arm64,如果不配置则默认为开发机的x86了,CROSS_COMPILE指定编译器
# bcm2711_defconfig在 arch/arm64/configs/bcm2711_defconfig,它会自己根据Makefile自己去找这个文件
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
# 在当前的.config基础上开始裁剪内核,如果没有要裁剪的按ESC两次退出即可,主要是我也没太了解,等我了解了再记
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
# 开始编译内核, 参数-j5的意思是 使用多处理器同时编译内核,数字最大为:cpu的核数 × 1.5,可自行修改
$ make -j5 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

中间不报错的话,大概会在1个小时左右,会在目录 arch/arm64/boot 中生成 Image 二进制内核文件;如果制作的是32为内核,会在 arch/arm/boot 生成zImage 二进制内核;同时会在linux目录中生成最原始的内核文件vmlimux。

vmlinunx:最原始的linux内核文件。

zImage: 经过压缩和去掉调试信息的可加载二进制内核文件。

Image:没有经过压缩的可加载二进制内核文件。

有关更多关于内核文件可Google。

安装kernel modules

这个modules在后面的根文件系统(rootfs)中用的到,先安装到 ~/build 工作目录中,记得把 [user] 替换为自己的用户名

$ cd ~/build/linux
# 切换到root用户下
$ su
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=/home/[user]/build/ modules_install
$ su [user]

树梅派boot文件

因为树莓派官方的 bootloader 是不开源的,但是提供了可以使用的boot目录下所有的东西,同样的我因为网速的原因下载太慢,迁移到了码云上。

$ cd ~/build
$ git clone --depth=1 https://github.com/raspberrypi/firmware/
or
$ git clone --depth=1 https://gitee.com/nzwxl/firmware/
# firmware/boot 下就是需要的文件了
$ ls firmware/boot

制作ARM64架构的rootfs

如何制作ARM64的rootfs我在另一篇笔记中记录了,到此步骤可以继续按照那篇笔记顺序操作,等rootfs制作完,回到此笔记与下一个步骤衔接

kernel、uboot、rootfs打包进镜像

制作镜像文件

  • 创建一个大小为 1000M 的根文件系统映像文件,然后对映像文件分区

经烧录后显示,1000M的话已经使用了90%,所以要是有许多要做的事情,或者上传一些东西的话,最好创建个比较大一点的映像文件

$ cd ~/build
$ fallocate -l 1000M rootfs.img
# 分区
$ fdisk rootfs.img
    a.输入o。这将清除映像文件上的任何分区。
    b.键入p以列出分区。应该没有分区。
    c.键入n,然后p为primary,1表示驱动器上的第一个分区,按ENTER接受默认的第一个扇区,然后为最后一个扇区键入+ 100M。
    d.键入t,然后c将第一个分区设置为键入W95 FAT32(LBA)。
    e.键入n,然后p表示主驱动器,2表示驱动器上的第二个分区,然后按两次ENTER键接受默认的第一个和最后一个扇区。
    f.写入分区表并键入w退出。
# 可通过命令查看设置好的分区
$ fdisk -l rootfs.img
  • 使用kpartx挂载镜像到loopX

在linux中,如果映像文件(.img)含有分区表的话,使用mount是挂在不上的;可以使用kpartx挂载; X是你显示的数字,Y也是数字,每个人不一定相同,第一个 loopXpY 是上面分区后的第一个分区,第二个就是第二个分区

$ sudo apt-get install kpartx
$ sudo kpartx -av rootfs.img
add map loopXpY (254:0): 0 204800 linear 7:0 2048
add map loopXpY (254:1): 0 407552 linear 7:0 206848
# 这两个设备可以在 /dev/mapper/ 目录中看到
$ ls /dev/mapper/
  • 格式化分区并指定分区LABEL名字
$ sudo mkfs.vfat -n BOOT /dev/mapper/loop5p1
$ sudo mkfs.ext4 -F -L ROOTFS -O "^has_journal" /dev/mapper/loop5p2
# 第一个命令中的-n 参数就是指定LABEL,可以通过  man mkfs.vfat 查看参数详情
# 第二个命令的 -F 是强制的意思(理解的不对可以纠正我),-L 指定LABEL 同样可以通过man mkfs.ext4 查看参数详情
  • 创建挂载点并挂载

经过格式化分区后,现在我们可以挂载loopXpY设备到文件

$ sudo mkdir {/mnt/loopXp1,/mnt/loopXp2}
$ sudo mount /dev/mapper/loop5p1 /mnt/loopXp1
$ sudo mount /dev/mapper/loop5p2 /mnt/loopXp2

复制内核和boot所需文件

$ cd ~/build
# 复制boot所需文件
$ sudo cp -r firmware/boot/* /mnt/loopXp1/
$ sudo cp linux/arch/arm64/boot/dts/broadcom/*.dtb /mnt/loopXp1
$ sudo cp linux/arch/arm64/boot/dts/overlays/*.dtb* /mnt/loopXp1/overlays/
$ sudo cp linux/arch/arm64/boot/dts/overlays/README /mnt/loopXp1/overlays/
# 复制内核
$ sudo cp linux/arch/arm64/boot/Image /mnt/loopXp1/kernel8.img

编写cmdline.txt和 config.txt

参考:RPi_cmdline.txt

编写cmdline.txt

# 添加内容并保存退出
$ sudo vim /mnt/loopXp1/cmdline.txt
console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait fsck.repair=yes
  • console=serial0,115200:串口使用哪个设备,以及传输速率
  • console=tty1:控制台输出使用tty1设备
  • root=/dev/mmcblk0p2:将内存卡第二分区设置为根分区
  • rootfstype=ext4:根分区类型 f2fs应更换为f2fs
  • rw:可写挂载跟分区
  • rootwait:等待内核识别根分区设备后再挂载
  • sck.repair=yes:启动时自动检查修复文件系统错误

编写config.txt

# 添加内容并保存退出
$ sudo vim /mnt/loopXp1/config.txt

# 以64位读取内核
arm_64bit=1
# 想要以ARMV8的模式启动,设置此选项
arm_control=0x200
# 内核的名字
kernel=kernel8.img

# u-boot进行引导kernel时延迟几秒
boot_delay=1

# 关闭蓝牙功能
# See /boot/overlays/README for all available options
dtoverlay=disable-bt
# 开启音频snd_bcm2835
dtparam=audio=on

同步rootfs进镜像第二分区

$ sudo apt-get install rsync
$ cd ~/build/linux-rootfs/
# 开始同步
$ sudo rsync -HPavz -q ./ /mnt/loopXp2

rsync参数详解: rsync命令

编写fstab文件

如果想要第一分区中的boot目录在系统启动后显示文件,需要在 /etc/fstab 中添加以下内容:

$ sudo vim /mnt/loopXp2/etc/fstab
<file system>   <mount point>    <type>   <options>  <dump> <pass>
LABEL=BOOT      /boot            vfat     defaults   0      1
  • file system: 可以是实际分区名,也可以是实际分区的卷标(Lable),卷标名上面已经规定过了
  • mount point: 是挂载点
  • type: 为此分区的文件系统类型,vfat位fat32的类型应该是
  • options: 是挂载的选项,用于设置挂载的参数,常见的有以下参数
    • defaults: rw, suid, dev, exec, auto, nouser, and async.
    • auto: 系统自动挂载,fstab默认就是这个选项
    • noauto 开机不自动挂载
    • nouser 只有超级用户可以挂载
    • ro 按只读权限挂载
    • rw 按可读可写权限挂载
    • user 任何用户都可以挂载
  • dump: 是备份设置 当其值设置为1时,将允许dump备份程序备份;设置为0时,忽略备份操作;
  • pass: 是fsck磁盘检查设置; 其值是一个顺序。当其值为0时,永远不检查;而 / 根目录分区永远都为1。其它分区从2开始,数字越小越先检查,如果两个分区的数字相同,则同时检查。

安装内核模块

因为在编译kernel时已经把modules安装到了工作目录build中了,直接复制到第二分区中就可以了,如果不放心,可以用安装modules的命令安装到第二分区下,更改下路径就可以了。

$ cd ~/build
$ sudo cp -r lib/modules/ /mnt/loopXp2/lib/

卸载烧录IMG文件到SD卡

$ cd ~/build
$ sync
$ sudo umount /mnt/loopXp1/
$ sudo umount /mnt/loopXp2/
$ sudo kpartx -dv rootfs.img

把 rootfs.img 下载到windowns下 烧录到SD卡中,烧录SD卡的方式可参考:

启动Raspberry后通过命令查看内核版本为64位

uname -a

参考