1.1 中断在实时控制、故障处理、计算机与外部设备之间数据传输往往采用中断系统【百度百科额】中断分为内中断(来自cpu内部的中断)和外中断(中断源来自cpu外部)。内中断主要有系统调用、软件异常、缺页中断等等, 外中断有io完成发出的中断、用户强行终止一个进程。中断是用户态进入内核态的唯一途径。中断发生意味着操作系统介入开始管理工作
PSW 程序状态寄存器 主要保存俩类信息 一是指令执行结果的状态信息 比如进位信息等,另一类是控制信息比如 是否允许中断的标志位IF.
1.2 外中断的处理过程
step1 执行完每一条指令后都要判断是否有外中断。
step2 如果发生了外中断 则需要保存正在执行的进程现场,具体包括PSW、程序计数器、通用寄存器等等。
step3 找到终中断号对应的中断程序 执行中断程序。
step4 恢复原进程cpu环境 执行。
2.1 系统调用的概念
系统调用是操作系统提供给编程人员操作系统功能的接口。
2.2 为什么要有系统调用
操作系统的核心功能有进程管理、设备管理、存储管理 等等,对于一些关键的资源,不能由任意用户随意使用,而是由操作系统统一接收请求,分配。
2.3 系统调用的过程
我们写的高级语言库函数封装了对操作系统接口的调用,对操作系统管理的资源进行操作的时候,需要陷入内核态,然后调用操作系统内核函数。高级语言 转化为汇编语言之后会 有int x 代表中断的指令,该指令会使得cpu由用户态陷入内核态,然后执行中断号x对应的中断服务程序。
3.1 进程的概念
进程是程序的一次执行过程。进程实体包括 数据段 代码段 PCB.
补充:代码段是存放代码+常量 数据段存放初始化的全局变量和静态变量 BSS(Block started by Symbol) 段 存放未初始化的全局变量和静态变量。
3.2 PCB 的组成
处理器状态:通用寄存器、psw
进程调度信息:进程状态、优先级
进程的控制信息:数据和程序地址 、进程同步 和通信机制指实现进程同步和进程通信时必需的机制,如消息队列指针、信号量等,它们可能全部或部分地放在PCB中、已经分配的资源清单等
3.3 进程的组织方式
多个PCB 的组织方式有链表 和索引的方式两种。链接方式 根据进程的状态 分为 就绪队列 、执行队列、阻塞队列。索引表的方式是根据进程状态的不同 建立索引表。
3.4 进程的状态
运行态 :已经在cpu执行的进程
就绪态:除了cpu资源 其他资源均已就绪
阻塞态:因等待某一事件而暂时不能运行 比如等待 操作系统分配打印机资源 、等待磁盘读入操作
创建态:进程的创建即操作系统为进程分配资源、创建PCB
终止态:进程的销毁即操作系统回收分配给进程的资源、撤销PCB等。
3.5 进程的控制
进程的控制简单来说就是对进程的各种状态转换做出控制。
3.5.1 如何实现进程控制
之前提到 进程的PCB 组织方式有链接的方式,那么当进程处于不同的状态就会在不同的链接队列中。比如就绪队列中的PCB 在相应的进程被cpu运行后就转移到运行队列中。那么如何实现进程控制呢?
原语实现进程控制:原语的特点是执行不接受中断 一气呵成。 开/关中断指令所需权限高 运行在内核态。
进程控制原语会导致进程的状态转换,无非做一下三件事情:
a 更新pcb中的信息
b 将pcb 加入合适的队列
c 分配/回收 资源
3.6 进程通信
进程是资源分配的单位,进程通信其实就是进程间的信息交换,进程有自己的地址空间,对于两个进程,进程1是不能随意访问进程2的地址空间的,否则进程1就可以随便改写其他进程的数据内容了。那么进程通信(信息交换)如何进行?
3.6.1 共享内存
共享内存是最快的进程通信的方式,如下图可以看到使用消息队列或者管道等进程间通信方式需要4次拷贝的过程。而使用共享内存 减少了系统调用的次数和文件拷贝的次数。
图片来源于该文章
https://blog.csdn.net/LU_ZHAO/article/details/105237107
3.6.2 管道和消息队列
3.6.2.1 管道
管道多用于同族进程之间的通信,管道其实就是连接读写进程的文件,本质是就是一块内存缓冲区。
消息队列相比于管道的优势是提供了一中任意进程之间的简单通信方式,消息队列独立于进程。消息队列中的消息是有类型的,但是管道中的就是数据流。
管道 是半双工通信,如果要全双工需要两个管道。管道的工作流程是一个进程写管道写完成之后另一个进程读。需要注意的当管道写满时写进程会阻塞直到读进程读完成、对于管道操作时,使用者需要进行对线程进行同步。
管道分为有名管道和匿名管道,有名管道可以用在任意进程间的通讯。
3.6.2.2 消息队列
消息队列本质上是内核中的链表,向消息队列上写数据就是向队列上加入结点、读消息队列数据就是删除结点,结点数据具有某种格式而不是字符流。
3.7 线程与进程
线程是操作系统调度的基本单位,进程是资源分配的基本单位,同一进程内的线程切换不会使得进程切换,不同进程的线程切换会使得进程切换。同一进程的多个线程可以共享进程的资源,比如地址空间、io设备等等系统开销较小。
3.8 进程调度
从就绪队列中选择 一个任务 为其分配cpu资源。 先来先服务 短作业优先、最高响应比优先等调度算法。
3.8.1 进程调度的时机
主动放弃cpu :任务完成、主动请求(阻塞等待io)、程序异常终止
被动放弃cpu:时间片用完、高优先级抢占
进程处于临界区中不能调度和切换(错误)
进程处于内核程序临界区中不能进行调度和切换(正确)
3.8.2 进程调度的时机
抢占式 和非抢占式 抢占式适合早期的批处理系统 非抢占式可以优先处理紧急任务 适合实时操作系统。
3.8.3 调度算法的指标
cpu利用率:忙碌时间 /总时间
系统吞吐量: 总共完成了多少道作业/总共花费了多少时间 单位 道/秒
周转时间:作业从提交给操作系统到完成的时间
3.8.4 调度算法
FCFS 先来先服务算法 :缺点是短作业如果排在长作业之后可能会有较长的周转时间和等待时间。
SJF 短作业优先算法:优点是可以获得较小的平均周转时间和平均等待时间。需要注意的是该算法会导致饥饿现象 即长作业会因为短作业不断涌入 不能够得到处理器运行
高响应比优先:先来先服务 其实是关注等待时间 等待时间越久 执行机会越大 而短作业优先关注的是作业执行时间 执行时间越短 越容易得到执行。两种算法都有缺点。高响应比优先是结合二者优势的一种算法 响应比 = 等待时间 /执行时间 + 1;
高响应比优先算法不会产生饥饿现象 长作业会随着等待时间的增加 而逐渐得到执行机会。
时间片算法:时间片调度算法的优点是使得各个进程都能得到cpu运行。但要注意时间片的长短:时间片不能过长过长则退化为先来先服务。过短也不行 过短则增加了进程切换的时间,cpu利用率低。
时间片算法的优点是每个任务都能够得到执行,公平,响应快。缺点是不区分紧急任务。
优先级调度算法:优先级调度算法适合实时系统,可以对优先级高的任务及时作出响应。缺点是可能当一直有高优先级的任务涌入,低优先级的任务会得不到执行而发生饥饿现象。
3.9 进程同步和互斥
3.9.1互斥实现方式
原则:空闲让进 忙则等待 优先等待 让权等待
使用软件方式
单标志法
双标志先检查法
双锁后检查法
peteson 算法
使用硬件方法实现互斥
中断屏蔽方法:该方法的缺点是只适合于单处理器,并且开关中断只适合内核进程不适合用户进程。
testAndSetLock 指令 :不满足让权等待
swap指令:同样会发生让权等待
使用信号量实现同步互斥
之前所讲的用软件方法实现互斥 或者使用硬件方法实现互斥 其实都是有问题的,不能满足实现互斥的原则:空闲让进 忙则等待 有限等待 让权等待。信号量在实现互斥时初始化为1,实现同步时设置为0。
信号量其实就是一种数据结构,可以是整型变量,也可以是记录型变量。
整型信号量
wait原语 和singal原语。其实就是对资源的申请和释放。
记录型信号量
整型信号量同样不满足让权等待原则。记录型信号量的数据结构不仅包含代表资源的整型变量,而且包含阻塞在该资源上的进程队列。p操作即申请资源的操作(wait操作)时会首先将资源数量减一,然后判断是否小于0,如果小于0 则需要在该信号量上阻塞,即加入信号量的进程队列等待v操作来唤醒。V操作是释放资源,首先会对整型变量加1操作,如果加1后的值小于0 则说明队列中有进程在等待,所以应该唤醒队列中的进程执行。
解决哲学家进餐问题
管程引入的原因:1 将各个进程中的临界区 和 pv 操作集中管理。2 防止进程有意或者无意违法同步操作。3 便于用高级程序来书写程序,也便于程序正确性验证。
管程的组成部分:共享数据结构 、访问共享数据结构的函数
银行家算法 -避免死锁
银行家算法最初用来解决的问题是:如何使得银行发放贷款时至少能满足一个用户的需要,如果银行的钱满足不了所有用户的需要,那么就是一种不安全的状态。比如银行共有30元,A需要最多借20 先借10,B最多借20先借10 C最多借20 先借10,此时银行就进入了一种不安全的状态,因为不能满足之后任意用户的贷款。
银行家算法的步骤:
判断此次预分配是否会产生一个安全的序列,即使用安全性算法判断是否会使得系统进入不安全的状态,如果能找到一个安全序列,则判断可已进行此次资源分配。
安全性算法:检查当前的可用资源是否满足某个进程的最大资源要求,若满足加入序列中,并将其所有的资源回收,循环判断,直到所有进程均能进入安全序列中。若不能满足所有进程进入安全序列则说明此次分配不安全。
死锁的检测
可以将已经申请的资源 和正在请求的资源表示为一种图示。如下资源分配图 p1、p2、p3代表进程 R1 R2代表资源 资源的出度代表已经分配的数目 入度代表进程请求的数目。下图就是发生了死锁(不能消除所有的边)。
检测算法一句话概括:在资源分配图中,一次消除非阻塞进程相连的边直到无边可以消除。
死锁的解除
资源剥夺
撤销进程(终止进程)
那么应该选择哪些进程做撤销或者剥夺资源呢?可以从优先级、执行时间、完成时间来看、进程占有的资源数量、进程是交互式的还是批处理式的等等进行选择。
内存管理 其实大致可以包括内存的分配和回收 、内存的地址转换、虚拟内存(逻辑上扩展内存空间)、内存保护 (进程1只能访问进程1的地址空间)等等
5.1 程序运行
为什么栈是从高地址向低地址扩展呢?
早期的内存有限,系统需要考虑内存布局问题。内存的一端放置静态代码、静态数据 另一端是动态数据和增长的栈。现在的问题是哪一端放静态数据和代码数据 哪一端放动态数据和栈。
I believe it comes from the very early days of computing, when memory was very limited, and it was not wise to pre-allocate a large chunk of memory for exclusive use by the stack. So, by allocating heap memory from address zero upwards, and stack memory from the end of the memory downwards, you could have both the heap and the stack share the same area of memory. If you needed a bit more heap, you could be careful with your stack usage; if you needed more stack, you could try to free some heapmemory. The result was, of course, mostly, spectacular crashes, as the
stack would occasionally overwrite the heap and vice versa.Back in those days there were no interwebz, so there was no issue of buffer overrun exploitations. (Or at least to the extent that the
interwebz existed, it was all within high security facilities of the united states department of defense, so the possibility of malicious data did not need to be given much thought.)
After that, with most architectures it was all a matter of maintaining compatibility with previous versions of the same architecture. That’s why upside-down stacks are still with us today.
5.2 内存分配
5.2.1 经典内存分配方式
内存分配方式有很多,一种为动态分区分配方式。那么利用该方式分配,应该遵守什么样的规则?当某个进程申请的内存有多个空闲内存块满足需求,那么应该如何选择?这就是动态分区分配算法解决的事情。
动态分区分配算法主要有四种
首次适应算法中的描述内存使用情况的分区表或者分区链是以分区地址递增的顺序排列的。
首次适应算法再分配内存时,会每次从分区链中找到合适的分区,这会导致链头部分会有许多较小的空闲分区。使用临近邻近适应算法会跳过这些小分区。
最佳适应算法中分区链表是以空闲分区大小递增排列的。缺点是产生很多很小的外部碎片
使用最坏适应算法将会优先使用大分区,减少外部碎片的产生。
5.2.2 现代操作系统内存分配方式
页式
操作系统的内存被分为4k大小的页框,进程的地址空间也被分为和页框大小一样的一个个“页”,进程的逻辑地址首先应该计算出页框号 ,然后得到该页框号对应的物理页号,然后利用偏移地址+
页框的地址(页面始址)。
页式存储管理的逻辑地址如何转化为物理地址:
那么我们可以通过逻辑地址轻易得到页号和页内偏移,那么页号对应的是哪个页框呢?页框的起始地址如何计算呢?
总结
二级页表
对于第一个问题 我们可以对页表 设置一个页目录表,之前我们使用页表解决内存连续固定分区的问题,现在我们仍然使用页表的页表来解决页表的连续分配问题。为2的20次方个页表项建立 2的10次方个页来放置,即使用1024个页框来装页表项,每个页框装1024个。然后再使用一个页框来放置页目录表。
段式存储管理 和页式存储管理各有优势,段式利于信息的共享和保护,而页式内存利用率比较高。自然想到将二者结合 利用进程的逻辑模块分段,段内分页。
段页式存储管理我觉得理解的时候主要应该知道段表项是段号 +页表长度(判断是否越界)+页表存放的块号(真正查找到放页表的地方)页表主要是页号+页框号
5.3 虚拟内存技术
虚拟内存是建立在离散内存管理方式(页式 段式 段页式)之上的,在内存不够的时候将已经调入内存的数据放回磁盘,再将需要调入的数据调入内存。所以操作系统应该提供请求调页 和 页面置换的功能。那么普通页表有物理页框号就可以了,但是要实现虚拟技术下的页表,应该加入更多字段,比如是否已经调入内存(状态位)、是否修改、访问次数、最后一次访问时间、外存地址等等信息来支持请求调页和页面置换算法。
5.4 页面置换算法
FIFO:性能差
LRU:性能好开销大
时钟置换算法:页表中的访问位0 代表最近访问 1代表最近未访问。将内存中的页面通过链接指针链接成为一个循环链表,当某页被访问时候,修改其访问位为1,当需要调出页面时,检查页面的访问位,如果为1则改为0,如果为0则找到被置换出去的页面。