基于qemu和busybox编写最简linux

一、创建空磁盘

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 mkdir -p /mnt/rootfs
sudo mount /dev/mapper/loop0p1 /mnt/rootfs
sudo mkfs.ext4 /dev/mapper/loop0p1


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
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不通,提示

宿主机理论上是不通的,因为qemu默认是nat模式

其它机器也不通,就不知道原因了,AI也没有分析出来