操作系统四个特性
- 并发:同一时段运行多个程序,并行为同一时刻运行多个程序
- 虚拟:通过时分复用,空分复用等手段,把物理实体虚拟为多个
- 共享:系统资源可以被内存中多个并发执行的进线程共同使用
- 异步:系统中的进程是走走停停的方式执行,且以不可预知的速度推进
操作系统的主要功能
- 处理机管理:进程控制、进程同步、进程通信和进程调度
- 存储器管理:内存的分配、内存保护、地址映射、内存扩充
- 设备管理:外围设备、完成用户IO请求、提高IO设备利用率、提高IO速度
- 文件管理:磁盘存储空间管理、目录管理、文件读写管理、文件共享和保护
- 提供用户接口:程序接口、用户接口
进程的状态与转换
- 有新建、就绪、运行、阻塞、结束五个状态
- 新建状态:进程正在被创建,还未到就绪状态。创建步骤:先申请空白PCB,向PCB表填写一些控制和管理进程的信息;然后系统为该进程分配所需的资源;最后将该进程转入就绪状态
- 就绪状态:进程已经获得了除了处理机外所有资源,一旦得到处理机即可运行
- 运行状态:单处理机下,同一时刻只有一个进程处于运行状态
- 阻塞状态:进程等待某一资源(除了处理机资源)或输入\输出完成
- 结束状态:系统首先需要将该进程设置为结束状态,然后再去进一步处理资源的释放和回收
进程线程区别
线程
- 线程是CPU的基本执行单元,线程自己不拥有系统资源,只有线程ID、程序计数器、寄存器和堆栈。它可以和别的线程共享进程的所有资源
进程线程区别
- 调度
- 线程是调度的基本单位,进程是资源分配的基本单位
- 一个进程内的多个线程切换时,不会造成进程切换。不同进程间的线程切换时,会造成进程的切换
- 拥有资源:进程是拥有资源的基本单位,线程只有少量资源,不过它可以共享隶属进程的资源
- 并发性:进程线程都可以并发
- 系统开销
- 进程的创建和销毁都需要系统分配或者回收资源,开销远大于线程的创建或销毁。
- 进程间的切换开销更大,需要系统保存当前执行进程CPU环境,以及新调度到进程CPU环境的设置,线程只需要保存和设置少量寄存器内容
- 同一进程的多个线程之间同步和通信十分容易
进程通信
- PV操作是低级的通信方式,高级通信方式是指可以用较高的效率传输大量数据的通信方式
- 共享存储
- 在内存中设置一块进程可以直接访问的空间,两个进程通过这块共享空间交换信息,对共享空间的读写需要进行控制。用户进程空间一般都是独立的,两个用户进程共享空间必须通过特殊的系统调用实现
- 消息传递
- 直接通信方式:发送进程将消息放在接收进程的消息缓冲队列上
- 间接通信方式:发送进程把消息发送到某个中间实习中,接收进程从中间实体中读取消息。这种中间实体一般称为信箱,这种通信方式又称为信箱通信方式
- 管道通信
- 本质是一个共享文件,又名pipe文件,其需要实现互斥、同步和确认对方存在
- 管道大小有限。当写管道满时,写进程将被阻塞
- 当管道空的时候,读进程会被阻塞
- 半双工通信,某一时刻只能单向传输
- 信号量
- 套接字
进程同步
多进程虽然提高了系统资源的利用率和吞吐量,但由于进程之间的异步性可能造成系统的混乱。进程同步就是指对多个相关进程在执行顺序上进行协调,使得并发执行的多个进程之间可以有效的共享资源和相互合作。
同步机制需要遵循的原则:
- 空闲让进:当临界区空闲的时候,允许请求进入临界区的进程立马进入临界区
- 忙则等待:当已有进程进入临界区的时候,其他试图进入的进程必须等待
- 有限等待:保证等待的进程能在有限时间内进入临界区
- 让权等待:当进程不能进入临界区而等待的时候,必须释放处理器资源,防止进程忙等待
经典同步问题
生产者-消费者问题
- 生产者和消费者既是互斥关系(对缓冲区的访问)也是同步关系(生产者生产后,消费者才能消费,反过来也是)
full、empty和mutex的位置可以互换
semaphore full = 0;
semaphore empty = n;
semaphore mutex = 1;
producer(){
while(1){
p(empty);//观察缓冲区是否有剩余空间,消耗什么p一下
p(mutex);
add item to buffer
v(mutex);
v(full);//提供什么v一下
}
}
consumer(){
while(1){
p(full);
p(mutex);
remove item form buffer
v(mutex);
v(empty);
consume the item
}
}
读者-写者问题
- 多个读进程可以同时访问数据
- 同一时刻只允许一个写进程写数据
- 在写操作完成前,不允许读进程访问数据
- 写进程执行写操作前,应让所有读进程和之前的写进程退出
semaphore mutex = 1;//保护count的更新
semaphore rw = 1;//保证读者写者互斥访问文件
int count = 0;//记录当前读者数
writer(){
while(1){
p(rw);
writing
v(rw);
}
}
reader(){
while(1){
p(mutex);
if(count == 0)
p(rw);
count++;
v(mutex);
reading
p(mutex);
count--;
if(count == 0)
v(rw);
v(mutex);
}
}
上述算法对读进程优先,当有读进程时,写进程必须等待,而同时别的读进程可以一直访问数据,有可能造成写进程“饿死”的情况,改进算法如下。允许有写进程进来时,禁止后续读进程进入。
semaphore mutex = 1;//保护count的更新
semaphore rw = 1;//保证读者写者互斥访问文件
semaphore w = 1;//用于实现写优先
int count = 0;//记录当前读者数
writer(){
while(1){
p(w);//无写进程时进入
p(rw);
writing
v(rw);
v(w);
}
}
reader(){
while(1){
p(w);//无写进程时进入
p(mutex);
if(count == 0)
p(rw);
count++;
v(mutex);
v(w);
reading
p(mutex);
count--;
if(count == 0)
v(rw);
v(mutex);
}
}
哲学家进餐
此处采用的是拿筷子前先判断走右边筷子是不是都可以拿,再允许拿
注意释放的时候不用mutex,如果用mutex反而会死锁
semaphore chopstick[5] = [1,1,1,1]
semaphore mutex=1;
Pi(){//i号哲学家进程
do{
p(mutex);
p(chopstick[i]);
p(chopstick[(i + 1) % 5]);
v(mutex);
eating
v(chopstick[i]);
v(chopstick[(i + 1) % 5]);
thinking
} while(1);
}
用户态和核心态
CPU通常执行两种不同性质的程序,一种是操作系统内核程序,另一种是用户自编程序或系统外层程序。前者是后者的管理者,因此它有一些特权指令,这些指令被管理程序不能执行。如I\O指令,置中断指令、存取用于内存保护的寄存器、送进程状态自到程序状态寄存器等指令。操作系统在执行上分为用户态(目态)和核心态(管态),以严格区分两类程序
内核
内核为计算机配置的底层软件,一般包括四个方面
- 时钟管理
- 中断机制:中断机制中,只有一小部分属于内核,负责保护和恢复中断现场,转移控制权到相关处理程序
- 原语
- 处于操作系统最底层,最接近硬件
- 运行具有原子性
- 运行时间段,而且调用频繁
- 定义原语的直接方法是关闭中断,等它的所有动作完成后再打开中断。常见有设备驱动、CPU切换、进程通信
- 系统控制的数据结构和处理 常见有以下三种
- 进程管理
- 存储器管理
- 设备管理
用户态切换到核心态
- 中断
- 又称外中断,指来自CPU执行指令以外的事件的发生,如设备IO结束中断,时钟中断
- 系统调用
- 可以认为是用户在程序中调用操作系统所提供的一些子功能
- 系统中各种资源都由操作系统统一管理,所以在用户程序中,所有与资源有关的操作(如存储分配、IO传输和文件管理),都必须通过系统调用方式向操作系统提出服务请求,由操作系统代为完成。
- 一般有设备管理、进程通信、进程管理、文件管理、内存管理
- 系统调用运行在核心态
- 中断返回为一条特权指令,只能在内核态执行,目的是从内核态返回用户态
- 程序由用户态转为内核态会调用访管指令,所以该指令不是特权指令
- 异常
- 又称内中断、例外或陷入。指源自CPU执行指令内部的时间,如程序非法操作码、地址越界、算数溢出、虚存缺页等。异常不能被屏蔽,一旦出现必须立即处理。
死锁
死锁是指多个进程竞争资源,造成互相等待的一种僵局,如果没有外力作用,这些程序都无法向前推进
死锁原因
- 互斥条件:资源同时只能为一个进程所有
- 不可剥夺条件:进程在未使用完资源之前,本能被别的进程夺走,只能主动释放
- 请求和保持条件:进程至少保持了一个资源,同时又请求另外的资源,自己的资源也不释放
- 循环等待条件:存在一种进程资源循环等待链
死锁处理
- 预防死锁
- 破坏死锁城成立的四个条件中的一个,实现起来比较简单,但如果限制过于严格会降低系统利用率
- 破坏互斥条件:不太可行,有的场合需要保护互斥条件
- 破坏不剥夺条件:当进程请求新的资源无法被分配时,其必须释放掉已有的资源,以后再申请。适合状态易于保存和恢复的资源,寄存器和内存资源可以用这种方式,但比如打印机就不适用。
- 破坏请求和保持条件:采用预先静态分配方法,在进程运行前一次申请完所需的资源,后续不能再申请。容易资源浪费,造成“饥饿”现象
- 破坏循环等待条件:顺序分配资源,需要给进程添加编号,限制了新类型设备的增加;也会给用户编程带来麻烦。
- 避免死锁
- 在资源分配的过程中,避免系统进入不安全的状态
- 银行家算法,找出能够安全执行的序列
- 有四个矩阵Available(可使用的资源)、Max(最大需求的资源)、Allocation(已分配的资源)、Need(还需要的资源,Max - Allocation)
- 死锁的检测
- 允许系统运行过程中产生死锁,死锁发生后,采用一定的算法检测出死锁的相关程序
- 利用资源分配图来检测
- 资源到进程的边为分配边
- 进程到资源的边为请求边
- 找到既不孤立又不阻塞的进程,消去它的请求边和分配边,再递归判断有无新的这样的进程
- 最后能够消去所有的边,成该图为可完全简化的,则没有发生死锁,否则称为不可完全简化的,发生了死锁,该条件为死锁定理(资源分配图是不可完全简化的)
- 死锁解除
- 将检测出来的死锁进程挂起或撤销,释放一些资源,分配给被阻塞的进程
- 资源剥夺法:挂起死锁进程,释放它的资源
- 撤销进程法:撤销进程,并剥夺这些进程的资源。按照进程优先级和撤销代价的高低运行。
- 进程回退法:让进程回退到足以避免死锁产生的地步,进程资源释放资源而不是被剥夺。需要系统保持进程历史信息,设置还原点
- 将检测出来的死锁进程挂起或撤销,释放一些资源,分配给被阻塞的进程
进程调度算法
进程调度层次
- 作业调度:给外存上处于后备状态的作业分配资源(内存,输入输出等必要的资源),并建立相应的进程,使它(们)获得竞争处理机的机会。一般用于多道批处理系统中。每个作业只会
调入一次、调出一次
。调用频率最低 - 中级调度(内存调度):引入该调度的目的是提高系统内存利用率和系统吞吐量。为此,将暂时不能运行的程序调至外存等待,此时进程状态成为挂起。当具备运行条件时,由
中级调度
来决定将进程重新调入内存,并修改其状态为就绪状态,挂在就绪队列上等待。调用频率中等 - 进程调度:主要任务是从就绪队列中选取一个进程分配处理机资源给它。调用频率最高
调度方式
- 非剥夺式调度
- 剥夺调度
调度算法
- FCFS first come first server 先来先服务
- SJF short job first 短作业优先
- 优先级调度
- 高响应比优先调度
- 响应比 = (等待时间 + 要求服务时间)/ 要求服务时间
- 要求服务时间相同时,等待久的优先。等同于FCFS
- 等待时间相同时,要求服务时间短的优先。等同于SJF
- 对于长作业,等待时间足够长后响应比就会增加,照顾了长作业,克服了饥饿状态
- 时间片轮转调度
- 适用于分时系统
- 将就绪进程按照到达顺序排列,先来先服务
- 当时间片很长,所有进程都能在一个时间片完成时,等同于FCFS
- 当时间片很短,那么进程将频繁切换,使处理机开销增大,降低效率
- 一般时间片的选择由三个因素决定:处理机能力,就绪队列长度,系统的响应时间
- 多级反馈队列调度
- 时间片轮转和优先级调度的结合
- 有多个就绪队列,1级队列优先级最高,同时时间片最短,往后优先级越低,时间片越短
- 当新进程进来时,它会被放在1级队列,按照FCFS执行,如果时间片结束后没执行完,反到2级队列中,一次类推往后面队列放
- 只有1 ~ (i - 1)级队列都为空时,i级才可以执行。此时若有进程进入,则返回执行1级队列,当前进程放回i级队列末尾
- 优点:对于终端型用户:短作业优先;短批处理作业用户:周转时间较短;长批处理用户:经过前面几个队列得到部分执行,不会长时间得不到处理
内存
内存管理
- 内存的分配和回收
- 地址转换:把程序中的逻辑地址转变为物理地址
- 内存空间扩充:用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存
- 存储保护:保证各道作业在各自的存储空间上运行,互不干扰
- 在CPU中设置一队上下限寄存器,存放用户作业在主存中的上下限地址
- 通过重定位寄存器(或基地址寄存器)和界地址寄存器(或限长寄存器)来实现。重定位寄存器存储最小的
物理地址
,界地址寄存器存储最大的逻辑地址
。访问时先判断程序的逻辑地址有无超过界地址寄存器,然后再加上重定位寄存器映射出物理地址
内存连续分配
- First Fit 首次适应 : 空闲分区按地址链接,顺序查找到第一个满足的空闲分区
- Best Fit最佳适应:空闲分区按照容量递增链接,找到第一个能满足要求的空闲分区
- Worst Fit最坏适应:空闲分区容量递减链接,找到首个满足的分区,即找到最大的分区
- Next Fit 邻近适应:循环首次匹配
非连续分配
分页式存储管理
- 固定分区会造成内部碎片,动态分区会造成外部碎片,空间利用率都比较低
- 页表将内存划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程运行时以块为单位申请主存空间
- 分页管理不会产生外部碎片,只会在为最后一个不完整的块申请一个主存块空间时产生内部碎片,平均碎片大小为半个碎片
- 地址结构为:第一部分为页号,第二部分为页内偏移量
- 页表结构为:第一部分为页号,第二部分为块号,即页表在内存中对应的块号
- 地址变换:页内偏移量和块号决定
- 为了加快查找页表的过程,根据局部性原理,可以设置一个快表-联想寄存器TLB
分段存储管理
分页管理方式是从计算机的角度出发,提高内存利用率,提升计算机性能,对用户透明。而分段管理方式是考虑了用户和程序员,以满足方便编程、信息保护和共享、动态增长及动态链接等多方面的要求
- 用户进程可以被划分为不同的段,每段从0开始编址,并分配一段连续的地址空间(段内要求连续,段外不要求连续)
- 逻辑地址分为段号和段内偏移量
- 页式系统中,页号和页内偏移量对用户是透明的;在段式系统中,段号和段内偏移量在高级程序设计语言中,由
编译程序
完成。 - 段表:段号:段长:本段在内存中的起始地址
- 地址映射:判断段内偏移是否超过段长,超过产生越界中断,否则继续执行。物理地址为段起始地址加上段内偏移量
- 会产生外部碎片
- 段表可以共享,让两个作业的段表项指针指向被共享的段的同一个物理副本来实现。页式没办法做到,因为段式可以单独为共享段划分个段,而页式都是连续的,没法单独划分共享部分。
段页表存储管理
结合段页的优点。将作业分为不同的段,每段分为若干个大小固定的页。
逻辑地址为;段号:页号:页内偏移量
虚拟内存
如果存在一个程序,其所需的内存比实际的物理内存大,那它就无法被载入内存中运行。扩充内存为一种解决办法,另一种方法是使用虚拟内存,程序运行时只将需要的部分装入内存,其余的仍留在外存,当需要时在替换进内存,同时把不需要的部分换回外存。这样就从逻辑上扩充了内存的大小,称为虚拟内存。
- 虚拟存储器的大小是由计算机的地址结构决定的,而不是简单的内存和外存大小相加
- 多次性:指无需将作业一次性全部装入内存,而是可以被分成多次调入内存
- 对换性:指无需在作业运行时一直常驻内存,而是可以在运行过程中进行换进和换出
- 虚拟性:指从逻辑上扩充内存容量,使用户看到的内存容量远大于实际的内存容量
虚拟内存中的页表会多加四个字段
- 状态位:表示该页是否已调入内存
- 访问字段:记录本页一段时间内被访问的次数,或者最近多长时间未被访问
- 修改位:表示该页在掉入内存后是否被修改过
- 外存地址:指出该页在外存上的地址,一般为物理块号
页面置换算法
- 最佳置换OPT:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去
- 先进先出FIFO:优先淘汰最早进入内存的页面,会产生belady异常,即物理块增大,页故障数不减反增;基于队列
- 最近最久未使用LRU:选择最近最长时间未被使用的页面;基于堆栈
- 时钟置换算法CLOCK(也叫最近未用NRU[not recently used]):为页面设置使用位,将页面链接成环形,页面被访问时置标志位为1.页面置换的时候,如果指针所指页面标志为0,则替换,否则置为0,并向下遍历直到找到第一个标志位为0的页面
- 改进型CLOCK算法:在CLOCK的算法上再添加一个标志位,表示最近有无修改。替换时优先替换使用位为0,修改位为0的页。如果没有则替换使用位为0,修改位为1的页。扫描过程中,每个跳过的页,都将使用位置为0. 改进点在于尽量保留修改过的页,因为修改过的页需要写回,而未修改的页只需覆盖就好。
- 最少使用算法LFU:置换最近使用次数最少的