操作系统简述
在硬件的基础上,这层软件通过响应用户输入的指令达到控制硬件的效果,从而满足用户的需求。
CPU
CPU 内部都会包含一些寄存器来保存一些关键变量和临时结果,在指令集中的一些指令用于关键字会从内存加载寄存器中,以及把关键字从寄存器中存入内存中。
一些特殊的寄存器:
- 程序计数器(Process Counter):会指示下一条需要从内存提取指令的地址,提取指令后,程序计数器将更新为下一条需要指令提取的地址
- 堆栈指针(stack pointer):它指向内存中当前的栈顶端。包括输入参数、局部变量以及没有保存在寄存器中的临时变量。
- 程序状态字寄存器(PSW:Program Status Word):这个寄存器由操作系统维护,会跟踪控制当前程序的两种状态,来处理用户程序的系统调用(System call)和I/O
当CPU执行第N条指令,还可以对 N+1 条指令进行解码,还可以对 N+2 条指令进行读取,这样的组织方式,称为流水线(pipeline)。
比流水线更先进的设计是超标量(superscalar)CPU。
该类型CPU会直接将两个或更多指令被一次性取出、解码放入缓冲区中,直至他们执行完毕。如果有一个执行单元空闲就会立即查看缓冲区是否有可以执行的指令。
CPU 运行模式
PSW寄存器中的一个二进制位会控制程序当前状态为内核态还是用户态。
- 内核态(管态):CPU能够运行任何指令集中的指令并且能够使用硬件的功能。
- 用户态:CPU只能执行指令集的一部分并且只能访问硬件的一部分功能,在用户态下,有关 I/O 和内存保护的所有指令是禁止执行
因此为了获取操作系统的服务,用户程序必须使用 系统调用(System call),系统调用会转换为 内核态 并调用操作系统,其中 TRAP 指令用于将用户态切换为内核态并启用操作系统。
内存
顶层的存储器速度越高,容量就会越小。
寄存器
存储器的顶层为 CPU 的寄存器。
高速缓存
它多数由硬件控制,主存被分割成 高速缓冲行(cache lines) 为64字节,内存地址的 0~63 对应高速缓存行 0,地址 64~127 对应高速缓冲行的 1。
因此使用最频繁的高速缓冲行保存在最接近CPU的高速缓冲中。
当应用程序需要从内存读取关键词的时候,就会检查是否高速缓冲行在高速缓存中,如果在则称为 高速缓存命中(cache hit)。高速缓存通常需要两个时钟周期,很快的,否则,缓存未命中则需要从内存中提取,会消耗大量的时间。
主存
通常叫做 RAM(Random Access Memory),还有少量的非易失性随机存取存储器,在电源断电后,ROM(Read Only Memory)并不会丢失内容。
另外还有 EEPROM(Erasable PROM) 和 闪存(Flash memory) 这些也是非易失性的,但与 ROM 相反,他们是可擦除的。
磁盘
信息会写在磁盘一系列的同心圆上,在任意一个给定臂的位置,每个磁头都可以读取一段环形区域,称为 磁道(Track) 。
如果将所有磁道合并起来,则组成了一个 柱面 。
每个磁道都划分了若干扇区,扇区的值是 512 字节。外部柱面相比较内部的有更多的扇区。
虚拟内存
其方法是将程序放在磁盘上,而将主存作为一部分缓存,用来保存最频繁使用的部分程序。
这种机制就需要快速映像内存地址,用来把程序生成的地址转换为在RAM中的物理地址。这种映像由CPU中的 存储器管理单元(Memory Management Unit,MMU) 来完成。
缓存和MMU的出现对系统的性能有很大的影响,
在多道程序中,从一个程序切换到另外一个程序的机制称为 上下文切换 (context switch)。
I/O 设备
忙等待
这种方式中将一直占据CPU,CPU会一直轮询 I/O 设备知道 I/O 操作完成。
中断
中断过程:
- 设备驱动程序会通过写入设备寄存器高速控制器应该做什么,然后控制器会启动设备。
- 当控制器完成读取被告知的数据后,会通过总线向中断控制器发送信号
- 如果中断控制器准备好接收中断信号,会告诉CPU
- 中断控制器把该设备的编号放在总线上,这样CPU就可以读取总线,并且知道哪个设备完成了操作
CPU实施中断的过程:
- PC 和 PSW 就会压入当前堆栈中并且CPU会切换内核态
- 设备编号就可以作为一个内存引用,用来寻找中断处理程序的地址,这个内存称为 中断向量(interrupt vector)
- 一旦中断处理程序开始后,它会移除栈中PC和PSW,并将他们保存
- 中断程序全部完成后,它就会返回之前用户程序尚未执行的第一条语句
直接存储器访问(Direct Memory Access, DMA)
无需CPU的干预
文件
管道
管道是一个虚文件,它可以连接两个进程。
如果A和B希望通过管道进行通信,它们必须提前建立管道,当进程A相对进程B发送数据时,首先将数据写到管道上,相当于管道就是输出文件。这样,Unix上的两个进程的通信就非常类似普通文件的读写了。
保护
Unix系统通过对每个文件赋予一个9位二进制保护代码,对Unix中的文件进行保护,有三个字段,一个是owner所有者,一个group与所有者同组,一个是其他人。而每个字段分为可读可写可执行,这就是著名的 rwx 位。
比如保护代码 rwxr-x–x 的含义是所有者可以读写执行,而同组的可以读执行但不能写,其他人只能执行。
系统调用
我们可以看出os为我们提供了两种功能:为用户提供应用程序的抽象和透明管理计算机资源。
而只有想系统调用(读写创建文件操作)才能够进入内核态,而过程调用进入不了内核态。
我们通过 read 方法来看一下调用的过程
count = read(fd, buffer, nbytes)
其中 count 为返回实际读出的字节数,这个值通常与 nbytes 相同,也可能更小,比如读到了文件的末尾的情况。
一共11个步骤。
1–3:将参数压入堆栈。其中参数fd与nbytes都是值调用,而buffer为引用传递即传递的是缓冲区的地址。
4:C调用系统库read函数。
5:由汇编语言写成的库的过程,一般系统调用的编号放在操作系统所期望的地方,如寄存器(把用于read代码放在寄存器中)。
6:执行 TRAP 指令,将用户态转换成内核态,该指令不同一般的过程调用不会跳转到任意地址,而是根据机器体系结构,跳到指定位置。
7:跟随着 TRAP 指令后的内核代码检查系统调用编号,然后 dispatch 到正确的系统调用处理器。
8:此时系统调用器运行系统调用处理程序。
9:一旦系统调用器完成之后,控制权会根据跟随TRAP之后的指令中返回系统调用库,即返回到调用者。
10:这个过程接着通常的过程调用的的方式,返回到客户应用程序。
11:调用完成后,系统清除掉用户堆栈,然后增加 堆栈指针(increment stackpointer),用来清除调用read之前压入的参数。
从而完成read调用过程。
重要的系统调用函数
用于进程管理的系统调用
在 Unix 中,fork 是唯一可以 POSIX 中可以创建进程的途径,它创建了一个原有进程的副本,包括所有的文件描述符、寄存器等内容。在fork之后,原有的进程以及副本就分开了,在fork的过程中,所有的变量都有相同的值,后续两者不会相互影响。fork调用会返回值0,
|