编译、运行linux kernel@qemu-based ARM

一、环境准备

下载Linux Kernel源代码。 下面是以当前最新版本5.5.8为例,可以根据自己需要更换为其他版本。

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.5.8.tar.xz

解压源代码到当前路径的默认目录中

tar xvJf linux-5.5.8.tar.xz

下载ARM交叉编译工具链。 笔者的平台为x86 ubuntu 19.10系统,故不能直接使用默认的GCC环境。ARM交叉编译工具链可以在ARM Developer网站上下载,具体可以在如下网址中找到:ARM官方交叉编译工具链。也可以直接使用如下命令下载。

# 说明:由于下载过程非常慢,这里我加入了-b选项作为后台任务自动下载。
wget -b -O gcc-arm-9.2-aarch64-none-linux-gnu.tar.xz https://developer.arm.com/-/media/Files/downloads/gnu-a/9.2-2019.12/binrel/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz?revision=61c3be5d-5175-4db6-9030-b565aae9f766&la=en&hash=0A37024B42028A9616F56A51C2D20755C5EBBCD7 

下载完成后,解压到/usr/local路径下

sudo tar xvJf gcc-arm-9.2-aarch64-none-linux-gnu.tar.xz -C /usr/local

增加交叉编译工具链路径到环境变量中

export PATH=$PATH:/usr/local/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin

这样之后就可以通过直接调用aarch64-none-linux-gnu-*来使用交叉编译工具链的各种命令了。

安装编译kernel需要的其他库文件。

sudo apt-get install -y libncurses-dev flex bison libssl-dev bc

二、安装QEMU

方案一: 直接通过apt-get安装。但根据linux版本不同,可能通过这种方式安装的qemu版本较低而出现各种问题,比如笔者在ubuntu 16.10上就只能安装到2.x版本,导致加载编译的Linux 4.x和5.x的内核均失败,困扰了很久才发现是版本过低问题。

sudo apt-get install -y qemu

方案二: 通过下载源代码编译最新版本。这在QEMU的官网上有很清晰的指导,按照指导做基本就OK。

wget https://download.qemu.org/qemu-4.2.0.tar.xz
tar xvJf qemu-4.2.0.tar.xz  # 这里也可以加上-C 指定源代码解压的路径
cd qemu-4.2.0
./configure
make -j 8	# 这里也可以只编译你需要模拟的平台从而节省时间,例如:make -j8 aarch64-softmmu/all

中间编译过程可能会提示失败,一般都是由于缺少了一些库导致,这时根据提示通过apt-get工具安装就可以了。笔者遇到的包括:glib-2.48 gthread-2.0 pixman,可以通过如下命令安装。

sudo apt-get install -y libglib2.0-dev libpixman-1-dev

编译成功后,一般调用qemu-4.2.0/aarch64-softmmu/qemu-system-aarch64作为ARM64的模拟器。

三、开始编译Linux内核

前面已经下载好了Linux Kernel 5.5.8,并解压到了linux-5.5.8路径。

  • 配置编译的架构和交叉编译器环境变量(配置完后可以通过export -p查看)。
export ARCH=arm64
export CROSS_COMPILE=aarch64-none-linux-gnu-
  • 清理make的环境,并加载默认配置。
# 如果是第一次编译可以使用make mrproper
make clean
# 加载默认配置。
# ARM64的默认配置在 linux-5.5.8/arch/arm64/configs/defconfig
# ARM32的默认配置在 linux-5.5.8/arch/arm/configs
make defconfig
# 在加载完默认配置后,可以通过make menuconfig进行选项调整
# 更新后的配置文件为 linux-5.5.8/.config
make menuconfig
  • 开始正式编译linux内核,同时根据CPU设置多个作业任务,这样效率比较高。
# 可以根据CPU的情况设置作业数量,笔者是8核8线程,这里设置为了8个
make -j8
# 编译过程可能会比较长,所以也可以把make任务放在后台运行,这样终端就可以同时作其他事情或关闭,这种策略下的命令变为
make -j8 > a.log 2>&1 &

到这里就可以完成基本的linux内核编译了,编译完的kernel镜像放在了arch/arm64/boot路径下,如下所示:

tree -L 1 arch/arm64/boot
arch/arm64/boot
├── dts
├── Image
├── Image.gz
├── install.sh
└── Makefile

可以用qemu进行内核加载实验。

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -smp 2 -m 2048 -nographic -kernel arch/arm64/boot/Image
# 在一串启动信息打印后,终端会停在下面一行
[    0.892053] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

系统停在这里的原因是没有找到根文件系统,接下来开始制作根文件系统。

四、制作根文件系统

  • 安装busybox工具包,并解压。
