xv6 book Chapter3 中文翻译

〇、前言

本文是 xv6 book 第三章的翻译,因为原来的中文版翻译地和英文原版意思有出入,几乎很难读下去(并没有否认他们的意思,可能是我自己的问题。后面我可能会考虑重新将整本书都翻译一遍),以下将开始翻译。

一、(译文)第三章 页表

页表是最流行的机制,操作系统通过它提供每个进程拥有自己的私有地址空间和内存。页面表确定了内存地址的含义,以及可以访问物理内存的哪些部分。它们使得 xv6 能够隔离不同进程的地址空间,并将它们多路复用到单个物理内存中。页面表是一种流行的设计,因为它们提供了一种间接级别,使操作系统能够执行许多技巧。xv6 使用了一些技巧:在多个地址空间中映射相同的内存(一个跳板页面),并用未映射的页面保护内核和用户堆栈。本章的其余部分解释了 RISC-V 硬件提供的页面表以及 xv6 如何使用它们。

3.1 分页硬件

值得提醒的是,RISC-V 指令(包括用户指令和内核内核指令)操作虚拟地址。机器的 RAM,或者物理内存,都使用物理地址进行索引。RISC-V 的页面硬件连接了这两种地址,通过将每个虚拟地址映射到一个物理地址。

xv6Sv39 RISC-V 上运行,这意味着仅使用了 64 位虚拟地址的底部 39 位;前 25 位未被使用。在这种 Sv39 配置下,一个 RISC-V 页面表在逻辑上是一个由 227(134,217,728)个页面表项(PTEs)组成的数组。每个 PTE 包含一个 44 位的物理页号(PPN)和一些标志位。分页硬件通过使用 39 位中的前 27 位来索引页面表以找到一个 PTE,并且构造一个 56 位的物理地址,其中前 44 位来自 PTE 中的 PPN,后 12 位来自原始虚拟地址的底部。

图 3.1:RISC-V 虚拟和物理地址,以及简化的逻辑页表

图 3.1:RISC-V 虚拟和物理地址,以及简化的逻辑页表

图 3.1 展示了这个过程,使用一个 PTE 简单数组的逻辑视图(详细内容见图 3.2)。页面表让操作系统能够以 4096(212)字节对齐的块的粒度控制虚拟地址到物理地址的转换。这样的一个块被称为一页。

在这里插入图片描述

图 3.2:RISC-V 地址转换细节

尽管 CPU 在执行加载或存储指令时会在硬件中遍历三级结构,但三级结构的一个潜在缺点是 CPU 必须从内存中加载三个页面表项(PTEs)来执行虚拟地址到物理地址的转换。为了避免从物理内存加载 PTEs 的开销,RISC-V CPU 将页面表项缓存到一个称为Translation Look-aside BufferTLB)的缓冲区中。

每个 PTE 包含标志位,告诉分页硬件关联的虚拟地址允许的使用方式。PTE_V 表示该 PTE 是否存在:如果未设置,则对页面的引用会导致异常(即不被允许)。PTE_R 控制指令是否被允许读取页面。PTE_W 控制指令是否被允许向页面写入。PTE_X 控制 CPU 是否可以将页面内容解释为指令并执行它们。PTE_U 控制用户模式下的指令是否被允许访问页面;如果未设置 PTE_U,则该 PTE 只能在核心模式下使用。图 3.2 展示了所有这些工作原理。这些标志和其他所有与页面硬件相关的结构在(kernel/riscv.h)中定义。

为了告诉硬件使用页面表,内核必须将根页面的物理地址写入 satp 寄存器。每个 CPU 都有自己的 satpCPU 将使用由自己的 satp 指向的页面表来转换后续指令生成的所有地址。每个 CPU 都有自己的 satp,这样不同的 CPU 可以运行不同的进程,每个进程都有一个由自己的页面表描述的私有地址空间。

典型情况下,内核将所有物理内存映射到其页面表中,这样它就可以使用加载/存储指令读写物理内存中的任何位置。由于页目录位于物理内存中,内核可以通过使用标准的存储指令向 PTE 的虚拟地址写入,来编程页目录中 PTE 的内容。

