背景
最近在做Qos的相关开发工作,打算借鉴linux内核中tc的实现。奈何自己阅读代码能力有限,看了几天代码理解还是不够深入,所以打算构建一个内核调试的环境,单步运行加深对tc实现逻辑的理解
工具版本介绍
调试环境主要涉及以下工具,及对应版本号
vmware 16 pro
ubuntu 20.04
qemu 4.2.0
busybox 1.35.0
Linux 5.4.20
内核编译
关于内核编译方法,查看上一篇博客:vmware + ubuntu 20.04 构建内核代码编译环境
需要注意的是在做make menuconfig时按照下面的进行配置。因为是要做tc htb算法的调试,所以将tc相关的功能编译为内核模块。
General setup --->
----> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
Device Drivers --->
[*] Block devices --->
<*> RAM block device support
(65536) Default RAM disk size (kbytes)
Networking support --->
Networking options --->
[*] QoS and/or fair queueing --->
<M> Hierarchical Token Bucket (HTB)
<M> Fair Queue Controlled Delay AQM (FQ_CODEL)
<M> Elementary classification (BASIC)
<M> Universal 32bit comparisons w/ hashing (U32)
<M> Flow classifier
Device Drivers --->
--- Network device support
--- Ethernet driver support
[*] Intel devices
<*> Intel(R) PRO/100+ support
<*> Intel(R) PRO/1000 Gigabit Ethernet support
<*> Intel(R) PRO/1000 PCI-Express Gigabit Ethernet support
使用busybox制作initrd.img、rootfs.img
qemu模拟虚机,除了准备内核,还需要文件系统。需要准备两个文件系统镜像,initrd.img挂载到ramdisk。rootfs.img为真实的挂载磁盘的文件系统。initrd.img和rootfs.img的制作用到了busybox。
busybox编译
设置编译选项
make menuconfig
Busybox Settings --->
Build Options --->
[*] Build BusyBox as a static binary (no shared libs)
编译并安装busybox
make && make install
编译完成后的busybox就安装在源码根目录下的_install目录了
制作initrd.img镜像
创建虚机启动目录
mkdir ~/kernel
cd ~/kernel
再虚机启动目录下创建busybox目录,将_install目录下的内容copy至busybox下
mkdir busybox
cd busybox
cp ../../busybox-1.35.0/* ./ -rf
查看busybox安装的内容,作为文件系统,还缺少一些必须的目录,创建这些目录
ls
# bin linuxrc sbin usr
mkdir dev etc proc sys tmp rootfs
创建设备
mknod console c 5 1
mknod ram b 1 0
在busybox目录下,创建init脚本
pwd
# /home/xxxx/kernel/busybox
vi init
#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
mount /dev/sda /rootfs
echo "" > /proc/sys/kernel/hotplug
umount -f /proc
umount -f /sys
echo "begin to switch rootfs"
exec /sbin/switch_root /rootfs /linuxrc
为init添加可执行权限
chmod +x init
生成镜像
find .|cpio -o --format=newc > ../initrd.img
制作rootfs.img镜像
创建镜像,并格式化为ext4
pwd
# /home/xxxx/kernel/busybox
dd if=/dev/zero of=rootfs.img bs=1M count=64
mkfs.ext4 rootfs.img
mount镜像,并在镜像里添加所需文件
mkdir rootfs
mount rootfs.img rootfs
cd rootfs
# rootfs.img和initrd.img内容类似,所以先将initrd里的内容copy过来,再做修改
cp ../busybox/* ./ -rf
修改rootfs目录下的内容
rm -rf init
mkdir etc/init.d
vi etc/init.d/rcS
#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
umount目录
umount rootfs
使用qemu启动虚机
###编译qemu
解压qemu,并进入qemu源码目录
./configure --target-list=x86_64-softmmu,x86_64-linux-user
编译并安装qemu
make & make install
###启动虚机
将内核编译镜像copy至,虚机启动目录kernel下
cp ../linux-5.4.20/arch/x86_64/boot/bzImage ./
执行启动虚机命令
qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init" -hdb rootfs.img -nographic
命令解释
-kernel bzImage 指定内核镜像
-initrd initrd.img 指定initrd镜像
-append “console=ttyS0 rdinit=/init” 执行initrd.img镜像里的/init脚本,也就是上文创建的/home/zhouzhx/kernel/busybox/init脚本
-hdb rootfs.img 将rootfs.img挂载为虚机内/dev/sda
虚机启动流程
先mount initrd.img,并执行init脚本。
init脚本里,比较重要的内容
mount /dev/sda /rootfs #将/dev/sda挂载到initrd.img里的/rootfs目录下
exec /sbin/switch_root /rootfs /linuxrc #将当前根目录切换到/rootfs下,并执行/linuxrc,也就是执行rootfs.img镜像下的/linuxrc命令。
linuxrc命令作用,执行当前文件系统/etc/init.d/rcS脚本,并启动shell。
添加网络设备
通过以上几步,实际上已经可以启动起来一个虚机。但是我的目的时调试tc htb代码,所以还需要创建网络接口。
创建/etc/qemu-ifup、/etc/qemu-ifdown脚本
vi /etc/qemu-ifup
#! /bin/sh
switch=br0
ifconfig $1 up
brctl addbr br0
brctl addif ${switch} $1
ifconfig ${switch} 192.168.101.200 up
vi /etc/qemu-ifdown
#! /bin/sh
switch=br0
ifconfig $1 down
ifconfig ${switch} down
brctl delif ${switch} $1
brctl delbr ${switch}
修改rootfs.img下的etc/init.d/rcS文件
mount rootfs.img rootfs
cd rootfs
vi etc/init.d/rcS
#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0
umount rootfs
修改虚机启动命令
qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init" -hdb rootfs.img -nographic -netdev tap,id=nd0,ifname=tap0 -device e1000,netdev=nd0
虚机启动后检查网络设备,如图可以看到网络设备启动正常,且能ping通物理机的br0网桥地址
命令解释
-netdev tap,id=nd0,ifname=tap0 #在宿主机上创建tap设备,设备id为nd0,设备名为tap0。此命选项会调用宿主机上的/etc/qemu-ifup脚本进行tap设备的创建
-device e1000,netdev=nd0 #虚机模拟e1000网卡设备,指定网络设备的另一端为宿主机上的id为nd0的设备也就是tap0
此时虚机内部只是模拟了物理网卡设备,并没有创建设备
所以rootfs.img根文件系统的/etc/init.d/rcS需要添加启动网络设备和添加路由的命令:
ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0
替换虚机内的tc命令
为了调试内核tc功能,用户态需要使用tc命令。在busybox创建的文件系统里,tc功能只能查看不能添加qdisc等。
所以将ubuntu宿主机上的tc命令替换掉busybox里的tc命令。需要cp至rootfs的bin目录下也就是和busybox可执行文件同目录
mount rootfs.img rootfs
rm -rf tc
cp /usr/sbin/tc rootfs/bin/
查看ubuntu宿主机tc命令的依赖,并将依赖copy至rootfs下的相同目录下
ldd /sbin/tc
linux-vdso.so.1 (0x00007fff34e73000)
libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007f718ad1c000)
libmnl.so.0 => /lib/x86_64-linux-gnu/libmnl.so.0 (0x00007f718ab16000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f718aafc000)
libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f718aaf3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f718a9a4000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f718a99e000)
libxtables.so.12 => /lib/x86_64-linux-gnu/libxtables.so.12 (0x00007f718a98a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f718a798000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f718a77c000)
/lib64/ld-linux-x86-64.so.2 (0x00007f718adda000)
将内核编译出的以下模块,添加到rootfs /lib/modules目录下
cd rootfs/lib/modules
cp /lib/modules/5.4.20/kernel/net/sched/sch_htb.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_basic.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_u32.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_flow.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/sch_fq_codel.ko ./
修改rootfs下的etc/init.d/rcS,添加加载模块的操作
vi etc/init.d/rcS
#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
insmod ${MODULES}/sch_htb.ko
insmod ${MODULES}/cls_basic.ko
insmod ${MODULES}/cls_u32.ko
insmod ${MODULES}/cls_flow.ko
insmod ${MODULES}/sch_fq_codel.ko
ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0
启动虚机,检查tc命令是否正常。如下图所示tc可以正确的添加htb类型的qdisc
使用gdb进行调试
使用gdb调试时,需要修改qemu启动参数,添加-smp 1和-s、-S三个选项。-append参数里添加nokaslr
qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init nokaslr" -hdb rootfs.img -nographic -netdev tap,id=nd0,ifname=tap0 -device e1000,netdev=nd0 -smp 1 -s -S
参数解释
-s shorthand for -gdb tcp::1234 启动1234端口给gdb远程调试,指的是宿主机的1234端口
-S freeze CPU at startup (use ‘c’ to start execution) 内核启动时挂起
# 进入gdb后,执行以下命令链接qemu进行调试
target remote localhost:1234