# 课程第一节 && 指导书第一、二章

  1. 操作系统一般需要做到:
    • 抽象硬件,实现了高层级的接口和抽象,例如进程,文件系统。
    • 在多个应用程序之间共用硬件资源,多个程序能够互不干扰地运行,multiplex(多路复用)。
    • 多个程序之间互不干扰。隔离性(Isolation),不同的活动之间不能相互干扰。
    • 能在需要的时候实现共享(Sharing)。
    • Security 或者 Permission System 或者是 Access Control System(权限系统或者门禁系统)。
    • 不阻止应用程序获得高性能,甚至需要帮助应用程序获得高性能(Performance)。
    • 同一个操作系统需要能够支持大量不同的用户场景。
  2. Kernel 中的服务:文件系统,进程管理系统。
  3. 系统调用与程序中的函数调用的区别是系统调用会实际运行到系统内核中,并执行内核中对于系统调用的实现。Kernel 会有特殊的权限能直接访问各种各样的硬件,例如磁盘。而普通的用户程序是没有办法直接访问这些硬件的。
  4. 构建操作系统时,在操作系统下面就是硬件,编程环境比较恶劣。1. 高效易用 — 接近硬件底层,为应用程序提供抽象的高层的可移植接口。2. 要提供一个简单的接口,同时又包含了强大的功能。3. 需要内核具备灵活的接口,又需要在某种程度上限制应用程序,因为你会想要安全性。我们希望给程序员完全的自由,但是实际上又不能是真正的完全自由。
  5. fork 还会拷贝文件描述符表单。两个进程的指令是一样的,数据是一样的,栈是一样的,同时,两个进程又有各自独立的地址空间,它们都认为自己的内存从 0 开始增长,但这里是不同的内存。有一些细节偶尔会导致父子进程不一致。子进程 fork () 返回 0, 父进程返回子进程进程号。子进程不会从 main () 函数开始执行,而是直接从 fork () 系统调用返回,就好像是他自己调用了 fork。
  6. exec("filename",argv) 从指定的文件中读取并加载指令,并替代当前调用进程的指令。会保留当前的文件描述符表单,通常来说 exec 系统调用不会返回,除非调用出错返回 - 1。
  7. Shell 会执行 fork,之后 fork 出的子进程再调用 exec5 系统调用。
  8. fork() 的子进程用 exit(num) 退出后,在父进程中可以用 wait(&status) 来获取子进程的退出状态。wait () 的返回值是子进程号, status 的值即是子进程 exit() 的参数的值。
  9. 如果没有子进程的进程调用了 wait,wait 会立即返回 - 1,表明出现错误了。没有直接的方法让子进程等待父进程退出。
  10. 如果一个进程调用 fork 两次,如果它想要等两个子进程都退出,它需要调用 wait 两次。每个 wait 会在一个子进程退出时立即返回。当 wait 返回时,你实际上没有必要知道哪个子进程退出了,但是 wait 返回了子进程的进程号,所以在 wait 返回之后,你就可以知道是哪个子进程退出了。
  11. int fd[2]; pipe(fd) 创建管道, fd[0] 用来从管道读数据, fd[1] 用来向管道写数据。返回 0 代表成功,返回 - 1 失败。管道是不是文件取决于操作系统创建管道的方式,一般来说用 pipe () 创建的管道都不是文件。而创建基于文件的管道,通常叫做有名管道或 FIFO 文件(先进先出文件)。两个进程只要知道管道的名字也能用它来通信,即使它们是非父子进程关系。如果试图读取一个空的管道,也不会发生错误,因为程序会等待管道中出现东西。当子进程结束时,管道会关闭。f gets() 将会收到 EOF (End Of File, 文件结束符) ,于是 fgets() 函数返回 0,循环就结束了。管道只能单向通信。但是可以通过创建两个管道,一个从父进程连接到子进程,另一个从子进程连接到父进程来实现双向通信 。

