操作系统

xv6Unix 操作系统的一种简化实现,这里着重于研究代码的实现。

下面先了解一下 xv6 所用到的启动扇区 bootblock 文件、内核代码 kernel 文件和磁盘影像文件。然后学习 xv6 进程管理与调度、内存管理、文件系统和设备的基本概念。

代码总览

查看汇编代码、C 头文件和 C 源代码。

ls *.S
ls *.h
ls *.c

另外还有一些辅助性的代码以及 Makefile 等编译有关的文件。

xv6 二进制代码与镜像

二进制代码分为两部分,一个是启动扇区的代码 bootblock ,另一个是内核代码 kernel 。由于都是 linux 系统下使用 gcc 工具生成的代码,因此都是使用 ELF 格式的目标文件,可以用 binutils 工具查看和分析。

1. 启动扇区

x86 PC 启动,执行主板上的 BIOS (Basic Input Output System),主要完成一些硬件自检的工作,然后读取第一个扇区(启动扇区 boot sector)的 512 字节数据到内存中,这 512 字节的代码就是我们熟知的 bootloader 。导入后 CPU 控制权由 BIOS 转交给 bootloader 。BIOS 会把 bootloader 导入到 0x7c00 开始的地方,然后把 PC 指针设成此地址,将控制权交给 bootloader

xv6 系统的启动扇区的代码 bootblock 就是扮演上述 bootloader 的角色,它将继续负责将 xv6 的内核代码 kernel 装载到内存,并将控制权交给 kernel ,从而完成启动过程。

2. 内核代码

bootblock 的主要任务就是将 xv6 内核 kernel 文件读入,并将控制权转交给 kernel 代码,从而运行 xv6 代码,从而运行 xv6 操作系统。kernel 代码本身又包括主体代码和辅助初始化代码两部分。

xv6 内核主体代码由许多独立的 C 源程序编译、链接而成,其中主要的源代码用于实现操作系统的进程管理、内存管理、文件系统和设备等,再加上辅助的初始代码等。

entry.S 代码负责从 x86 的线性地址模式转向分页的虚地址模式、建立页表映射等内核启动的初始化工作。而 $(OBJS) 所对应的 xv6 内核主体就是运行在分页地址模式之下,使用页表映射的虚拟内存。

3. 辅助初始化代码

kernel 文件中包含了一部分代码用于 init 进程的初始化、其他处理器的启动初始化的代码。其中 init 进程的代码将被装载到 0x000 地址,而其他核启动初始代码将装入 0x7000 地址(和链接时的地址一致)给其他处理器作为启动代码。具体查看 Makefile 注释。

4.kernelmemfs 的生成

当使用内存盘的时候,对应的内核不是 kernel 文件,而是 kernelmemfs 。该内核将完整的磁盘文件系统影像包含进来。当启动扇区装载这个 kernelmemfs 内核的时候,已经把整个文件系统一起装进内存,因此后续的磁盘操作都按内存操作方式来完成。kernelmemfs 的生成方式查看 Makefile 。类似 kernel 。

  1. 编译

    当用 kernelmemfs 镜像的时候,内核的主体代码不是由 $(OBJS) 构成的,而是用 $(MEMFSOBJS) 弃掉 ide.o 加上 memide.o 构成。

  2. 链接

    kernelmemfs 的链接和 kernel 类似,也是使用 kernel.ld 链接脚本,因此具有相同的内存和布局。不同之处在于 -b binary 后面多了个 fs.mg 。即磁盘镜像和内核链接成一个单一的 ELF 文件。其中 fs.img 是磁盘文件系统的影像,fs.img 稍后分析。

    执行 make kernelmemfs ,然后比较 kernelmemfskernel 的大小,发现 kernelmemfs 远大于 kernel 文件,和我们预想一样。

    接着我们通过readelf -l kernelmemfs查看 kernelmemfs 的段和节的情况,可以看出需要 LOAD 装入的 00 段比较大(对比 kernel 的 0x0a526

    但是查看 kernelmemfs 的节,从 .data 节的大小我们可以知道 fs.img 是以数据的形式并入到 .data 节(Size 为 07f516)。

    再进一步,用 readelf -s kernelmemfs | grep fs.img 查看符号表,发现在数据节(5 号节)有 _binary_fs_img_start ,这是连接器以 binary 方式将 fs.img 链接进来的时候创建的符号。其地址位于 0x8010a516 ,正好是在原来 kernel 结束的地方。(kernel 大小为 0x516),另外 fs.img 结束符号 _binary_fs_end=80187516 。链接器也为 fs.img 的大小创建了符号 ` ABS _binary_fs_img_size=0007d000 ,正好等于 fs.img 的大小 0x7d000 = 51200` 。

4. 磁盘镜像

在内核启动之后,需要有文件系统保持程序和数据,例如 shell 中所执行的外部指令:ls、mkdir、rm、cp 等程序,以及用户所需的其他数据文件等。由于我们将 xv6 运行于 QEMU 仿真环境,因此需要把相应的文件系统内容形成磁盘影像文件。