关于术语的一些说明。物理内存指的是 DRAM 中的存储单元。物理内存的每个字节都有一个地址,称为物理地址。指令仅使用虚拟地址,分页硬件将虚拟地址翻译为物理地址,然后发送给 DRAM 硬件进行读取或写入。与物理内存和虚拟地址不同,虚拟内存并不是一个物理对象,而是指内核提供的一系列抽象和机制,用于管理物理内存和虚拟地址。

在这里插入图片描述

图 3.3:左侧,xv6 的内核地址空间。 RWX指 PTE 读、写、执行 权限。 右侧是 xv6 期望看到的 RISC-V 物理地址空间

3.2 内核地址空间

xv6为每个进程维护一个页面表,描述了每个进程的用户地址空间,另外还有一个页面表描述了内核的地址空间。内核配置其地址空间的布局,以便在可预测的虚拟地址上让自己访问物理内存和各种硬件资源。图 3.3 展示了这个布局是如何将内核虚拟地址映射到物理地址的。文件(kernel/memlayout.h)声明了 xv6 内核内存布局的常量。

QEMU模拟的计算机包括从物理地址0x80000000开始并持续至至少0x86400000RAM(物理内存),在 xv6 中称为 PHYSTOPQEMU模拟还包括诸如磁盘接口之类的I/O设备。QEMU将设备接口作为内存映射的控制寄存器暴露给软件,这些控制寄存器位于物理地址空间的0x80000000以下。内核可以通过读取/写入这些特殊的物理地址与设备进行交互;这些读取和写入与设备硬件通信,而不是与RAM通信。第4章解释了xv6 如何与设备进行交互。

内核通过“直接映射”方式访问RAM和内存映射设备寄存器;即在虚拟地址空间中将资源映射到等于物理地址的虚拟地址上。例如,内核本身位于虚拟地址空间和物理内存中的KERNBASE=0x80000000处。直接映射简化了读取或写入物理内存的内核代码。例如,当fork为子进程分配用户内存时,分配器返回该内存的物理地址;fork在将父进程的用户内存复制到子进程时直接使用该地址作为虚拟地址。

有一些内核虚拟地址并非直接映射:

  • 跳板页面。它映射在虚拟地址空间的顶部;用户页表也有相同的映射。第四章讨论了跳板页面的作用,但这里展示了页面表的一个有趣用例;内核虚拟地址空间中的一个物理页面(存储跳板代码)被映射了两次:一次在虚拟地址空间的顶部,另一次是直接映射。
  • 内核堆栈页面。每个进程都有自己的内核堆栈,映射在高地址处,这样在其下 xv6 可以保留一个未映射的守护页。守护页的 PTE 是无效的(即 PTE_V 未设置),因此如果内核的堆栈溢出,可能会引发异常并导致内核崩溃。没有守护页,溢出的堆栈会覆盖其他内核内存,导致错误操作。宁愿发生崩溃也不愿意堆栈溢出影响其他内核内存。

虽然内核通过高内存映射使用其堆栈,但它们也可通过直接映射的地址访问。另一种设计可能只使用直接映射,并使用直接映射地址上的堆栈。然而,在这种安排下,提供守护页将涉及取消对本应引用物理内存的虚拟地址的映射,这会使得其难以使用。

内核将跳板页面和内核文本页的页面映射权限设置为 PTE_RPTE_X。内核从这些页面读取并执行指令。内核将其他页面的映射权限设置为 PTE_RPTE_W,以便读取和写入这些页面中的内存。守护页的映射是无效的。

3.3 代码:创建地址空间

