【Mit6.s081】课程记录

实验代码记录:https://github.com/anda522/LabCode/tree/main/Mit6.S081

课程教材翻译:https://xv6.dgs.zone/ ,对理解感觉很重要

xv6系统为 RISC-V

1 Unix utilities

Knowledge

操作系统结构:

  • 用户空间程序:正在运行的程序,他们处于同一个空间

  • Kernel:Kernel程序只有一个,维护数据管理每一个用户空间进程,维护硬件资源

Kernel中存在各种服务:文件系统、进程管理系统、访问控制等等

应用程序访问Kernel通过 系统调用 来完成,Kernel具有对硬件操作的特殊权限。

管道 pipe 特性:

  • 管道具有先进先出(FIFO)的特性。数据写入管道的一端,从管道的另一端读出时是按写入顺序读取的。
  • 父进程在写入管道后,会通过 read 操作等待子进程的响应。该 read 操作会被阻塞,直到有数据可读。

Lab

/user 目录为用户区代码,所有用户区相关的函数都在此处。

/kernel 目录为内核区代码,所有内核区相关的操作都在此处,如系统调用实现,内存映射,中断处理等。

如要使用 sleep 命令时,需要将 sleep 程序添加进 Makefile 文件的 UPROGS 中,将其纳入编译范围,否则操作系统将不能识别你的命令。后续程序同理。

UPROGS 列表:添加在之中说明用户区可以执行对应的可执行程序,例如在命令行执行对应命令。而其他无需生成可执行程序的系统调用可以不用添加在其中。

参考:

[1] MIT 6.S081 Lab Util 实验

2 System calls

Knowledge

系统调用

// 所有相关的系统调用
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);

部分系统调用解释:

exit(int) :导致进程停止执行并释放资源。exit接受一个整数状态参数,通常0表示成功,1表示失败。

fork(void) :拷贝当前进程的内存,并创建一个新的进程,这里的内存包含了进程的指令和数据。

之后就拥有了一个完全一样内存的进程,在原始进程中 fork() 系统调用返回大于 0 的数,这个数为子类的 PID , 在子进程中,返回的数为 0

exec(char*, char**) :以新的进程代替原来的进程,该系统调用不会返回,因为exec会完全替换当前进程的内存,相当于当前进程不复存在了。并没有创建新的进程,还是原来进程的 PID

exec有两个参数:可执行文件的文件名和字符串参数数组。

wait(int*) :等待之前创建的子进程退出。如果当前进程有任何子进程,并且其中一个已经退出了,那么wait会返回。

wait系统调用返回当前进程的已退出(或已杀死)子进程的PID,并将子进程的退出状态复制到传递给wait的地址;如果调用方的子进程都没有退出,那么wait等待一个子进程退出。如果调用者没有子级,wait立即返回-1。如果父进程不关心子进程的退出状态,它可以传递一个0地址给wait

write(fd, buf, n) :将 buf 中的 n 字节写入文件描述符,并返回写入的字节数。只有发生错误时才会写入小于 n 字节的数据。

read(fd, buf, n) :从文件描述符读取最多 n 字节,将它们复制到 buf ,并返回读取的字节数。

文件描述符是一个小整数(small integer),表示进程可以读取或写入的由内核管理的对象,进程从文件描述符0读取(标准输入),将输出写入文件描述符1(标准输出),并将错误消息写入文件描述符2(标准错误)

系统调用执行过程

系统调用在内核区,执行时需要从用户态过渡到核心态,CPU提供一个特殊的指令,将CPU从用户模式切换到管理模式,并在内核指定的入口点进入内核(RISC-V为此提供ecall指令)。

一个进程可以通过执行RISC-V的ecall指令进行系统调用,该指令提升硬件特权级别,并将程序计数器(PC)更改为内核定义的入口点,入口点的代码切换到内核栈,执行实现系统调用的内核指令,当系统调用完成时,内核切换回用户栈,并通过调用sret指令返回用户空间,该指令降低了硬件特权级别,并在系统调用指令刚结束时恢复执行用户指令。

Lab

3 Page tables

Knowledge

