操作系统实验二
环境问题
在ARM平台的iMac上可以直接装个amd64
的fedora docker镜像,而不用再搞个aarch64
平台镜像了,也就不用跨平台交叉编译了。
方法如下:
设置docker,在docker的Settings界面,启用Use Virtualization framework
, 启用 Use Rosetta fo x86/amd64 emulation on Apple Sillicon
获取fedora amd64平台的特定版本镜像
docker pull --platform=linux/amd64 fedora:latest
重新命个更清晰的名字
docker tag fedora:latest fedora_amd64:39
把容易混淆的名字删掉
docker rmi fedora:latest
如果想看fedora:latest
的具体版本
docker run --rm fedora:latest cat /etc/os-release
如果想看fedora镜像支持的平台
docker manifest inspect fedora
启动一个容器
docker run --privileged -itv ~/workspace:/root/workspace --name fedora39_amd64 fedora_amd64:39
要安装的软件包参考 《操作系统实验》
关于busybox(1.36.1)编译,如果tc编译不过,可以不要这个。
Networking Utilities --->
[ ] tc (8.3 kb)
调试
在宿主机获取bzImage
docker cp fedora39_amd64:/root/linux/linux-6.6.27/arch/x86/boot/bzImage .
让docker帮助生成initramfs
#!/bin/bash
container=fedora39_amd64
docker exec -it fedora39_amd64 bash -c "cd /root/linux && rm -rf initramfs && mkdir -p initramfs/bin/ && cp busybox-1.36.1/busybox initramfs/bin"
docker cp init $container:/root/linux/initramfs
docker exec -it $container bash -c "cd /root/linux/initramfs && find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz"
docker cp $container:/root/linux/initramfs.cpio.gz .
生成initramfs
所需要的init
脚本
#!/bin/busybox sh
bb=/bin/busybox
for cmd in $($bb --list); do
$bb ln -s $bb /bin/$cmd
done
mkdir -p /proc
mount -t proc none /proc
sh
用qemu启动
qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "console=ttyS0 rdinit=/init" -serial mon:stdio
启动后执行mount
得到如下结果
rootfs on / type rootfs (rw,size=43640k,nr_inodes=10910)
none on /proc type proc (rw,relatime)
此时还未挂载devtmpfs
到/dev
我们可以
mkdir /dev
mount -t devtmpfs none /dev
就会有如下结果
rootfs on / type rootfs (rw,size=43640k,nr_inodes=10910)
none on /proc type proc (rw,relatime)
none on /dev type devtmpfs (rw,relatime,size=43640k,nr_inodes=10910,mode=755)
如果不挂载,也可以通过cat /proc/devices
看到内核已经注释的设备
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
21 sg
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
202 cpu/msr
203 cpu/cpuid
226 drm
249 hidraw
250 usbmon
251 bsg
252 ptp
253 pps
254 rtc
Block devices:
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 virtblk
254 mdp
259 blkex
对于devtmpfs的理解
我们的这个initramfs其实是Linux系统启动时加载的一个临时的根文件系统,它包含了一些基本的工具和脚本,这些工具和脚本可以运行在这个临时的根文件系统中,用于实际的根文件系统挂载之前执行一些初始化的任务。
包括加载必要的内核模块、检测硬件设备、设置网络等。在这些初始化任务完成后,initramfs里的工具或脚本会挂载实际的根文件系统(例如,将某个硬盘分区做为根文件系统)
在initramfs的这个临时阶段,用户空间的程序就可以借助devtmpfs来访问设备节点,以帮助其完成初始化任务。
在initramfs将硬盘某分区挂载为/
之前,这个系统已经有一个目录树了,从initramfs的目录树到最终硬盘的根目录树的转换过程是什么样的呢?我们假设以/dev/sda
作为根文件系统为例
- 在initramfs阶段创建一个新的临时目录
/mnt/root
- 将
/dev/sda
挂载到/mnt/root
- 转换部分文件系统
mount --move /proc /mnt/root/proc mount --move /sys /mnt/root/sys mount --move /dev /mnt/root/dev
- 在
/mnt/root/bin/
下再生成一次busybox
命令 - 在
/mnt/sbin/init
创建一个可执行的脚本init
#!/bin/busybox sh /bin/busybox sh
- exec switch_root /mnt/root /sbin/init
就可以把根目录切换到/mnt/root了。这里不用switch_root
而是使用pivot_root
的话会提示pivot_root: Invalid argument
。这是因为:initramfs is rootfs: you can neither pivot_root rootfs, nor unmount it.
出自ramfs-rootfs-initramfs.txt
switch_root
是busybox提供的一个命令。
这个在docker上执行貌似不会生成/dev/loop0p1,可以在真linux上按下面的方法创建一个
dnf install parted
dd if=/dev/zero of=hd.img bs=512 count=409600
fdisk hd.img
losetup -fP --show hd.img
partprobe /dev/loop0
mke2fs /dev/loop0p1
losetup -d /dev/loop0
将硬盘自动挂载为根文件系统
#!/bin/busybox sh
bb=/bin/busybox
for cmd in $($bb --list); do
$bb ln -s $bb /bin/$cmd
done
# 检测并挂载根文件系统
echo "detecting root filesystem on $root_dev..."
# 要先挂载devtmpfs才能检测
mkdir -p /proc
mkdir -p /dev
mount -t proc none /proc
mount -t devtmpfs none /dev
root_dev=/dev/sda1
mnt_root=/mnt/root
if [ ! -b $root_dev ]; then
echo "no $root_dev to mount"
sh
exit 0
fi
mkdir -p $mnt_root
mount /dev/sda1 $mnt_root
umount /dev
umount /proc
mkdir -p $mnt_root/sbin
mkdir -p $mnt_root/bin
cp -d /bin/* /mnt/root/bin/
# 为硬盘根文件系统准备初始化脚本
cat << 'EOF' > $mnt_root/sbin/init
#!/bin/busybox sh
echo "root dir switched"
mkdir /proc
mkdir /dev
mkdir /sys
mount -t proc none /proc
mount -t devtmpfs none /dev
mount -t sysfs none /sys
sh
EOF
chmod +x $mnt_root/sbin/init
# 切换根文件系统
exec switch_root /mnt/root /sbin/init
按上面的写法如果启动qemu的命令是qemu-system-x86_64 -kernel bzImage -initrd initramfs.cpio.gz -append "earlyprintk=serial,ttyS0 console=ttyS0 nokaslr rdinit=/init root=/dev/sda1" -serial mon:stdio -hda hd.img
也就是说传递给console的是ttyS0
的话,这个shell也会在ttyS0
启动。
如果想在tty1
也就是qemu窗口上启动shell,就需要改一下脚本。
把sh
换成setsid /bin/busybox sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1'