OS操作系统重点
第一章导论
操作系统概述
- 并发指的是多个程序可以同时运行的现象。并发的"同时"是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象。
- 并行的"同时"是同一时刻可以多个进程在运行(处于running)
RAM随机存取存储器,也叫主存,它可以随时读写,速度很快,通常作为 操作系统 或者其他正在运行中的程序的临时数据存储介质
BIOS基本输入输出系统,是个人电脑启动时加载的第一个软件,其实,它是一组固化到计算机内主板上一个ROM芯片上的程序,保存着计算机最重要的基本输入输出程序,开启后自己按程序和系统自启动程序。它可从CMOS中读写系统设置的具体信息。 其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。
ROM只读存储的简称,是一种只能读出实现所存数据的固态半导体存储器。通常用在不需经常变更资料的电子或电脑系统中,并且资料不会因为电源关闭而消失。
EPROM是一种断电后仍能保留数据的计算机储存芯片——即非易失性的(非挥发性)
cpu的状态
- 特权指令:在内核态(管态)下运行的指令
- 不仅能访问用户空间,还能访问系统空间。
- 如启动外部设备、设置系统时钟、管中断、切换执
行状态、I/O指令
- 非特权指令:在用户态(目态)下运行的指令
- 应用程序所使用的都是非特权指令。
- 防止应用程序的运行异常对系统造成破坏。
- 仅能访问用户空间
处理器每执行完一条指令以后,硬件的中断扫描机构立即检查有无中断发生。若无,继续执行,若有,暂停当前进程,转由OS内核的中断处理程序接手。
操作系统作为用户和计算机硬件系统之间的接口,可以通过以下3种方式使用计算机:
-
命令行界面(CLI)
-
图形用户界面(GUI)
-
应用程序接口(API)
OS结构设计
- 模块化结构:将OS划分为若干个具有一定
独立性的模块。 - 分层式结构:划分层次,每一层仅使用下一层所提供的功能和服务。
- 微内核OS结构:内核仅保留极少的功能,提供服务之间的通信机制。在设计微内核OS时,采用了面向对象的技术,与传统的操作系统相比,其优点是提高了系统的灵活性、可扩充性,增强了系统的可靠性,提供了对分布式系统的支持,增强了系统的可靠性和可移植性。但是,微内核与文件管理、设备驱动、虚拟内存管理、进程管理等其他上层模块之间需要有较高的通信开销。
- 宏内核则将所有功能整合在一起,各个功能模块之间可以直接调用。这样的优势就是性能极高,但其缺点也很明显,就是其耦合度高,一旦其中一个模块出现问题,其他所有的模块都可能会受到影响。
系统调用
系统调用是操作系统内核提供给应用程序的基础接口,需要运行在操作系统的核心模式下,以确保有权限执行某些 CPU 特权指令。
系统调用本身并非内核函数,但它是由内核函数实现的。
用户程序可以利用这组接口来调用系统服务,例如磁盘I/O会用到的open,write,read等服务。
进程的描述与控制
进程与程序
- 程序是永久的,进程是暂时的。
- 同一程序可以对应多个进程。
- 程序是一个静态的概念。而进程是一个动态的概念。
- 进程是动态的、多个进程可以含有相同的程序、多个进程可以并发运行
- 动态性、并发性、独立性和异步性(不可预知的速度同时向前推进)
- 进程实体由三部分构成:程序段、数据集、进程控制块
- 进程的定义:进程是执行中的程序。
- 进程是程序的执行过程,是系统进行资源分配和调度的一个独立单位。
- 进程实体 = 程序段 + 数据段 + 进程控制块(PCB)
进程的状态
-
运行态
- 当一个进程已分配到CPU,它的程序正在被CPU执行时进程所处的状态称执行状态,也称为运行状态。对于单CPU系统而言,处于执行状态的进程只可能有一个,多处理机系统中则有多个
-
就绪态
- 若进程已具备了运行条件,只因CPU被别的进程占用而不能被CPU执行,则称此时进程处于就绪状态。一旦把CPU分配给它,该进程就可以运行。系统中处于就绪状态的进程可能有多个,通常将它们按某种策略(优先级)排成一个队列,称为就绪队列
-
阻塞态
- 正在执行的进程因等待某种事件的发生而暂时不能运行便放弃CPU的状态称阻塞状态(等待状态、封锁状态),例如,等待输入/输出、等待进程间的同步/互斥等。一旦引起等待的原消失,进程便转为就绪状态。以便在适当的时候占用CPU。系中处于等待状态的进程可能有多个,通常也将它们排成一个列。有的系统按照进程不同的等待原因,把处于等待状态的进程排成多个阻塞队列
-
创建状态
- 一个进程刚刚建立,但还未将它插入就绪队列时的状态(fork,CreateProcess),通常是PCB已经创建,但还没有加载到内存中
-
终止状态
- 一个进程已经正常结束(exit,TerminateProcess)或异常结束,
OS已将它从就绪队列中移出,但尚未将它撤消时的状态
- 一个进程已经正常结束(exit,TerminateProcess)或异常结束,
新建→就绪
- 许可(加载):操作系统准备好接纳一个新进程
- 不许可:超过操作系统最大进程/虚存数量限制
就绪→运行
- 调度:调度进程选择一个新进程运行
运行→结束
- 释放:进程已经完成,或出错结束
运行→阻塞
- 等待事件:请求系统调用,如
- 等待I/O操作完成(printf、scanf)
- 等待新任务的到来(等待网络发数据包)
- 访问暂不能被使用的资源(其他进程正在使用打印机)
- 等待另一个进程提供输入(wait)
- 申请外设
阻塞→就绪
- 事件发生:如I/O完成
运行→就绪
- 时间片到:Linux-5~800ms;Windows-20ms
- 抢占(preempted):更高优先级进程已就绪
- 自愿释放:如周期性维护进程
挂起状态
使正在执行的进程暂停执行;若此时进程正处于就绪状态而未执行,则该进程暂不接受调度,我们把这种静止状态称为挂起(suspend)状态。
引入挂起状态的原因有:
- 交换(Swapping) 操作系统需要释放内存空间,调节负荷
- 其他OS原因 OS挂起后台进程、工具进程,或怀疑有问题的进程
- 用户请求 用户希望调试进程或连接进程资源
- 父进程请求 挂起后代进程,以便考查修改,或协调子进程的活动
- 定时 周期性的记账或系统监视进程
进程控制块
进程控制块PCB(Process Control Block)记录了操作系统所需的,用于描述进程的当前情况以及管理进程的全部信息,是操作系统中最重要的记录型数据结构。
对系统而言,PCB是进程存在的惟一标志。
PCB常驻内存。操作系统将所有的PCB组织成若干个队列,存放在系统内核空间中专门开辟的区域内
-
进程标识符 用于唯一标识一个进程。
- 内部标识符 操作系统为每一个进程赋予一个惟一的
- 数字标识符(进程的序号,Process ID,PID )
- 用户标识符(User ID,UID)
- 父进程标识符(Parent PID,PPID
-
处理机状态信息 又称上下文(Context)
- 通用寄存器(General Purpose Registers),又称用户可见寄存器,8~32个/CISC,100+个/RISC
- 程序计数器(Program Counter),指令计数器,存放下一条指令的地址
- 程序状态字(Program State Word),存放状态信息,如x86的EFLAGS
- 栈指针(Stack Pointer),指向栈顶
-
进程调度信息
- 进程状态:运行、就绪、等待、停止……
- 优先级
- 事件:在等待的具体事件,即阻塞原因
- 其它信息:已等待时间,已使用CPU时间
-
进程控制信息
- 内存相关:程序和数据的(首)地址、段表、页表
- 进程间通信:标记、信号量、消息队列……
- 资源清单:打开的文件、设备……
- 连接指针:指向其它PCB
进程(仅概念)
进程的创建
引起创建进程的事件
- 用户登录(由系统内核创建)
- 作业调度(由系统内核创建)
- 提供服务(由系统内核创建)
- 应用请求(由应用进程自已创建)
- 子进程与父进程的关系可以是并发或递归
创建进程的基本过程:创建原语Creat
(1)申请空白PCB;
(2)为新进程的程序和数据分配所需资源(内存、文件、I/O和CPU时间)
(3)初始化PCB。向该PCB中填写各种参数;
(4)New→Ready,将新进程插入到就绪队列。
进程的终止
- 正常结束 进程任务已完成,退出运行。
- 异常结束 如:越界错误、无可用内存、保护错、无效指令、特权指令错、超时、算术运算错、I/O故障等
- 外界干预 进程应外界的请求而终止运行。包括:操作员或操作系统干预、父进程请求、父进程终止。
进程的终止过程是: 终止原语Halt
(1)根据进程PID检索出该进程的PCB,读出进程状态
(2)若进程处于执行状态,立即终止(Run→Terminate),并重新进行调度
(3)某些系统可能将其子孙进程终止
(4)把进程拥有的资源归还给父进程或者系统内核
(5)等待其他程序来搜集信息。
进程的阻塞
引起进程阻塞的事件
- 请求系统提供服务
- 启动某种操作
- 新数据尚未到达
- 无新工作可做
进程的阻塞(等待)过程 阻塞(等待)原语block
(1) 进程立即停止执行,把其PCB中的状态Run→Blocked;
(2) 将其PCB插入到相应的阻塞队列中去;
(3) 转调度程序进行重新调度,并进行切换
进程的唤醒
引起进程唤醒的事件——被阻塞进程所期待的事件出现
进程的唤醒过程:唤醒原语wakeup
(1) 把被阻塞进程从相应事件的阻塞队列中移出;
(2) 将其PCB中的状态由Blocked→Ready;
(3) 该PCB插入到就绪队列中。
进程通信
合作的进程通过进程间通信机制实现数据和信息的交换的锁、信号量、管程等同步机制,都可以交换进程的信息,也属于进程通信(但是传输的信息量很小,称为低级通信)
高级通信机制:
- 共享内存:进程通过共享某些数据结构或内存区域通信,程序员负责处理同步问题
- 消息传递:以格式化的消息(message)为单位,用户通过OS提供的一组通信原语(send, receive)进行通信。当前应用最广泛的IPC机制
- 直接通信
- send (P, message) – 发送信息到进程P
- receive(Q, message) – 从进程Q接受消息
- 间接通信(借助中介数据结构)
- send(A, message) – 发送消息(信件)到队列A
- receive(A, message) – 从队列A接受消息(信件)
- 信���通信:A=mailbox
- 直接通信
- 管道通信:用一个共享文件连接一个读进程和一个写进程采用先进先出(FIFO)机制的循环队列/缓冲区管道通信机制必须提供互斥、同步和确定对方是否存在的协调能力
- 客户机-服务器系统:在网络环境的各种应用中已经成为主流
主要实现方法- 套接字(Socket)
- 远程过程调用(Remote Procedure Call, RPC)
线程
进程 = 一个资源 + 多个指令执行序列(即多个线程)
只切换指令执行序列,而不切换资源保留了并发的优点,避免了进程切换代价
它是进程中的指令执行流的最小单元,是CPU调度的基本单位。
现代操作系统将进程并发调度的部分和占有资源的部分分离
-
进程作为资源分配的基本单位
-
线程作为系统调度的基本单位,是能独立运行的基本单元(线程可以并行在多核处理器上)
- 资源共享:默认共享地址空间、文件等
- 响应性:拆分应用中的多种活动
- 考虑字处理程序
- 轻量级:创建、撤销、切换线程的代价比进程小
- 可扩展:多个线程可在多CPU系统��并行
- 可以并发
- 线程仅拥有少量自己的资源(寄存器、PC指针、栈指针等上下文信息,不能共享),多是共享的
- 系统开销比进程小
- 独立性比进程小
- TCB(线程控制块)
缺点:任何一个线程出问题,可能导致整个进程崩溃
用户级线程
用户级线程是指不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。但是它不能像内核级线程一样更好的运用多核CPU。
优点:
(1) 线程的调度不需要内核直接参与,控制简单。
(2) 可以在不支持线程的操作系统中实现。
(3) 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,可以节约更多的系统资源。
缺点:
(1) 一个用户级线程的阻塞将会引起整个进程的阻塞。
(2) 用户级线程不能利用系统的多重处理,仅有一个用户级线程可以被执行。
内核级线程
切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态。可以很好的运用多核CPU,就像Windows电脑的四核八线程,双核四线程一样。
优点:
(1)当有多个处理机时,一个进程的多个线程可以同时执行。
(2) 由于内核级线程只有很小的数据结构和堆栈,切换速度快,当然它本身也可以用多线程技术实现,提高系统的运行速率。
缺点:
(1) 线程在用户态的运行,而线程的调度和管理在内核实现,在控制权从一个线程传送到另一个线程需要用户态到内核态再到用户态的模式切换,比较占用系统资源。(就是必须要受到内核的监控)
关联性
(1) 它们之间的差别在于性能。
(2) 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
(3) 用户级线程的创建、撤消和调度不需要OS内核的支持。
(4) 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
(5) 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
(6) 用户级线程的��序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
处理机调度与死锁
调度
调度是为同时需要资源的多方,分配所需资源的方法。
凡有稀缺资源(“排队”)之处,皆有调度。
调度的资源
- 处理机(CPU)
从就绪队列中挑选下一个占用CPU运行的进程 - 临界区
信号量V操作后,从等待队列挑选一个进程唤醒 - 内存
从外存的挂起队列挑选一个进程激活 - I/O设备
决定I/O设备处理等待队列中的哪个I/O请求
调度的时机
- 进程从运行态切换到等待态(非抢占调度)
I/O操作、wait操作、sleep…… - 进程退出(非抢占调度)
return、exit、出错…… - 进程时间片完(抢占调度)
高优先级进程抢夺
时钟中断 - 进程从等待到就绪、新建(抢占调度)
I/O中断、fork
调度算法
目标和相关定义
-
运行时间:占用cpu运行的时间
响应时间在等待时间里面
带权(归一化)周转时间
是进程的周转时间, 是该进程接受服务的时间(即运行时间)
先来先服务,排队算法
FCFS (Fisrt Come Fisrt Serve) 或 FIFO (First In First Out)
- 缺点:周转时间长
I/O型进程必须等待计算型进程用完CPU,将导致
设备使用率低,对短作业不公平
最短作业优先
SJF(Shortest Job First) 或 Shortest Process Next-
在目前的假设条件下,可以证明,SJF调度算法的平均
T周转时间是最优(optimal)的
- 缺点:作业可能在任何时刻到达,周转时间也会变长
最短剩余时间优先
SRT(Shortest Remaining Time)或STCF(Shortest Time-to-Complete First-
新来作业产生中断,重新调度
缺点
- 可能产生饥饿(starvation)
在上面的例子中,从10时刻起,如果源源���断有短进程到来,则作业A始终得不到执行- SJF也可能饥饿,比如0时刻到来10和100作业,之后不断有10作业到来
- 由于允许抢占,SRT比SJF更容易饥饿
- 对长作业不公平
- 算法的实现更困难,开销更大
- 必须支持中断处理(抢占)
- 需要计算“已运行的时间”
- 每次中断都要调度
轮转(轮询)调度算法
响应时间比上面三种都要好
RR( Round Robin ),又称时间片调度
-
每运行一个时间片(time slice, scheduling quantum,时钟周期的倍数)产生时钟中断,并重新调度
-
公平,长短作业都能兼顾,不会饥饿
-
调度时算法简单(可仅用FCFS)
时间片大小的选取
缺点
是各种调度算法中较差的,甚至可能还不如FCFS
高响应比优先
短作业尽快响应,长作业也能照顾到,平均周转作业时代的一个折中方案
响应比(Response Ratio)
当等待时间同样长,短作业的响应比高,优先执行
一般来说,没有抢占,即等待时间 == 响应时间
随等待时间的增加,响应比会上升,从而照顾到长作业
终极解决方案-MLFQ
多级队列(静态优先级)调度:
多级=多个队列,各有不同的优先级(Priority)
具体怎样实现是机制(mechanism ,战术),如:
- 应该定多少个优先级?
- 每级队列使用什么调度算法(FCFS、RR)?
- 每级队列应该分多大的时间片?
- 当用完时间片,降多少级?
- 满足什么条件提升优先级?提升多少?
- 上述内容用什么方式实现?(查表、数学公式……)
规则
- if Priority(A)>Priority(B),运行A(不运行B),大于小于号要具体确定
- if Priority(A)=Priority(B),以RR方式运行A和B
- 新来的作业的优先级定为最高
- 如果一个作业(在一定时间S内累计)用完了它的时间片,则降低其优先级
- 如果作业在时间片前释放CPU,则保持不变
- 一定时间S后,将所有作业提升为最高优先级
缺点:
- 对未知进程难以确定优先级;
- 饥饿;
- 误判:假如一个进程刚开始是计算型的,但随着时间变化为交互式的,却因为降级而永远不能被平等对待
- 博弈问题:吃透了规则的计算型进程,对普通计算型进程不公平
公平共享调度
- 优先级调度的目标:优化周转时间、响应时间、资源利用率
- 公平调度的目标:让每个任务都能获得一定份额的系统资源
Lottery调度(摆烂调度)
- 为每个进程提供至少一张彩票
- 更重要的进程获得较多彩票
- 每次调度时,随机选取一张,握有该票的进程运行
- 可在需要时将彩票交给合作进程
调度算法总结
进程同步
海森堡bug:不可重现的bug。如果程序重启,bug就可能不再出现。
可能原因:
- 调试器本身影响了bug的产生;
- 编译器的不恰当优化;
- 未��始化变量的值;
- 时间敏感的bug:常在多进程/多线程并发的程序发生
对共享变量的并发写操作(读没关系),有必要互斥共享变量,如果不做相关保护措施,会有极大的可能造成bug
在实现同步之前,进/线程:
- 可能在任何时间被暂停与重启;
- 一旦暂停,其等待的时间未知;
- 多个进/线程可能以任意顺序运行;
进程同步的基本概念
- 竞争条件(race condition):多个进程在操作一个共享数据时,结果取决于多个进程的指令执行顺序
- 同步( Synchronization ): 协调多个进程的执行次序,确保并发的进程之间按照一定的规则共享系统资源,很好的相互合作,使程序的执行具有可再现性。
- 互斥( Mutual Exclusion ):当某进/线程正在做某件事时,不允许其它进/线程也做这件事
- 临界资源(Critical Resource):操作系统中一次允许一个进程访问的资源
- 如:打印机、文件、全局变量……
- 对临界资源应采用互斥方式共享
- 临界区( Critical Section ): 一次只有一个进程以执行的那段代码。即进程中访问临界资源的那段程序。
- 实现了互斥,也就有了临界区
- 锁( Lock ): 防止其它进程进入的工具
- 进入临界区前上锁
- 离开后解锁
- 如果已有锁,则在临界区外等待
进程同步的准则
原子操作
所有动作要么全做,要么全不做,操作过程中不能被打断又称原语(Primitive)
实现互斥的软件方法
忙等: 不能进入临界区的进程,一直占用CPU等待进入
- 等待中的进程白白占用处理机周期
- 拿着锁的进程的时间被无效的等待进程占用
- 优先级反转(priority inversion)
例外:
- 在多核CPU系统中,对于只占用很少时间的临界区,忙等
反而可以节约上下文切换的开销。
Peterson算法
- 当Note && turn 的条件不满足,进入忙等
- 代码必然让线程PA,PB都至少执行到了给turn复制的一步(哪个线程还没到,另一个就会等待)
- 完成后,就是看谁先执行turn赋值,谁先进入临界区,另一个仍然在等待
- 先执行的线程完成后,收走自己的纸条,让另一个线程运行,实现了互斥
软件同步的局限
纯软件方法利用最低限度的原子操作支持(Load和Store),也可以实现同步,但是
- 算法的设计和实现都很困难
- 在现代计算机架构下可能失效
- 编译器/CPU可能使指令乱序执行(需内存屏障)
所以很少使用纯软件同步方法,多是操作系统配合硬件实现同步,封装成各个api函数给程序员使用,所以不能直接调用。
- 编译器/CPU可能使指令乱序执行(需内存屏障)
硬件同步
设法实现两个原语(原子操作)
Lock() – 加锁,当无人用锁时获得锁;若多人同时尝试拿锁,则只有一人能拿到,其余人等待;
Unlock() – 解锁,唤醒在等待的人
Lock(); //进入区
if (noPaper) {
buy Paper; //临界区
}
Unlock(); //退出区
需要硬件提供更多的原子操作。
关中断
进程切换
- 内因:进程做了某事(系统调用或异常),自己释
放了CPU - 外因:中断导致内核介入,进程失去CPU
如果关闭了中断,无论内因外因均不再响应,则可以避开进程切换
关中断方案,又称屏蔽(mask)中断
问题
- 不能允许用户使用这种锁!只可能内核使用
- 由于临界区的代码运行的时间未知,不能保证实时性;更重要任务发生时,系统也无法响应(死循环关闭中断)
- 仅能关闭单个CPU,不适合多核CPU
读-修改-写型原子操作swap
可以用于多核处理器的指令
Test-and-Set
锁
上述硬件原语通常不直接给程序员使用,于是操作系统和高级语言将它们封装为各种接口,供程序员使用。最常见的一类工具就是“锁(locks)”。
- 锁是原子操作,多个进程同时lock,最终须依次执行
- 最先执行lock的进入临界区并加锁,后来的无法进入临界区
- 进不去的进程有两种选择:忙等,或睡眠
- 非忙等——互斥锁——重量级(悲观)锁
- 忙等——自旋锁——轻量级(乐观)锁
信号量
并发问题分为两类:
- 互斥(Mutual Exclusion):确保临界资源被互斥的使用
- 工具:锁(Lock)
- 程序员只需识别出临界区
- 竞争共享资源,用mutex=1,P、V操作成对出现
- 同步(Synchronization):确保进程按照期望的先后顺序运行
- 工具:条件变量(condition variable)
- 不满足条件的进程等待(wait),直到条件满足,才通知(signal)其执行
- 控制进程间的先后顺序,初值根据具体情况设置,P、V分开在不同的进程使用对每个约束设置一个信号量
信号量是一种抽象数据类型
- 由一个整形变量(Sem)和两个原子操作(P, V)组成
- P——wait()——等待,信号量值-1(可以负数)
- V——signal()——唤醒,信号量值+1
用法
- 实现进程互斥,信号量mutex
- 实现进程间前趋后继(又称同步)关系,可以让进程之间相互等待
生产者和消费者问题
问题描述:
- 一个或多个生产者将产品放入一个共享的缓冲区
- 多个消费者从缓冲区中取出使用
- 生产者和消费者之间并发运行,保持同步
以自动售货机为例,约束条件包括:
- 每次只允许一个人对售货机进行操作(互斥约束)
- 当饮料已售空,消费者必须等待生产者(同步约束)
- 当饮料已放满,生产者必须等待消费者(同步约束)
用信号量实现问题时的准则:
对每个约束条件,各使用一个单独的信号量
- Semaphore mutex; //互斥的约束
- Semaphore drinkSlots ; //消费者的约束
- Semaphore emptySlots; //生产者的约束
生产者和消费者的工作并不需要互斥。
生产者每次只能一个(互斥),消费者同样
因为buffer的容量只有1,PV操作在实现信号量同步,保持生产者消费者之间的关系时,也天然实现了互斥,最多只有一个访问共享条件。
- P表示等待这个条件
读写者问题
问题描述:
- 共享数据,有两类使用者
- 读者:只读取/查询数据
- 写者:修改数据
- 读-读允许:同一时刻,允许有多个读者同时读
- 读-写互斥:
- 当有读者在,写者不能写
- 当有写者在,读者不能读
- 写-写互斥:两个写者不能同时写
示例:访问查询型数据库,通常,读者的数量远大于写者
可否直接用一个互斥信号量实现?
- 不能。当读者获得数据库后无法让其它读者访问。
能否借用生产者-消费者中的同步思路?
- 与生产者-消费者问题不同,不存在一个有界的缓冲区,因此不能用一组信号量来控制读者-写者的互相等待
读者-写者问题解决方案
读者-写者锁
一些程序语言和操作系统提供了读写锁(Readers-writer lock)接口
读写锁适合于:
- 读进程和写进程可以区分开
- 读者进程数量比写者进程多
读者优先
- 又称第一类读者写者问题
- 只要有读者正在读,后来的读者都能直接进入
- 如读者持续不断进入,则写者就处于饥饿
写者优先
- 第二类读者写者问题
- 只要有写者就绪,写者应尽快执行写操作
- 如写者持续不断就绪,则读者就处于饥饿
- 该方法并发度和效率较低
读写锁替代策略:RCU
读写锁允许读者之间不互斥的读取数据
- 但是对于Rcount变量的操作需要加锁
- 当99%以上都是读者,且数量很大时,开销太大
- 读-复制-更新(Read-Copy-Update)
哲学家就餐问题
5个哲学家围圆桌而坐,每2人之间放一只叉子,哲学家的动作包括思考和进餐,同时拿到左右两边的叉子才能进餐,思考时将叉子放回原处。
可能的解决方案
- 至多只允许有四位哲学家去拿左边的筷子,最终能保证至少
有一位哲学家能够进餐,并可在用毕时释放出他用过的筷子,
从而使更多的哲学家能够进餐。 - 仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子
进餐。 - 规定奇数号哲学家先拿左边的筷子再拿右边的筷子;而偶数
号哲学家先拿右边的筷子再拿左边的筷子。 - 增加一根筷子(完美解决)
管程
管程是一种抽象数据类型(ADT),代表共享资源,及对该资源的互斥操作确保任一时刻最多只有一个进程进入管程,使用共享数据
思想:用锁(locks,通常是隐含的)实现互斥,用条件变量
(condition variable)实现同步,提供与信号量相同的功能
包含面向对象思想,封装了同步机制
死锁
如果一个进程集合中的每一个进程都在等待只能由该进程集合中的其它进程才能引发的事件,那么该进程集合就是死锁(deadlock)的。
大部分死锁和资源有关。资源是进程/线程为了完成工作,所需要的实体
- 可重用资源:CPU、内存、文件、设备……
- 可消耗资源:缓冲区产品、IPC的消息、中断……
- 可抢占资源:CPU、内存(有挂起)……
- 不可抢占资源:打印机、内存(无挂起)、文件……
死锁发生的条件
- 互斥(Mutual exclusion)
- 任何时刻只能有一个进程使用一个资源实例
- 请求和保持(Hold-and-wait,持有并等待 )
- 进程持有至少一个资源,并正在等待获取其他进程持有的资源
- 不可抢占(No preemption)
- 资源只能在进程使用后自愿释放
- 循环等待(Circular Wait)
- 存在一个进程集合
- P0正在等待P1所占用的资源,P1 正在等待P2占用的资源……PN正在等待P0所占用的资源
如果四个条件中的任何一个不满足,死锁将不会发生
如果发生了死锁,四个条件一定都已满足
死锁的预防
死锁预防(Deadlock Prevention)
破坏四个必要条件之一,确保系统永远不会进入死锁状态
-
破坏“互斥”条件(资源不可分享,任一时刻只有一个进程可以使用该资源的一个实例)
-
破坏“持有并等待”条件(进程持有至少一个资源,并正在等待获取其他进程持有的资源)进程在开始执行时,一次请求所有需要的资源,要么拥有全部资源,要么不拥有资源
例:申请全局锁后再申请全部资源
缺点:
- 资源利用率低,并发性被降低;
- 饥饿,如果所需资源不断被其它进程使用
-
破坏“不可抢占”条件(资源只能在进程使用后自愿释放)
如果资源本身是可以抢占的,则并不会死锁。当进程请求了不能立即分配的资源,则将自己已占用的资源也释放。
在交通死锁的例子中,车辆如果发现不能通过就倒车
缺点:
- 代价太大(打印机、刻录机、扫描仪)
- 可能造成反复申请-释放资源,进程的执行被无限制的延迟(活锁)
-
破坏“循环等待”条件(存在一个进程-资源的循环链)
对资源排序,要求进程按顺序请求资源
缺点:
- 应用程序员完全可以无视此规定
- 由于大型程序资源依赖关系复杂且普遍封装,难以设计和测试
小结
死锁避免(Deadlock Avoidance)
- 提前进行判断,只允许不会出现死锁的进程请求资源
避免死锁就是确保系统不会进入不安全状态
安全状态一定不会死锁,不安全状态可能死锁
所谓安全状态,是指系统能按某种进程顺序如<P1,P2,…,Pn>,来为每个进程Pi分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成。
若系统不存在这样一个安全序列,称系统处于不安全状态
银行家算法:判断是否存在某个进程执行顺序,让系统能安全运行,并找到他
死锁检测和恢复(Deadlock Detection & Recovery)
- 在检测到运行系统进入死锁状态后,进行恢复
资源分配图,可以看出是否出错
终极方法:鸵鸟方法
操作系统忽略死锁,不做任何处理
存储器管理
物理地址
- 又称实地址、绝对地址
- 内存所看到的地址
物理寻址
- CPU直接使用物理地址访问主存储器
虚拟地址(virtual address)
- 又称逻辑地址
- CPU所生成的地址
虚拟寻址(virtual addressing)
- CPU通过虚拟地址访问主存,虚拟地址经过地址翻译转换为物理地址
- 地址翻译由CPU里的内存管理单元(MMU)负责.
程序的装入
-
绝对装入
- 用户在程序设计时直接给出物理地址
- 或程序包含符号地址,编译/汇编时转换成物理地址
缺点:不能加载到内存任意位置;只适合单道程序
-
可重定位装入,静态重定位
- 编译器仅产生相对地址
- 加载器在加载时将相对地址转化为绝对地址
缺点:不允许在运行时改变在内存中的位置;
-
动态运行时装入,动态重定位
- 加载器在加载时仍使用相对地址
- CPU在运行时将相对地址转化为绝对地址
- 需要地址转换硬件(MMU,重定位寄存器)支持
程序的链接
链接器(linker)把一组目标模块作为输入,产生一���包含完整代码和数据的加载模块(load module),传递给加载器
-
静态链接(static linking)
缺点:常用的库不能共享,库函数若更新,需重新链接
-
动态链接(static linking)
加载时动态链接
基地址-界限
分配一块连续的内存
基地址+逻辑地址实现地址转换
界限地址实现地址保护
内存分配
-
单一连续分配
-
固定分区分配
-
动态分区分配
管理动态分区分配的数据结构,要能动态跟踪每个已占用和空闲的分区情况
可用:二维数组;链表;位图(bitmap)
动态分区分配算法
-
First Fit算法
分配n个字节,使用第一个可用的空间比n大的空闲块。
-
Next Fit算法
分配n个字节,从上一次找到的分区向下寻找,使用下一个可用的空间比n大的空闲块。
-
Best Fit
分配n字节分区时, 查找并使用大于n的最小空闲分区
-
Worst Fit
分配n字节,使用尺寸不小于n的最大空闲分区
基于索引搜索的分配算法
-
快速适应
根据空闲区的容量作分类,设立大、中、小等多个链表方便索引
-
伙伴系统
分配时可能多次分割,回收时可能多次合并时间性能和利用率折中了快速适应与顺序搜索
可以兼顾大小进程的分区需求
没有外部碎片,但有内部碎片,每块最大为
分页式存储管理
基于基地址-界限的存储管理方式简单有效,可以实现内存
分配、地址转换、地址保护,但始终解决不好碎片的问题
原因在于,内存总是只能寻找一段连续的空间才能分配
解决方案:分页(Paging)管理
核心思想:离散分配,将进程打散,分到不同的空间中去
页式地址变换
虚拟地址结构:页号(page number)+页偏移量(page offset)
物理地址:物理块(frame number)+页偏移量(page offset)
页框:以页框为单位为各个进程分配内存空间,最小的一块储存单元大小
将用户程序的地址空间分为若干个固定大小的区域,称为“页”或“页面”。相应地,将内存空间分为若干个物理块或页框(frame),页和页框大小相同。
在页表中,每一行存储的是属于那个物理页面的信息,又称为“页表项(Page Table Entry) 按位数
没有外部碎片
- 因为块是物理内存分配的最小单位
- 空闲的物理块可以用位图(bitmap)或链表(freelist)管理
- 每一块都可以分配出去,不存在浪费;回收简单
很小的内部碎片
- 每个进程最多在最后一个页产生浪费
- 内碎片最大为:页面大小 – 1Byte
存取控制字段
页表是系统为每个进程建立的页面映射表
每个页表项的结构为:页号 + 物理块号
问题
- 访问页表时间
- 基地址-界限方式,直接通过MMU的寄存器转换地址
- 页表在内存里,每次寻址多了一次内存访问
- 页表占据空间
- 随着系统位数提升,页表会占有越来越多的空间
快表
通过缓存,加速页的访问
t: 访问一次内存的时间
λ:查找快表的时间
α:快表命中率
有效访问时间:
多级页表
减少直接寻址存储器的大小空间
反向页表
让页表与物理地址空间的大小相对应
基于Hash映射值查找页表对应的帧号f
用链表处理hash冲突
减少直接寻址存储器的大小空间
段式存储管理
虚拟存储器
https://zhuanlan.zhihu.com/p/391327282
页面置换算法
当需要调入新页面,但内存已满时,页面置换算法 选择一个被换出的页面,再将新页面载入
先进先出算法
OPT页面置换算法
最佳算法(OPTimal):选择未来最久不被访问的
不可能实现!(预测未来最热卖的商品)
意义:给出理论上的最好结果,以供比较
最近最久未使用(Least Recently Used)LRU
选择自上次被访问以来经历时间最久的
硬件开销太大,难以实现
实际OS中,采用近似LRU的方法
Clock算法
循环队列,依次检查。若A(访问位)=0,换出;若A=1,置A=0,检查下一个。
改进后
不仅利用访问位A,还利用修改位D
- A=0,D=0,最近未访问,也未被修改;
- A=0,D=1,最近未访问,但被修改;
- A=1,D=0,最近被访问,但未被修改;
- A=1,D=1,最近被访问,且被修改。
AD=00最应该被替换,AD=11最不应替换
页面分配策略
给每个进程分配至少多少个物理块
固定分配和可变分配
给每个进程分配多少个物理块?
固定
- 平均分配算法
- 比例分配算法
- 优先级分配算法
可变
允许分配给进程的物理块数随时间变化
全局&局部置换
可变分配在置换时,换出属于谁的页面?
若每个进程已经固定分配物理页面数量,则不可能去抢夺其它进程的页面,因而也就不可能全局置换。
全局置换(Global Replacement)
- 可以置换所有进程的页面
局部置换(Local Replacement)
- 仅置换进程自己的页面
页面缓冲算法
页缓冲算法(PBA,Page Buffering Algorithm)
- 空闲页面池,统一管理空闲的物理页面
- 修改页面池,将D=1的页面暂存
好处:防止将被换出的页面马上又要使用的情况(house keeping)
实用策略-工作集
- 进程开始执行后,随着访问新页面逐步建立较稳定的工作集
- 当内存访问的局部性区域的位置大致稳定时,工作集大小也大致稳定
- 局部性区域的位置改变时,工作集快速扩张和收缩过渡到下一个稳定值
抖动
- CPU利用率与并发进程数存在相互促进和制约的关系
- 进程数少时,提高并发进程数,可提高CPU利用率
- 并发进程进一步增加,导致内存访问增加
- 分配给每个进程的物理块太少,导致缺页率上升和CPU利用下降
- 抖动:每个进程频繁的缺页,换进/换出
- 抖动
- 进程物理页面太少,不能包含工作集
- 造成大量缺页,频繁置换
- 进程运行速度变慢
- 产生抖动的原因
- 随着驻留内存的进程数目增加,分配给每个进程的物理页面数
- 不断减小,缺页率不断上升
- 抖动的预防方法
- 采用局部置换策略
- 把工作集算法融入处理机调度
- “L=S”准则
- 选择暂停的进
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析