上一篇[http://www.cnovirt.com/archives/616] 说明了如何基于qemu-riscv64运行由debian提供的完整系统镜像。
该镜像中包含的kernel是5.5.0。
本篇介绍如何编译最新版本的内核,并加入到debian镜像中。
我的宿主机仍然是Ubuntu 18.04 LTS(X86_64)。
交叉编译器(重要)
上一篇中,我们安装了Ubuntu 18.04自带的gcc-riscv64-linux-gnu这个包,实际上隐含安装和指向的是gcc-7-riscv64-linux-gnu编译器。
如果继续使用这个版本的交叉编译器,当我们启动内核时,内核报错
ext4: #The unexpected relocation type ‘R_RISCV_ALIGN’ from PC = XXX#
其原因是:gcc-7-riscv64-linux-gnu不支持-mno-relax选项,导致arch/riscv/Makefile中如下这一行不生效
KBUILD_CFLAGS_MODULE += $(call cc-option,-mno-relax)
结果编译生成的模块(ext4.ko等)会默认包含R_RISCV_ALIGN一类的relaxation指示信息。
通过查看arch/riscv/kernel/module.c文件中apply_r_riscv_align_rela函数的实现,我们发现内核在加载模块时,会检查并处理各种relaxation types,目前不支持R_RISCV_ALIGN这一类型,直接报错。
幸运的是Ubuntu 18.04 LTS还包含一个高版本的交叉编译器,它支持-mno-relax选项。
所以需要卸载gcc-7-riscv64-linux-gnu并安装gcc-8-riscv64-linux-gnu;然后创建链接/usr/bin/riscv64-linux-gnu-gcc指向/usr/bin/riscv64-linux-gnu-gcc-8
update-alternatives --install /usr/bin/riscv64-linux-gnu-gcc riscv64-linux-gnu-gcc /usr/bin/riscv64-linux-gnu-gcc-8 1
下载内核
从国内的镜像下载内核代码并切换到开发分支for-next。这个镜像服务应该是阿里提供的,他们干了一件有意义的大好事,表扬一下 :)
git clone http://kernel.source.codeaurora.cn/pub/scm/linux/kernel/git/riscv/linux.git git checkout for-next
建立配置
为了方便,直接以debian镜像中包含的config-5.5.0-1-riscv64为模板创建.config。
确保当前目录为kernel源码的根目录
guestmount -a image.qcow2 -m /dev/sda1 /mnt/ cp /mnt/boot/config-5.5.0-1-riscv64 ./.config guestunmount /mnt
编辑.config,找到CONFIG_SYSTEM_TRUSTED_KEYS=”debian/certs/debian-uefi-certs.pem”这一行,
改为CONFIG_SYSTEM_TRUSTED_KEYS=””,因为我们没有这个证书。
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- menuconfig
此时弹出menuconfig交互界面,直接选择Save,然后Exit。这样就得到配置文件.config。
编译内核
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j $(nproc)
创建一个输出目录/tmp/output,准备接收生成的内核与模块文件目录。
mkdir /tmp/output make ARCH=riscv install INSTALL_PATH=/tmp/output make ARCH=riscv modules_install INSTALL_MOD_PATH=/tmp/output
建立FIT Image格式的内核文件
在用u-boot启动这个新内核之前,我们还是要把vmlinuz转换成U-boot FIT Image。
假设编译产生的原始文件为vmlinuz-5.7.0-rc1,将其更名为image
ITS文件的内容如下:
/* * Simple U-Boot uImage source file containing a single kernel and FDT blob */ /dts-v1/; / { description = "Simple image with single Linux kernel and FDT blob"; #address-cells = ; images { kernel { description = "Linux kernel"; data = /incbin/("./output/vmlinuz-5.7.0-rc1"); type = "kernel"; arch = "riscv"; os = "linux"; compression = "none"; load = ; entry = ; hash-1 { algo = "crc32"; }; hash-2 { algo = "sha1"; }; }; fdt-1 { description = "Flattened Device Tree blob"; data = /incbin/("./qemu-virt.dtb"); type = "flat_dt"; arch = "riscv"; compression = "none"; hash-1 { algo = "crc32"; }; hash-2 { algo = "sha1"; }; }; }; configurations { default = "conf-1"; conf-1 { description = "Boot Linux kernel with FDT blob"; kernel = "kernel"; fdt = "fdt-1"; }; }; };
注意”data = /incbin/(“./image”);”这一行指向是原始vmlinuz文件。
另外,确保qemu-virt.dtb在当前目录。延用上一篇的方法生成的qemu-virt.dtb,这个没有变化。
执行mkimage转换格式:
~/u-boot/tools/mkimage -f ./kernel_fdt.its ./vmlinuz-5.7.0-rc1
这样就生成了新的vmlinuz-5.7.0-rc1,只不过新文件的格式是FIT image。
把新内核加入debian镜像
新的内核文件和模块文件目录都在/tmp/output目录下,且vmlinuz已经是FIT Image格式。
下面我们用guestmount把debian镜像挂到/mnt目录,执行替换:
guestmount -a image.qcow2 -m /dev/sda1 /mnt/ cp ./output/* /mnt/boot/ cp -r ./output/lib/modules/5.7.0-rc1 /mnt/lib/modules/ guestunmount /mnt
生成initrd
现在debian镜像中已经包含了新的内核即模块,我们还需要生成initrd,然后再启用新内核。
启动qemu进入虚拟机,此时仍然是原内核5.5.0。
首先,修改/etc/initramfs-tools/initramfs.conf,设置MODULES=dep(原来是most),这样生成的initrd大约只有15M。而如果采用默认值most,生成的initrd将会达到300M以上,这样内核在解压此内存盘时会超出内存界限而崩溃。
然后在/boot目录下,执行如下脚本生成initrd。注意5.7.0-rc1是当前实验的内核版本。
mkinitramfs -o ./initrd.img-5.7.0-rc1 5.7.0-rc1
启用新内核
下面让u-boot产生新的启动项。
首先,修改/etc/default/u-boot中的引导参数U_BOOT_PARAMETERS,把它的值改为
“rw noquiet root=LABEL=rootfs earlycon=uart8250,mmio,0x100000000,115200n8 console=ttyS0”
如此将会引入earlycon,这样就可以显示内核启动早期的信息,便于排查错误。
然后更新u-boot引导启动项
u-boot-update
启动新内核
再次启动qemu,此次会出现新内核的引导项,正常就是第1项。
启动后可以在shell下执行uname -a确认启动新内核成功。