很多教程,写作模式都是 前面N多步操作和配置,最后一步开宝箱看结果。
这有点儿像撞大运, 八成会失败, 失败后也不知道从何查起。
我们期望任何事情,都是一步一步来,步子不要太大,每一步是否成功了,取得了什么效果,都是可验证的。
这样哪步失败了,也好排查。
另外,即使后面某一步卡壳了,但前面的步骤还是学会了。

一、创建空磁盘

qemu-img create -f raw gf.os 100M

hexdump -C -n 512 gf.os #可以看到每个字节都是0,也就是空白磁盘

二、用空磁盘启动

qemu-system-x86_64 -hda gf.os -nographic

打印信息如下

SeaBIOS (version 1.13.0-2.el8)

iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F90A50+07ED0A50 CA00                                                                               

Booting from Hard Disk...
Boot failed: not a bootable disk

Booting from Floppy...
Boot failed: could not read the boot disk

Booting from DVD/CD...
Boot failed: Could not read from CDROM (code 0003)
Booting from ROM...
iPXE (PCI 00:03.0) starting execution...ok
iPXE initialising devices...ok

iPXE 1.0.0+ -- Open Source Network Boot Firmware -- http://ipxe.org
Features: DNS HTTP iSCSI TFTP VLAN AoE ELF MBOOT PXE bzImage Menu PXEXT

net0: 52:54:00:12:34:56 using 82540em on 0000:00:03.0 (open)
  [Link:up, TX:0 TXE:0 RX:0 RXE:0]