# 课程第三节 && 指导书第三章

  1. 需要操作系统的隔离性,保证应用程序间不会相互影响。需要操作系统抽象硬件资源,不让应用程序看到硬件资源。一个应用程序不能长时间占用 CPU, 需要协同调度,如果没有操作系统,这很难。从内存的角度来说,如果应用程序直接运行在硬件资源之上,那么每个应用程序的文本,代码和数据都直接保存在物理内存中。这可能会导致一个程序越界操作另一个程序的内存。

  2. fork 创建了进程。进程本身不是 CPU,但是它们对应了 CPU,它们使得你可以在 CPU 上运行计算任务。应用程序不能直接与 CPU 交互,只能与进程交互。操作系统内核会完成不同进程在 CPU 上的切换。所以,操作系统不是直接将 CPU 提供给应用程序,而是向应用程序提供 “进程”,进程抽象了 CPU,这样操作系统才能在多个应用程序之间复用一个或者多个 CPU。

  3. 处理器有几个核,就可以同时运行几个进程,但可以分时复用 CPU,比如一个进程用 100ms , 之后,另一个进程用 100ms。

  4. 我们可以认为 exec 抽象了内存。当我们在执行 exec 系统调用的时候,我们会传入一个文件名,而这个文件名对应了一个应用程序的内存镜像。内存镜像里面包括了程序对应的指令,全局的数据。应用程序可以逐渐扩展自己的内存,但是应用程序并没有直接访问物理内存的权限,例如应用程序不能直接访问物理内存的 1000-2000 这段地址。不能直接访问的原因是,操作系统会提供内存隔离并控制内存,操作系统会在应用程序和硬件资源之间提供一个中间层。exec 是这样一种系统调用,它表明了应用程序不能直接访问物理内存。

  5. files 基本上来说抽象了磁盘。应用程序不会直接读写挂在计算机上的磁盘本身,并且在 Unix 中这也是不被允许的。在 Unix 中,与存储系统交互的唯一方式就是通过 files。Files 提供了非常方便的磁盘抽象,你可以对文件命名,读写文件等等。之后,操作系统会决定如何将文件与磁盘中的块对应,确保一个磁盘块只出现在一个文件中,并且确保用户 A 不能操作用户 B 的文件。通过 files 的抽象,可以实现不同用户之间和同一个用户的不同进程之间的文件强隔离。

  6. 操作系统应该具有防御性(Defensive),能够应对恶意的应用程序,阻止其控制内核。

  7. 硬件对于强隔离的支持包括:user/kernle mode 和虚拟内存。

  8. user/kertnle mode (用户态和内核态)是处理器的两种操作模式(还有第三种机器模式,主要用于配置计算机),运行在 kernel mode 时,CPU 可以运行特定权限的指令(privileged instructions);当运行在 user mode 时,CPU 只能运行普通权限的指令(unprivileged instructions)普通程序运行在 user mode,内核空间的程序运行在 kernel mode , 操作系统位于内核空间。

  9. 普通指令如寄存器相加减的 ADD , SUB , 跳转 JRC、BRANCH 等,所有程序都允许执行。特殊权限指令是一些直接操纵硬件的指令和设置保护的指令,只能被内核执行,列如:page table 寄存器,关闭时钟中断等。用户代码都会通过内核访问硬件。

  10. 在处理器里面有一个 flag , 为 1 是 user mode,为 0 是 kernel mode。用户程序通过系统调用来切换到 kernel mode, 执行系统调用时会通过 ECALL(处理器的一个指令)触发一个软中断(software interrupt),软中断会查询操作系统预先设定的中断向量表,并执行中断处理程序。中断处理程序在内核中,这样就完成了 user mode 到 kernel mode 的切换,并执行用户程序想要执行的特殊权限指令,内核会检查并判断是否允许应用执行系统调用。

  11. BIOS 是一段计算机自带的代码,它会先启动,之后它会启动操作系统。

  12. 操作系统会给每个进程设置一个他自己独有的 page table 页表(首先是指令,然后是全局变量,然后是栈区,每个进程有两个栈区:一个用户栈区和一个内核栈区,最后是一个堆区域)来映射一块和其他进程不重合的物理内存。两个正在运行的进程都有内存地址 0 , 但并不是同一个物理内存地址,而是映射到不同的物理内存地址。这样就给了我们内存的强隔离性。C 让你得到更多对于硬件资源的底层控制能力。

  13. 内中的操作系统代码越多,Bug 越多。让整个操作系统代码都运行在 kernel mode 中称为 Monolithic Kernel Design(宏内核),缺点是容易出 Bug,优点是每个模块都在一个程序中,紧密集成,可以提供良好的性能。

  14. 尽量减少内核中的代码,被称为 Micro Kernel Design(微内核),将操作系统的大部分代码运行在内核之外,作为普通的用户程序来运行(比如文件系统),这意味着更少的 Bug , 问题是,这会出现两次用户空间 <-> 内核空间的切换,比如:shell 通过内核中的 IPC 系统(进程间通信系统:管道,FIFO , 消息队列,信号量,共享内存。这里是消息队列)发送一条消息,内核查看后把消息发给文件系统,文件系统完成工作后发回给 IPC 系统结果,IPC 系统再将消息发给 shell。性能更差,而且 page cache 被隔开了,不好共享。

  15. 内核编译过程:Makefile 会读取一个 C 文件(pro.c);之后调用 gcc 编译器,生成一个汇编语言文件(proc.s);之后走到汇编解释器,生成汇编语言的二进制格式(proc.c)。对所有内核文件都做相同的操作。之后系统加载器(Loadee)会将所有的.o 文件链接在一起,并生成内核文件,就是在 QUME 中运行的文件。

  16. QEMU 相当于模拟了一个计算机系统或者计算机主板,它仿真了 RISC-V 处理器,来运行 XV6 。QEMU 主循环中每个 CPU 核做一件事情:读取 4 或 8 字节的 RISC-V 指令;解释 RISC-V 指令,并止找出对应的操作码(op code)(ADD,SUB 之类);在软件中执行相应的指令。它要维护寄存器状态。仿真普通权限指令和特殊权限指令。跑在 QEMU 上的代码和跑在真正的 RISC-V 处理器上是一样的。

  17. 操作系统必须满足三个要求:多路复用、隔离和交互。多路复用要求可以运行比处理器数量更多的进程;隔离要求进程间互不影响,交互又要求进程间可以交互。

  18. RISC-V 指令(用户和内核指令)使用的是虚拟地址,而机器的 RAM 或物理内存是由物理地址索引的,RISC-V 页表硬件通过将每个虚拟地址映射的物理地址来为这两种地址建立联系。RISC-V 页表在逻辑上是一个页表( Page Table Entries/PTE)条目组成的数组(数组中含有 PPN 和一些标识位 Flags),如下图,通过虚拟地址寻找页表数组中的 PPN,再加上原虚拟地址中的后十二位组成一个物理虚拟地址。

    <img src="/home/origin/Code/repository/Note/All_picture/6.s081_note/p1-1669426857667-10.png" alt="img" style="zoom: 67%;" />

    实际的转换分为三个步骤,页表以三级树形结构存储在物理内存中。页硬件使用 27 位中的前 9 位在根页表页面中选择 PTE,中间 9 位在树的下一级页表页面中选择 PTE,最后 9 位选择最终的 PTE。

    <img src="/home/origin/Code/repository/Note/All_picture/6.s081_note/p2.png" alt="img" style="zoom:67%;" />

    如果转换地址所需的三个 PTE 中的任何一个不存在,页式硬件就会引发页面故障异常(page-fault exception),并让内核来处理该异常。

  19. Xv6 为每个进程维护一个页表来描述每个进程的用户地址空间,外加一个单独描述内核地址空间的页表。Xv6 物理起始地址是 0X80000000 , 内核通过读写这个地址以下的物理地址与设备交互。

阅读次数