目录
一.安装交叉编译链(根据自己的需求去安装编译链,我这里安装的是32位)
step1:制作一个最小的initrd(initial ramdisk)
step3:配置busybox:配置为静态编译,避免其他库的问题
之前一直折腾过几次qemu,但是一直没有做记录,系统重装后每次再装qemu很费尽,踩过的坑又要踩一次,这次花了点时间,将搭建流程做了复现和总结,希望帮到自己的同时也可以帮到别人。
首先准备一个linux系统,ubuntu或者centos都可以(如何装系统这里不再赘述)。我这里的环境是centos7.4,如果你已经有了ubuntu,那么也不影响,只是再下载源码的方式上有所区别,其余步骤都没有什么区别。
在这之前先创建一个目录用来存放此次实验的资源,我这里在主目录下创建了一个qemu-lab目录,后续的所有步骤都在qemu-lab目录中
下面是详细的流程:
一.安装交叉编译链(根据自己的需求去安装编译链,我这里安装的是32位)
step1:下载编译链,执行命令:
wget https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz
关于编译链的资料参考:
https://www.cnblogs.com/linuxbo/p/4297680.html arm交叉编译器gnueabi、none-eabi、arm-eabi、gnueabihf的区别
https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/
step2:解压源码
tar -xjf gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz
step3:添加环境变量,使你的编译链全局可用
gedit /etc/profile
在最后一行加入:
export PATH=$PATH:/qemu-lab/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf/bin
该路径是你编译链加压后的编译工具所在的路径
step4:保存退出后使该变量生效:
source /etc/profile
此时在终端敲arm-linux + tab看下,一切正常的话会看到安装好的编译链
二,安装qemu
step1:
wget https://download.qemu.org/qemu-2.11.0.tar.xz
step2:
tar xvJf qemu-2.11.0.tar.xz
step3:
cd /qemu-lab/qemu-2.11.0
step4:
./configure
step5:
make -j4
step6:
make install
上述步骤完成后,终端敲qemu+tab看下,会有如下显示:
qemu-
qemu-aarch64 qemu-mipsel qemu-sparc64 qemu-system-or1k
qemu-alpha qemu-mipsn32 qemu-system-aarch64 qemu-system-ppc
qemu-arm qemu-mipsn32el qemu-system-alpha qemu-system-ppc64
qemu-armeb qemu-nbd qemu-system-arm qemu-system-ppcemb
qemu-cris qemu-nios2 qemu-system-cris qemu-system-s390x
qemu-ga qemu-or1k qemu-system-i386 qemu-system-sh4
qemu-hppa qemu-ppc qemu-system-lm32 qemu-system-sh4eb
qemu-i386 qemu-ppc64 qemu-system-m68k qemu-system-sparc
qemu-img qemu-ppc64abi32 qemu-system-microblaze qemu-system-sparc64
qemu-io qemu-ppc64le qemu-system-microblazeel qemu-system-tricore
qemu-m68k qemu-pr-helper qemu-system-mips qemu-system-unicore32
qemu-microblaze qemu-s390x qemu-system-mips64 qemu-system-x86_64
qemu-microblazeel qemu-sh4 qemu-system-mips64el qemu-system-xtensa
qemu-mips qemu-sh4eb qemu-system-mipsel qemu-system-xtensaeb
qemu-mips64 qemu-sparc qemu-system-moxie qemu-tilegx
qemu-mips64el qemu-sparc32plus qemu-system-nios2 qemu-x86_64
qemu工具都装了,每种工具的含义可以百度下
三.下载内核
step1:下载内核:
https://www.kernel.org/pub/linux/kernel/
step2:配置内核config:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm vexpress_defconfig
这里我们的config选用了网上资料较多,qemu支持也比较好的vexpress_defconfig,你也可以选用其他的
step3:编译内核:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm -j4
编译成功后会有这行打印,会告诉我们的镜像地址:Kernel: arch/arm/boot/zImage is ready
此时建议建立一个qemu-image目录用于存放所有编译出来的镜像文件,这里我们将内核镜像拷贝到这个目录备用
cp ./arch/arm/boot/zImage ./qemu-image/
四.使用qemu启动内核
因为我目前不太关注uboot,所以此次暂且不去深究uboot启动内核的细节,qemu可以绕过uboot直接指定内核启动,接下来我们直接启动内核
启动内核命令:
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "init=/linuxrc root=/dev/mmcblk0p1 rw console=ttyAMA0" -nographic
-M用于指定机器型号
-kernel用于指定内核镜像
init用于指定文件系统起来后执行的linux的第一个进程
root用于指定使用哪个设备作为根文件系统
console用于指定终端
-nographic表示不使用图形界面
NOTE:tty配置错的话将看不到打印输出,这是我踩的最深的坑
tty的区别可以看内核文档:kernel/src/Documentation/serial-console.txt
The format of this option is:
console=device,options
device: tty0 for the foreground virtual console
ttyX for any other virtual console
ttySx for a serial port
lp0 for the first parallel port
ttyUSB0 for the first USB serial device
options:depend on the driver. For the serial port this
defines the baudrate/parity/bits/flow control of
the port, in the format BBBBPNF, where BBBB is the
speed, P is parity (n/o/e), N is number of bits,
and F is flow control ('r' for RTS). Default is
9600n8. The maximum baudrate is 115200.
上述命令执行完后,会看到终端有这样的打印:
0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
input: ImExPS/2 Generic Explorer Mouse as /devices/mb:kmi1/serio1/input/input1
VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6
Please append a correct "root=" boot option; here are the available partitions:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
CPU: 0 PID: 1 Comm: swapper/0 Not tainted 3.10.104 #1
[<800147c8>] (unwind_backtrace+0x0/0xe0) from [<80011a18>] (show_stack+0x10/0x14)
[<80011a18>] (show_stack+0x10/0x14) from [<8036ac30>] (panic+0xa0/0x1e4)
[<8036ac30>] (panic+0xa0/0x1e4) from [<80489030>] (mount_block_root+0x1e0/0x274)
[<80489030>] (mount_block_root+0x1e0/0x274) from [<804891d0>] (mount_root+0x10c/0x114)
[<804891d0>] (mount_root+0x10c/0x114) from [<80489328>] (prepare_namespace+0x150/0x188)
[<80489328>] (prepare_namespace+0x150/0x188) from [<80488cb0>] (kernel_init_freeable+0x224/0x234)
[<80488cb0>] (kernel_init_freeable+0x224/0x234) from [<8036617c>] (kernel_init+0xc/0x150)
[<8036617c>] (kernel_init+0xc/0x150) from [<8000e040>] (ret_from_fork+0x14/0x34)
这是因为此时kernel发生了panic,表示没有找到VFS文件系统,但是起码我们的内核是跑起来了,这是让我们有动力进行下一步的基础
五,制作一个最简单的文件系统
我不打算直接去制作一个我们常见的文件系统,如果没有这一步,我们会失去很多乐趣,尽管他很简单,但是我还是先从一个最简单的文件系统走起
step1:制作一个最小的initrd(initial ramdisk)
vim init.c:
#include <stdio.h>
void main()
{
printf("welcome to qemu test init\n");
while(1);
}
step2:编译:
arm-linux-gnueabihf-gcc -o init init.c
我们得到了一个init可执行文件
step3:然后将init制作为cpio:
echo init | cpio -o --format=newc > initramfs
此时你会得到一个initramfs,这就是我们将要用到的文件系统,然后将initramfs拷贝到我们的资源文件夹qemu-image中
step4:然后我们使用qemu引导一下这个文件系统:
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "init=/init console=ttyAMA0" -nographic -initrd ./qemu-image/initramfs
注意看参数的变化,修改init=/int,表明内核启动后引导的第一个应用程序,-initrd ./initramfs表示指定initrd,initramfs就是刚才我们制作的initrd
运行结果:
Freeing unused kernel memory: 192K (80488000 - 804b8000)
Failed to execute /init
Failed to execute /init. Attempting defaults...
Kernel panic - not syncing: No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance.
此时我们观察打印输出,又发生了panic,但是别灰心,一定要分析遇到的问题,我们仔细看下,此时的panic原因,不再是nable to mount root fs on unknown-block 而是No init found,这意味着此时已经加载了文件系统只是没有找到init
这是为什么呢,其实这是因为我们制作的这个init可执行文件有问题,执行失败了,怎么验证呢?,我们可以使用qemu直接执行我们制作的init可执行程序,看有什么问题:
[root@localhost qemu-lab]# qemu-arm init
[root@localhost qemu-lab]#/lib/ld-linux-armhf.so.3: No such file or directory
看完提示,问题清楚了,它没有库,因为我们制作的这个文件系统很简陋,没有库,执行的时候会失败。
所以重新制作:
arm-linux-gnueabihf-gcc -static -o init init.c//使用静态编译,把库给编上
重新打包:
echo init | cpio -o --format=newc > initramfs
然后再引导一次:
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "init=/init console=ttyAMA0" -nographic -initrd ./qemu-image/initramfs
结果:
#0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
Freeing unused kernel memory: 192K (80488000 - 804b8000)
welcome to qemu test init
看到这句welcome to qemu test init就代表ok了,表示已经将我们的init可执行文件已经加载起来了
六,使用busybox制作一个真正的根文件系统
有了步骤四的感受,我们再来步骤五,制作一个真正的根文件系统
step1:下载busybox源码:
wget https://busybox.net/downloads/busybox-1.30.1.tar.bz2
step2:解压:
tar xvf busybox-1.30.1.tar.bz2
step3:配置busybox:配置为静态编译,避免其他库的问题
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm menuconfig
NOTE:此时若报错:
HOSTCC scripts/kconfig/lxdialog/checklist.o
In file included from scripts/kconfig/lxdialog/checklist.c:24:0:
scripts/kconfig/lxdialog/dialog.h:31:20:error
这是因为缺乏库的问题,导致menuconfig执行失败,我们安装库:
yum install ncurses-libs
yum install ncurses-devel
ubuntu使用apt-get isntall 命令安装
配置界面:
Settings --->
[*] Build static binary (no shared libs)
step3:编译busybox:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm -j4
step4:安装:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm install
成功后会有如下打印:
--------------------------------------------------
You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.
--------------------------------------------------
step5:制作根文件系统:
这个过程有些繁琐,我将这个过程写成一个脚本如下:
gedit creat_cpio_rootfs.sh
#!/bin/sh
busybox_folder="./busybox-1.30.1"
rootfs="rootfs"
echo $base_path
if [ ! -d $rootfs ]; then #判断文件是否存在
mkdir $rootfs
fi
cp $busybox_folder/_install/* $rootfs/ -rf
cd $rootfs
if [ ! -d proc ] && [ ! -d sys ] && [ ! -d dev ] && [ ! -d etc/init.d ]; then
mkdir proc sys dev etc etc/init.d
fi
if [ -f etc/init.d/rcS ]; then
rm etc/init.d/rcS
fi
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -t proc none /proc" >> etc/init.d/rcS
echo "mount -t sysfs none /sys" >> etc/init.d/rcS
echo "/sbin/mdev -s" >> etc/init.d/rcS
chmod +x etc/init.d/rcS
if [ -f ../qemu-image/rootfs.img ]; then
rm ../qemu-image/rootfs.img
fi
find . | cpio -o --format=newc > ../qemu-image/rootfs.img
这里要注意一下,确保你的当前目录是有一个之前我提到qemu-image这个存放镜像的目录,否则脚本会执行出错,或者你也可以修改上述脚本适配你的目录
执行脚本:sh creat_cpio_rootfs.sh
脚本执行后会在当前目录下得到一个rootfs目录和在qemu-image目录下得到一个文件rootfs.img
rootfs.img就是我们要引导的文件系统
step6.然后再次引导:
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "root=/dev/ram rdinit=sbin/init console=ttyAMA0" -nographic -initrd ./qemu-image/rootfs.img
此时我们可以看到:
NET: Registered protocol family 17
VFP support v0.3: implementor 41 architecture 3 part 30 variant 9 rev 0
input: AT Raw Set 2 keyboard as /devices/mb:kmi0/serio0/input/input0
rtc-pl031 mb:rtc: setting system clock to 2019-03-11 11:26:19 UTC (1552303579)
ALSA device list:
#0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 43
Freeing unused kernel memory: 192K (80488000 - 804b8000)
Please press Enter to activate this console. input: ImExPS/2 Generic Explorer Mouse as /devices/mb:kmi1/serio1/input/input1
/ # ls
bin etc proc sbin usr
dev linuxrc root sys
/ #
再尝试使用qemu自带的图形界面启动,注意看参数的变化::
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "root=/dev/ram rdinit=sbin/init console=ttyAMA0" -initrd ./qemu-image/rootfs.img
此时我们的整个内核开发环境就搭建完成了,对于文件系统这块,可以根据自己的需求,尝试制作其他的文件系统类型,我这里只是一个最简单的。
环境搭建起来后,可以很方便的验证自己对内核的一些想法,也可以验证学习些内核的debug工具,非常的方便。
本文的目录结构如下:
[root@localhost qemu-lab]# ls
busybox-1.30.1 creat_cpio_rootfs.sh linux-3.10.104 qemu-image rootfs
七.新增使用sd卡的方式去加载镜像
前面的文件系统是一个临时的ram文件系统,没有使用到存储分区,所以我们的引导参数中为-initrd。
这次新增一个从sd卡上加载文件系统,文件系统格式为ext3的加载方式,会使用到存储分区mmcblk0。
文件系统的脚本:create_ext3_rootfs.sh,注意最后2句命令的差别
#!/bin/sh
busybox_folder="./busybox-1.30.1"
rootfs="rootfs"
echo $base_path
if [ ! -d $rootfs ]; then #判断文件是否存在
mkdir $rootfs
fi
cp $busybox_folder/_install/* $rootfs/ -rf
cd $rootfs
if [ ! -d proc ] && [ ! -d sys ] && [ ! -d dev ] && [ ! -d etc/init.d ]; then
mkdir proc sys dev etc etc/init.d
fi
if [ -f etc/init.d/rcS ]; then
rm etc/init.d/rcS
fi
echo "#!/bin/sh" > etc/init.d/rcS
echo "mount -t proc none /proc" >> etc/init.d/rcS
echo "mount -t sysfs none /sys" >> etc/init.d/rcS
echo "/sbin/mdev -s" >> etc/init.d/rcS
chmod +x etc/init.d/rcS
if [ -f ../qemu-image/a9rootfs.ext3 ]; then
rm ../qemu-image/a9rootfs.ext3
fi
#生成一个32M的镜像
dd if=/dev/zero of=../qemu-image/a9rootfs.ext3 bs=1M count=32
mkfs.ext3 ../qemu-image/a9rootfs.ext3
接着执行下面的操作(root下):
mkdir tmpfs
mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
cp -r rootfs/* tmpfs/
sudo umount tmpfs
接着执行命令引导内核:
qemu-system-arm -M vexpress-a9 -kernel ./qemu-image/zImage -append "root=/dev/mmcblk0 console=ttyAMA0" -nographic -sd ./qemu-image/a9rootfs.ext3
此时跑起来后发现文件系统中无法创造文件,提示是只读文件系统,但是mount命令显示分区都挂载为了可读写,后面发现是append参数中的没有指定rw参数导致的,加上后引导的文件系统就是一个可读写的文件系统,新的命令为:
qemu-system-arm -M vexpress-a9 -m 1024M -cpu cortex-a9 -kernel ./qemu-image/zImage -append "root=/dev/mmcblk0 rw rootfstype=ext3 mem=512M rdinit=sbin/init console=ttyAMA0" -nographic -sd ./qemu-image/a9rootfs.ext3