MIT 6.S081 第二章笔记 | 操作系统结构

操作系统应该实现三个功能:并发隔离交互。即能保证多个程序都能分到硬件资源;各个进程之间的内存、指令、数据相互隔离,一个进程崩溃不会影响到其他进程;进程之间能通过受控的接口来进行通信。

操作系统提供了高级别的抽象,来管理硬件资源。例如,用文件描述符来抽象磁盘、内存、管道等资源,用户程序能通过简单的readwriteclose来访问所有存储资源,而不用关心是和磁盘、内存、管道、还是标准输入输出交互。

2.2 User mode、 supervisor mode、 machine mode

为了实现进程隔离,\(RISC-V\) CPU在硬件上提供3种执行命令的模式:machine mode, supervisor mode, user mode。

  1. \(machine \space mode\): 机器模式拥有全权限。CPU以机器模式启动。机器模式大多时候用于配置计算机。xv6执行必要的几行指令后就转为监管模式。

  2. \(supervisor \space mode\): 在监管模式下,CPU可以执行特权指令(\(privileged \space instructions\)), 比如中断管理、对存储页表的寄存器进行读写操作、执行系统调用。运行在监管模式也称为运行在内核空间(\(kernel \space space\))。运行在内核空间的程序被称作内核

  3. \(user \space mode\): 用户模式只能执行用户指令,例如addjump等简单无害的指令。运行在用户模式也称为运行在用户空间

运行在用户空间的程序如果执行了特权指令,CPU会转换到特权模式并将该程序强制停止。

2.3 The kernel organization

monolithic kernel: 整个操作系统在kernel中,所有system call都在supervisor mode下运行。xv6是一个monolithic kernel。

micro kernel: 将必须运行在supervisor mode下的操作系统代码压到最小,保证kernel的安全性和简洁,将大部分的操作系统代码执行在user mode下。

宏内核易于设计,但是系统调用较复杂,并且一旦任意一条特权指令出错,整个操作系统都会崩溃。

如下图所示,文件系统作为用户级别的进程执行,用户通过进程间通信请求文件系统的服务。这种运行在用户模式的内核模块称作server。微内核更轻便、稳定,但是难于设计和实现。

Figure: 1.1 A microkernel with a file-system server

下图列出了 xv6 的所有内核文件和其对应的功能。模块间接口定义在kernel/defs.h文件中。 xv6's kernel source file

2.4 Process overview

隔离的单元叫作进程, 一个进程不能破坏或监听另一个进程的内存、CPU、文件描述符,也不能破坏 kernel 本身。

为了加强隔离,内核为每个进程提供了一块私有、独立的内存,称作地址空间(address space),这让进程认为自己拥有一个独立的机器,而不用和其他进程共享硬件资源。其他的进程不能访问这块内存。

操作系统使用页表(page table)的概念来实现内存独立。页表提供 虚拟地址(RISC-V操作的地址)到物理地址(CPU芯片发送到内存的地址)的映射(或转换)。

xv6 为每一个进程维护一个独立的页表,如下图所示。地址空间从 \(0\) 号地址开始,首先是指令,然后是全局变量(栈空间),之后是进程可以根据需要灵活拓展的堆空间(用于malloc)。

题外话:操作系统中的堆栈和数据结构中的堆栈没有关系。是指在运行时动态分配的空间,是在运行前确定的静态空间。

Figure: 1.2 Layout of a process’s virtual address space

RISC-V使用 \(64\) 位指针,但是 xv6 只使用低 \(38\) 位就够了,因此最大地址是 \(2^{38} - 1 = 0x3fffffffff = MAXVA\)

xv6 使用struct proc(声明在kernel/proc.h)来维护每个进程的状态。进程最重要的几个信息: 1. 页表(p->pagetable). 2. 进程栈(p->kstack). 3. 进程运行状态(p->state)。

每个进程中都有线程(\(thread\)),是执行进程命令的最小单元,可以被暂停和继续。

每个进程有两个栈:用户栈(user stack)和内核栈(kernel stack)。当进程在user space中进行时只使用用户堆栈,当进程进入了内核(比如进行了system call)使用内核堆栈。

操作系统给进程提供了两种假象: 地址空间给进程提供了独自拥有内存的假象、线程给进程提供了独自拥有 CPU 的假象。

2.5 Starting the first process

当RISC-V芯片通电后,它会自动读取 \(ROM\) 的指令初始化自己,并运行引导程序(在 xv6 中为kernel/kernel.ld)将内核加载入内存中。然后在machine mode从_entry(kernel/entry.S)开始运行xv6。bootloader将xv6 kernel加载到0x80000000的物理地址中,因为前面的地址中有I/O设备。

start函数中,先以machine mode做了一些配置,然后调用mret指令跳转到supervisor mode, 并通过修改 PC 寄存器的值跳转到kernel/main.c

main先对一些设备和子系统进行初始化,然source/_posts/2024/MIT-6.S081-lab03.md 进程将要请求的系统调用号写入p->trapframe->a7, 其中p为当前进程的struct proc。并且将参数写入p->trapframe->a0和其他寄存器。之后进程执行ecall指令,并保存进程相关信息(其中就包括trapframe)。然后开始执行syscall(kernel/syscall.c:95)

syscall()trapframe->a7中拿到索引,通过一个函数指针数组syscall[](定义在kernel/syscall.c中)获取对应系统调用的函数指令。然后将系统调用的返回写入p->trapframe->a0

系统调用号( \(system \space call \space number\) )定义在kernel/syscall.h中,作为内核找到函数指针的索引。

2.7 System call arguments

对应教材第4章第4节。

trap相关的代码将用户寄存器保存在当前进程的trapframe中,内核函数argintargaddrargfdtrapframe中的指定寄存器得到数据,并分别按照整数、地址、文件描述符解析。通过argrow能方便的读取第\(n\)个寄存器的内容(这些函数都定义在kernel\syscall.c中)。

大部分参数都通过指针来传递,有时用户程序还会请求内核访问特定内存并写入数据。为了防止用户程序传入恶意参数,内核使用fetchstrcopyinstr来实现安全地与用户提供的地址之间传输数据的功能。


MIT 6.S081 第二章笔记 | 操作系统结构
https://acmicpc.top/2024/02/25/MIT-6.S081-lab02 Notes/
作者
江欣婷
发布于
2024年2月25日
许可协议