OS: ubuntu 18.04 LTS x64
Qemu
windows qemu
https://qemu.weilnetz.de/w64/2023/
Install
需要模拟arm64平台,所以要安装aarch64版本sudo apt-get install qemu-system-aarch64
Cross-compile
安装交叉编译工具链,需要把一些依赖的其他库安装好
sudo apt-get install flex bison build-essential pkg-config libglib2.0-dev libpixman-1-dev libssl-dev
这里不使用系统仓库自带的gcc-aarch64-linux-gnu
,仓库里面的gcc版本不好调整为自己需要的,所以直接下载Linaro网站的.
Linaro网站提供了多个平台的交叉编译工具,也一直有更新,ubuntu 64位的系统选择x86_64_aarch64-linux-gnu
版本,我用的是gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu
下载到开发目录arm下后,解压
1 | $ cd arm |
Busy Box
下载busybox代码也到arm目录下,解压
1 | $ cd arm |
进入busybox根目录,先配置当前的环境变量为arm64
1 | $ export ARCH=arm64 |
执行make menuconfig
打开编译配置菜单,其中做以下配置
- 勾选静态编译
Settings->Build static binary (no shared lib)
- 指定交叉编译器为:
/home/arm/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
- General Configuration –> Dont use /usr
- Busybox Libary Tuning–> 勾选:[*]Username completion、[*]Fancy shell prompts 、[*]Query cursor position from terminal
保存配置后,会更新.config
编译配置文件,可以打开确认编译信息的正确性
开始编译make -j4
最后执行make install
在busybox根目录生成_install
目录
Linux kernel
Linux Kernel下载
Kernel官网下载4.9.11版本的内核,不能下载太旧的版本,例如3.19和最新的gcc7.4不兼容,编译总是失败,提示COMPILE版本的错误信息。最好选择长期支持的版本,这样功能更稳定一些。
解压内核后配置环境变量后,可以对内核进行配置
在执行make menuconfig
时会遇到
In file included from scripts/kconfig/mconf.c:23:0:
scripts/kconfig/lxdialog/dialog.h:38:20: fatal error: curses.h: No such file or directory
include CURSES_LOC
compilation terminated.
make[1]: * [scripts/kconfig/mconf.o] Error 1
make: * [menuconfig] Error 2
此时需要安装ncurses devel sudo apt-get install libncurses5-dev
1 | tar -xvf linux-4.19.11.tar |
配置Kernel
根据需要把支持的设备勾选,不想支持的就不要勾选,否则编译时间太长.可以第一次多裁减一些,如果需要,后面在配置增加功能,把每一次修改的.config
文件版本管理起来
Platform Selection只选择ARMv8 based Freescale Layerscape SoC family
和ARMv8 software model (Versatile Express)
Device Driver中普通程序不要支持的也可删除
因为要通过内存镜像启动内核,还需要配置使用内存文件系统
General setup->Initial RAM filesystem and RAM disk (initramfs/initrd) support
Device Drivers->Block devices-><*> RAM block device support
,其中配置1个block(1) Default number of RAM disks
内存大小为128M(131072) Default RAM disk size (kbytes)
如果需要调试内核,需要打开调试信息
1 | kernel hacking--> |
配置完成后,执行make -j12
开始编译内核,时间需要1个多小时
Run kernel
创建根文件系统
在编译内核的过程中,可以准备内核启动的根文件系统,这里参考了摩斯电码的脚本文件,做了简单修改
1 |
|
The loop device is a block device that maps its data blocks not to a
physical device such as a hard disk or optical disk drive, but to the
blocks of a regular file in a filesystem or to another block device. This can be useful for example to provide a block device for a filesystem image stored in a file, so that it can be mounted with the mount(8)
command
其中etc目录结构如下
1 | etc |
/etc/init.d/rcS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
#mount -n -t usbfs none /proc/bus/usb
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
mkdir -p /var/lock
ifconfig lo 127.0.0.1
ifconfig eth0 192.168.43.202 netmask 255.255.255.0 broadcast 192.168.43.255
/bin/hostname -F /etc/sysconfig/HOSTNAME/etc/sysconfig/HOSTNAME
1
aarch64
/etc/fstab
1
2
3
4
5
6
7
8#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
var /dev tmpfs defaults 0 0
ramfs /dev ramfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0/etc/inittab
1
2
3
4
5
6# /etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::restart:/sbin/init/etc/profile
1
2
3
4
5
6
7
8
9USER="root"
LOGNAME=$USER
export HOSTNAME=`/bin/hostname`
export USER=root
export HOME=/root
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
对于生成的image文件可以通过mkimage -l ramdisk.img
查看文件信息
1 | Image Name: ramdisk |
使用Qemu运行
- run.sh
1
2
3
4
5
6
7
8
9
10
11
12qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-smp 2 \
-m 1024M \
-kernel ./linux-4.19.11/arch/arm64/boot/Image \
-nographic \
-append "root=/dev/ram0 rw rootfstype=ext4 console=ttyAMA0 init=/linuxrc ignore_loglevel" \
-initrd ./rootfs/ramdisk.img \
-netdev tap,helper=/usr/lib/qemu/qemu-bridge-helper,id=hn0 -device virtio-net-pci,netdev=hn0,id=nic1 \
-fsdev local,security_model=passthrough,id=fsdev0,path=/home/edison/develop/arm/nfsroot \
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare
共享目录
使用9p共享目录,内核在编译时默认是支持的
新建目录mkdir nfsroot
启动时这两个选项
1 | -fsdev local,security_model=passthrough,id=fsdev0,path=/home/edison/arm/nfsroot \ |
指明了共享目录的位置
在内核启动起来之后,把共享目录挂载上来,就可以看到文件了
也可以把这个mount添加到内核启动程序中,不用每次都执行一遍
1 | [root@aarch64 ]# mount -t 9p -o trans=virtio,version=9p2000.L hostshare /mnt |
Network with Qemu
使用网桥方式,可以让qemu和host主机之间直接进行网络通信
- 安装网桥工具
sudo apt install bridge-utils
和sudo apt install uml-utilities
- 新建一个网桥
sudo brctl addbr br0
网桥会在重启后消失 - 启用此网桥
sudo ip link set br0 up
- 确认
/etc/qemu/bridge.conf
中allow br0
- 给帮助程序权限
sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper
- qemu 启动时增加
-netdev tap,helper=/usr/lib/qemu/qemu-bridge-helper,id=hn0 -device virtio-net-pci,netdev=hn0,id=nic1
- qemu 启动后会自动在host主机上新建一个tap0的网卡
- 使用
brctl show
查看br0和tap0已经关联上了 - 把host主机的一个网卡也和br0关联起来,主机wifi的网卡由于是dhcp获取的ip,无法与br0绑定,需要使用有线网卡绑定
sudo brctl addif br0 enp5s0
1 | bridge name bridge id STP enabled interfaces |
- host设置各个网卡和网桥的ip,此处需要注意先设置br0的ip和tap0的ip,再设置host网卡的ip,否则guest里面无法ping外部主机的ip,最终使br0的mac和tap0的mac地址相同,具体原因还没来及查
sudo ifconfig br0 192.168.43.210 netmask 255.255.255.0
sudo ifconfig tap0 192.168.43.51 netmask 255.255.255.0
sudo ifconfig enp5s0 192.168.43.50 netmask 255.255.255.0
1 | br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 |
- guest设置eth0的ip 与br0的ip在一个网段内 例如 192.168.43.202
qemu-bridge-helper
使用/etc/qemu-ifup
和/etc/qemu-ifdown
来控制虚拟虚拟机网卡tap0启动
- 如果想使用其他定义的网桥,
/etc/qemu/bridge.conf
中添加allow qemubr0
1
2qemu linux.img
-netdev tap,helper="/usr/local/libexec/qemu-bridge-helper --br=qemubr0",id=hn0 -device virtio-net-pci,netdev=hn0,id=nic1
Gdbserver
到GDB网站下载gdb的源码,其中gdbserver在里面的子目录gdbserver中,进入gdbserver的源码目录
1 | $ cd ~/develop/arm/gdb-8.3/gdb/gdbserver |
把编译出来的gdbserver放到共享目录
qemu 作为客户机执行
#./gdbserver 192.168.43.202:10000 all
192.168.43.202 is guest ip address
output:
1 | Process /mnt/code/all created; pid = 1066 |
主机host run:
/home/edison/develop/arm/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gdb all
in gdb console, connect to the guest gdbserver:
1 | (gdb) target remote 192.168.43.202:10000 |
测试程序
1 |
|
- 简单的makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32# marcros
CROSS_COMPILE := /home/edison/develop/arm/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc
LD := $(CC) -nostdlib
CPP := $(CC) -E
CCFLAGS := -Wall
DBGFLAG := -g
CCOBJFLAG := $(CCFLAG) -c
# Path
BIN_PATH := bin
OBJ_PATH := obj
SRC_PATH := src
DBG_PATH := debug
# compile
TARGET_NAME := main
TARGET := $(BIN_PATH)/$(TARGET_NAME)
TARGET_DEBUG := $(DBG_PATH)/$(TARGET_NAME)
all: main.o
$(CC) -o $@ $^
main.o: main.cpp
$(CC) $(CCOBJFLAG) $(DBGFLAG) $^
clean:
rm -rf *.o all
启动运行信息
1 | [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034] |