ZYB ARTICLES REPOS

操作系统实验二

环境问题

在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作为根文件系统为例

  1. 在initramfs阶段创建一个新的临时目录 /mnt/root
  2. /dev/sda挂载到/mnt/root
  3. 转换部分文件系统
    
    mount --move /proc /mnt/root/proc
    mount --move /sys /mnt/root/sys
    mount --move /dev /mnt/root/dev
    
  4. /mnt/root/bin/下再生成一次busybox命令
  5. /mnt/sbin/init创建一个可执行的脚本init
    
    #!/bin/busybox sh
    /bin/busybox sh
    
  6. 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'