操作系统
第一章 介绍
1.2 计算机系统组织
1.2.1 计算机系统操作
I/O设备可以和CPU同时工作
每一类I/O设备都有自己的控制器
每一个控制器都有一个缓冲区
CPU将数据在缓冲区和内存之间移动
I/O操作将数据从I/O设备移动到缓冲区
控制器通过中断告知CPU自己已经完成了I/O操作。
中断的类型
- trap: trap类型的中断因软件产生,程序执行过程中出现错误(算数移除,访存越界)或用户请求时会发生此类型的中断。
- interrupt: interrupt类型的中断因硬件产生。I/O完成,计时器到时,硬件故障时会发生此类型的中断。
中断发生时要:
- 启动中断服务程序
- 保存现场
- 屏蔽其他中断
操作系统是由中断驱动的(换言之,操作系统从不自己主动做事情)。
处理中断
- CPU保存寄存器状态和程序计数器
- 判断发生了哪种中断
- 调用对应的代码段处理中断
中断循环
- 处理器在取下一条指令之前,先判断有没有中断
- 若没有中断,则继续取下一条指令执行当前程序
- 若有中断,暂停现有程序,运行中断处理程序
中断时间线
挺好的一张图,看懂就好
1.2.2 存储结构
计组那一套
- 分级存储
- 主存
- 第二存储
- 磁盘
- 磁带
1.2.3 I/O结构
- Programmed I/O(有规划的I/O,我自己起的名字)
- CPU没有拿到它想要的那一个word之前,就一直等着。一直等I/O设备的status变成可用,然后给它读了那一个word,然后才结束。
- Interrupt-Driven I/O(中断驱动的I/O)
- CPU给I/O设备一个“我要用你了”的信号,然后就去做别的事情。等I/O设备准备好了(或者出现了故障),会中断CPU的进程,然后为CPU做它想要做的事情。
- 同步I/O:I/O工作开始之后,一直等到I/O完成,控制权才返还给用户。
- 异步I/O:I/O工作开始之后,控制权不等到I/O完成就返还给用户。
在实际的I/O工作开始前,到底是不是在坐等I/O设备可用是区分Programmed I/O和I-D I/O的标准。
在I-D I/O中,是否坐等I/O工作完成,是区分同步和异步I/O的标准。
有两个东西方便实现上述功能
- 设备状态表:设备处于什么状态
- 系统调用:操作系统要怎么用设备
DMA(Direct Memory Access)
- 高速(与存储器读写速度相近的)I/O设备可以通过DMA一块一块的直接进行读写内存操作,中途不加CPU的干预
- CPU只在I/O开始和结束时介入。
- 每次读写一块数据时,CPU只在结束时被中断一次。
- 期间CPU可以做其他事情。
I/O通道控制程序
- 与DMA类似
- CPU通过I/O指令控制I/O设备
- I/O指令包括:通道号,I/O设备号,通道程序的首地址
- 通道程序由若干通道指令组成
- 当CPU需要一系列I/O操作时,只需要向I/O通道发送一条I/O指令即可。
- 通道程序举例
- P:=1时代表是通道程序的最后一条指令
- R:=1时代表这一条指令是处理当前记录的最后一条指令
1.4 操作系统结构
- 单线编程:再执行一个任务时,到了I/O环节,CPU总是要等I/O进行完毕再进行下一个动作,CPU和IO设备永远不会同时工作。
- 多线编程:保证CPU一直在运转,将所有要做的工作存起来。在执行一个任务到了不得不等I/O的环节时,操作系统切换到另一个工作。
- 时分复用
- CPU频繁的不同工作之间切换,确保用户可以在一个进程运行的期间和它交互
- 至少有一个工作在内存中执行
- 多个工作同时执行时要按照多线编程的方式选择工作进行
- 如果要进行的进程无法放进内存中,需要把内存中的东西移出去(有可能把别的工作移出去)。
- 多用户通过终端同时访问
- 时分复用系统的特点
- I/O规划由操作系统安排
- 操作系统为不同的工作分配内存
- 设立虚拟内存,为了使工作不只能在内存中进行
- 文件系统实现磁盘管理
- 安排CPU要做的事情,避免死锁
- 分配资源
1.5 操作系统行为
- 现代操作系统是由中断驱动的
- 硬件触发的中断叫做Interrupt,软件叫做trap或exception
- 双模式行为:硬件支持系统处于两种状态下
- User mode: 用户执行
- Monitor mode: 系统执行
- 双模式行为:
- 硬件设置标志位:0为Monitor mode,1为User mode
- 可以分辨当前处于什么模式
- 某些有特殊权限的指令只能在kernel mode下执行
- 一个system call就会转移到kernel mode,从system call返回时转移到user mode
- 设立计时器防止资源一直被某一进程占用
- 操作系统维护一个倒计时的计时器
- 当倒计时为0时,触发一个中断
- 在一个进程重获控制权时开始计时,在一个进程执行的时间超过预期时中断
- 用于改变这个计时器值的指令是一种特殊权限指令
1.6 进程管理
- 进程是一个系统正在执行的程序,是系统工作的单位。
- 进程需要资源
- 进程在结束时需要把资源归还
- 单线程的进程只有一个PC,多线程的进程每个线程都有一个PC
- 不同处理器上同时运行着多个进程
- 操作系统需要管理以下和进程有关的事件
- 创建/销毁用户/系统进程
- 挂起/恢复执行进程
- 同步进程
- 进程通信
- 防止死锁
第二章 操作系统结构
2.1 操作系统提供的功能和服务
- 一类用于方便用户使用
- 用户交互,程序执行,I/O,操纵文件,进程间通信,错误检测
- 一类用于高效执行自己的业务
- 分配资源,统计,保护
- 多用户可以通过分配资源来提高效率
2.2 用户与系统交互的方式
- CLI 命令行
- MS-DOS在内核中实现,内置命令
- Windows和UNIX在系统程序中实现
- Windows的命令控制界面
- 运算符:&,&&,||,(),;,%1
- 批处理文件:.bat和.cmd
- GUI 图形化界面
- 操作系统汉化
- CCDOS:第一台长城机汉字操作系统
- HHBIOS:CCDOS的进阶版
- UCDOS:企业研发,性能优良,灵活方便
- 中文之星:Windows外围汉化代表
- 语音控制(VUI)
- 图像
- 手势
- 脑机
2.3 系统调用
- 对于系统提供的服务的编程式交互
- 例:写一个打开a文件,把a文件中的内容写入b文件
- 通常使用高级编程语言
- 多数由高级程序提供的接口(API)实现,而不是直接进行系统调用
- API会提供一系列的函数方便编程者使用
- 常见的API:
- Windows API:Windows
- POSIX API:UNIX,LINUX,macOS
- Java:JVM
- API比直接进行系统调用有着更好的迁移性,且屏蔽了一些不需要的细节。
- API
Windows API:UNIX and Linux:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*
* @retrun BOOL
* @name ReadFile
* @param file The file to be read
* @param buffer A buffer where the data will be written or read from
* @param bytes to read The number of bytes to be read next time
* @param bytes read The number of bytes read to the buffer during the last read
* @param ovl Indicates if overlapped I/O is used
*/
BOOL ReadFile(
HANDLE file,
LPVOID buffer,
DWORD bytes To Read,
LPDWORD bytes Read
LPOVERLAPPED ovl
);1
2
3
4
5
6
7
8
9
10#include <unistd.h>
/*
* @retrun ssize_t
* @name read
* @param fd The file descriptor to be read
* @param buf A buffer where the data will be read into
* @param conunt The maximum number of bytes to be read into the buffer
*/
ssize_t read(int fd, void *buf, size_t count); - 支持运行时的系统会专门有一个系统调用交互
- 有一个系统调用索引表,每一个系统调用都对应一个数字
- 系统调用交互会:
- 屏蔽API里的系统调用
- 启用OS中必要的系统调用
- 返回结果
- 用户并不知道系统调用是怎么做的,他们只需要知道怎么调用API和获得返回值就可以了。
- 向系统调用传递参数的三种方式
- 寄存器
- 内存块
- 栈:程序入栈,OS出栈
2.4 系统调用的类型
- 进程控制
- 文件管理
- 设备管理
- 信息维护:获取,设置系统时间,文件属性
- 交流:进程间
- 内存共享:借用内存共享块通信,不需要协助,不方便进程间同步,安全性差,高速量大
- 消息传递:用send和receive,包含系统调用,必须提前建立连接,效率较低但是可以保持同步,用于交换信息量较少的数据,安全性好
- 保护:设置资源权限
2.5 系统程序
- 文件管理
- 状态信息
- 文件编辑
- 编程语言支持
- 程序加载和执行
- 用户,进程,系统间通信
- 系统工具
- 后台服务:自启动
2.6 操作系统的设计与实现
- 并不存在一个完美的解决方案
- 不同操作系统的内部原理相差甚远
- 从理清目的和特点出发
- 受硬件类型,操作系统类型(批处理,时分复用…)影响
难以具体化目标
- 用户:易用,性能强大,可靠,快速
- 系统:易实现,易维护,可靠,高效,可迁移- 设计和实现一个操作系统是软件工程中极具创造性的工作
- 将目标(Policy)从手段(Machanism)中分离出来
- 二者分离是实现灵活性的重要途径
- 操作系统的实现
- 相差甚远
- 时间发展顺序:汇编语言————Alogol,PL/1————C,C++
- 实际上是多种语言的混杂,底层汇编,主体C,系统程序C/C++,Python,PERL,shell scrpits
- 更多的高级语言会导致
- 硬件之间的迁移性好
- 运行速度变慢
- 仿真可以让操作系统运行在非原生的硬件上
- 影响操作系统性能的重要因素:优秀的数据结构和算法
- 只有小部分代码的性能对系统总体性能有至关重要的影响
- 中断处理,I/O处理
- 内存管理,CPU管理
- 在可以正常工作后,优化瓶颈:将其转换为等价的汇编语言
2.7 操作系统结构
2.9 迁移操作系统
- 一个SYSGGEN文件用来描述操作系统所需的硬件参数
- CPU,内存大小,设备,缓冲区大小
- 迁移操作系统的方式
- 复制并修改原本操作系统的源代码
- 选择之前操作系统库中的部分功能
- 配置一个合适的表用来描述系统
2.9 启动操作系统
- Booting:通过加载内核启动计算机
- Bootstrap Program:一小段在ROM中的程序,定位内核,将其加载进内存,启动执行
- 两步走
- bootstrap loader:诊断初始化系统,将boot program写入内存准备执行
- boot program:将整个操作系统加载进内存开始执行
第三章 进程
3.1 进程概念
执行中的程序叫进程
程序:静态存储在磁盘上的可执行文件
进程:程序加载进内存开始动态执行成为进程
一个进程内包括:
- 程序代码
- 数据区:全局变量
- 堆:动态分配的内存
- 栈:临时变量,包括:函数参数,返回地址,局部变量
- 当前操作:程序计数器
进程在执行过程中会改变状态
一个进程总是在以下几个状态之一:
- 新创建
- 正在执行
- 等待:等待某个事件的发生
- 就绪:准备加载到一个处理器开始执行
- 终止
一个处理器只能同时执行一个进程,其他进程都在等待和就绪
每个进程都由一个进程控制块(PCB)反映
一个程序块中包含:
- 进程状态
- 程序计数器
- CPU寄存器
- CPU给它的规划:优先级,执行队列
- 内存管理信息
- 统计信息:总共用了多少CPU时间,当前时间片用了多少时间,时间限制是多少…
- I/O信息:打开的文件,分配给进程的I/O设备
- 进程ID
进程在状态之间转换的时机
- 时钟打断:已经执行到了最大时间
- I/O打断
- 内存错误:访问到虚拟内存,必须从外存把数据带进来
- 异常
- 系统调用
一个进程中可以有多个线程
- 多个线程可以同时进行
- 在PCB中记录多个程序计数器
3.2 进程调度
multiprogramming的目的:最大化CPU利用率
时分复用的目的:让用户可以在一个进程执行时与它交互
调度队列
- 工作队列:系统中所有进程
- 就绪队列:系统中所有就绪的进程
- 一般用链表存储
设备队列:每个设备有一个属于自己的队列,用于记录等着要使用它的进程
长周期调度(job scheduler)
- 选择进入就绪队列的进程
- 不频繁被打断
- 长周期调度要控制同时在内存里的进程数量
短周期调度(CPU scheduler)
- 选择下一个要执行的进程
- 频繁被打断
进程可以分为以下两类
- 绑定I/O的进程
- 绑定CPU的进程
绑定I/O的进程多了,就会有很多进程卡在等待状态,就绪队列的进程很少,CPU调度不需要做什么工作
绑定CPU的进程多了,很多进程都会在就绪状态,设备队列的进程就很少,硬件会大量闲置
中周期调度(medium term scheduler)
- 将就绪队列和设备队列里的进程换进(到内存)换出(到磁盘),让两个队列比较均衡
- CPU处理的比I/O设备快,所以所有进程可能都在设备队列
- 这时候把设备队列里的进程换出,状态变为挂起等待
- 新增两个状态
- 挂起等待
- 挂起就绪
- 换出的原因:
- CPU需要为一个要执行的进程腾出一些内存空间
- 系统怀疑有一个进程产生了异常
- 用户主动要求
- 一个进程是每过一段固定时间才执行
- 父进程要求
语境切换
每次CPU切换要执行的进程时,要进行语境切换。一个进程的语境存储在PCB里,语境切换包括以下步骤:- 保存处理器的执行环境:包括程序计数器和寄存器状态
- 更新当前正在执行的进程PCB
- 把PCB挪到它应该去的地方
- 选择另一个要执行的进程
- 更新要执行的进程的PCB
- 更新内存管理数据结构
- 恢复要执行的进程的语境
语境切换是顶级操作:CPU在这个过程中不做任何实际的工作
切换速度取决于硬件
3.3 进程操作
创建进程
- 创建进程的原因
- 执行一项批处理工作
- 用户登录
- 要打印东西
- 被其他进程创建
- 创建进程的步骤
- 给新进程分配一个ID
- 给新进程分配内存空间
- 初始化PCB
- 进行合适的链接:让需要知道这个进程存在的东西都知道它被创建了,比如把它连进CPU调度链表
- 创建别的数据结构:比如一个统计信息表
- 确定资源共享方案:共享父进程的所有资源,共享父进程的部分资源,不和父进程共享资源
- 当一个进程被创建时,他可能包含着从父进程传给它的初始化参数
- 执行时的两种可能:
- 与父进程同步执行
- 父进程等待他的一些子进程结束后继续执行
- 地址空间的两种可能:
- 与父进程完全相同
- 有一段程序专门加载子进程的地址空间
- PPT上的练习
终止进程
- 终止进程的原因
- 正常执行结束
- 执行时间超过限制
- 内存不可用
- 越界访问
- 越权操作:尝试写入只读文件
- 算数异常:分母为0
- 等待(I/O)时间超过最大值
- I/O失败
- 错误指令
- 越权指令:用户进程试图执行只有内核才能执行的指令
- 数据异常:类型不匹配
- 操作系统介入:死锁
- 父进程终止
- 父进程要求子进程终止
- 进程执行了最后一条指令,请求操作系统删除它
- 将数据还给父进程
- 收回分配的资源
- 父进程要终止子进程的原因
- 子进程用了超出给它分配的内存空间
- 分配给子进程的任务已经不再需要了
- 父进程自己要终止
3.4 进程间通信
独立进程不会影响其他进程执行,也不会被其他进程执行影响
合作进程会影响其他进程执行,也会被其他进程执行影响
两个基础模型
- 共享内存:可以达到通信的最高速度和便利性,可以以内存读写的速度进行通信
- 通信管道:适用于数据量小的通信,因为不需要考虑读写冲突;跨计算机通信更好实现
共享内存系统——生产者-消费者问题
- 一个经典模式:生产者进程生产数据,消费者进程消费数据
- 我们要提供一个缓冲区,让生产者把数据放进来填满,让消费者搬空
- 生产者生产和消费者消费可以同时进行
- 两者必须同步(synchorize)
- 两种缓冲区
- 无界缓冲区
- 有界缓冲区:循环队列
通信管道——IPC
- 通信并同步他们的执行节奏
- 建立一个管道,然后通过
send()
和receive()
通信 - 管道的介质
- 物理介质:共享内存,硬件总线,网络
- 逻辑介质:逻辑属性
- 直接通信
- 进程之间必须显式指定通信双方的名字
- 两个通信进程之间有且仅有一根管道(可单向可双向)
- 间接通信
- 进程之间依赖邮箱通信,一个进程把数据放到信箱里,另一个进程来取
- 一个信箱可能有多个进程使用
- 两个进程之间可能用多个信箱
- 如何杜绝间接通信中的冲突问题
- 最多允许两个进程共享一个信箱
- 最多同时允许一个进程调用
receive()
函数 - 让系统随机选择一个接收者,让发送者知道谁拿到了
- 一个信箱可以被操作系统或者进程占用
- 如果这个信箱是被进程创建的
- 这个进程只能用这个信箱接受信息
- 别的进程只能往这个信箱发送信息
- 如果这个信箱是系统创建的:不属于任何进程
- 操作系统向进程提供以下几种操作
- 创建和删除一个信箱
- 从信箱中接收消息,发送信息到信箱
- 同步/异步
- 限制发送者
- 通道0容量:发送者必须等接收者可以接收的时候才能发
- 通道n容量:发送者必须等通道有空闲了再发
- 通道无限容量:发送者的发送不受任何限制