xv6 中,用于操作地址空间和页面表的大部分代码都位于 vm.ckernel/vm.c:1)中。其核心数据结构是 pagetable_t,实际上是指向一个 RISC-V 根页面表页的指针;pagetable_t 可能是内核页面表,也可能是每个进程的页面表。主要的函数有 walk,用于找到虚拟地址的 PTE,以及 mappages,用于加载新的映射的 PTE。以 kvm 开头的函数操作内核页面表;以 uvm 开头的函数操作用户页面表;其它函数则同时用于两者。copyoutcopyin 将数据从系统调用参数提供的用户虚拟地址复制到内核中,并位于 vm.c 中,因为它们需要明确地将这些地址转换为相应的物理内存。

在引导序列的早期阶段,main 调用 kvminitkernel/vm.c:54)使用 kvmmakekernel/vm.c:20)创建内核的页面表。这个调用发生在 xv6RISC-V 上启用分页之前,所以地址直接引用物理内存。kvmmake 首先分配一个物理内存页面来容纳根页面表页。然后调用 kvmmap 安置内核需要的转换。这些转换包括内核的指令和数据,一直到 PHYSTOP 的物理内存以及实际上是设备的内存范围。proc_mapstackskernel/proc.c:33)为每个进程分配一个内核堆栈。它调用 kvmmap 将每个堆栈映射到由 KSTACK 生成的虚拟地址上,这样留出了无效的堆栈守护页的空间。

xv6 中,kvmmapkernel/vm.c:127)调用 mappageskernel/vm.c:138),它为一段虚拟地址范围到相应的物理地址范围在页面间隔下,分别在页面表中安装映射。对于要映射的每个虚拟地址,在范围内,mappages 分别调用 walk 来找到该地址的 PTE 的地址。然后,它初始化 PTE,包括相关的物理页号、所需的权限(PTE_WPTE_XPTE_R),以及将 PTE_V 设置为将其标记为有效(kernel/vm.c:153)。

walkkernel/vm.c:81)模拟了 RISC-V 分页硬件,它查找虚拟地址的 PTE(参见图 3.2)。walk 一次性下降 9 位来遍历 3 级页面表。它使用每个级别的 9 位虚拟地址来查找下一级页面表或最终的页的 PTEkernel/vm.c:87)。如果 PTE 无效,则表示所需的页面尚未分配;如果 alloc 参数已设置,walk 将分配一个新的页面表页并将其物理地址放入 PTE 中。它返回树中最底层的 PTE 的地址(kernel/vm.c:97)。

上述代码依赖于将物理内存直接映射到内核虚拟地址空间。例如,当 walk 遍历页面表的级别时,它从 PTE 中获取下一级页面表的(物理)地址(kernel/vm.c:89),然后将该地址作为虚拟地址使用来获取下一级的 PTEkernel/vm.c:87)。

在引导过程中,main 调用 kvminithartkernel/vm.c:62)来安装内核页面表。它将根页面表页的物理地址写入寄存器 satp。此后,CPU 将使用内核页面表来转换地址。由于内核使用恒等映射,下一个指令的虚拟地址将映射到正确的物理内存地址。

RISC-V CPU 中,页面表项被缓存在一个转址旁路缓存(TLB)中,当 xv6 更改页面表时,必须告知 CPU 使对应的缓存 TLB 条目失效。如果没有这样做,那么在稍后某个时刻,TLB 可能会使用一个旧的缓存映射,指向一个在此期间已经分配给另一个进程的物理页,结果可能导致一个进程能够篡改另一个进程的内存。RISC-V 有一个指令 sfence.vma,用于刷新当前 CPUTLBxv6kvminithart 中重新加载 satp 寄存器后执行 sfence.vma,并在切换到用户页面表的跳板代码中执行,然后返回用户空间(kernel/trampoline.S:79)。

为了避免刷新整个 TLBRISC-V CPU 可能支持地址空间标识符(ASIDs)。内核可以只刷新特定地址空间的 TLB 条目。

3.4 物理内存分配

在运行时,内核必须为页面表、用户内存、内核堆栈和管道缓冲区分配和释放物理内存。

xv6 使用内核结束后的第一个存储单元和 PHYSTOP 之间的物理内存进行运行时分配。它以整个 4096 字节页面的方式进行分配和释放。通过将一个链接列表通过页面本身进行线程化,它跟踪哪些页面是空闲的。分配包括从链接列表中移除一个页面;释放包括将释放的页面添加到列表中。

