前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >xv6: 第一章 操作系统组织结构 节5

xv6: 第一章 操作系统组织结构 节5

原创
作者头像
王白石
发布2023-09-28 17:48:53
4220
发布2023-09-28 17:48:53
举报
文章被收录于专栏:数学/人工智能/机器学习

注: 翻译自 MIT xv6 rev11 book, 为了方便阅读,会附上相关的源码;本文中专有名词统一不做翻译

运行第一个进程

为了更详细的描述 xv6 的组织结构,让我们来看一下 kernel 是如何为自己创建第一个地址空间的,以及如何创建并启动第一个进程和执行第一个系统调用的。通过跟踪这些过程,我们可以清楚的了解到 xv6 是如何提供进程间强隔离的。

一台 PC 通电启动后,BIOS 会执行一些初始化工作,接着从磁盘(主引导分区)加载一个 boot\ loader 到(物理)内存中执行。 xv6 的 boot\ loader 会从磁盘上加载 kernel ,然后从 entry (entry.S 1044) 开始执行。 kernel 在最初启动阶段,x86 的分页硬件是关闭的,分页机制尚未开启,此时虚拟地址会直接对应物理地址。

代码语言:python
代码运行次数:0
复制
entry.S
1042 # Entering xv6 on boot processor, with paging off.
1043 .globl entry
1044 entry:
1045 # Turn on page size extension for 4Mbyte pages
1046 movl  %cr4, %eax
1047 orl   $(CR4_PSE), %eax
1048 movl  %eax, %cr4

boot\ loader 会将 kernel 加载到物理内存 0x10 0000 地址处,之所以不是 0x8000 0000 是考虑一些小机器上并没有这么多的物理内存。另外不把 kernel 加载到 0x0 的原因是地址段 0xA 0000 : 0x10 0000 预留给了 I/O 设备(DMA)。

为了使 kernel 的剩余代码可以正常运行,entry 会设置一个 page table (entrypgdir) 把从 0x8000 0000 开始的虚拟地址映射到 0 开始的物理地址。将二段虚拟地址映射到同一段物理地址是页表的一种常见使用技巧,我们在后面会看到更多的例子。

代码语言:c
复制
 main.c
 // The boot page table used in entry.S and entryother.S.
 // PTE_PS in a page directory entry enables 4Mbyte pages.
 1306  pde_t entrypgdir[NPDENTRIES] = { 
 1307      // Map VA’s [0, 4MB) to PA’s [0, 4MB) 
 1308      [0] = (0) | PTE_P | PTE_W | PTE_PS, 
 1309      // Map VA’s [KERNBASE, KERNBASE+4MB) to PA’s [0, 4MB) 
 1310      [KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS, 
 1311  };

entry 的页表定义在 main.c 1306 我们会在第二章了解更多细节,这里我们只需要知道第 0 项会将虚拟地址 0 : 0x40 0000 映射到物理地址 0 : 0x40 0000,这个映射之所以需要是因为 entry 是执行在低内存地址处的,最终我们会去掉这项映射。

第 512 项(KERNBASE>>PDXSHIFT = 0x8000 0000 >> 22 = 512) 将虚拟地址 KERNBASE : KERNBASE + 0x40 0000 映射到物理地址 0 : 0x40 0000,这项将在 entry 执行结束后由 kernel 使用,kernel 将从这个高内存地址处开始寻找指令和数据,同时这个映射也将 kernel 指令和数据大小限制在 4M 字节。

附录 B 提供了更多细节:kernel 是一个 ELF 格式的二进制文件, linker 链接器将起始地址设置成 0x80100000 (虚拟地址),见 kernel.ld 文件,KERNLINK 表示这个地址

entry 建立的地址映射
entry 建立的地址映射

回到 entry ,它会将 entrypgdir 的物理地址加载到控制寄存器 \%cr3 , \%cr3 的值必须是物理地址,存放虚拟地址是没有意义的,因为分页硬件并不知道如何翻译虚拟地址。符号 entrypgdir 会引用到一个高位地址,C 语言中的宏 V2P_WO 会减去 KERNBASE 来找到对应的物理地址。xv6 会通过设置控制寄存器 \%cr0 的 CR0_PG 标记位来开启分页硬件。

代码语言:python
代码运行次数:0
复制
entry.S
1049 # Set page directory
1050 movl $(V2P_WO(entrypgdir)), %eax
1051 movl %eax, %cr3

1052 # Turn on paging.
1053 movl %cr0, %eax
1054 orl $(CR0_PG|CR0_WP), %eax
1055 movl %eax, %cr0

在开启分页后,CPU 处理器仍然在低地址处执行指令,之所以可以这样是因为 entrypgdir 的第 0 项映射,如果把第 0 项去掉,那么当计算机执行开启分页后的指令就会崩溃。

现在 entry 需要转移到 kernel 在高内存地址处执行,首先将栈指针 \%esp 指向用作栈的内存区域 (1057)。所有的符号 symbol 都有高位地址,包括 stack , 所以栈在低位地址映射被去掉后将依然有效。最终,entry 将跳转到 main, 也是一个高位地址。1065 行的间接跳转是必要的,不然的话,汇编器就会生成 PC-relative 直接跳转指令, 这将会导致执行低位地址版本的 main. 注意 Main 是不会返回的,因为栈中没有返回地址 (return PC)。现在 kernel 开始在高内存地址处执行函数 main 了。

代码语言:python
代码运行次数:0
复制
entry.S
1057 # Set up the stack pointer.
1058 movl $(stack + KSTACKSIZE), %esp
1059
1060 # Jump to main(), and switch to executing at
1061 # high addresses. The indirect call is needed because
1062 # the assembler produces a PC−relative instruction
1063 # for a direct jump.
1064 mov $main, %eax
1065 jmp *%eax
1066
1067 .comm stack, KSTACKSIZE

链接器将 kernel 的 C 代码和 entry.S 链接在一起,使得 entry.S 里可以引用 kernel 里的符号,比如 main

注:在汇编语言中,.comm 是一个伪指令,用于定义一个全局可见,未初始化的共享内存区域(或者称为共享块)。它的主要用途是为程序中的全局变量或共享数据分配内存空间,但不会为这些变量提供初始值

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 运行第一个进程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档