mit6.s081 lecture3
Lecture3
别问为什么第一篇就到3了… Lecture1混进OperatingSys了。
Isolation 隔离
稻草人设计
所谓的操作系统是在硬件和程序之间的一层抽象。
假如说没有了操作系统这一层抽象的话,会发生什么?
首先就是上下文切换这一点,虽然说程序本身可以做到类似主动让步让出系统资源这一操作(yield)。但是假如程序在正常的执行过程中出现了类似死循环,或panic等等异常。此时由于没有操作系统的介入(SysCall)。就会导致无法退出,或者说无法recovery。不能强制的实现一般性多应用程序间切换。multiplexing
另外的话,操作系统保证了空间资源等的分配。假如没有操作系统的分配。应用程序之间有可能会使用到同一份物理空间(不是虚拟内存)。此时数据会被覆盖。影响正常运行等结果不必多说。
两者概括为:
内存隔离
multiplexing
*在实时操作系统中,应用程序可能会相互信任,所以不会有这些问题。
所以
Unix interface
Abstract the hardwares
Provide Strong Isolation
To Application
以进程单位抽象了CPU(操作系统以进程为单位抽象CPU的分配)
对于其他的系统调用如
exec
抽象了内存
应该是说:
exec本质上是执行程序,执行程序的时候会操作内存。而用户不需要跟内存接触,只需要操作exec即可。
file
抽象了磁盘,用户不需要操作磁盘中的物理块。操作系统提供了文件这一抽象来进行使用。
Defensive 防御性
user / kernel mode
这是最基本的一个构建,通过这个方式隔离应用程序执行一些不能执行的指令。
如何判断什么是特殊权限的指令 什么是普通权限的指令?
会有一个标志位寄存器。特殊置1,普通置0。并且会存在一个本身为特殊权限的指令,该指令就能修改这个寄存器,随意置0或1。
这么说有点套娃,既然说本身修改权限位的指令就需要特殊权限。那是否就意味着普通用户不能操作特殊权限指令?
是的,本身这就是为了达成防御性。所设置的特性,或者说是机制。
虚拟内存
操作系统是以进程为单位分配内存的。而分配内存的单位本质上就是页表。而页表存储的本质上是一个虚拟逻辑地址。操作系统会通过这个逻辑地址计算出其实际存储的物理空间。
这就带来内存的强隔离性。
SysCall
通过 user / kernel mode
这一模式。我们可以很好的隔离操作系统和应用程序。
但是程序执行类似write read
等操作的时候还是需要内核态的权限。所以就会有了一个SysCall。通过这些暴露出来的接口,用户态的应用程序可以操作操作系统内核中的资源。
在xv6中,每一个系统调用都会有一个它自己的编号。
而提供给用户的系统调用接口本质上是一个再封装。真正的执行是通过执行eCall
函数,附带对应的系统调用的编号。
如何进行鉴权合法性校验等?
在用户态再封装函数的时候就会进行一个前置检查。而在内核端也会再次判断。例如说是write函数的话,会判断write所写的地址是不是合法的(是否重叠其他进程)。
本质上就是说。用户态会有一套校验规则,内核态本身也会有一套后置校验的规则。
综上
内核的安全性非常重要
将应用程序看作是恶意的(有点像面向失败编程)
没bug
宏内核 & 微内核
宏内核:将常用的所有都集成到操作系统内核态中。
内核代码量大 -> 容易出bug -> 不安全
模块多 -> 通讯快&效率高
微内核:内核中仅保留必需的模块。尽量将程序放到用户态中。

通过通信来进行模块之间的交流,但是如图,这样就会导致每一次通信都需要两次内核&用户空间之间的转换。
如何理解因为宏内核的集成,所以它的效率比微内核高呢?
主要是因为有page cache页缓存
的存在。多个模块之间可以共享到到这个缓存,就减少了模块见写读的时空成本。 见教材中2.2
xv6的启动过程
entry.S
在操作系统启动的时候,一般来说是引导加载程序(GRUB)设置基本硬件环境,加载内核镜像,跳转内核入口点
在xv6中设置好这三者之后,会跳转到这里的这个kernel/entry.S
中
.S
后缀指的是可以进行预处理的汇编文件.s
的进化。通过预处理,可以让这些文件包含类似宏定义,条件编译等功能。
文件扩展名 预处理 描述 .asm
无 直接写的汇编语言代码,不包含预处理指令,直接交给汇编器处理。 .s
少许 一般是编译器生成的汇编代码,通常不包含预处理指令,直接交给汇编器处理。 .S
有 包含预处理指令(如宏、条件编译等),需要先经过预处理器(如 cpp
)处理,然后交给汇编器处理。
下面结合其中的内容大致解释一下
1 | # qemu -kernel loads the kernel at 0x80000000 |
此处的stack0没有标注具体的值,怎么得到?
在start.c或其他文件中进行标志。在链接的时候(多个文件的值等链接到一起),entry.S该文件可通过符号引用查找到其对应的值或地址等。
start.c
1 | // entry.S jumps here in machine mode on stack0. |
这个文件本质上是将机器模式的一些权限转交给supervisor模式 注册了定时器机器中断 最后直接以supervisor的权限模式return
以机器模式的身份配置 中断寄存器 异常寄存器等组件的使用权限
并且注册初始化配置定时器机器中断
通过MPP澄清当前为机器模式 并且设置mret之后的模式为supervisor模式 即内核模式
对于timerinit()
内联了一个主动让出线程资源的汇编脚本 以中断为介质 配置:
配置中断的触发条件(时间片的长度等) :具体方式是会有一个寄存器计算出下次中断的时间 一旦到达这个时间 就会自动触发中断
需要保存的数据结构
scratch
中断的时候 需要执行的逻辑 :执行内联汇编脚本 让运行中的程序让出进程资源。
enable机器中断
执行完这里 就到了kernel/main.c
当中了
在main.c当中 会对各部分进行初始化 并且会创建运行用户空间的第一个进程userinit()
其中会进程uvminit()
等工作 这些细说起来太多了 感觉可以单独写一篇
启动的时候会执行initcode.S
当中的汇编代码 通过汇编执行init.c
单独编译后的二进制可执行文件
这个过程本身进行了系统调用exec 执行失败的时候 会调用exit…
成功执行则启动sh.
启动是通过exec
这一系统调用 这一系统调用的话则是使用了ecall
这一RISC-V的汇编命令
调用命令就会 从用户空间陷入内核空间 陷入就会跳转到trampoline.S
中