3.5 代码:物理内存分配器

分配器位于 kalloc.ckernel/kalloc.c:1)中。分配器的数据结构是一系列可用于分配的物理内存页面的空闲列表。每个空闲页面的列表元素都是一个名为 struct run 的结构体(kernel/kalloc.c:17)。那么分配器从哪里获取内存来保存该数据结构呢?它将每个空闲页面的 run 结构体存储在空闲页面本身中,因为那里没有其他存储。空闲列表由一个自旋锁保护(kernel/kalloc.c:21-24)。该列表和锁被包装在一个结构体中,以明确锁保护该结构中的字段。暂时忽略锁和获取/释放的调用;第6章将详细讨论锁定问题。

函数 main 调用 kinit 来初始化分配器(kernel/kalloc.c:27)。kinit 将空闲列表初始化为内核结束后的第一个存储单元和 PHYSTOP 之间的每一页。xv6应该通过解析硬件提供的配置信息确定可用的物理内存有多少。然而,xv6 假设机器有 128 兆字节的 RAMkinit 调用 freerange 来通过对 kfree 的每页调用向空闲列表添加内存。PTE 只能引用按 4096 字节边界对齐的物理地址(即 4096 的倍数),因此 freerange 使用 PGROUNDUP 来确保它只释放对齐的物理地址。分配器初始时没有内存;这些对 kfree 的调用给了它一些要管理的内存。

分配器有时将地址视为整数进行运算(例如,在 freerange 中遍历所有页面),有时将地址用作指针读写内存(例如,操作存储在每个页面中的 run 结构体);这种地址的双重用途是导致分配器代码充斥着 C 类型转换的主要原因。另一个原因是释放和分配在本质上会改变内存的类型。

函数 kfreekernel/kalloc.c:47)首先将要释放的内存中的每个字节设置为值 1。这将导致在释放后继续使用内存(使用“悬空引用”)的代码读取垃圾而不是旧的有效内容;希望这样可以更快地使此类代码出现故障。然后 kfree 将页面添加到空闲列表中:它将 pa 转换为指向 struct run 的指针,记录了空闲列表的旧开头在 r->next 中,并将空闲列表设置为 rkalloc 从空闲列表中移除并返回第一个元素。
在这里插入图片描述

图 3.4:进程的用户地址空间及其初始堆栈

3.6 进程地址空间

xv6 中,每个进程都有一个单独的页面表,并且当 xv6 在进程之间切换时,它也会更改页面表。正如图 2.3 所示,一个进程的用户内存从虚拟地址零开始,最多可以增长到 MAXVAkernel/riscv.h:360),原则上允许一个进程寻址 256 GB 的内存。

当一个进程请求用户内存时,xv6 首先使用 kalloc 来分配物理页面。然后将 PTE 添加到进程的页面表中,指向新的物理页面。xv6 在这些 PTE 中设置了 PTE_WPTE_XPTE_RPTE_UPTE_V 标志。大多数进程不使用整个用户地址空间;xv6 在未使用的 PTE 中将 PTE_V 设置为清除状态。

我们在这里看到了几个页面表使用的良好例子。首先,不同进程的页面表将用户地址翻译为不同的物理内存页面,因此每个进程都有私有的用户内存。其次,每个进程将其内存视为从零开始的连续虚拟地址,而进程的物理内存可以是不连续的。第三,内核在用户地址空间的顶部映射了带有跳板代码的一个页面,因此单个物理内存页面显示在所有地址空间中。

图 3.4 更详细地展示了在 xv6 中执行进程的用户内存布局。堆栈是一个单独的页面,并显示了由 exec 创建的初始内容。包含命令行参数的字符串,以及指向它们的指针数组,位于堆栈的顶部。在此之下是一些值,允许程序从 main 开始,就好像 main(argc, argv) 函数刚刚被调用一样。

