【MIT 6.S081】笔记

学习MIT 6.S081过程中的一些理解,并非完整的操作系统理论知识笔记

进程

  • 内核将每个进程与一个进程标识符pid关联

fork

  • 进程创建子进程的函数

  • 创建子进程的过程可以视作,父进程的所有资源复制一份,即父子进程拥有独立的资源,子进程与父进程代码相同但是从fork()函数之后开始执行

  • 进程用exit 函数退出,子进程向父进程返回 pidpid ,子进程的函数返回 00

  • 父进程用wait函数等待子进程完成

exec

  • 从指定的文件中读取并加载指令,并替代当前调用进程的指令(丢弃了原进程的资源),原进程不需要单独写返回(完全替代了原进程)

I/O

  • 系统调用 readwrite 从文件描述符所指的文件中读或者写 n 个字节。read(fd, buf, n)fd 读最多 n 个字节(fd 可能没有 n 个字节),将它们拷贝到 buf 中,然后返回读出的字节数。

管道

  • 进程间通信

  • 读和写描述符

用户/内核模式

  • 普通权限/特定权限指令

页表

  • 内存管理单元(MMU,Memory Management Unit)将指令中的(虚拟)地址转换成物理地址

  • SATP的寄存器会保存页表的物理地址(指向最高一级的页表)

  • index和offset,index用来查找page,offset对应的是一个page中的哪个字节

  • 一般三级页表

Traps(X86的中断)

  • 用户程序想要进入内核(设备信号interrupt、非法操作exception),OS需要在用户/内核模式间转换
  • 系统必须保存寄存器以备将来的状态恢复

Page fault

信息:引起page fault 的内存地址、原因类型、程序计数值

lazy allocation

内核不提前分配物理内存,应用程序使用到新申请的内存时会触发page fault,希望内核能够分配一个内存page,并且重新执行指令。

p->sz指向stack的顶,heap的底,新旧p->sz之间就是未分配的部分。

在page fault handler中,通过kalloc函数分配一个内存page;初始化这个page内容为0;将这个内存page映射到user page table中在page fault handler中,通过kalloc函数分配一个内存page;初始化这个page内容为0;将这个内存page映射到user page table中

0 fill on demand

用户程序的地址空间:text区域(指令),data区域(初始化了的全局变量),BSS区域(未被初始化、初始化为0的全局或者静态变量)

物理内存只分配一个page内容是0,虚拟空间的全0的page全映射到这一个物理内存(只读),需要写的时候page fault,重新给个page。

copy-on-write fork(COW fork)

创建子进程时,直接共享父进程的物理内存page,设置子进程的PTE指向父进程对应的物理内存page,PTE设只读防止被修改。触发page fault后拷贝新page给子进程,二者PTE就可设可读可写了。

重新执行用户指令是指调用userret函数(lab 4见过)

PTE最后两位RSW保留给supervisor software(内核),将bit8标识为当前是一个copy-on-write page。

但由于共享,需要对于每一个物理内存page的引用进行计数,确保释放是安全的。

Demand paging

线程

线程就是单个串行执行代码的单元,它只占用一个CPU并且以普通的方式一个接一个的执行指令。

线程的状态包含线程的状态包含:程序计数器、寄存器、栈。

多线程的并行运行:多CPU、一个CPU切换多线程。

XV6内核共享内存,支持内核线程的概念,每个用户进程都有一个内核线程来执行来自用户进程的系统调用。所有的内核线程都共享了内核内存。

每一个用户进程都有独立的内存地址空间,包含一个线程控制用户进程代码指令的执行,用户线程之间没有共享内存。

每个CPU核都有一个线程调度器(Scheduler)。

位于内核的定时器中断处理程序,自愿把CPUyield给线程调度器。

pre-emptive scheduling:将一个RUNNING转换成RUNABLE。

一个用户进程切换到另一个用户进程:

  • 第一个用户进程接入内核(一个定时器中断强迫CPU从用户空间进程切换到内核,trampoline代码将用户寄存器保存于用户进程对应的trapframe对象中),保存用户进程的状态并运行第一个用户进程的内核线程(usertrap)。

  • 从第一个用户进程的内核线程(包括一些工作和swtch;swtch函数会保存用户进程P1对应内核线程的寄存器至context对象,恢复之前这个CPU核的调度器线程保存的寄存器和stack pointer,之后就在调度器线程的context下执行schedulder函数中;schedulder再调用swtch,保存自己的寄存器到调度器线程的context对象,找到第二个进程之前保存的context,恢复其中的寄存器,第二个进程之前的swtch函数会被恢复,并返回到进程P2所在的系统调用或者中断处理程序中)切换到第二个用户进程的内核线程。

  • 之后,第二个用户进程的内核线程暂停自己,并恢复第二个用户进程的用户寄存器。

  • 最后返回到第二个用户进程继续执行。

用户进程的内核线程context对象保存在用户进程对应的proc结构体中;调度器线程没有对应的进程和proc结构体,所以调度器线程的context对象保存在cpu结构体中(对应一个CPU核,有一个context字段)。

context保存在trapframe中不是不可以,但是设计成两个也许是简化/让代码清晰。