Xv6为每个进程维护一个单独的页表,定义了该进程的地址空间。一个进程的虚拟地址空间分布如下。首先是指令,然后是全局变量,然后是栈区,最后是一个堆区域。有许多因素限制了进程地址空间的最大范围: RISC-V上的指针有64位宽;硬件在页表中查找虚拟地址时只使用低39位;xv6只使用这39位中的38位。因此,最大地址是 2^38-1=0x3fffffffff ,即MAXVA(定义在kernel/riscv.h:348)在地址空间的顶部,xv6为trampoline(用于在用户和内核之间切换)和映射进程切换到内核的trapframe分别保留了一个页面。

Lab

argaddr()argint() 函数都是从用户栈空间中提取参数。当用户调用系统调用时,需要将参数传给内核。参数通常会放到用户的栈中。

walk 函数在操作系统中通常用于页表的遍历和访问。在虚拟内存管理中,页表是一种数据结构,用于将虚拟地址映射到物理地址。walk 函数的主要作用是在给定的页表中查找特定虚拟地址对应的页表项。

copyout 函数通常用于从内核空间将数据复制到用户空间。

PTE_A 的默认值是第六位

参考:

[1] MIT 6.S081 2021: Lab page tables

[2] MIT 6.S081 Lab Pgtbl 实验 (非常详细)

4 Traps

Knowledge

Trap机制:用户空间和内核空间的切换,目的是实现操作系统的安全和隔离

用户空间和内核空间的切换发生的场景:

  • 程序执行系统调用
  • 程序出现页面错误、除以零等错误
  • 触发中断导致程序需要响应内核设备驱动

Lab

proc 结构体包含了进程中的所有信息,可以通过 myproc() 获取当前运行的进程的结构体。

5 Copy-on-Write

Knowledge

Lab

  • 我们很有可能需要标记当前的 PTE 为 COW 内存页映射,因此我们可以使用 RSW(reserved for software)标识位来实现:

  • 如果 COW 下的缺页中断产生了,但是也没有足够的物理内存空间,那么进程应当被杀死。

  • 设置引用计数,将物理内容RAM划分成 (PHYSTOP - KERNBASE) / PGSIZE 个页面,对每个页面设置一个引用计数(代表页面被引用的次数,当引用计数变为0时,就要释放对应的空间)。

6 Multithreading

Knowledge

Xv6有两种类型的锁:自旋锁(spinlocks)和睡眠锁(sleep-locks)。

自旋锁和中断的交互引发了潜在的危险。假设sys_sleep持有tickslock,并且它的CPU被计时器中断中断。clockintr会尝试获取tickslock,意识到它被持有后等待释放。在这种情况下,tickslock永远不会被释放:只有sys_sleep可以释放它,但是sys_sleep直到clockintr返回前不能继续运行。所以CPU会死锁,任何需要锁的代码也会冻结。

为了避免这种情况,如果一个自旋锁被中断处理程序所使用,那么CPU必须保证在启用中断的情况下永远不能持有该锁。Xv6更保守:当CPU获取任何锁时,xv6总是禁用该CPU上的中断。中断仍然可能发生在其他CPU上,此时中断的acquire可以等待线程释放自旋锁;由于不在同一CPU上,不会造成死锁。

自旋锁的另一个缺点是,一个进程在持有自旋锁的同时不能让出(yield)CPU,然而我们希望持有锁的进程等待磁盘I/O的时候其他进程可以使用CPU。

Xv6以睡眠锁(sleep-locks)的形式提供了这种锁。acquiresleep (kernel/sleeplock.c:22) 在等待时让步CPU

Lab


   转载规则


《【Mit6.s081】课程记录》 行码棋 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
折腾长久,终退役-致我的ACM生涯 折腾长久,终退役-致我的ACM生涯
先留坑,打完终幕场便填上,静候。 从2023-11-20建文件,打完最后一场济南就算正式退了,打完比赛还要立马交一个实训课的证明,还需搞一下,所以先起个草稿,打算写一篇正式的退役记来记录和纪念我这跌跌撞撞、无所成绩的ACM生涯。 对我的
2023-11-22 2024-02-20
下一篇 
代码中的技巧或习惯 代码中的技巧或习惯
1.需要一个数组或者字符串的目前的元素和前一个元素做相关运算时: 直接遍历这个序列,当 i 等于0时不满足条件,只有i 等于 1时才会执行if语句 for(int i = 0; i < len; ++i) { if( i &
2023-11-06 2024-02-20
  目录