为了检测用户堆栈是否超出了分配的堆栈内存,xv6 在堆栈的正下方通过清除 PTE_U 标志,放置了一个不可访问的守护页面。如果用户堆栈溢出,并且进程尝试使用堆栈下方的地址,硬件将生成页面错误异常,因为守护页面对运行在用户模式的程序是不可访问的。真实的操作系统可能会在用户堆栈溢出时自动为其分配更多内存。

3.7 代码:sbrk

sbrk 是用于进程收缩或扩展其内存的系统调用。该系统调用由函数 growprockernel/proc.c:253)实现。growproc 调用 uvmallocuvmdealloc,取决于 n 是否为正数或负数。uvmallockernel/vm.c:221)使用 kalloc 分配物理内存,并使用 mappages 向用户页面表添加 PTEuvmdealloc 调用 uvmunmapkernel/vm.c:166),它使用 walk 查找 PTE,并使用 kfree 释放它们所引用的物理内存。

xv6 中,进程的页面表不仅告诉硬件如何映射用户虚拟地址,而且也作为记录分配给该进程的物理内存页面的唯一依据。这就是为什么释放用户内存(在 uvmunmap 中)需要检查用户页面表的原因。

3.8 代码:exec

exec 是一个系统调用,用于创建地址空间的用户部分。它从文件系统中存储的文件初始化地址空间的用户部分。execkernel/exec.c:13)使用 nameikernel/exec.c:26)打开指定的二进制路径,namei 在第 8 章有解释。然后,它读取 ELF 头。xv6 应用程序采用广泛使用的 ELF 格式描述,定义在(kernel/elf.h)中。一个 ELF 二进制文件包括一个 ELF 头,struct elfhdrkernel/elf.h:6),后面跟着一系列程序段头,struct proghdrkernel/elf.h:25)。每个 proghdr 描述了必须加载到内存中的应用程序部分;xv6 程序只有一个程序段头,但其他系统可能会有指令和数据的单独段。

首先是对文件是否可能包含 ELF 二进制的快速检查。ELF 二进制以四字节的“魔数”0x7F,‘E’,‘L’,‘F’,或 ELF_MAGICkernel/elf.h:3)开头。如果 ELF 头具有正确的魔数,exec 假设二进制文件格式正确。

exec 使用 proc_pagetablekernel/exec.c:38)分配一个没有用户映射的新页表,使用 uvmallockernel/exec.c:52)为每个 ELF 段分配内存,并使用 loadsegkernel/exec.c:10)将每个段加载到内存中。loadseg 使用 walkaddr 查找已分配内存的物理地址,以便将每个 ELF 段的每页写入,并使用 readi 从文件读取数据。

exec 创建的第一个用户程序 /init 的程序段头如下:

# objdump -p _init
user/_init: file format elf64-littleriscv
Program Header:
LOAD off 0x00000000000000b0 vaddr 0x0000000000000000
		paddr 0x0000000000000000 align 2**3
		filesz 0x0000000000000840 memsz 0x0000000000000858 flags rwx
STACK off 0x0000000000000000 vaddr 0x0000000000000000
		paddr 0x0000000000000000 align 2**4
		filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw

程序段头的 filesz 可能小于 memsz,这表明它们之间的间隙应该用零填充(用于 C 全局变量),而不是从文件中读取。对于 /initfilesz2112 字节,而 memsz2136 字节,因此 uvmalloc 分配足够的物理内存来容纳 2136 字节,但仅从文件 /init 中读取了 2112 字节。
接下来,exec 分配并初始化用户栈。它仅分配了一页栈空间。exec 逐个将参数字符串复制到栈顶,同时在 ustack 中记录它们的指针。它在即将传递给 mainargv 列表末尾放置一个空指针。ustack 中的前三个条目是假的返回程序计数器argcargv 指针
exec 在栈页的正下方放置了一个不可访问的页面,以便那些试图使用多于一页的程序会发生故障。这个不可访问的页面还允许 exec 处理过大的参数;在这种情况下,exec 用于将参数复制到栈中的 copyoutkernel/vm.c:347)函数会注意到目标页面是不可访问的,并返回 -1
在准备新内存镜像期间,如果 exec 检测到类似无效程序段的错误,它会跳转到标签 bad,释放新镜像,并返回 -1exec 必须等待释放旧镜像,直到确信系统调用将成功:如果旧镜像消失了,系统调用就无法返回 -1exec 中唯一的错误情况发生在镜像的创建过程中。一旦镜像完成,exec 可以确认新的页表(kernel/exec.c:113),并释放旧的页表(kernel/exec.c:117)。

