操作系统(Operating System,简称OS)是管理系统资源、控制程序执行、改善人机界面、提供各种服务、合理组织计算机工作流程和为用户有效使用计算机提供良好运行环境的一种系统软件。
操作系统的主要功能是资源管理与运行程序,OS直接安装在硬件上,屏蔽硬件细节,向上提供统一抽象接口。
运行程序:编译链接形成可执行文件→外存→内存分配空间→加上PCB控制块形成进程→执行程序→回收内存等软硬件资源
资源包括硬件与软件资源,硬件资源包括CPU、内存、外存、输入输出设备等。资源管理包括处理器管理(进程/线程)、存储管理(内存)、设备管理(I/O)、文件管理、网络与通信管理等
操作系统的资源管理技术主要包括:
操作系统的主要目标为:
操作系统特性:
单道程序设计与多道程序设计
涉及到操作系统的发展历史,在打卡片的年代里,程序运行时的很多步骤都手动处理,单道程序即一次只执行一个程序,多道程序设计则是指允许多个程序同时进入一个计算机系统的主存储器并启动进行交替计算的方法(并行性,如在CPU运转的时候另一个程序的输入也可同时进行)CPU利用率=(CPU运转时间/同一时间所处理的所有程序的总运行时间)
内核:OS在硬件上的首层抽象,屏蔽硬件细节,向上提供统一抽象接口
系统调用:内核中函数,获取操作系统服务的唯一途径
系统调用过程:请求系统调用→由用户态转换为核心态→发生中断,现场保护→执行完毕,内核(系统)调用返回
系统调用与过程(函数)调用的区别
(1)调用形式不同
过程(函数)使用一般调用指令,其转向地址包含在跳转语句中;系统调用不包含处理程序入口,仅仅提供功能号,按功能号调用。
(2)被调用代码的位置不同
在过程(函数)调用中,调用程序和被调用代码在同一程序内,经过连接编译后作为目标代码的一部分。当过程(函数)升级或修改时,必须重新编译连接。
系统调用的处理代码在调用程序之外(在操作系统中),系统调用处理代码升级或修改时,与调用程序无关。
(3)提供方式不同
过程(函数)由编译系统提供或用户编写,不同编译系统提供的过程(函数)可以不同;
系统调用由操作系统提供,一旦操作系统设计好,系统调用的功能、种类与数量就固定不变了。
(4)调用的实现不同
程序使用一般机器指令(跳转指令)来调用过程(函数),是在用户态运行的;
程序执行系统调用,是通过中断机构来实现的,需要从用户态转变到核心态,在管理态执行。
操作接口:是操作系统为用户提供的操作控制计算机工作和提供服务手段的集合。包括图形用户界面,命令行工具等
OS结构:
单体式结构:整个OS为一个大型可执行二进制文件,在内核中以单一程序方式运行
层次式结构:将OS划分为内核与多个模块,垂直排列,为单向调用关系,低层为高层提供服务
客户机/服务器与微内核结构:划分功能模块与微内核,功能模块为服务器进程,用户进程为客户机进程,使用消息传递进行通信,用户进程向服务器进程请求资源,内核管理服务器进程
OS运行模型:
处理器属于分时复用型的共享资源,其中的寄存器被操作系统和各个进程所共享,任意瞬时的寄存器内容构成处理器工作现场。当进程或任务发生切换时,寄存器内容必须被保存,以便进程或任务恢复执行时,还原处理器工作现场。
指令分特权指令与非特权指令:特权指令仅提供给操作系统核心程序使用
处理器状态转换:
PSW即程序状态字,处理器的工作状态记录在程序状态字(PSW)寄存器中;每个正在执行的程序都有一个与其执行相关的PSW(进程与一个PSW对应);每个处理器都设置一个程序状态字寄存器。
PSW存储的主要信息包括:
程序基本状态:程序计数器、处理器状态位、条件码等
中断码:记录发生的中断事件
中断屏蔽位:决定是否响应中断事件
中断:指程序执行过程中发生某个事件时,终止CPU当前程序,转而执行事件的过程
中断是改变指令执行流程、实现操作系统并发多任务功能的重要硬件机构,也是操作系统实现计算机控制的重要途径。检测中断的方法为:在每两条指令或某些特殊指令执行期间,每当一条指令执行结束时都检测是否发生中断以此来检测并响应中断。
中断源是引起中断的事件,中断装置是发现中断源并产生中断的硬件,当中断事件发生后,它能改变处理器内操作执行的顺序。
分类
中断与异常的区别:
(1)中断不是现行程序引发的,而是由与现行指令无关的中断信号触发的;异常是现行程序引发的
(2)中断处理程序提供的服务一般不是现行程序需要的,通常两条机器指令之间才可响应中断;异常是由程序本身产生的,异常处理一般是现行程序所需要的
中断处理:
使用硬件+软件实现中断处理,中断寄存器存储中断字,中断字每一位代表一个中断事件,每条指令执行完毕后检查中断字。中断指令刺激中断源产生中断信号,中断信号置中断字对应位为1,检查中断字发现中断,CPU响应中断请求,中断位置0
发现中断源,响应中断请求→保护现场→启动中断处理程序→中断返回
中断处理程序主要用于处理事件与恢复现场,当响应中断时调用中断处理程序,然后保护现场,不同的中断源对应不同的中断处理程序,维护一个中断处理程序入口的向量表
问题1:在系统中只有一个进程运行时,该进程会不间断地运行吗?
不一定,只要发生了系统调用仍然会产生中断,同时程序运行过程中会产生各种各应的中断信号,如I/O中断,时钟中断或各种内中断,一般来说一个进程运行时不会不间断的运行,总会产生某些中断。
问题2:在系统中没有任何进程运行时,操作系统在干什么?系统会静止吗?
不会,此时会有时钟中断处理程序在运行,等待服务程序的到来;中断犹如计算机系统的心脏,无论何时不能停止跳动,否则,计算机系统将失去驱动力;中断也像刹车,可以使用户程序及系统程序适时停止运行,以便重新分配系统资源,尤其是处理器资源,避免某些程序过长时间地垄断系统资源,破坏系统资源使用的公平性。
进程是一个可并发执行的具有独立功能的程序关于某个数据集合的一次执行过程,也是操作系统进行资源分配和保护的基本单位。
进程与程序的区别
(1)程序是静态的,进程是动态的,进程是按照程序运行的过程。
(2)同一个程序在一段时间内可以同时存在多个执行活动(即进程)分别对不同的数据进行处理
进程的属性:
进程状态:
划分进程状态对于简洁、条理地分类管理进程、合理调度系统资源非常必要。
进程至少包含程序代码与数据集两个部分,即包括一系列顺序指令与该指令集合要操作的数据
要让OS管理进程,则必须还加上进程控制块(PCB),进程状态
在三态模型中,进程的运行、就绪、阻塞都由OS在内存中进行调度管理,随着进程数的不断增加,系统资源会被瓜分干净,为了平滑系统负荷,必须把某些进程挂起换到磁盘镜像中,因此引入挂起态:
OS控制结构:操作系统的控制结构是控制进程、管理系统资源的数据结构,同样类型的数据结构组成表,称为控制表。
进程实体/进程映像
进程实体包括程序块、数据块、进程控制快(PCB)、核心栈
进程控制块PCB:进程创建时建立进程控制块,进程撤销时回收进程控制块,进程控制块与进程一一对应。主要包括标志信息(唯一标识一个进程)、现场信息(让出CPU时当下的CPU现场,便于后来恢复)、控制信息(用于管理和调度进程,如调度信息,资源清单等)等。
核心栈主要记录进程运行在核心态下时跟踪过程调用和过程间参数传递信息
进程实体的内容随着进程的执行不断发生变化,某时刻进程实体的内容及其状态集合称为进程映像。
进程上下文
进程物理实体和支持进程运行的环境合称为进程上下文。进程物理实体主要包括PCB、程序块、数据块等,进程运行环境主要包括核心栈、用户栈、寄存器等
进程队列
处于同一状态的所有PCB链接在一起的数据结构称为进程队列。队列可以按照进程状态进行细分,如就绪队列、等待队列等,通过管理该队列控制进程调度
进程切换
进程切换即中断一个进程的执行转而执行另一个进程。保存被中断进程上下文到其进程控制块中,然后装入新进程上下文,使其从上次断点恢复执行,或者调度一个新的进程。
进程调度的时机
(1)当进程进入等待态时:一个进程陷入阻塞,调一个就绪态的进程来执行
(2)当进程完成其系统调用返回用户态,但不是最有资格获得CPU时:把处理器分配给最有资格获得它的进程,进程切换由此发生。
(3)当内核完成中断处理,进程返回用户态但不是最有资格获得CPU时:发生强迫性中断时的处理情况,同(2)
(4)当进程执行结束时:回收资源,调度处理其他进程
进程的调度依赖于中断,中断是激活操作系统的唯一方法,只有中断发生时操作系统才能接管处理器,才有可能发生进程上下文的切换
进程切换的步骤(假设P1被中断,调度P2开始执行):
中断、保存P1现场信息→修改P1的PCB,将运行态改为等待态或就绪态→将P1加入相关进程队列(等待队列或者就绪队列)→在就绪队列中选择一个进程来执行,如P2→修改P2的PCB,将状态改为运行态→运行P2,直接执行或恢复上次断点状态继续执行
进程切换包含两次模式切换:一个进程的用户态→管态;管态→另一个进程的用户态
进程的控制与管理
OS对进程的控制与管理通过原语来实现
**原语是在管态下执行、完成系统特定功能的不可中断的过程,具有原子操作性。**原语可以采用屏蔽中断的系统调用来实现
(1)进程的创建:
①在主进程表中增加一项,并从PCB池中取一个空白PCB,为新进程分配唯一的进程标识符
②为新进程的进程映像分配地址空间,装入程序和数据。
③为新进程分配内存空间外的其它资源
④初始化进程控制块,如进程标识符、处理器初始状态、进程优先级等
⑤把进程状态置为就绪态并加入就绪进程队列
⑥通知操作系统的某些模块,如记账程序、性能监控程序
(2)进程的阻塞:
进程阻塞是指一个进程让出处理器,去等待一个事件。
①停止进程执行,保存现场信息到PCB
②修改PCB的有关内容,如进程状态由运行改为等待,并把修改状态后的PCB加入相应等待队列
③转入进程调度程序调度其他进程运行
(3)进程的唤醒:
①从相应等待队列中移出进程
②修改进程PCB有关信息,如进程状态改为就绪并把修改PCB后的进程加入就绪队列
③若被唤醒进程优先级高于当前运行进程,则重新设置调度标志
(4)进程的撤销:
①根据撤销进程标识号,从相应队列找到它的PCB
②将该进程拥有的资源归还给父进程或操作系统
③若该进程拥有子进程,则先撤销它的所有子孙进程,以防它们脱离控制
④撤销进程出队,将它的PCB归还到PCB池
(5)进程的挂起和激活:
挂起时:检查要被挂起进程的状态,若处于活动就绪态就修改为挂起就绪态,若处于阻塞态,则修改为挂起阻塞态。被挂起进程PCB的非常驻部分要交换到磁盘对换区。
激活时:把进程PCB非常驻部分调进内存,修改它的状态,挂起等待态改为等待态,挂起就绪态改为就绪态,加入相应队列中。
挂起原语既可由进程自己也可由其他进程调用,但激活原语只能由其它进程调用。
线程:运行在同一个进程中的各个并发任务称为该进程的线程。线程是处理器调度的基本单位
线程实体
线程控制块TCB:包含唯一标识符、状态信息、线程上下文等
私有存储区:包含局部变量,用户栈等
核心栈:线程在核心态工作下存放的函数调用传递的参数、返回地址等信息
线程状态
只有三个关键状态:运行、就绪、等待(阻塞)
多线程优点
切换速度快、切换开销小
易于实现任务间的通信协作
提高并发度
多线程实现
处理器调度考虑两个问题:如何从多个作业中选择一些作业加载到内存中并为其创建进程?如何从多个进程中选择一个进程交由CPU处理
高级调度:从磁盘后备作业队列中挑选若干作业进入内存,为其分配资源,创建进程,决定一个进程能否被创建及创建后能否被置位就绪态
中级调度:决定哪些进程进入挂起状态,哪些进程从挂起状态中被唤醒,即决定哪些进程参与竞争处理器资源,哪些进程换出到磁盘,哪些进程从磁盘中换入内存
低级调度:决定哪一个就绪进程占用CPU,中断是执行低级调度的时机
调度时选择调度算法的因素
作业、进程和线程
作业对应一个完整的业务处理过程,该过程包含若干个相对独立又相互关联的顺序加工步骤,每个步骤对应着一个进程或线程
主要讲低级调度算法,可以分为两种类型:
两者一般混合使用,内核关键程序使用非剥夺方式、用户进程使用剥夺方式
调度算法:
(1)先来先服务算法FCFS:谁先来就先服务谁,非剥夺
(2)最短作业优先算法SJF:对每一个到来的作业进行时间估计,那个时间最少就运行谁,非剥夺
(3)最短剩余时间优先算法SRTF:对每一个到来的作业进行时间估计,对当前时刻的所有进程进行时间评估,时间少的可抢占CPU优先处理,剥夺式
(4)响应比最高者优先算法HRRF:计算响应比,响应比 = 作业的响应时间/作业处理时间,选择响应比最高的先执行,非剥夺
(5)优先级调度算法:根据确定的优先级选取进程/线程,每次总是选择就绪队列中优先级最高者运行。
(6)轮转调度算法:每次把CPU分配给就绪队列首进程/线程使用一个时间间隔(称为时间片),就绪队列中的每个进程/线程轮流运行一个时间片。
进程并发性:进程的并发性是指一组进程的执行在时间上是重叠的,即一个进程执行的第一条指令是在另一个进程执行的最后一条指令完成之前开始的。其实质是一个CPU在多个进程间复用,消除计算机部件之间的互等现象以提高系统资源利用率
并发带来的问题:
竞争CPU处理时间带来的问题(即进程指令执行顺序的先后):
竞争资源带来的问题:
进程互斥:指若干进程要使用同一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。
进程同步:指两个以上进程基于某个条件协调彼此的活动,一个进程的执行依赖于协作进程的消息或信号,当一个进程没有得到来自于协作进程的消息或信号时需等待,直到消息或信号到达才被唤醒。即同步进程间必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。
临界区:并发进程中与共享变量有关的程序段叫做临界区,即一次只能有一个进程执行的程序段
临界资源:共享变量代表的资源叫做临界资源,不可共享资源
Peterson算法
Peterson算法是一个正确的临界区管理算法
bool inside[2];//标志对方进程是否进入临界区
inside[0]=false;
inside[1]=false;
enum {0,1} turn;//标志对方进程是否到达了临界区前
cobegin
process P0( )
{
inside[0]=true;//标志该进程开始
turn=1;
while(inside[1]&&turn==1);//另一个进程开始但还没有更改turn时等待,一旦更改则那个进程等待而自己执行
{临界区};
inside[0]=false;
}
process P1( )
{
inside[1]=true;
turn=0;
while(inside[0]&&turn==0);//等待
{临界区};
inside[1]=false;
}
coend
指令是顺序执行的,对于turn来说在临界区前只有一个值,根据turn的值来判断哪一个进程先进入临界区,对于上述程序来说,谁先更改turn谁就先执行,先执行的进程依赖于另一个进程更改turn的值。
实现临界区管理的硬件条件
硬件条件应该保证对于临界区的访问是互斥的
关中断:进程在进入临界区之前先关中断,退出临界区时开中断。虽然简单但一般不使用,长时间关中断会影响系统效率且并不适用于多CPU系统
TS指令:
TS(x)
{
若x=true,则x=false;return true;
否则 return false;
}
该指令配合外部临界资源相当于一把锁,进入时上锁,退出时解锁
bool s=true;//临界资源,相当于锁
cobegin
process Pi( )
{ //i=1,2,...,n
while(!TS(s)); //上锁
{临界区};
s=true;//解锁
}
coend
交换指令SWAP(a,b):
bool lock=false;//锁,只有开关两种状态,通过SWAP保证互斥
cobegin
Process Pi( )
{//i=1,2,...,n
bool keyi=true;
do
{
SWAP(keyi,lock);//将两个值互换
}while(keyi); //上锁
{临界区};
SWAP(keyi,lock);//解锁
}
coend
信号量:多个进程间可以通过简单的信号进行合作,可强迫一个进程在某个位置停止直到接收到一个特定的信号。
通常信号量的结构可以描述为:
typedef struct semaphore
{
int value; //信号量值
struct pcb *list; //信号量队列指针
} semaphore;
value表示可以进入临界区的进程的数量,>0表示还可以使用的资源数量,<0则表示正在等待的进程数量。list为指向阻塞的进程队列指针
P操作:表示请求进入临界区的操作:
void P(semaphore &s)
{
s.value--;//如果<=0则直接将调用进程加入阻塞队列
if(s.value<0) W(s.list); //将P操作调用者进程置为阻塞状态并移入s信号量队列,转进程调度
}
V操作:表示从临界区出来,释放资源的操作:
void V(semaphore &s)
{
s.value++;
if(s.value<=0) R(s.list);
//从信号量s队列中释放一个等待信号量s的进程并转换成就绪态,自己则继续执行
}
使用PV操作实现互斥的基本步骤为:
semaphore mutex;
mutex=1;
cobegin
process Pi( )
{ //i=1,…,n
P(mutex);
{临界区};
V(mutex);
}
coend
为了吃面,每个哲学家必须获得两把叉子,且每人只能从自己左边或右边去取叉子。
错误的解决方法:
semaphore fork[5];//表示对每个叉子上锁
for (int i=0;i<5;i++)
fork[i]=1;
cobegin
process philosopher_i( )
{ //i= 0,1,2,3,4
while(true)
{
think( );
P(fork[i]);
P(fork[(i+1)%5]);
eat( );
V(fork[i]);
V(fork[(i+1)%5]);
}
}
coend
假设5个哲学家同时执行到P(fork[i]);,相当于每个人都先拿到了左边的叉子,再也没有人能够拿到右边的叉子,导致出现了死锁。
为避免死锁,可用的解决方案有:
(1)至多允许四位哲学家同时去拿左边的叉子
因此必须新增信号量表示同时拿左边的叉子的人数
semaphore fork[5];//表示对每个叉子上锁
for (int i=0;i<5;i++)fork[i]=1;
semaphore count=4;//表示能够拿到左边1的叉子的人数
cobegin
process philosopher_i( )
{ //i= 0,1,2,3,4
while(true)
{
think( );
P(count);//相当于对count上锁,最多同时四个哲学家能够拿叉子
P(fork[i]);
P(fork[(i+1)%5]);
eat( );
V(fork[i]);
V(fork[(i+1)%5]);
V(count);
}
}
coend
(2)规定奇数号哲学家先拿他左边的叉子,然后再去拿右边的叉子;偶数号哲学家先拿他右边的叉子,然后再去拿左边的叉子
只要奇偶互相竞争,总会有一个哲学家空出来能同时拿到两把叉子,即先进行一次两两配对,保证有哲学家先拿到一把叉子,再进行另一把叉子的竞争
semaphore fork[5];//表示对每个叉子上锁
for (int i=0;i<5;i++)fork[i]=1;
cobegin
process philosopher_i( )
{ //i= 0,1,2,3,4
while(true)
{
think( );
if(i%2==0){
P(fork[(i+1)%5]);
P(fork[i]);
}else{
P(fork[i]);
P(fork[(i+1)%5]);
}
eat( );
V(fork[i]);
V(fork[(i+1)%5]);
}
}
coend
(3)仅当哲学家的左右两把叉子均可用时,才允许他拿起叉子进餐,否则一把叉子也不取:
semaphore fork[5];//表示对每个叉子上锁
for (int i=0;i<5;i++)fork[i]=1;
semaphore mutex=1;//全局锁
bool flag[5];
for (int i=0;i<5;i++)flag[i]=true;//true表示该叉子没有被拿走
cobegin
process philosopher_i( )
{ //i= 0,1,2,3,4
while(true)
{
think( );
if(!flag[i]||!flag[i+1%5])continue;//只要有一把拿不到就不拿
P(mutex);
if(flag[i]&&flag[i+1%5]){//只有两把叉子都能拿时才设置拿走状态,否则就一把也不拿
flag[i]=false;
flag[i+1%5]=false;
V(mutex);
}else{
V(mutex);
continue;
}
if(!flag[i]&&!flag[i+1%5]){//达到正确状态,可以直接拿左右两把叉子
P(fork[i]);
P(fork[i+1%5]);
eat( );
V(fork[i]);
V(fork[(i+1)%5]);
P(mutex);//重置状态
flag[i]=true;
flag[i+1%5]=true;
V(mutex);
}else continue;
}
coend
item B[k];
semaphore empty=k;//可以使用的空缓冲区数
semaphore full=0;//缓冲区内可以使用的产品数
semaphore mutex=1; //互斥信号量,B的锁
int in=0; //写缓冲区指针
int out=0; //读缓冲区指针
cobegin
process producer_i( )
//生产者进程
{
while(true)
{
produce( );
P(empty); //可添加槽减一
P(mutex);
append to B[in];//向全局资源中添加产品
in=(in+1)%k;
V(mutex);
V(full); //可用资源加一
}
}
process consumer_j( )
//消费者进程
{
while(true)
{
P(full);//可用资源减一
P(mutex);
take( ) from B[out];
out=(out+1)%k;
V(mutex);
V(empty);//可添加槽加一
consume( );
}
}
coend
一次可以有多个读者,但最多只能有一个写者,读写不能同时进行
读者优先解决方案:
int readcount=0; //读进程计数
semaphore writeblock=1;//写锁,一次只能有一个
semaphore mutex=1;//资源锁
cobegin
process reader_i( )//读者进程
{
P(mutex);
readcount++;
if(readcount==1) P(writeblock);//可以有多个读者,一旦读者进入,则写者不能进入
V(mutex);
{读文件};
P(mutex);
readcount--;
if(readcount==0) V(writeblock);//没有读者则释放写锁
V(mutex);
}
process writer_j( )//写者进程
{
P(writeblock);//上写锁
{写文件};
V(writeblock);
}
coend
上述解决方案可能导致写者的饥饿,为避免写者的饥饿,可使用写者优先方案,在写者想要写时不允许读者在进入以避免写者的饥饿。
写者优先方案:
int readcount=0; //读进程计数
semaphore readblock=1;//读锁,标志是否有读进程
int writecount=0;//写进程技术
semaphore writeblock=1;//写锁,标着是否有写进程
semaphore readmutex=1;//资源锁,保证readcount的原子性
semaphore writemutex=1;//资源锁,保证writecount的原子性
semaphore mutex=1;//资源锁,读写资源的加锁
cobegin
process reader_i( )//读者进程
{
P(readblock);//一次只允许一个读进程尝试进行一次操作
P(writeblock);//写者优先,读者只能先获得写者的锁进行一次操作,如果写者获取到了,则读者不可以在进入
P(readmutex);
readcount++;
if(readcount==1)P(mutex);//没有写者,读者可以有多个
V(readmutex);
V(writeblock);
V(readblock);
{读文件};
P(readmutex);
readcount--;
if(readcount==0) V(mutex);//没有读者则释全局资源锁
V(readmutex);
}
process writer_j( )//写者进程
{
P(writemutex);
writecount++;//写者数
if(writecount==1)P(writelock);//有写者进来,因为写者优先,读者不能进来,但是写者可以进来
V(writemutex);
P(mutex);//写者虽然进来了,但是还是要竞争唯一资源锁
{写文件};
V(mutex);
P(writemutex);
writecount--;
if(writecount==0)V(writelock);//没有写者了,释放写者标志锁
V(writemutex);
}
coend
理发店有一位理发师、一把理发椅和n把椅子供顾客等候理发休息。如果没有顾客,理发师便在理发椅上睡觉。
一个顾客到来时,他必须叫醒理发师。如果理发师正在理发时又有顾客到来,则如果有空椅子可坐,顾客就坐下来等待,否则离开。
int waiting=0; //等候理发顾客坐的椅子数
int CHAIRS=N; //为顾客准备的椅子数
semaphore customers=0;
semaphore barbers=0;
semaphore mutex=1;//waiting的锁
cobegin
process barber( ) //理发师进程
{
while(true)
{
P(customers);//理发师需要顾客来进行唤醒,尝试获取顾客
P(mutex); //若有顾客时,以顾客当产品,取一个顾客消费
waiting--; //等候顾客数少一个
V(barbers); //理发师喊一个顾客来准备为他理发,即理发师可用
V(mutex); //退出临界区
cut_hair(); //理发师正在理发(非临界区)
}
}
process customer_i( ) //顾客进程
{
P(mutex); //进入临界区
if(waiting<CHAIRS)
{//若有空椅子,则等候顾客数加1,否则顾客离开
waiting++;
V(customers); //可用顾客数增1,即顾客可用
V(mutex); //退出临界区
P(barbers); //尝试获取理发师
get_haircut(); //否则顾客坐下理发
}else V(mutex); //人满了,走吧!
}
coend
管程是由局部于自己的若干公共变量及其说明和所有访问这些公共变量的过程所组成的软件模块。将共享资源放入管程中由管程进行保护,管程确保同一时间只有一个进程进入临界区,其余进程进入队列
管程的一般结构为:
管程内部的一般部件为:
条件变量与P、V操作中信号量的区别
条件变量是一种非计数信号量,维护队列时不对其中的等待进程计数。因此在使用条件变量x时,通常需要定义一个与之配套使用的整型变量x-count用于记录条件变量x所维护等待队列中的进程数。而P、V操作中的计数信号量不仅维护相关队列,还记录队列中进程数。
管程的一般实现:
在管程内部使用PV操作完成互斥
typedef struct InterfaceModule//管程内部维护的数据结构
{
semaphore mutex1=1;//进程调用管程过程前使用的互斥信号量
semaphore next=0; //发出signal的进程挂起自己的信号量
int next_count=0;//在next上等待的进程数
};
//进入管程
void enter(InterfaceModule &IM)
{
P(IM.mutex);//P操作进入管程
}
//退出管程
void leave(InterfaceModule &IM)
{
if(IM.next_count>0) V(IM.next);//优先唤醒管程内紧急等待队列中的等待进程
else V(IM.mutex);//当管程内无等待进程时再唤醒管程外入口处的等待进程
}
//管程内部的资源等待(阻塞)操作,让一个进程来获取资源x_sem,自己则阻塞于x_sem等待被唤醒
void wait(semaphore&x_sem, int&x_count, InterfaceModule&IM)
{
x_count++;//等待资源的进程数增1,等待资源的进程将加入x_sem所管理的资源等待队列
if(IM.next_count>0) V(IM.next);//等待资源的进程睡眠之前,优先唤醒管程内紧急等待队列中的就绪进程
else V(IM.mutex);//若管程内紧急等待队列空,则唤醒管程入口处等待进入管程的队列中的进程
P(x_sem);//唤醒其它进程之后,等待资源的进程加入x_sem所管理的资源等待队列睡眠,下面的x_count--会暂停执行,待该进程被唤醒后再执行
x_count--;
}
//管程内部的唤醒操作,释放自己持有的x_sem资源并将自己阻塞于紧急等待队列等待自己被唤醒
void signal(semaphore&x_sem, int&x_count, InterfaceModule&IM)
{
if x_count > 0 {//若有等待资源的进程,即x_sem资源等待队列若有进程
IM.next_count++;//执行signal操作的进程将加入紧急等待队列,因此该队列进程数增1
V(x_sem);//执行signal操作的进程自己阻塞前,先释放一个等待资源的进程
P(IM.next);//执行signal操作的进程进入管程内紧急等待队列,下面的IM.next_count--暂停执行,只有进程被唤醒后才会执行
IM.next_count--;
}
}
使用管程解决生产者消费者问题:
type producer_consumer=monitor //管程定义
{
item B[k]; //缓冲区个数
int in,out; //存取指针
int count; //缓冲中产品数
semaphore notfull,notempty; //条件变量
int notfull_count,notempty_count;
InterfaceModule IM;
//封装两个生产与消费操作
void append(item x){
enter(IM);//进入管程
if(count==k) wait(notfull,notfull_count,IM);//缓冲已满,在notfull队列等待空白缓冲区
B[in]=x;
in=(in+1)%k;
count++; //增加一个产品
signal(notempty,notempty_count,IM);//唤醒notempty队列中的等待消费者,自己入紧急等待队列
leave(IM);//离开管程
}
void take(item &x){
enter(IM);
if(count==0) wait(notempty,notempty_count,IM);//在notempty队列中等待满缓冲区
x=B[out];
out=(out+1)%k;
count--;//减少一个产品
signal(notfull,notfull_count,IM);//唤醒notfull队列中的等待生产者
leave(IM);
}
}
cobegin
process producer_i() //进程定义
{
item x;
produce(x);//生产
producer_consumer.append(x);//追加
}
process consumer_j()
{
item x;
producer_consumer.take(x);//取出
consume (x);//消费
}
coend
使用管道:
管道是连接读写进程的一个共享文件,允许进程以先进先出(FCFS)方式写入和读出数据,并对读写操作进行同步。发送进程以字符流形式把大量数据送入管道尾部,接收进程从管道头部接收数据。
管道可借助文件系统实现,包括(管道)文件的创建、打开、关闭和读写。
管道应互斥使用,管道读写不能同时进行;读写双方必须能够知道对方是否存在,只有存在才能通信;管道空间大小通常是固定的,读写操作时需要考虑写满和读空问题。
使用共享内存
JVM的内存模型也使用该思想,每个线程都有自己的工作内存,存储主内存中的变量拷贝,只能在自己的工作内存中工作,线程间无法互相访问,只能通过主内存进行传递赋值。
共享内存是允许两个或多个进程共同访问的物理内存区域,是实现进程通信的一种手段。共享内存区映射到进程中未使用的虚地址区,以免与进程映像发生冲突。共享内存区属于临界资源,读写共享内存区的代码属于临界区。
共享内存需要有标识符进行标识,第一个进程创建共享内存,其它进程则通过创建操作获得共享内存标识符,并据此读写共享内存(进行系统调用创建共享内存)。通信进程将先前创建的共享内存映射到自己的虚拟地址空间,使共享内存成为进程地址空间的一部分。通信结束时,解除共享内存到通信进程虚地址空间的映射。当所有进程不再需要共享内存时删除共享内存。
使用消息传递:
消息是格式化的数据,在计算机网络中称报文。消息由消息头和消息体组成。
消息传递通信机制由信箱、发送原语(send)和接收原语(receive)组成。
Linux消息队列通信机制:
Linux消息队列通信机制属于消息传递通信机制。消息队列是内核地址空间中的内部链表,每个消息队列具有唯一的标识符。消息可以顺序地发送到队列中,并以几种不同的方式从队列中获取。
套接字通信:
套接字(Socket)通信允许互联的位于不同计算机上的进程之间实现通信功能。套接字用于标识和定位特定计算机上特定进程的地址,以便数据准确传输给目标进程。
Socket一般包含三个参数:
Socket的连接过程为:
操作系统中的死锁是指:如果在一个进程集合中的每个进程都在等待只能由该集合中的其它进程才能引发的事件,而无限期陷入僵持的局面称为死锁。
死锁产生的四个必要条件:
只要破坏这四个条件之一,死锁就可防止
死锁的防止
死锁防止通过破坏产生死锁的四个条件之一来实现
死锁的避免:
死锁避免方法允许系统中同时存在死锁的三个必要条件,即互斥、占有且等待和非抢占;
每当进程提出资源申请时,系统分析满足该资源请求时系统是否会发生死锁,若不会发生则实施分配,否则拒绝分配。
避免死锁的银行家算法:
问题描述:
一个银行家拥有资金M,被N个客户共享,银行家对客户提出下列约束条件:
(1)每个客户必须预先说明自己所要求的最大资金量;
(2)每个客户每次提出部分资金量申请和获得分配;
(3)如果银行满足了客户对资金的最大需求量,则客户在资金运作后一定可以很快归还资金。
在上述问题中,银行家就是操作系统;资金就是系统资源;客户就是进程
银行家算法的步骤为:
(1)系统中的所有进程进入进程集合
(2)在安全状态下对进程请求的资源进行试探性分配
(3)系统用剩余的可用资源和进程集合中其它进程还要的资源数作比较,找到剩余资源能满足最大需求量的进程A,保证A运行完毕并归还全部资源
(4)把进程A从集合中去掉,相当于回收其资源。如果进程集合非空,则返回(2)
(5)若进程集合为空,则系统处于安全状态,可实施本次分配;否则,系统处于不安全状态,本次资源分配暂不实施,申请进程等待
银行家算法缺乏实用价值:很难在进程运行前知道其所需的资源最大量;同时算法要求系统中的进程必须是无关的,相互间没有同步要求;并且进程的个数和分配的资源数目应该是固定的。资源总是会动态变化的,预先分配的方案会导致资源利用率降低
死锁的检测:
系统定时运行一个“死锁检测”程序,如果检测到系统发生了死锁,再采取措施解除它。
进行死锁检测时,系统维护一个进程-资源图,进程-资源分配图是描述进程和资源间申请与分配关系的一种有向图,由进程结点P、资源结点R和有向边组成,可用以检测系统是否处于死锁状态。
死锁在逻辑上的判定可由下图说明(前提是进程间互相不孤立):
死锁解除:
(1)立即结束所有进程的执行,并重新启动操作系统。以前工作全部作废,损失可能很大。
(2)剥夺陷于死锁的进程占用的资源,但并不撤销它,直至死锁解除。
(3)撤销陷于死锁的所有进程,解除死锁继续运行。
(4)逐个撤销陷于死锁的进程,回收其资源,直至死锁解除。
(5)根据系统保存的检查点,使所有进程回退,直到足以解除死锁。
(6)当检测到死锁时,如果存在某些未卷入死锁的进程,且它们会进一步建立一些新的抑制进程能执行到结束,则它们可能释放足够的资源来解除死锁。
即OS对于内存与外存的管理
基础概念:
静态重定位与动态重定位:
静态重定位:根据程序所装入的内存位置由装入程序依据重定位信息一次性将程序中所有的逻辑地址转变为物理地址,然后程序开始执行,这种重定位方式称为静态重定位。静态重定位无须硬件支持,易于实现,但静态重定位不允许程序在内存中移动位置。
动态重定位:地址转换工作穿插在指令执行的过程中,每执行一条指令,CPU对指令中涉及的逻辑地址进行转换,这种重定位方式称为动态重定位。动态重定位必须借助硬件地址转换机构实现,动态重定位允许程序在内存中移动位置。
存储保护:
连续存储管理对每个进程分配一个连续的存储区域。
固定分区存储管理:
固定分区存储管理将内存空间划分为若干个位置和大小固定的连续区域,每一个连续区域称为一个分区,各分区大小可相同,也可不同。每当进程进来时都直接分配一个可用的分区给它。
缺点很明显:
(1)分区数目和大小在系统启动阶段已经确定,限制了系统中活动进程的数目,也限制了当前分区方案下可运行的最大进程。
(2)当分区长度大于其中进程长度时,造成存储空间浪费。
固定分区会产生内部碎片。内部碎片:由于进程所在分区大于进程大小而造成的分区内部浪费部分称为内部碎片。
可变(动态)分区存储管理:
按照作业的大小划分分区,划分的时间、大小和位置都是动态的,属于一种动态分区方法。
每当进程进入时,系统根据当前内存状况动态分配一块分区给它。随着时间的推移,会产生很多外部碎片。外部碎片:**内存中的小到难以利用的分区,这种分区称为外部碎片。**可使用压缩技术合并外部碎片,但不相邻空闲分区的移动合并消耗CPU大量时间
伙伴系统:
也称为buddy算法,是固定分区和可变分区折中的主存管理算法,由Knuth在1973年提出。伙伴系统采用称为伙伴的可以分割、合并的不同规格的内存块作为分区单位。利用二叉树结构进行内存分区的划分。
伙伴:两个大小相等且由同一个尺寸为2i的空闲块分割而来的内存块互为伙伴。
分配:运用伙伴系统分配内存空间的过程是一个对空闲内存区不断对半切分,直到切分出的内存块为大于或等于进程大小的最小伙伴为止的过程。
回收:伙伴系统回收内存的过程是不断将相邻空闲伙伴合并为更大伙伴单位,直到伙伴不空闲,无法合并为止的过程。
分页存储管理将全部内存划分为长度相等的若干份,每一份称为一个物理块或页框。
作业自动被分页系统划分为与每个物理块相等的若干等份,每一份称为一页或一个页面。
使用分页存储时,一个作业的任一页可以装入到内存任一空闲物理块,并不要求逻辑上相邻的页所在内存物理块也相邻。此时,逻辑地址结构=页号+页内位移;物理地址结构=物理块号+块内位移
若 页 面 大 小 设 置 为 4 K B , 地 址 总 线 宽 度 为 32 位 , 则 页 号 为 2 20 , 业 内 位 移 为 2 12 ( 4 K B ) 若页面大小设置为4KB,地址总线宽度为32位,则页号为2^{20},业内位移为2^{12}(4KB) 若页面大小设置为4KB,地址总线宽度为32位,则页号为220,业内位移为212(4KB)
由逻辑地址映射物理地址,物理块号=页号,物理块内位移=业内位移
操作系统需为每个作业建立一张页表,页表用于实现地址变换,页表记载了逻辑地址到物理地址的对应关系,系统通过页表可以准确访问内存中属于一个作业的所有页面。
例题:若页面大小设置为4KB,地址总线宽度为32位,则页内位移位数为12位,页号位数=32-12=20,逻辑地址60000在第几页?页内偏移是多少?若该页被装进物理块1280中,则物理地址是多少?
60000 = ( 0000 E A 60 ) 16 , 16 进 制 中 , 前 5 位 为 页 号 , 后 3 位 为 页 偏 移 。 页 号 = ( E ) 16 , 页 内 偏 移 = ( A 60 ) 16 。 60000=(0000EA60)_{16},16进制中,前5位为页号,后3位为页偏移。页号=(E)_{16},页内偏移=(A60)_{16}。 60000=(0000EA60)16,16进制中,前5位为页号,后3位为页偏移。页号=(E)16,页内偏移=(A60)16。
物 理 地 址 同 理 , 物 理 块 号 为 1280 = ( 500 ) 16 , 物 理 偏 移 = 页 偏 移 , 则 物 理 地 址 为 ( 00500 A 60 ) 16 物理地址同理,物理块号为1280=(500)_{16},物理偏移=页偏移,则物理地址为(00500A60)_{16} 物理地址同理,物理块号为1280=(500)16,物理偏移=页偏移,则物理地址为(00500A60)16
快表:
引入快表的原因是CPU每存取一个指令/数据时,需要两次访问内存
快表同样使用缓冲的思想以加快速度。为了减少分页存储管理系统中的内存访问速度下降一倍问题,在存储管理部件中增设一个专用的高速缓冲存储器,用来存放最近访问过的部分页表项,这种高速缓冲存储器称为快表或联想存储器。
有了快表,根据页号查找对应的物理块号时,首先查找快表中的局部页表,若找到则将物理块号和页内地址(也就是偏移地址)拼接形成物理地址,根据该物理地址访问相应的内存单元。若在快表中未找到物理块号,则再查找内存页表,获取物理块号一方面形成物理地址,另一方面将该表项抄到快表中,以备下次再次访问该页面时从快表中获得物理块号。查快表和查内存页表是同时进行的,一旦从快表中找到了对应项,则立即停止对内存页表的查找。
多级页表:
如果页表很大,页表占用的内存物理块也允许是离散的,而且页表也可以按需装入内存,这样就需要再建立页表的页表,即页目录,这就是二级页表机制。需要的还可建立更多级的页表。无论多少级页表顶级页表必须完全驻留内存。
在二级页表中,页目录表是一级页表,页表页是二级页表,其逻辑地址就变为:页目录号(一级页表号)+页号(二级页表号)+页内位移。其实就是个一对多、一对一的映射
分段存储管理以段为单位进行存储分配,作业每一段被分配一个连续的主存空间,各段存储位置不一定相邻,各段大小不一。即将程序进行分段,由OS维护进程段表,其逻辑地址=段号+段内位移。
由段号查找进程段表,获取起始位,物理地址=起始位+偏移量(段内位移)
若 段 长 最 大 为 16 K B , 计 算 机 地 址 总 线 为 32 位 , 则 段 号 最 多 2 18 , 段 内 位 移 最 多 2 14 若段长最大为16KB,计算机地址总线为32位,则段号最多2^{18},段内位移最多2^{14} 若段长最大为16KB,计算机地址总线为32位,则段号最多218,段内位移最多214
例题:若段长最大为16KB,计算机地址总线为32位,逻辑地址60000在第几段?段内偏移是多少?若该段被装进物理起始地址1280中,则该逻辑地址对应的物理地址是多少?
60000 = ( E A 60 ) 16 = ( 1110101001100000 ) 2 ( 前 导 0 省 略 ) 60000=(EA60)_{16}=(1110 1010 0110 0000)_2(前导0省略) 60000=(EA60)16=(1110101001100000)2(前导0省略)
前18位为段号,即占第三段,后14位为段内偏移,即
10101001100000 = ( 2 A 60 ) 16 10 1010 0110 0000= (2A60)_{16} 10101001100000=(2A60)16
物理地址=物理起始地址(由段号查找段表而来)+段内偏移,即
1280 = ( 500 ) 16 , 物 理 地 址 = ( 500 ) 16 + ( 2 A 60 ) 16 = ( 2 F 60 ) 16 1280=(500)_{16},物理地址=(500)_{16}+(2A60)_{16}=(2F60)_{16} 1280=(500)16,物理地址=(500)16+(2A60)16=(2F60)16
分段地址是二维的,即必须先将程序分段,分配段号与段偏移,再根据段号从段表中去查找物理起始地址,加上段偏移地址获得真正物理地址。
也就是虚存管理。虚拟内存指加载进程进入内存时只加载进程运行所必须的几个块(页/段),需要时再进行I/O,以提供给用户看起来多的内存空间。以时间换空间,以较小的内存空间运行大的进程或较多的进程将消耗较多的进程对换时间。
局部性原理:
在一段时间内,程序访问的存储空间仅限于某个区域(这称为空间局部性),或者最近访问过的程序代码和数据很快会再次被访问(这称为时间局部性)。局部性原理描述了进程中程序和数据的集簇倾向,表明虚拟内存方案是可行的。
虚拟内存的分页式管理:
在进程开始运行之前,装入全部页面集合中的一个或几个页面,进程运行过程中,访问的页面不在内存时,再装入所需页面;若内存空间已满,而又需要装入新的页面时,则根据某种算法淘汰某个页面,以便装入新的页面。
缺页中断与普通中断的区别
(1)普通中断在两条指令之间才会响应;缺页中断涉及的指令在执行期间就需要响应缺页中断。
(2)当指令本身或者指令所处理的数据跨页时,在执行一条指令的过程中可能发生多次缺页中断。
缺页中断的处理过程:
总的来说,处理过程为:①查看内存是否有空闲物理块,如有则装入页面到空闲物理块,同时修改页表相应项以及内存分配表②如果内存中没有空闲物理块,则按替换算法选择一个页面淘汰,若该页面被写过或修改过,则写回外存;否则只简单淘汰该页面。淘汰页面之后要修改页表相应项,然后调入页面到淘汰页面释放的物理块中
缺页中断率:对于进程P的一个长度为A的页面访问序列,如果进程P在运行中发生缺页中断的次数为F,则f = F/A称为缺页中断率。
抖动(颠簸):在请求分页虚拟存储管理系统中,刚被淘汰的页面立即又要访问,而调入不久即被淘汰,淘汰不久再被调入,如此反复,使得系统的页面调度非常频繁,以致大部分时间消耗在页面调度上,而不是执行计算任务,这种现象称为“抖动”(或者颠簸)。
虚拟内存的分段式管理:
对于程序来说有两种分配方式:
页面淘汰算法:
(1)最佳页面淘汰算法(OPT)
调入一页而必须淘汰一个旧页时,所淘汰的页是以后不再访问的页或距现在最长时间后再访问的页。OPT是逻辑上的理想算法,实际上不可能实现。
OPT是可用于衡量各种具体算法的标准
(2)先进先出页面淘汰算法(FIFO)
先进先出页面淘汰算法总是淘汰最先调入主存的那一页,或者说在主存中驻留时间最长的那一页(常驻的除外)。
(3)最近最久未使用页面淘汰算法(LRU)
淘汰的页面是在最近一段时间里较久未被访问的那一页
维护一个栈,每当命中时就将命中的页放到栈尾,每当缺页时就替换掉栈顶的页并将换进来的页放到栈尾
(4)时钟页面替换算法
①页面首次装入主存时其“引用位”置1
②主存中的任何页面被访问时,“引用位”置1
③淘汰页面时,从指针当前指向的页面开始扫描循环队列,把遇到的“引用位”是1的页面的“引用位”清0,跳过这个页面;把所遇到的“引用位”是0的页面淘汰掉并换进来新的页,指针推进一步
④扫描完循环队列,若未淘汰任何页,则再次扫描
设备类别:
从数据交换单位来分可分为块设备与字符设备
从设备访问方式来分可分为顺序设备与随机设备:
I/O控制方式:
(1)轮询
轮询也称为忙等待,CPU向设备下达操作命令后,不断查询设备操作完成状态。在轮询控制方式下,CPU与设备的工作完全是串行(顺序)的,而不是并发的。
(2)中断控制
中断控制下,CPU向设备发出读写命令后,不再查询设备执行状态,转而执行其它计算任务。当设备完成读写操作后以中断的方式主动向CPU报告完成情况。CPU响应中断执行一个中断处理程序,将设备从外界获得的数据取走放到内存或者相反。
(3)DMA直接存储器访问
DMA(直接存储器访问)方式允许I/O设备与内存之间直接交换一个连续的信息块,在传输期间无需CPU的干预,而是由专用处理器–DMA控制器完成具体传输控制操作。
在开始DMA传输时,CPU对DMA芯片进行设置,说明需要传送的字节数、有关的设备和内存地址以及操作方向,接着启动DMA。当DMA芯片完成设备I/O时,引发一个中断。在控制设备传输期间,DMA挪用指令周期,控制总线,CPU暂时不能访问内存。周期挪用会减少CPU计算时间,但是,DMA对CPU时间的占用远少于轮询方式中的循环检测和中断方式中的中断处理程序执行开销。DMA将设备I/O中断CPU的频率由字节中断降低为块中断,即每传送一个连续的信息块才中断CPU一次。
(4)通道
通道也叫输入输出处理器,是独立于CPU专门负责数据输入/输出传输工作的处理机,能执行自己的指令程序,代替CPU完成复杂的输入/输出操作,完成主存和外围设备间的信息传送,与CPU并行操作。
在通道方式下,当进程需要执行I/O操作时,CPU只需启动通道,即可返回执行其它进程,通道则执行通道程序,对I/O操作进行控制。
一个CPU可以连接若干通道,一个通道可以连接若干控制器,一个控制器可以连接若干台设备。
I/O通道与CPU的主要区别
(1)通道指令类型单一,主要局限于与I/O操作有关的指令。
(2)通道所执行的通道程序是放在主机内存中的,因此,通道与CPU共享内存。这样,通道与CPU对内存的使用是分时的。
所谓I/O软件系统,就是要对硬件设备进行分层抽象,将I/O软件组织成层次结构,低层软件屏蔽硬件细节,高层软件提供简洁、友好的界面。设计I/O软件系统时要考虑的主要问题有:
分为四个层次:用户空间的I/O软件、设备无关I/O软件、设备驱动程序、中断处理程序
设备驱动程序
每类设备控制器都是不同的,需要不同的软件进行控制。专门与控制器对话,发出命令并接收响应的软件称为设备驱动程序。
由于设备供多个进程共享,进程对设备的访问必须由操作系统仲裁,因此,设备驱动程序通常必须是操作系统内核的一部分。如果添加了一个新设备,该设备的驱动程序必须安装到操作系统内核中。
设备驱动程序的功能是接收设备无关软件的抽象读写请求,并监视请求的执行,以及设备初始化、对电源需求和日志事件进行管理。
设备驱动程序的典型工作过程如下为:驱动程序启动时要检查输入参数是否有效,若无效则返回错误信息,否则将抽象请求中的抽象参数转换为物理参数。接着,驱动程序检查设备当前是否正在使用。如果正在使用,请求被加入等待队列,否则启动设备开始处理请求。驱动程序依次将所需命令写入设备控制器寄存器,依次执行。
设备无关I/O软件
设备无关I/O软件的基本功能是执行所有设备公共的I/O功能,并向用户层软件提供统一的接口。主要功能包括:
磁盘由若干个涂有磁性介质的圆形盘面构成,经过低级格式化,每个盘面(包括正反两面)被划分成一系列同心圆,每个同心圆称为一个磁道,每个磁道再划分为若干等份,称为扇区,数据就以扇区为存储单位保存在磁盘上。扇区是磁盘访问的基本单位,访问扇区的某些字节意味着访问整个扇区。所有盘面上同等大小的磁道构成柱面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTNxKcax-1578144306486)(https://s2.ax1x.com/2020/01/04/l0Arsf.md.png)]
磁盘块物理地址:访问磁盘上的数据时需要给出物理块(扇区)的物理存储地址,该地址是三维的,采用(柱面号,磁头号,扇区号)表示。
磁盘块逻辑地址:磁盘物理块(扇区)的逻辑地址是一维线性的,称为逻辑块号(扇区号)。所有扇区依次排列,从0开始连续编号。扇区编排顺序是自外向里对各个柱面内的扇区排序,同一柱面内的各个扇区先按磁道顺序、再按磁道内扇区顺序排列。
访问扇区时,可以给出逻辑块号,系统自动将逻辑块号转换为(柱面号,磁头号,扇区号)构成的三维物理地址。
磁盘调度算法:
读写一个磁盘块时,影响其访问的时间因素主要有三个方面:
当多个磁盘I/O请求到来时,磁盘驱动程序需要安排I/O请求的处理顺序,这称为磁盘调度或移臂调度。
为了提高设备利用率,尤其是提高独占设备的利用率,减少作业周转时间,系统利用共享设备模拟独占设备的功能,使得独占设备成为能够共享的设备,这就是设备虚拟。SPOOLing(同时外围操作,假脱机技术)就是一种具体的设备虚拟技术。
SPOOLing是对脱机输入/输出系统的模拟。该技术利用一类物理设备模拟另一类物理设备,使独占设备变成可共享设备。
实例
在联机方式下,打印机正在打印一个作业时,其它打印请求必须等待。
在脱机方式下,进程的打印请求不是等待提交给打印机,而是提交到磁盘缓冲区中保存起来,进程即可返回执行其它计算任务。真正的打印动作待打印机空闲时,由专门的打印进程取出各个作业逐个打印。
当用户进程请求打印输出时,SPOOLing并不立即把打印机分配给它,只是做两件事:
(1)输出进程在输出井中为该进程申请一个空闲磁盘块区,将要打印的数据送入其中;
(2)输出进程再为用户进程申请一张空白的用户请求打印表,将用户的打印要求填入其中,再将该表挂到请求打印队列上。
打印机空闲时,输出进程从请求打印队列队首取出一张请求打印表,根据其中的要求将要打印的数据从输出井送到内存缓冲区,再由打印机打印。
打印完成后,SPOOLing再取出下一张请求打印表,打印下一组数据,直到请求打印队列为空时,输出进程阻塞;当有新的打印请求到来时被唤醒。
文件:文件是记录在外存上具有名称的相关信息的集合。文件是对存储设备的抽象,它将以存储介质物理块为单位的信息存储单元抽象为以文件为单位的逻辑存储单元。完成物理到逻辑上的映射,从而向用户提供一个简单的外存信息访问接口。
文件属性:
文件内容访问方式:
目录:目录是查找文件的实体,用于帮助查找文件
目录提供了访问文件的入口,每个文件在目录表中都有一个目录项(文件控制块FCB),目录项用于记载文件的属性信息,如名称、位置、大小和类型等
全部目录项也可构成文件,称为目录文件。目录文件非空,至少包含当前目录项和父目录项。文件目录的作用是将逻辑名转换为物理名,即将文件名转换为文件的磁盘地址。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQOGuSQ6-1578144306487)(https://s2.ax1x.com/2020/01/04/l0m1bV.md.png)]
层次目录系统允许用户创建多级目录,各级目录形成树型结构,也称为目录树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J99CU54D-1578144306488)(https://s2.ax1x.com/2020/01/04/l0mfKI.md.png)]
文件逻辑结构
文件的逻辑结构是从用户角度看到的反映一定逻辑意义的文件内容组成单位及各部分之间的关系。
文件物理结构
文件的物理结构是指文件内容及其各部分之间逻辑关系在物理存储空间中的存储和实现方法。这时文件看作物理文件,是相关物理块的集合。
构造文件物理结构的实质是建立逻辑记录与其物理存储位置之间的对应关系。主要有两种方法:
内存管理已经说过了,此处的文件空间管理主要是指对文件外存(即磁盘块)的管理。
文件空间管理不仅需要分配并记录存放文件的磁盘块,还需要记录空闲磁盘块的情况。
文件空间分配方法
实际上就是给文件分配磁盘块的方法
(1)连续分配
给文件分配一个连续的物理块,OS需要登记各个空闲区的位置和大小以供分配使用。因此前提是OS必须事先知道文件长度才能分配,同时对文件的修改、插入、增加会变得困难,适用于已知长度且不需要经常修改的情况,如光盘刻录。
(2)链接分配
给文件分配物理上不连续的物理块,但是每个物理块有一个指向下一块的指针,因此逻辑上文件是连续的(实际上就是链表了)
(3)索引分配
为每个文件都建立一张索引表,也就是一个map,文件包含索引块地址,由索引块地址找到索引表,索引表内顺序存储了该文件的物理块地址
(4)多级索引分配
当索引块很多时,索引表也变得较大,因此使用多级索引分配。
在顶级索引表中,前几项直接指向物理块,称为直接寻址;后几项划为多个部分,分别是一级、二级、三级……等多级索引,一级索引项指向一个新的索引表,在该索引表中的索引项直接指向物理块,称为一次间接寻址;二级索引项指向一个二级索引表,该索引表的索引项又指向一个索引表,这个索引表的索引项才指向一个实际的物理块,称为二次间接寻址……以此类推
文件外存管理
也就是要记录空闲块的数量和位置
当分配49个空闲块出去时:
此时要是再分配一个空闲块则先将300#的内容复制到专用管理块filesys中,然后再将300#分配出去
1.进程轮流使用处理器与多个打印任务共享打印机的区别。
进程轮流使用处理器属于同时共享、资源分时复用,也叫并发共享,并发进程释放处理器时,其任务通常并未结束。多个进程可以同时使用处理器。
多个打印任务共享打印机属于资源的互斥共享,也叫顺序共享,在一个打印任务结束之前,另一个打印任务不能开始。即多个打印任务不能同时使用打印机。
2.资源虚化与资源抽象的区别
(1)资源虚化强调从逻辑上扩充资源数量。
(2)资源抽象强调封装、隐藏软硬件内部细节,简化资源应用接口。
3.并发性、并行性与顺序性的区别
(1)并发性是指多个事件或活动在同一时间段内(一个时间段)开始或者说发生,注意不是完成或者结束。
(2)并行性是指多个事件或活动在同一时刻(一个时间点)开始或者说发生,注意不是完成或者结束。
(3)顺序性是指一个事件或活动结束前,另一个事件或活动不可以开始。在任何一个时刻或者时间段内,只会有一个事件或活动在进行。其它事件或活动要么已经结束,要么尚未开始。
4.说明特权指令、处理器状态、程序状态字寄存器之间的关系
(1)处理器指令系统分为特权指令和非特权指令,处理器状态分为用户态和核心态。程序状态字寄存器记录有处理器状态信息。
(2)特权指令在核心态下执行,中断是处理器由用户态切换到核心态的唯一途径。特权指令与系统共享资源的分配和使用相关。当多个进程竞争公共资源时,其仲裁权在于操作系统。
因此,特权指令的执行权在于操作系统。操作系统向用户提供了大量为其使用公共资源服务的系统调用处理程序。用户程序通过执行访管中断可以委托操作系统为其提供公共资源服务。
在用户态下执行特权指令是非法的,引起中断后,系统会捕捉到这种非法操作并中止其执行。
5.剥夺调度与非剥夺调度和中断的关系
(1)剥夺调度:在进程自身未出现等待事件的情况下,由于时间片用完或者出现了优先级更高的进程而被迫让出处理器。
(2)非剥夺调度:一旦获得处理器,只有进程自身出现等待事件时才会让出处理器。
(3)无论剥夺调度还是非剥夺调度,进程都有可能出现等待事件而中断,让出处理器。
6.虚拟存储器大小有什么限制?
虚拟存储器是利用外存的一部分(对换区),通过在内外存之间对换进程,从逻辑上扩充内存容量。
外存容量远大于内存容量,但能够用来扩充内存容量的外存仅是其中的一小部分,因为虚拟存储器的容量受地址总线宽度的制约。地址总线宽度决定了程序可访问的最大地址空间,超过该空间的存储区或者说地址是无法访问的。
虚存受到地址总线宽度的制约,因此即使理论上虚存可以无限大,但是实际上同时最多能用的也还是内存大小,只不过使用虚存可以通过I/O来回换数据,速度将低了很多
7.DMA与中断的区别
DMA 传送方式与程序中断对CPU的干扰程度不同。程序中断请求不但使CPU停下来,而且还要CPU执行中断服务程序,需要完成断点和处理器现场保护和CPU与外设之间的数据传送工作,消耗CPU较多时间。
DMA请求仅仅使CPU暂停一下,该暂停不属于中断,不需要执行中断处理程序,不需保存断点和处理器现场信息。DMA挪用CPU指令周期控制外设与主存之间的数据传送,无需CPU的干预。
DMA与CPU交替控制总线,交替执行指令和数据传输工作。在整个数据块传输结束时,DMA才向CPU发中断信号,CPU执行中断处理程序。
8.SPOOLing系统的虚拟设备原理
SPOOLing系统即假脱机系统。将原来以联机方式使用的独占设备改造成为脱机使用的虚拟共享设备。
在联机方式下,每个需要设备I/O的进程排队等待向设备提交输入输出作业。
在SPOOLing系统中,每个需要设备I/O的进程并不直接向设备提交输入输出作业,也不控制设备操作,而是向SPOOLing系统提交输入输出作业,SPOOLing系统再向设备提交作业。SPOOLing系统类似于批处理系统中的操作员。
9.虚拟文件系统的原理是什么,体现了什么资源管理技术思想?
(1)虚拟文件系统(VFS)定义了一个代表不特定文件系统通用特征和行为的文件模型。
VFS抽象出所有文件系统的公共部分,形成一个简单、统一的抽象文件系统接口提供给用户。
用户仅通过抽象文件系统接口层表达文件操作意图,文件操作的具体执行则由底层的实际文件系统来完成。
从抽象文件系统到某一具体文件系统的转换工作由映射模块完成。
(2)虚拟文件系统体现了资源抽象的资源管理技术思想。不同文件系统有着不同的实现方法和细节,虚拟文件系统则封装、隐藏了各种具体文件系统的细节差异,使得用户仅需面对一种统一的、抽象的文件系统,即虚拟文件系统,简化了用户对文件系统的学习和使用。
《操作系统原理与Linux实践教程》申丰山 王黎明 著
《操作系统——精髓与设计原理》William Stallings 著