wget https://busybox.net/downloads/busybox-1.31.1.tar.bz2 
tar xvjf busybox-1.31.1.tar.bz2
  • 配置编译环境
make menuconfig

其中:“settings\Don’t use /usr”建议关闭,避免在install时覆盖了系统路径文件;“settings\Build static binary (no shared libs)”建议勾选,采用静态编译后可以省去拷贝各种库的麻烦。

  • 编译busybox,完成后会在_install路径下包含需要的基本工具。
make -j8
make install

_install 路径下的内容已包括常用的linux命令,接下来将以此为基础制作initramfs。

_install
├── bin
├── linuxrc -> bin/busybox
└── sbin

首先需要了解linux kernel启动init有两种方案,分别是initrd和initramfs,在kernel 2.6 以后就都是 initramfs了。其中:

  • initrd:即init ram disk,把一块内存当做磁盘挂载,然后找到里面的init进程执行。
  • initramfs:即直接在内存中挂载文件系统,执行该文件系统中的init进程。另外,tmpfs是ramfs的增强版方案,而initramfs就是tmpfs的一个特殊实例。我们可以用如下命令生成initramfs镜像,但此时还缺少一些文件,是无法正常运行的。
cd busybox-1.31.1/_install
# newc means : the new (SVR4) portable format, which supports file systems having more than 65536 i-nodes. 
find . | cpio -o -H newc | gzip > ~/myinitramfs.gz
  • 建立一系列目录,并从busybox的examples中拷贝框架示例。
# Here is a reasonable minimum set of directories for your root filesystem [1]:
#    /dev -- Device files, required to perform I/O
#    /proc -- Directory stub required by the proc filesystem
#    /etc -- System configuration files
#    /sbin -- Critical system binaries
#    /bin -- Essential binaries considered part of the system
#    /lib -- Shared libraries to provide run-time support
#    /mnt -- A mount point for maintenance on other disks
#    /usr -- Additional utilities and applications
mkdir dev var sys mnt etc proc lib lib64 home tmp usr
mkdir var/log
touch var/log/dmesg
touch var/log/messages
touch var/log/kernel.log
ln -s bin/busybox init 
cp ../examples/bootfloppy/etc/* ./etc -ra
touch etc/syslog.conf
  • 创建设备文件。
cd dev
sudo mknod console c 5 1
sudo mknod null c 1 3
  • 修改inittab文件。
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::restart:/sbin/init 
::ctrlaltdel:/sbin/reboot 
::respawn:/sbin/klogd -n
::respawn:/sbin/syslogd -n
::shutdown:/bin/umount -a -r 
::shutdown:/sbin/swapoff -a
::shutdown:/bin/killall klogd
::shutdown:/bin/killall syslogd
  • 修改etc/init.d/rcS文件,添加打印信息记录init启动状态,并收集kernel启动信息到dmessages文件中。
#! /bin/sh
mount -o remount,rw /
mount -a
clear
echo "Start My Linux..."
dmesg > /var/log/dmesg
  • 修改etc/fstab文件,挂载必要的文件系统。文件系统的详细信息可以参考linux社区文档。
proc		/proc	proc	    defaults    0	0
sysfs       /sys    sysfs       defaults    0   0
devtmpfs    /dev    devtmpfs    defaults    0   0
  • 修改syslog.conf文件,将不同等级的消息分发到不同的Log文件中。
user.* /var/log/messages
kern.* /var/log/kernel.log
authpriv.* /var/log/messages
  • 修改profile文件,增加用户登陆后的默认配置。
# 增加库函数的默认搜索路径,对基于动态链接的程序来说,是非常必要的。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/lib64:/usr/lib
  • 制作shutdown命令。这里可以直接将busybox中的examples/shutdown-1.0/script/shutdown脚本拷贝到_install/bin下,并添加执行权限 。
cp examples/shutdown-1.0/script/shutdown _install/bin
mkdir _install/app
cp examples/shutdown-1.0/ _install/app/ -ra
  • 打包initramfs镜像文件。
cd busybox-1.31.1/_install
find . | cpio -o -H newc | gzip > myinitramfs.gz

至此已制作了一个最基础的根文件系统。

五、启动内核

可以通过QEMU启动编译好的linux内核与根文件系统。

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -smp 2 -m 2048 -nographic -kernel linux-5.5.8/arch/arm64/boot/Image -initrd myinitramfs.gz

之后能看到内核启动的各种打印信息通过终端输出,最后停在如下画面:

Start My Linux...

Please press Enter to activate this console.

敲击回车键后进入熟悉的命令行界面,并可以通过熟悉的linux命令进行基本操作。至此,一个最基本的linux系统的制作就完成了。

你可能感兴趣的:(linux,kernel,arm,busybox)