execELF文件加载字节到由ELF文件指定的内存地址。用户或进程可以将它们想要的任何地址放入ELF文件中。因此,exec存在风险,因为ELF文件中的地址可能无意或故意地引用内核。对于一个不谨慎的内核,后果可能从崩溃到对内核隔离机制的恶意破坏(即安全漏洞)不等。xv6执行了许多检查以避免这些风险。例如,if(ph.vaddr + ph.memsz < ph.vaddr) 检查求和是否溢出了64位整数。危险在于用户可以构造一个ELF二进制文件,其中ph.vaddr指向用户选择的地址,而ph.memsz足够大,导致求和溢出到0x1000,看起来像是一个有效值。在旧版的xv6中,用户地址空间也包含了内核(但在用户模式下不可读/写),用户可以选择一个对应于内核内存的地址,因此将数据从ELF二进制文件复制到内核中。在RISC-V版本的xv6中,这种情况不会发生,因为内核有自己独立的页表;loadseg加载到进程的页表中,而不是内核的页表中。

对于内核开发人员来说,很容易忽略一个关键的检查,而真实世界的内核在长期历史中存在许多遗漏的检查,这些遗漏可以被用户程序利用来获得内核特权。xv6很可能并未完全验证提供给内核的用户级数据,这可能被恶意用户程序利用以规避xv6的隔离机制。

3.9 现实情况

大多数操作系统都利用分页硬件进行内存保护和映射,但与大多数操作系统相比,xv6的分页使用相对简单。其他操作系统结合了分页和页错误异常,这将在第四章中讨论。

xv6简化了内核对虚拟和物理地址之间的直接映射,并假设物理RAM位于地址0x8000000处,即内核期望加载的位置。这在QEMU上运行良好,但在真实硬件上却不是一个好主意;真实硬件将RAM和设备放置在不可预测的物理地址上,因此(例如)在xv6期望存储内核的0x8000000处可能没有RAM。更严肃的内核设计利用页表将任意硬件物理内存布局转换为可预测的内核虚拟地址布局。

RISC-V支持在物理地址级别进行保护,但xv6并未使用该功能。在内存较多的计算机上,使用RISC-V对“超级页”(super pages)的支持可能是有意义的。当物理内存较小时,小页面可以以细粒度进行分配和页面置换。例如,如果一个程序只使用了8K字节的内存,给它一个完整的4M字节超级页面的物理内存是不划算的。较大的页面在RAM较多的计算机上是合理的,并且可能减少页表操作的开销。

xv6内核缺乏类似于malloc的分配器,无法为小对象提供内存,这阻碍了内核使用需要动态分配的复杂数据结构。内存分配一直是一个持久热门话题,基本问题是高效利用有限内存并为未知的未来请求做准备。现在人们更关心速度而不是空间效率。此外,一个更复杂的内核可能会分配许多不同大小的小块,而不仅仅是像xv6一样的4096字节块;一个真正的内核分配器需要处理小的分配和大的分配。

二、翻译感想

翻译在今天看来不是一个技术活,而是一个体力活。配合一些对话模型(比如 ChatGPT)就能很好地进行翻译了。或许翻译工作者,要将自己的能力提升到很高才能在于对话模型的竞争中略微胜出。

我做的事情就是复制粘贴,仔细阅读原文和译文,并对译文进行少量的修复(事实上,绝大部分情况,我不做任何修改)。

我还对比了谷歌翻译和对话模型的翻译质量,以下是两者的对比:

原文

