关于RISC-V OS开发 1 OS启动,开始起飞

关于RISC-V OS开发 1 OS启动,开始起飞

76757b44c328d2ef0bab497755596b66.png

33654c0a02331b5f3b3e452a15e8bb0e.png

///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程,想要的同学找我拿就行了免費的,私信我就可以哦~点我头像黑色字体加我地球呺也能领取哦。最近比较闲,带做毕设,带学生参加省级或以上比赛///

a3b1417660be960f27641d892d3d9e37.pngQEMU模拟的是整个SoC,我们需要验证并调试OS,外设是必不可少的。QEMU会把bootloader映射到物理地址空间的0x1000-0xf000的这段ROM中,把RAM映射到0x8000000处。剩下主要是各种外设和支持QEMU自身。

xv6的结构:

b96860ccb818e513b1dca7246da67e2f.png

xv6是宏内核,kernel下的文件(.c/.h/.S)会被编译成一个叫做kernel的二进制文件,然后这个二进制文件会被运行在kernle mode中。

第二个部分是user。这基本上是运行在user mode的程序。这也是为什么一个目录称为kernel,另一个目录称为user的原因。
第三部分叫做mkfs。它会使用fs.img创建一个空的文件镜像,我们会将这个镜像存在磁盘上,这样我们就可以直接使用一个空的文件系统。

makefile会将kernel下的文件编译成.o文件,之后会使用ld链接成可执行文件kernel,并使用objdump创建kernel.asm便于调试。

最后会调用qemu来运行起来xv6。

xv6编译过程

目标qemu有两个依赖文件:$K/kernel和fs.img。其中$K/kernel这样生成:

$K/kernel: $(OBJS) $K/kernel.ld $U/initcode

$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)

$(OBJDUMP) -S $K/kernel > $K/kernel.asm

$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym

$K/kernel就是整个xv6内核的二进制文件。它的依赖文件就是/kernel下的所有内容。.c和.S文件由隐含规则自动生成.o文件,xv6的编译首先就是编译/kernel下的所有.c和.S文件生成相应.o文件,之后再根据kernel.ld进行链接生成kernel二进制文件,并将其反汇编成用于调试的汇编文件。此外对于$U/initcode.S这个文件也要编译、链接、反汇编,具体作用见后文。

再之后是对user文件夹下的用户程序进行编译、链接、反汇编。然后使用makefs把这些用户程序的二进制文件写到fs.img这个磁盘镜像上。最后就可以使用qemu-system-riscv64命令启动虚拟机了。这里本质上是通过C语言来模拟仿真RISC-V处理器。

qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic

-drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

我们来看传给QEMU的几个参数:
-kernel:这里传递的是内核文件(kernel目录下的kernel文件),这是将在QEMU中运行的程序文件。
-m:这里传递的是RISC-V虚拟机将会使用的内存数量
-smp:这里传递的是虚拟机可以使用的CPU核数
-drive:传递的是虚拟机使用的磁盘驱动,这里传入的是fs.img文件

这样,XV6就在QEMU中启动了。QEMU就是个C程序,它通过C语言来完全模拟硬件的行为。

xv6启动过程

RISC-V采用内存映射I/O的方式,主板的设计人员决定了,在完成了虚拟到物理地址的翻译之后,如果得到的物理地址大于0x80000000会走向DRAM芯片,如果得到的物理地址低于0x80000000会走向不同的I/O设备。这是由这个主板的设计人员决定的物理结构。如果你想要查看这里的物理结构,你可以阅读主板的手册,手册中会一一介绍物理地址对应关系。QEMU也会默认0x80000000是物理内存的起始处,它会从这里开始执行指令。而kernel/kernel.ld会把kernel/entry.S中的指令放到0x80000000处,让每个CPU都从这里开始执行。kernel/entry.S的作用就是为每个CPU接下来运行C程序设置了4KB大小的栈。之后会跳到kernel/start.c中的start()函数。它在M模式下进行一些初始化,然后通过mret以S模式进入到main.c中。在main()中,CPU0会完成绝大多数初始化并且分配init进程。接着等待CPU1和CPU2完成自己的一些per-CPU初始化之后,进入scheduler()中,开始选择进程来执行。从此就进入了OS的“死循环”当中,永远不会返回了。

猜你喜欢

转载自blog.csdn.net/danpianji777/article/details/124868191