核心流程
首先,本文主要講述如何編譯Linux內核並在qemu虛擬機上運行。這里針對的架構是aarch64。
本文的實驗平台是Ubuntu 16.04。
為了達成目標,我們需要有qemu、buildroot和linux安裝包或源碼。
首先確保qemu-system-aarch64命令可用,可以通過在命令行執行qemu-system-aarch64 --version判斷。
下載buildroot源碼,鏈接見下文平台工具。假設其絕對路徑保存在變量BUILD_ROOT_PATH中。執行以下命令
cd $BUILD_ROOT_PATH
make menuconfig
在彈出的配置界面中,設置Target option ---> Target Architecture為AArch64 (little endian);設置Toolchain ---> Toolchain type為External toolchain,這時我們可以看到Toolchain ---> Toolchain的值為linaro AArch64 xxxx.xx;設置System configuration ---> Enable root login with password開啟,並設置System configuration ---> Root password為xxxx(任意的你喜歡的密碼);設置System configuration ---> Run a getty (login prompt) after boot ---> TTY port的值為ttyAMA0(這一條非常重要,不然虛擬機可能啟動不了);設置Target packages ---> Show packages that are also provided by busybox開啟;設置Target packages ---> Debugging, profiling and benchmark ---> strace開啟;設置Filesystem images ---> cpio the root filesystem開啟。
在配置完成之后,執行
make
注意:這里可能需要配置wget代理。
生成的rootfs.cpio在目錄$BUILD_ROOT_PATH/output/images下面。
下載Linux內核源碼,鏈接見下文平台工具。假設其絕對路徑保存在變量LINUX_KERNEL_PATH中。執行以下命令
cd $LINUX_KERNEL_PATH
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make defconfig
vim .config
在打開的文件中,找到CONFIG_CMDLINE這個配置選項,設置其值為console=ttyAMA0;找到CONFIG_INITRAMFS_SOURCE這個配置選項,設置其值為$BUILD_ROOT_PATH/output/images/rootfs.cpio(注意,這里要自己展開變量BUILD_ROOT_PATH);設置CONFIG_DEBUG_INFO配置項為y。
配置結束后,執行以下命令
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j16
執行結束后,我們可以看到生成了一些文件,其中包括$LINUX_KERNEL_PATH/vmlinux和$LINUX_KERNEL_PATH/arch/arm64/boot/Image
為了能在 QEMU 里面使用網絡功能,這里使用 tap 作為后端網絡設備。在主機中,創建tap_linux設備,命令如下:
$ sudo ip tuntap add dev tap_linux mod tap
$ sudo ifconfig tap_linux up
$ sudo ifconfig tap_linux 192.168.10.10 netmask 255.255.255.0
在當前目錄創建一個shell腳本,避免重復輸入指令。其文件名為start.sh,其內容為
qemu-system-aarch64 -machine virt -cpu cortex-a57 \
-machine type=virt -nographic -smp 1 \
-m 2048 \
-kernel ./arch/arm64/boot/Image \
--apend "console=ttyAMA0" \
-netdev tap,id=mynet1,script=no,downscript=no,ifname=tap_linux \
-device virtio-net-device,netdev=mynet1,mrg_rxbuf=off,csum=off,guest_csum=off,gso=off,guest_tso4=off,guest_tso6=off,guest_ecn=off,guest_ufo=off \
$1 $2
執行./startup.sh,這是可以用qemu啟動linux內核。在進入命令行之前,需要輸入buildroot login: 的值,其值為root,然后需要輸入Password: ,這是前文構建rootfs.cpio的時候,配置項System configuration ---> Root password的值。然后就可以進入命令行執行以下常用命令了。(注意,需要先cd /)。使用網絡功能需要先給網卡配置 ip,例如ifconfig eth0 192.168.10.11。
如果要退出qemu,可以先按Ctrl + A,然后按X。
為了在主機和qemu虛擬機之間共享文件,我們可以創建一個目錄,其絕對路徑為SHARED_FILE_PATH。然后執行以下命令
cd $BUILD_ROOT_PATH
vim .config
修改BR2_ROOTFS_OVERLAY配置項的值為$SHARED_FILE_PATH(注意,自行展開變量)。
保存后執行以下命令重新創建 rootfs.cpio。
rm $BUILD_ROOT_PATH/output/images/rootfs.*
make
然后需要重新編譯內核,即
cd $LINUX_KERNEL_PATH
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j16
依然通過./start.sh啟動虛擬機跑linux內核,進入命令行后,執行cd /; ls,我們可以看到$SHARED_FILE_PATH目錄下的文件。
重新執行./start.sh -s -S進入qemu的調試狀態,然后開一個新的shell,輸入命令
cd $LINUX_KERNEL_PATH
aarch64-linux-gnu-gdb ./vmlinux -ex "target remote :1234"
現在,可以像以往一樣使用gdb進行調試了……
下面介紹如何編譯一個linux內核模塊。
cd $SHARED_FILE_PATH
vim hello.c
hello.c的內容為
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
int helloinit(void)
{
printk(KERN_DEBUG "hello world !!\n");
return 0;
}
void helloexit(void)
{
printk(KERN_DEBUG "goodbye world !!\n");
}
module_init(helloinit);
module_exit(helloexit);
MODULE_LICENSE("GPL");
然后在當前目錄創建Makefile內容如下
ifneq (${KERNELRELEASE},)
obj-m := hello.o
else
KERNEL_SOURCE := $LINUX_KERNEL_PATH # 注意自行展開變量LINUX_KERNEL_PATH
PWD := $(shell pwd)
default:
${MAKE} -C ${KERNEL_SOURCE} M=${PWD} modules
clean:
${MAKE} -C ${KERNEL_SOURCE} M=${PWD} clean
endif
然后執行交叉編譯命令
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make
OK,重新編譯rootfs.cpio和linux內核,然后運行qemu。在進入linux命令行后,執行以下命令
cd /
insmod hello.ko
dmesg | tail # 可以看到打印信息 hello world !!
rmmod hello.ko
dmesg | tail # 可以看到打印信息 goodbye world !!
相關經驗
Linux Kernel
- 使用
qemu並開啟gdb server功能之后,在gdb窗口輸入b start_kernel,進入最初的內核初始化函數。