The allocator resides in kalloc.c (kernel/kalloc.c:1). The allocator’s data structure is a free list
of physical memory pages that are available for allocation. Each free page’s list element is a
struct run (kernel/kalloc.c:17). Where does the allocator get the memory to hold that data structure? It store each free page’s run structure in the free page itself, since there’s nothing else stored
there. The free list is protected by a spin lock (kernel/kalloc.c:21-24). The list and the lock are
wrapped in a struct to make clear that the lock protects the fields in the struct. For now, ignore the
lock and the calls to acquire and release; Chapter 6 will examine locking in detail.
The function main calls kinit to initialize the allocator (kernel/kalloc.c:27). kinit initializes
the free list to hold every page between the end of the kernel and PHYSTOP. xv6 ought to determine how much physical memory is available by parsing configuration information provided
by the hardware. Instead xv6 assumes that the machine has 128 megabytes of RAM. kinit calls
freerange to add memory to the free list via per-page calls to kfree. A PTE can only refer to
a physical address that is aligned on a 4096-byte boundary (is a multiple of 4096), so freerange
uses PGROUNDUP to ensure that it frees only aligned physical addresses. The allocator starts with
no memory; these calls to kfree give it some to manage.

谷歌翻译

分配器驻留在 kalloc.c (kernel/kalloc.c:1) 中。 分配器的数据结构是一个空闲列表
可用于分配的物理内存页数。 每个空闲页面的列表元素是结构运行(kernel/kalloc.c:17)。 分配器从哪里获取内存来保存该数据结构? 它将每个空闲页面的运行结构存储在空闲页面本身中,因为没有存储其他任何内容那里。 空闲列表受自旋锁保护 (kernel/kalloc.c:21-24)。 列表和锁是包装在结构中以明确锁保护结构中的字段。 现在,忽略锁以及获取和释放的调用; 第 6 章将详细研究锁定。
函数 main 调用 kinit 来初始化分配器 (kernel/kalloc.c:27)。 kinit 初始化用于保存内核末尾和 PHYSTOP 之间每个页面的空闲列表。 xv6 应该通过解析提供的配置信息来确定有多少物理内存可用通过硬件。 相反,xv6 假设机器有 128 MB RAM。 kinit 调用freerange 通过每页调用 kfree 将内存添加到空闲列表中。 PTE只能参考在 4096 字节边界(是 4096 的倍数)上对齐的物理地址,因此自由范围使用 PGROUNDUP 确保它只释放对齐的物理地址。 分配器开始于没有记忆; 这些对 kfree 的调用给了它一些管理空间。

ChapGPT 3.5翻译

分配器位于kalloc.c文件中(kernel/kalloc.c:1)。分配器的数据结构是一个可用于分配的物理内存页面的空闲列表。每个空闲页面的列表元素是一个struct run(kernel/kalloc.c:17)。分配器从哪里获取用于保存该数据结构的内存呢?它将每个空闲页面的运行结构存储在空闲页面本身中,因为那里没有其他存储。空闲列表由自旋锁保护(kernel/kalloc.c:21-24)。列表和锁被封装在一个结构体中,以清楚地表明锁保护了结构体中的字段。暂时忽略锁以及获取和释放的调用;第6章将详细讨论锁定。
函数main调用kinit来初始化分配器(kernel/kalloc.c:27)。kinit初始化空闲列表,以保存内核末尾到PHYSTOP之间的每一页。xv6应该通过解析硬件提供的配置信息来确定有多少物理内存可用。但实际上,xv6假设机器有128兆字节的RAM。kinit调用freerange通过每页调用kfree来将内存添加到空闲列表中。PTE只能引用4096字节边界对齐的物理地址(即4096的倍数),因此freerange使用PGROUNDUP确保它只释放对齐的物理地址。分配器最初没有内存;这些对kfree的调用为其提供了一些要管理的内存。

两者的差异

看了这两者的翻译,谷歌翻译可以说是完败,而 ChapGPT几乎是完美翻译。因此,以后的翻译可能都要交给大模型了。

全文完,感谢阅读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值