Configuring (net0 52:54:00:12:34:56).............. ok
net0: 10.0.2.15/255.255.255.0 gw 10.0.2.2
net0: fec0::5054:ff:fe12:3456/64 gw fe80::2
net0: fe80::5054:ff:fe12:3456/64
Nothing to boot: No such file or directory (http://ipxe.org/2d03e13b)
No more network devices

No bootable device.  

另起一个终端,杀掉qemu
pkill -9 qemu-system

三、对磁盘做一下分区。

从前面的信息看,默认走的是bios启动
fdisk默认也是bios分区,如果要创建gpt分区,需要先敲m帮助看一下,g是设置为gpt分区格式,后面创建分区,都是同样的命令n,创建出来的结果也可以对比一下,有一点差别。

用n创建分区,创建一个主分区即可,w保存并退出。

hexdump -C -n 512 gf.os 可以查看fdisk到底写入了啥,就是在0扇区写了分区信息,以及0分区最后的魔数 55aa

四、再次启动qemu

qemu-system-x86_64 -hda gf.os -nographic

打印信息如下

SeaBIOS (version 1.13.0-2.el8)

iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F90A50+07ED0A50 CA00
                                                                               
Booting from Hard Disk...

五、格式化文件系统,安装grub

sudo losetup -f --show gf.os
# 输出为 /dev/loop0

sudo kpartx -av /dev/loop0
# 此时会生成 /dev/mapper/loop0p1

# 不用时,可以通过下面的命令释放
# sudo kpartx -d /dev/loop0
# sudo losetup -d /dev/loop0

sudo mkfs.ext4 /dev/mapper/loop0p1

sudo mkdir -p /mnt/rootfs
sudo mount /dev/mapper/loop0p1 /mnt/rootfs



sudo grub2-install --target=i386-pc --root-directory=/mnt/rootfs --boot-directory=/mnt/rootfs/boot /dev/loop0
--此时,grub 写了0扇区,2~63扇区, 还有文件系统中的grub2目录  /mnt/rootfs/boot/grub2

sudo umount /mnt/rootfs

六、再次启动qemu

qemu-system-x86_64 -hda gf.os -nographic

打印信息如下

SeaBIOS (version 1.13.0-2.el8)


iPXE (http://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F90A50+07ED0A50 CA00
                                                                               

Booting from Hard Disk...
..
error: ../../grub-core/disk/i386/pc/biosdisk.c:546:failure reading sector 0x0 fr
om `fd0'.
error: ../../grub-core/kern/disk.c:237:disk `lvmid/IQGNsA-CSsQ-2A7y-rNYy-VbQx-tj
WB-S12NQ8/VYObIf-KSbT-k4Mi-gFY9-sPDE-3YE8-wjpdkY' not found.
Entering rescue mode...
grub rescue> 
grub rescue> 
grub rescue> 

七、拷贝本机内核,编辑grub.cfg

sudo mount /dev/mapper/loop0p1 /mnt/rootfs

下面拷贝内核,编辑grub.cfg
sudo cp /boot/vmlinuz-4.19.112-2.el8.x86_64 /mnt/rootfs/boot/
sudo vi /mnt/rootfs/boot/grub2/grub.cfg
menuentry "My GF OS" {
    set root=(hd0,msdos1)
    linux /boot/vmlinuz-4.19.112-2.el8.x86_64 root=/dev/sda1 rw console=ttyS0
}

sync

sudo umount /mnt/rootfs

八、再次启动qemu

qemu-system-x86_64 -hda gf.os -nographic
内核启动了,打印信息如下,缺少init

[ 5.979636] —[ end Kernel panic - not syncing: No working init found.

九、拷贝busybox

sudo mount /dev/mapper/loop0p1 /mnt/rootfs
ls code/gitcode/busybox/gfos   # 提前用默认配置编译busybox,编译之前要改为静态链接
bin  sbin  usr

cp -r code/gitcode/busybox/gfos/* /mnt/rootfs/
rm /mnt/rootfs/sbin/init  #删除这个init程序后,内核会把shell当做init

sudo umount /mnt/rootfs

十、再次启动qemu

qemu-system-x86_64 -hda gf.os -nographic

打印如下

[    5.401956] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    5.403218] rodata_test: all tests were successful
[    5.403786] Run /sbin/init as init process
[    5.407514] Run /etc/init as init process
[    5.408012] Run /bin/init as init process
[    5.411401] Run /bin/sh as init process
/bin/sh: can't access tty; job control turned off
~ # ls
bin         boot        lost+found  sbin        usr
~ # ps
PID   USER     TIME  COMMAND
ps: can't open '/proc': No such file or directory
~ # 

#ls命令可以使用,ps命令不能使用

#手工创建 /proc文件夹,然后挂载 mount -t proc none /proc
#ls /proc 就可以看到进程信息了, ps 也可以看到进程了。top命令也可以用了。

~ # mkdir /sys
~ # mount -t sysfs none /sys
~ # ls /sys
block       class       devices     fs          kernel      power
bus         dev         firmware    hypervisor  module

~ # mkdir dev
~ # mount -t devtmpfs devtmpfs /dev
~ # ls /dev
autofs              tty22               tty7
bsg                 tty23               tty8
console             tty24               tty9
cpu                 tty25               ttyS0
cpu_dma_latency     tty26               ttyS1
full                tty27               ttyS10
hpet                tty28               ttyS11
hwrng               tty29               ttyS12
input               tty3                ttyS13
kmsg                tty30               ttyS14
mapper              tty31               ttyS15
mcelog              tty32               ttyS16
md0                 tty33               ttyS17
mem                 tty34               ttyS18
memory_bandwidth    tty35               ttyS19
network_latency     tty36               ttyS2
network_throughput  tty37               ttyS20
null                tty38               ttyS21
nvram               tty39               ttyS22
port                tty4                ttyS23
ptmx                tty40               ttyS24
random              tty41               ttyS25
raw                 tty42               ttyS26
rtc0                tty43               ttyS27
sda                 tty44               ttyS28
sda1                tty45               ttyS29
sg0                 tty46               ttyS3
sg1                 tty47               ttyS30
snapshot            tty48               ttyS31
sr0                 tty49               ttyS4
tty                 tty5                ttyS5
tty0                tty50               ttyS6
tty1                tty51               ttyS7
tty10               tty52               ttyS8
tty11               tty53               ttyS9
tty12               tty54               urandom
tty13               tty55               usbmon0
tty14               tty56               vcs
tty15               tty57               vcs1
tty16               tty58               vcsa
tty17               tty59               vcsa1
tty18               tty6                vcsu
tty19               tty60               vcsu1
tty2                tty61               vga_arbiter
tty20               tty62               zero
tty21               tty63


~ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/root                94069     18027     68947  21% /
devtmpfs                 48644         0     48644   0% /dev

十一、优化

到上一步,这个系统已经可以基本运行了。
但是挂载的动作是手工做的,init也是用sh代替的。
下面启动真的init

sudo mount /dev/mapper/loop0p1 /mnt/rootfs

# 创建inittab,在表中第一行配置要先启动/etc/init.d/rcS脚本
vi etc/inittab

# 系统启动时最先执行一次,阻塞后续动作
::sysinit:/etc/init.d/rcS

# 在控制台提供一个交互式 Shell,崩溃或退出后自动重启
# 因为你用了 -nographic,所以指定 ttyS0 作为控制台
#::respawn:/sbin/getty -L ttyS0 115200 vt100
ttyS0::respawn:/bin/sh 

# 按下 Ctrl+Alt+Del 时重启
::ctrlaltdel:/sbin/reboot

# 系统关机时执行的操作
::shutdown:/bin/umount -a -r
# 创建启动脚本
vi etc/init.d/rcS
#!/bin/sh

# 挂载虚拟文件系统(让内核和用户空间能沟通)
mount -t proc none /proc
mount -t sysfs none /sys

# 动态创建设备节点(强烈建议加上,这样就不需要手动 mknod 了)
mount -t devtmpfs devtmpfs /dev

# 设置主机名
hostname gf-os

# 打印启动成功信息
echo "GF-OS is up and running!"

最后,要给这个脚本设置权限

chmod +x etc/init.d/rcS

sudo umount /mnt/rootfs

这次启动,各项功能就正常了。
ps、vi、reboot、poweroff 都可以使用

十二、再优化(支持登陆)

sudo mount /dev/mapper/loop0p1 /mnt/rootfs

1、
inittab中,改为首先启动getty,这个命令会调用login,以及sh
::respawn:/sbin/getty -L ttyS0 115200 vt100
#ttyS0::respawn:/bin/sh

2、创建root用户的目录,把root的密码设置为空
mkdir /mnt/rootfs/root  目录
echo 'root:x:0:root' > /mnt/rootfs/etc/group
echo 'root:x:0:0:root:/root:/bin/sh'>/mnt/rootfs/etc/passwd
echo 'root::20634:0:99999:7:::'>/mnt/rootfs/etc/shadow
chmod 600 /etc/shadow

sudo umount /mnt/rootfs

然后启动qemu进入linux系统后,就会提示输入登陆用户名和密码

然后可以在linux中用passwd命令修改root密码。

十三、网络

下载linux内核,使用默认配置(默认也会编译这个网卡)

make defconfig

Device Drivers -> Network device support -> Ethernet driver support -> Intel devices
找到 Intel® PRO/1000 PCI-Express Gigabit Ethernet support

make -j$(nproc)

编译好的内核拷贝到磁盘镜像中
sudo cp arch/x86/boot/bzImage /mnt/rootfs/boot/

启动qemu
qemu-system-x86_64 -hda gf.os -nographic -netdev user,id=net0 -device e1000,netdev=net0

进去后可以看到三个网卡(一个环回网卡,一个eth0,还有一个sit0)
但网卡都没有启动,且无ip地址

ip link set lo up
ip addr add 127.0.0.1/8 dev lo

ip link set eth0 up
ip addr add 10.0.2.15/24 dev eth0

ip route add default via 10.0.2.2 dev eth0

然后就可以ping通127.0.0.1和10.0.2.15, 另外网关10.0.2.2也可以ping通

但宿主机10.137.239.107和其它机器还是ping不通
这是因为 SLIRP 只实现了 TCP/UDP 协议栈,没有实现 ICMP 协议

虚拟机访问宿主机

在宿主机内开启一个tcp服务
(base) bin$ ./busybox nc -lvp 8080
listening on [::]:8080 …
connect to [::ffff:127.0.0.1]:8080 from localhost:60872 ([::ffff:127.0.0.1]:60872)
hello

在虚拟机中连接宿主机(对,就用10.0.2.2)的tcp服务,并发送消息
/bin # nc 10.0.2.2 8080
hello

如果宿主机上跑了 HTTP 服务,虚拟机内直接 wget http://10.0.2.2:端口/url 即可验证

虚拟机访问外部网络

在虚拟机和宿主机中都可以访问这个外部服务器

/bin # ./busybox nc 10.230.39.86 22
SSH-2.0-OpenSSH_8.5

(base) bin$ ./busybox nc 10.230.39.86 22
SSH-2.0-OpenSSH_8.5

宿主机访问虚拟机

默认情况下不行,因为 SLIRP 是单向 NAT(只出不进)。你需要通过 hostfwd 端口转发来实现:
hostfwd=tcp::10022-:22
端口转发规则:将宿主机的 TCP 10022 端口转发到虚拟机的 TCP 22 端口(SSH)。格式为 协议:宿主机IP:宿主机端口-虚拟机IP:虚拟机端口,IP 留空表示 0.0.0.0(监听所有地址)

qemu-system-x86_64 -hda gf.os -nographic -netdev user,id=net0,hostfwd=tcp::10022-:22 -device e1000,netdev=net0

在虚机中,先把网卡和ip配好,然后在22端口启动tcp服务

~ # nc -lvp 22
listening on [::]:22 ...

connect to [::ffff:10.0.2.15]:22 from [::ffff:10.0.2.2]:35403 ([::ffff:10.0.2.2]:35403)
hello

在宿主机中,检查转发端口已经开启,然后连接并发送hello消息

(base) bin$ netstat -nat|grep 10022
tcp        0      0 0.0.0.0:10022           0.0.0.0:*               LISTEN     

(base) bin$ ./busybox nc 127.0.0.1 10022
hello

更多推荐