下载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
方案一: 直接通过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 Kernel 5.5.8,并解压到了linux-5.5.8路径。
export ARCH=arm64
export CROSS_COMPILE=aarch64-none-linux-gnu-
# 如果是第一次编译可以使用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
# 可以根据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) ]---
系统停在这里的原因是没有找到根文件系统,接下来开始制作根文件系统。
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)”建议勾选,采用静态编译后可以省去拷贝各种库的麻烦。
make -j8
make install
_install 路径下的内容已包括常用的linux命令,接下来将以此为基础制作initramfs。
_install
├── bin
├── linuxrc -> bin/busybox
└── sbin
首先需要了解linux kernel启动init有两种方案,分别是initrd和initramfs,在kernel 2.6 以后就都是 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
# 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
::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
#! /bin/sh
mount -o remount,rw /
mount -a
clear
echo "Start My Linux..."
dmesg > /var/log/dmesg
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devtmpfs /dev devtmpfs defaults 0 0
user.* /var/log/messages
kern.* /var/log/kernel.log
authpriv.* /var/log/messages
# 增加库函数的默认搜索路径,对基于动态链接的程序来说,是非常必要的。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/lib64:/usr/lib
cp examples/shutdown-1.0/script/shutdown _install/bin
mkdir _install/app
cp examples/shutdown-1.0/ _install/app/ -ra
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系统的制作就完成了。