作者博客:
Karos – 一个热衷于前后端开发分享的个人博客网站 (wzl1.top)
本文有对以下文章进行参考:
计算机操作系统知识点总结(有这一篇就够了!!!)_原来如此呀的博客-CSDN博客_操作系统
操作系统常见面试题总结 | JavaGuide(Java面试+学习指南)
【操作系统】生产者消费者问题_niliushall.的博客-CSDN博客_生产者消费者问题
哲学家进餐问题的三种解决方法(C++11)_凌桓丶的博客-CSDN博客_哲学家进餐问题3种代码
死锁的四个必要条件_Hyacinth_Dy的博客-CSDN博客_死锁的四个必要条件
首次适应算法和最佳适应算法_莫顾尔在的博客-CSDN博客_首次适应
操作系统——段式存储管理、段页式存储管理 - 王陸 - 博客园 (cnblogs.com)
操作系统——页式存储管理 - 王陸 - 博客园 (cnblogs.com)
概述
什么是操作系统
管理计算机硬件和软件资源的系统软件
- 管理计算机系统的硬软件
- 分配调度资源的系统软件
操作系统的目标
方便性、有效性、可扩充性、开放性
提高系统资源的利用率,提高系统的吞吐量
基本功能
- 统一管理计算机资源
- 处理器资源
- IO设备资源
- 存储器资源
- 文件资源
- 实现计算机资源的抽象
- IO设备管理软件提供读写接口、文件管理软件提供操作文件接口
- 提供用户与计算机之间的接口
- GUI
- 命令行事
- 系统调用形式
特征
最基本的特征,互为存在的条件:并发、共享
-
并发:
指两个和多个事件可以在同一时间间隔发生(同一时间段,但这个事件段很短),宏观的同时,实际交替执行
-
并行:
指两个或多个事件可以在同一时刻(同一个时间点)发生,多个CPU可以实现并行,一个CPU同一时刻只有一个程序在运行,同理可以在单个CPU上实现并发
-
共享:
OS中的资源可以共多个并发的程序共同使用
- 互斥共享:当资源被占用时,其他想使用的程序智能等待
- 同时访问:某种资源并发的被多个程序访问
-
虚拟
把一个物理实体转变为若干个逻辑实体
- 时分复用技术:资源在时间上进行复用,不同程序并发使用,多道程序分时使用计算机的硬件资源,提高资源的利用率。
- 空分复用技术:用来实现虚拟磁盘(物理磁盘虚拟为逻辑磁盘,电脑上的C盘、D盘等)、虚拟内存(在逻辑上扩大程序的存储容量)等,提高资源的利用率,提高编程效率。
-
异步
在多道程序环境下,允许多个进程并发执行,但由于资源等因素的限制,使进程的执行以“停停走走”的方式运行,而且每个进程执行的情况(运行、暂停、速度、完成)也是未知的。
中断处理
中断机制的作用:为了在多道批处理系统中让用户进行交互;
一.单道批处理系统
1.概念
2.特点
- 自动:作业自动运行,无需干预
- 批量:磁带上的各个作业按顺序地进入内存,先调入先完成
- 单道:内存中仅有一道程序运行,可以看成是串行的
3.CPU的利用情况
分析:外设和CPU交替空闲和忙碌,CPU和外设利用效率低
4.缺点
从单道批处理系统对CPU的利用情况可看出,作业运行过程中若发生IO请求,高速的CPU要等待低速的I/O操作完成,导致CPU资源利用率和系统吞吐量降低。
二. 多道批处理系统
1.概念
内存中存放多道程序,当某道程序因某种原因如执行I/O操作时而不能继续运行放弃CPU时,操作系统便调度另一程序运行,这样CPU就尽量忙碌,达到提高系统效率的目的。
2.特点
-
多道:内存同时存放多道程序
-
宏观上并行:进入系统的多道程序先后开始了自己的运行,但都未运行完毕
-
微观上串行:内存中多道程序轮流占有CPU,交替执行
3.CPU的利用情况
分析:程序A要通过操作系统的调度进行磁盘操作,B则进行磁带操作。当程序A执行I/O请求时,A放弃了CPU,操作系统接着调度B,B开始占用CPU(红宽线),此时程序A的磁盘操作也在同时进行。
结论:A,B两道程序相互穿插运行,使CPU和外设都尽量忙碌。
4.缺点
- 作业处理时间长
- 交互能力差
- 运行过程不确定
其他
中断产生:
-
发生中断时,CPU立马切换到管态,开展管理工作;(管态又叫特权态,系统态或核心态,是操作系统管理的程序执行时,机器所处的状态。)
-
发生中断后,当前运行的进程回暂停运行,由操作系统内核对中断进行处理;
-
对于不同的中断信号,会进行不同的处理。
中断的分类:
-
内中断(也叫“异常”、“例外”、“陷入”)------- 信号来源:CPU内部,与当前执行指令有关;
-
外中断(中断)----------信号来源:CPU外部,与当前执行指令无关。
外中断的处理过程:
- 每执行完一个指令后,CPU都需要检查当前是否有外部中断信号;
- 如果检查到外部中断信号,则需要保护被中断进程的CPU环境(如程序状态字PSW,程序计数器PC、各种通用寄存器)把他们存储在PCB(进程控制块中);
- 根据中断信号类型转入相应的中断处理程序;
- 恢复原进程的CPU环境并退出中断,返回原进程继续执行。
其他概念
指令
- 特权指令:不允许用户程序使用(只允许操作系统使用)。如IO指令、置中断指令
- 非特权指令:普通的运算指令
程序
- 内核程序:系统的管理者,可执行—切指令、运行在核心态
- 应用程序:普通用户程序只能执行非特权指令,运行在用户态
处理机状态
- 用户态(目态):CPU只能执行非特权指令
- 核心态(又称管态、内核态):可以执行所有指令用户态到核心态:通过中断(是硬件完成的)
- 核心态到用户态:特权指令psw的标志位 0用户态 1核心态
原语
- 处于操作系统的最低层,是最接近硬件的部分。
- 这些程序的运行具有原子性,其操作只能一气呵成
- 这些程序的运行时间都较短,而且调用频繁。
中断和异常
- 内中断(异常,信号来自内部)
- 自愿中断
- 指令中断
- 强迫中断
- 硬件中断
- 软件中断
- 外中断(中断,信号来着外部)
- 外设请求
- 人工干预
系统调用
系统给程序员(应用程序)提供的唯一接口,可获得OS的服务。在用户态发生,核心态处理
体系结构
- 大内核
- 微内核
进程管理
进程实体
引入进程目的
为了更好地描述和控制程序并发执行,实现操作系统的并发性和共享性(进程是动态的,程序是静态的)
进程的定义
是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(没有引入线程时 )
为什么需要进程:
- 进程是系统进行资源分配和调度的基本单位;
- 进程作为程序独立运行的载体保障程序正常执行;
- 进程的存在使得操作系统资源的利用率大幅提升。
进程控制块(PCB)
用于描述和控制进程运行的通用数据结构,记录进程当前状态和控制进程运行的全部信息,是进程存在的唯一标识。
进程(Process)与线程(Thread):
- 线程:操作系统进行运行调度的最小单位。
- 进程:系统进行资源分配和调度的基本单位。
区别与联系:
- 一个进程可以有一个或多个线程;
- 线程包含在进程之中,是进程中实际运行工作的单位;
- 进程的线程共享进程资源;
- 一个进程可以并发多个线程,每个线程执行不同的任务。
进程管理五状态模型
-
创建状态:创建进程时拥有PCB但其它资源尚未就绪。
-
就绪状态:其它资源(进程控制块、内存、栈空间、堆空间等)都准备好、只差CPU的状态。
-
执行状态:进程获得CPU,其程序正在执行。
-
阻塞状态:进程因某种原因放弃CPU的状态,阻塞进程以队列的形式放置。
-
终止状态:进程结束由系统清理或者归还PCB的状态。
经典问题
生产者-消费者问题
有一群生产者进程在生产产品,并将这些产品提供给消费者进程进行消费,生产者进程和消费者进程可以并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程需要将所生产的产品放到缓冲区中(+1操作),消费者进程可以从缓冲区取走产品消费(-1操作)。
(感觉有点微服务的意思了)
产生问题:当两者并发执行时可能出差错,导致预期的结果与真实的结果不相符:当执行生产者+1和消费者-1操作之后,缓冲区的值从10变为了11。
问题解决
- 在缓冲区为空时,消费者不能再进行消费
- 在缓冲区为满时,生产者不能再进行生产
- 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
- 注意条件变量与互斥锁的顺序
由于前两点原因,因此需要保持线程间的同步,即一个线程消费(或生产)完,其他线程才能进行竞争CPU,获得消费(或生产)的机会。对于这一点,可以使用条件变量进行线程间的同步:生产者线程在product之前,需要wait直至获取自己所需的信号量之后,才会进行product的操作;同样,对于消费者线程,在consume之前需要wait直到没有线程在访问共享区(缓冲区),再进行consume的操作,之后再解锁并唤醒其他可用阻塞线程。
哲学家进餐问题
有5个哲学家,他们的生活方式是交替的思考和进餐,哲学家们共同使用一张圆桌,分别坐在5张椅子上,圆桌上有5只碗和5只筷子。平时哲学家们只进行思考,饥饿时则试图取靠近他们的左右两只筷子,只有两只筷子都被拿到的时候才能进餐,否则等待,进餐完毕后,放下左右筷子进行思考。
这会导致以下的问题,筷子就相当于临界资源:
临界资源指的是一些虽作为共享资源却又无法同时被多个线程共同访问的共享资源。当有进程在使用临界资源时,其他进程必须依据操作系统的同步机制等待占用进程释放该共享资源才可重新竞争使用共享资源。
问题解决
方法一:当两边的叉子都可用时才拿
当某一个哲学家能够同时拿起左右两只叉子时,才让他拿,这样就能够保证不会因为每个科学家都只拿了一只叉子而导致死锁。
为了保证能够同时拿起,我们需要对拿叉子这一步骤进行加锁,保证哲学家能够同时拿起一双叉子,而不会拿了一边后另一边被人抢走
class DiningPhilosophers {
public:
DiningPhilosophers()
{}
void wantsToEat(int philosopher,
function<void()> pickLeftFork,
function<void()> pickRightFork,
function<void()> eat,
function<void()> putLeftFork,
function<void()> putRightFork)
{
//对拿叉子进行这一流程进行加锁,保证其能同时拿起一双,而不会被其他人抢走
_lock.lock();
_fork[philosopher].lock();
_fork[(philosopher + 1) % 5].lock();
_lock.unlock();
//拿起左右叉子
pickLeftFork();
pickRightFork();
eat(); //吃饭
//放下左右叉子
putLeftFork();
putRightFork();
//解锁,让其他人获取叉子
_fork[philosopher].unlock();
_fork[(philosopher + 1) % 5].unlock();
}
private:
mutex _lock;
mutex _fork[5];
};
方法二:限制就餐的哲学家数量(或者说,多加一支筷子)
如果要保证至少有一个哲学家能够进餐,那么我们可以采用最简单粗暴的方法,限制人数,只要同时进餐的哲学家不超过四人时,即使在最坏情况下,也至少有一个哲学家能够拿到多出来的那一个叉子。
我们需要用到一个计数器来表示当前就餐的人数,为了保证线程安全我们需要用到一个互斥锁和一个条件变量对其进行保护
class DiningPhilosophers {
public:
DiningPhilosophers()
:_count(0)
{}
void wantsToEat(int philosopher,
function<void()> pickLeftFork,
function<void()> pickRightFork,
function<void()> eat,
function<void()> putLeftFork,
function<void()> putRightFork)
{
unique_lock<mutex> lock(_mtx);
_cond.wait(lock, [this]()->bool{
return _count < 4;
}); //当就餐人数不超过四人的时候允许拿叉子
++_count;
_fork[philosopher].lock();
_fork[(philosopher + 1) % 5].lock();
pickLeftFork();
pickRightFork();
eat();
putLeftFork();
putRightFork();
_fork[philosopher].unlock();
_fork[(philosopher + 1) % 5].unlock();
--_count;
_cond.notify_one(); //就餐完成,让下一个人进来就餐
}
private:
mutex _fork[5];
mutex _mtx;
condition_variable _cond;
int _count;
};
方法三:奇数先左后右,偶数先右后左
由于餐桌是一个如下图的圆环,如果我们此时规定奇数位的哲学家先拿左边的叉子,再拿右边的叉子。而偶数位的哲学家先拿右边的再拿左边的,此时竞争情况如下图所示
此时2号和3号哲学家争抢3号叉子,4号和5号哲学家争抢5号叉子,1号没有竞争对手,直接获取叉子1。
可以看到,在第一轮中所有哲学家先去争抢奇数叉子,抢到偶数叉子后再去争抢偶数叉子,这样就能够保证至少有一个科学家能够获得两只叉子
class DiningPhilosophers {
public:
DiningPhilosophers()
{}
void wantsToEat(int philosopher,
function<void()> pickLeftFork,
function<void()> pickRightFork,
function<void()> eat,
function<void()> putLeftFork,
function<void()> putRightFork)
{
//如果是奇数则先抢左后抢右
if(philosopher & 1)
{
_fork[philosopher].lock();
_fork[(philosopher + 1) % 5].lock();
pickLeftFork();
pickRightFork();
}
//如果是偶数则先抢右后抢左
else
{
_fork[(philosopher + 1) % 5].lock();
_fork[philosopher].lock();
pickRightFork();
pickLeftFork();
}
eat(); //吃饭
putLeftFork(); //放下叉子
putRightFork();
_fork[philosopher].unlock();
_fork[(philosopher + 1) % 5].unlock();
}
private:
mutex _fork[5];
};
进程通信
管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
有名管道(Named Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
进程同步
进程同步的作用
对竞争资源在多进程间进行使用次序的协调,使得并发执行的多个进程之间可以有效使用资源和相互合作。
进程间同步的四原则:
- 空闲让进:资源无占用,允许使用;
- 忙则等待:资源被占用,请求进程等待;
- 有限等待:保证有限等待时间能够使用资源;
- 让权等待:等待时,进程需要让出CPU。
进程同步的方法
使用fork系统调用创建进程
使用fork系统调用无参数,fork会返回两次,分别返回子进程id和0,返回子进程id的是父进程,返回0的是子进程。
如果创建失败,返回-1
- fork系统调用是用于创建进程的;
- fork创建的进程初始化状态与父进程一样;
- 系统会为fork的进程分配新的资源
子进程一般继承父进程:用户信息、权限、目录信息、信号信息、环境表、共享存储段和资源限制。
(86条消息) 子进程和父进程的关系和示例_xujiali5172923的博客-CSDN博客
【Linux 进程】fork父子进程间共享数据分析 - 我得去图书馆了 - 博客园 (cnblogs.com)
进程——父子进程共享 - _程序兔 - 博客园 (cnblogs.com)
(1)父子进程
子进程通过父进程创建,子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程什么时候结束。
当子进程退出的时候,内核会释放子进程所有资源,包括打开的文件,占用的内存等。但是依然会保留部分信息(进程id,退出状态,运行时间),直到父进程通过wait/waitpid来调用获取子进程状态信息后才释放
孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程,孤儿进程将被init进程(1号进程)托管,由init进程负责完成状态收集工作
- IDEA启动SpringBoot项目,关掉IDEA后,SpringBoot(未终止没有断开连接),仍在后台
僵尸进程
通常情况下,子进程退出后,父进程会使用 wait
或 waitpid
函数进行回收子进程的资源,并获得子进程的终止状态。(如果父进程在子进程结束之前退出,则子进程由init接管。init将会以父进程身份对僵尸状态的子进程进行处理)
但是,如果父进程先于子进程结束,则子进程成为孤儿进程。孤儿进程将被 init 进程(进程号为1)领养,并由 init 进程对孤儿进程完成状态收集工作。
而如果子进程先于父进程退出,同时父进程太忙了,无瑕回收子进程的资源,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程,如下图所示:
共享内存
在某种程度上,多进程是共同使用物理内存的,但是由于操作系统的进程管理,进程间的内存空间是独立的,因此进程默认是不能访问进程空间之外的内存空间的。
- 共享存储允许不相关的进程访问同一片物理内存;
- 共享内存是两个进程之间共享和传递数据最快的方式;
- 共享内存未提供同步机制,需要借助其他机制管理访问;
Unix域套接字
域套接字是一种高级的进程间通信的方法,可以用于同一机器进程间通信。
套接字(socket):为网络通信中使用的术语。
Unix系统提供的域套接字提供了网络套接字类似的功能,如Nfinx、uWSGI等。
服务端和客户端分别使用Unix域套接字的过程:
线程同步
线程同步的方法
-
互斥锁
互斥锁是最简单的线程同步的方法,也称为互斥量,处于两态之一的变量:解锁和加锁,两个状态可以保证资源访问的串行。 原子性:指一系列操作不可被中断的特性,要么全部执行完成,要么全部没有执行。目前最常用,也是sync的底层实现
-
自旋锁
自旋锁是一种多线程同步的变量,使用自旋锁的线程会反复检查锁变量是否可用,自旋锁不会让出CPU,是一种忙等待状态,即死循环等待锁被释放,自旋锁的效率远高于互斥锁。特点:避免了进程或者线程上下文切换的开销,但是不适合在单核CPU使用。
常见的例子:分布式锁设计
-
读写锁
是一种特殊的自旋锁,允许多个读操作同时访问资源以提高读性能,但是对写操作是互斥的,即对多读少写的操作效率提升很显著。
-
条件变量
是一种相对比较复杂的线程同步方法,条件变量允许线程睡眠,直到满足某种条件,当满足条件时,可以给该线程信号通知唤醒。
线程同步方法的对比
Linux的进程管理
进程类型
-
前台进程:具有终端,可以和用户交互;
-
后台进程:没有占用终端,基本不和用户交互,优先级比前台进程低(将需要执行的命令以“&”符号结束);
-
守护进程:特殊的后台进程,在系统引导时启动,一直运行直到系统关闭(进程名字以“d”结尾的一般都是守护进程),如crond、sshd、httpd、mysqld…
进程标记
- 进程ID:非负整数,进程的唯一标记,每个进程拥有不同的ID;
- 进程的状态标记:R表示进程处于运行状态,S表示进程处于睡眠状态…
操作Linux进程的相关命令:
- ps命令:列出当前的进程,结合-aux可以打印进程的详细信息(ps -aux);
- top命令:查看所有进程的状态;
- kill命令:给进程发送信号。
作业管理(处理机调度)
处理机是什么?
简单来说,处理机指的是硬件,它包含cpu在内(早期CPU由运算器和控制器组成,称为中央处理机),而内核是操作系统中的概念,是操作系统的核心,是属于软件部分。
- 处理机包括中央处理器,主存储器,输入-输出接口,加接外围设备就构成完整的计算机系统。
- 处理机是处理计算机系统中存储程序和数据,并按照程序规定的步骤执行指令的部件。
- 程序是描述处理机完成某项任务的指令序列。 指令则是处理机能直接解释、执行的信息单位。
概念
是对处理机进行分配,即从就绪队列中按照定的算法(公平、高效)选择一个进程并将处理机分配给它运行,以实现进程并发地执行。
分类
-
高级调度(作业调度)
按一定的原则从外存上处于后备队列的作业中挑选一个(或多个)作业,给他们分配内存等必要资源,并建立相应的进程(建立PCB),以使它(们)获得竟争处理机的权利。
高级调度是辅存(外存)与内存之间的调度。每个作业只调入一次,调出一次。作业调入时会建立相应的PCB,作业调出时才撤销PCB。高级调度主要是指调入的问题,因为只有调入的时机需要操作系统来确定,但调出的时机必然是作业运行结束才调出。
-
中级调度(内存调度)
为了使内存中的内存不至于太多,有时需要把某些进程从内存中调到外存。在内存使用情况紧张时,将一些暂时不能运行的进程从内存中对换到外存中等待。当内存有足够的空闲空间时,再将合适的进程重新换入内存。
-
低级调度(进程调度)
主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统中最基本的一种调度,在一般的操作系统中都必须配置进程调度。进程调度的频率很高,一般几十毫秒一次。
调度方式
- 剥夺式(抢占式):进程1在运行,进程2优先级比进程1高,进程2直接上处理器
- 原则1:优先级原则,允许优先级高的并且是新到的进程可以抢占当前进程的处理机。
- 原则2:短进程原则
- 原则3:时间片原则
- 非剥夺式(非抢占式):进程1在运行,即使进程2优先级比进程1高,进程2也得等待进程1执行完上处理器
非抢占式调度:只能由当前运行的进程主动放弃CPU;
处理器一旦分配给某个进程,就让该进程一直使用下去;
调度程序不以任何原因抢占正在被使用的处理器;
调度程序不以任何原因抢占正在被使用的处理器;
抢占式调度:可由操作系统剥夺当前进程的CPU使用权。
允许调度程序以一定的策略暂停当前运行的进程;
保存好旧进程的上下文信息,分配处理器给新进程;
进程调度的三大机制
就绪队列排队
为了提高进程调度的效率,将就绪进程按照一定的方式排成队列,以便调度程序可以最快找到就绪进程。
选择运行进程委派
调度程序以一定的策略,选择就绪进程,将CPU资源分配给它。
新老进程上下文切换
存当前进程的上下文信息,装入被委派执行进程的运行上下文
调度准则
-
CPU利用率
-
系统吞吐量
单位时间内cpu完成作业的数目
-
周转时间
作业的完成时间-提交时间
-
等待时间
进程与等待处理机的时间之和
-
响应时间
从提交到第一次开始运行的时间
算法
先来先服务(FCFS):
算法原理:按照作业(进程)到达的先后次序来进行调度,谁先来,谁就先被调度。
缺点:忽略了作业的运行时间
短作业优先(SJF):
算法原理:以作业的长短来计算优先级,作业越短优先级越高,作业长短以所要求的运行时间来衡量。
缺点:必须预先知道作业的运行时间、对长作业不利,未考虑作业的紧迫程度。
例题:
解:“作业被调度进入运行后不再退出"意为非抢占式调用,job2到来时也得等待job1执行完
①job1最先达到,运行60分钟,此时job2-6已经全部提交,此时从job2-6中挑选运行时间最短的,那么顺序依次为1→5→6→3→4→2
标准流程如图(要写出作业号、提交时间、运行时间、开始时刻、完成时刻、周转时间):
②周转时间=完成时间-提交时间
平均周转时间=1/n *(N1+N2+……+Nn)
(n为作业过程总数,N1、N2为周转时间)
优先级调度算法:
算法原理:FCFS、SJF两种算法都不能反映进程的紧迫程度。而优先级调度算法是外部赋予进程相应的优先级,来体现出进程的紧迫程度,紧迫性进程优先运行
(如何确定优先级:
1、利用某一范围内的一个整数,优先数
2、响应比的大小,谁响应比大,谁优先级就大——高响应比优先调度算法)
高响应比优先调度算法
响应比=作业周转时间/作业处理时间=(作业处理时间+作业等待时间)/作业处理时间=1+(作业等待时间/作业处理时间)
时间片轮转
适合系统:分时系统
算法原理:基于时间片的轮转,非常公平,就绪队列中的每一个进程每次仅仅运行一个时间片,并且每个进程是轮流运行。
首先按照FCFS策略把就绪进程排成一个就绪队列,设置时间片,从第一个进程开始分配处理机,第一个进程的时间片执行完后,再从就绪队列中新的队首进程开始。若进程已经运行完,注意此时第一个进程就已经不在就绪队列的队首,而是从就绪队列中删除。若未执行完只是时间片完了,则是调度程序把它送往末尾去了。
多级反馈队列调度算法
算法原理(调度机制):
设置多个就绪队列,每个队列赋予不同的优先级,第一个队列优先级最高,并且首先调度最高优先级,也就是第一个队列里面的所有进程,仅当第一个队列空闲时,才开始调度第二个队列中的进程运行。优先级越高的队列,时间片越小。
总结
- 先来先服务算法:按照在就绪队列中的先后顺序执行。
- 短进程优先调度算法:优先选择就绪队列中估计运行时间最短的进程,不利于长作业进程的执行。
- 高优先权优先调度算法:进程附带优先权,优先选择权重高的进程,可以使得紧迫的任务优先处理。
- 时间片轮转调度算法:按照FIFO的原则排列就绪进程,每次从队列头部取出待执行进程,分配一个时间片执行,是相对公平的调度算法,但是不能保证就是响应用户。
死锁
进程死锁、饥饿、死循环的区别
- 死锁:两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。永远在互相等待的进程称为死锁进程。
- 饥饿:由于长期得不到资源导致进程无法推进;
- 死循环:代码逻辑BUG。
死锁的产生
竞争资源(共享资源数量不满足各进程需求)、进程调度顺序不当,当调度顺序为A->B->C->D时会产生死锁,但改为A->D->B->C则不会产生。
死锁的必要条件
- 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
- 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所申请地资源。
以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。
死锁的处理策略
预防死锁的方法
破坏四个必要条件的中一个或多个。
-
破坏互斥条件:将临界资源改造成共享资源(Spooling池化技术);(可行性不高,很多时候无法破坏互斥条件)
-
破坏请求保持条件:系统规定进程运行之前,一次性申请所有需要的资源;(资源利用率低,可能导致别的线程饥饿)
第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。
第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
-
破坏不可剥夺条件:当一个进程请求新的资源得不到满足时,必须释放占有的资源;(实现复杂,剥夺资源可能导致部分工作失效,反复申请和释放造成额外的系统开销)
一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
-
破坏环路等待条件:可用资源线性排序,申请必须按照需要递增申请;(进程实际使用资源顺序和编号顺序不同,会导致资源浪费)
采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程
银行家算法
检查当前资源剩余是否可以满足某个进程的最大需求;如果可以,就把该进程加入安全序列,等待进程允许完成,回收所有资源;重复1,2,直到当前没有线程等待资源;
死锁的检测和解除
死锁检测算法,资源剥夺法,撤销进程法(终止进程法),进程回退法;
存储管理
存储管理为了确保计算机有足够的内存处理数据;确保程序可以从可用内存中获取一部分内存使用;确保程序可以归还使用后的内存以供其他程序使用。
内存分配的过程
- 单一连续分配(已经过时)
- 固定分区分配
- 动态分区分配(根据实际需要,动态的分配内存)
动态分配算法
-
首次适应算法(First Fit):该算法从空闲分区链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。
- 特点: 该算法倾向于使用内存中低地址部分的空闲区,在高地址部分的空闲区很少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。
- 缺点:低地址部分不断被划分,留下许多难以利用、很小的空闲区,而每次查找又都从低地址部分开始,会增加查找的开销。
-
最佳适应算法(Best Fit):该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
- 特点:每次分配给文件的都是最合适该文件大小的分区。
- 缺点:内存中留下许多难以利用的小的空闲区。
-
最坏适应算法(Worst Fit):最坏适应算法是将输入的作业放置到主存中与它所需大小差距最大的空闲区中。空闲区大小由大到小排序。
- 特点:尽可能地利用存储器中大的空闲区。
- 缺点:绝大多数时候都会造成资源的严重浪费甚至是完全无法实现分配。
内存回收的过程
- 回收区在空闲区下方:不需要新建空闲链表节点;只需要把空闲区1的容量增大即可;
- 回收区在空闲区上方:将回收区与空闲区合并;新的空闲区使用回收区的地址;
- 回收区在空闲区中间方:将空闲区1、空闲区2和回收区合并;新的空闲区使用空闲区1的地址;
- 仅仅剩余回收区:为回收区创建新的空闲节点;插入到相应的空闲区链表中去;
分区存储管理
固定分区存储管理
这一部分是内存分配过程的详细介绍,可以简单看一下
把主存中可分配的用户区域预先划分成若干个连续的分区,每个连续区的大小可以相同,也可以不同。但是,一旦划分好分区之后,主存中分区的个数就固定了,且每个分区的大小也固定不变。这是一种静态分区法。
在固定分区方式管理下,每个分区用来装入一个作业。由于主存中有多个分区,就可同时在每个分区中装入一个作业。所以,这种存储管理方式适用于多道程序系统。
主存空间的分配与释放
了管理主存空间的使用,必须设置一张“主存分配表”(分区说明表),以说明各分区的分配情况。主存分配表中应指出各分区的起始地址和长度,并为每个分区设一个标志位。当标志位为“0”时,表示对应的分区是空闲分区,当标志位为非“0”时,表示对应的分区已被某作业占用。空闲分区可以用来装作业。
当作业队列中有作业要装入主存时,存储管理可采用“顺序分配算法”进行主存空间的分配。
顺序查看主存分配表,找到一个标志为“0”的并且长度大于或等于欲装入作业的地址空间长度的分区,则把此分区分配给该作业,相应表目的标志位改成作业名的标识;若找不到一个这样的空闲分区,则该作业暂时不能装入主存。
主存空间的释放很简单。某作业执行结束后必须归还所占的分区,这时存储管理根据作业名查看主存分配表,找到相应的表目后,把其中的标志位重新置成“0”即可。
地址转换
固定分区管理方式下作业的地址转换常采用静态重定位技术。
存储保护
固定分区管理方式下只考虑判断其物理地址即可。常采用“界限寄存器对”法。
If 下限地址<=物理地址<=上限地址
Then 继续
Else 产生“越界中断” ,转越界中断的处理子程序
内存扩充
采用覆盖技术
覆盖技术是指一个程序的若干程序段和几个程序的某些部分共享一个存储空间。
固定分区的优缺点
优点:实现简单,无外部碎片
缺点:
- 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术解决,但这又会降低性能
- 会产生内部碎片,碎片大,存在小分区占用大作业的情况,内存利用率低。
解决办法:采用可变分区存储管理
可变分区存储管理
内存管理的可变分区模式,又称变长分区模式、动态分区分配模式。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。
与固定分区的区别就是:动态的划分分区。
克服固定分区管理的“内碎片”问题。
- 可变分区模式下,刚开始,OS就绪,但任何用户程序未进入内存前整个用户内存区是一大空间。已占用区和空闲分区并不是绝对的。
- 必须有表来记录分区的情况。
- 程序进入内存时的例行工作就是分配空闲区和装入程序,并修改相应的空闲表和已分配区表。
- 一旦一个内存分区被分配给一个进程,该进程可以被装入该块中执行,装入时需重定位。
可变分区分配的数据结构
可变分区分配算法
把一个新作业装入内存时,需要按照一定的可变分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。
在可变分区分配方式中,当有很多空闲分区都满足需求时,应该使用哪个分区进行分配?
这里介绍三种可变分区分配算法
算法
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
实现步骤:
空闲区地址由低到高排序
=>1.顺序查找各个空闲区,把第一个找到能容纳申请要求的内存区分配给申请者.(若空闲区比作业长度大,则分割该空闲区。一部分分配给作业一部分空闲。)
=>2.调整相应的空闲分区表和已分配分区表。
评价:性能一般但实现比较简单直接,易于释放时合并相邻空间分区。比较容易的满足大作业的需要。完成一次分配平均需要的搜索次数较大,影响了工作效率。
尽可能地利用存储器中低地址的空闲区,而尽量保存高地址的空闲区。
最佳适应算法
算法思想:由于可变分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,优先使用更小的空闲区。
实现步骤:
空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区表,找到大小能够满足要求的第一个空闲分区。
评价:尽可能地保留了较大的空间。 产生大量的不能被使用的很小的空闲区。因此这种方法会产生很多的外部碎片。所以该算法分配效果不一定是最佳的。
尽可能地利用存储器中小的空闲区,而尽量保存大的空闲区。
最坏适应算法
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时,优先使用最大的连续空闲区,这样分配后的空闲区就不会太小,更方便使用。
实现步骤:
空闲分区按照容量递减次序链接。每次分配内存时顺序查找空闲分区表,找到大小能满足要求的第一个空闲分区。
评价:分割后产生的空闲区一般仍可以供以后分配使用。工作一段时间后,不能满足大作业对空闲区的请求。
尽可能地利用存储器中大的空闲区。
三种算法的比较:
可变分区内存回收
只比固定分区管理增加了合并相邻空闲区的操作。
主要是为了及时减少“外碎片”,利于今后大作业的到来。
实现回收内存空间,关键是修改空闲分区表和已分配分区表。
回收内存分区时可能会遇到的四种情况:
(a)若释放区R既有上邻空闲区,又有下邻空闲区。将三个空闲区合并成一个大空闲区。
先将R与F2合并记为F2,
再将F2与F1合并记为F1,并将F2从链中删除。
IF (B+H1=C) AND (C+L2=D)
THEN 修改空闲表,分配表。
(b)若释放区R只有上邻空闲区F1。
则只修改空闲区F1大小即可。
IF (D+H2=E)
THEN 修改空闲表,分配表。
(c)只有下邻空闲区
修改空闲区F2的首地址。
F2的大小=F2的大小+R的大小
(d)既无上邻又无下邻空闲区
Else 修改释放区的首地址为空闲区的起始地址
地址转换
动态重定位
(74条消息) 静态重定位和动态重定位_阿肆_Maggie的博客-CSDN博客_静态重定位
分区的存储保护
If 下限地址<=物理地址<=上限地址
Then 继续
Else 产生“越界中断” ,转越界中断的处理子程序
内存扩充
消除了固定分区管理造成的“内碎片”,但是不可避免的在内存空间造成“外碎片”。
采用移动(紧缩)技术。定时的或在内存紧张时,将内存中所有作业移到内存的一端,使其相邻。
经过紧缩后的进程在内存中的位置发生了变化,若不对程序和数据的地址进行修改,在进程就无法运行。
要使其运行,必须进行“动态重定位”
注意:
=》紧缩的时机:
(1)一旦有归还的分区便进行紧缩,系统开销大。
(2)分配算法发现各空闲区不够用,但其和够用时。此法紧缩开销小,更实用。因此,实际的可变分区分配算法比固定分区分配算法主要增加了“紧缩”操作
页式存储管理
- 分页存储管理的思想:把内存分为一个个相等的小分区,再按照分区大小把进程拆分成一个个小部分。
- 分页存储管理分为:实分页存储管理和虚分页存储管理
实分页式存储管理
实分页式存储最大的优点是内存利用率高,与目前流行的虚分页存储管理相比,具有实现简单,程序运行快的优点。
基本原理
假设一个大型饭店,所有的客房都是标准的双人间,部分客房已经住进客人,现在又有一个旅游团要求入住。接待员统计了一下,对旅游团领队说:“贵团全体成员都能住下,两人一个房间,但是不能住在同一楼层了,因为每层空着的客房不够,更没有几个挨着的。请原谅!”。对于这样的安排,一般人不会感到奇怪。因为旅游团本来就是由一位位个人或夫妻等组成的,而饭店的客房本来也是两人一间的,两人一组正好可住在一个客房里;另外,饭店几乎每天都有入住的和退房的客人,想在同一楼层找几间挨着的客房实在不容易。
-
将整个系统的内存空间划分成一系列大小相等的块,每一块称为一个物理块、物理页或实页,页架或页帧(frame),可简称为块(block)。所有的块按物理地址递增顺序连续编号为0、1、2、……。
这里的块相当于饭店的客房,系统对内存分块相当于饭店把大楼所有的客房都设计成标准的双人间。
-
每个作业的地址空间也划分成一系列与内存块一样大小的块,每一块称为一个逻辑页或虚页,也有人叫页面,可简称为页(page)。所有的页按照逻辑地址递增顺序连续编号为0、1、2、……。
这里,对作业地址空间分页就相当于把旅游团成员分成两人一组。
-
一个作业,只要它的总页数不大于内存中的可用块数,系统就可以对它实施分配。系统装入作业时,以页为单位分配内存,一页分配一个块,作业所有的页所占的块可以不连续。系统同时为这个作业建立一个页号与块号的对照表,称为页表。
这就像饭店有个记录客户入住情况的客户登记表一样。另外,饭店安排客户入住是要查看全部客房的使用情况一览表,相应地系统给作业分配内存时要查看主存分配表或者内存块说明表。
-
每个块的大小是固定的,一般是个1/2KB~4KB之间的数值(请读者思考:块尺寸为什么太大或太小都不好),而且必须是个2的幂次。
对块尺寸这样规定相当于饭店规定客房是双人间。可以设想一下,如果上例中饭店所有的客房都是十人间的话,效益肯定不如全是双人间的好
实模式下分页存储管理的基本原理:
操作系统以页框为单位为各个进程分配内存空间。系统自动地将作业的地址空间分页,将系统的主存空间分块,页与块等大小,在作业运行时,一次性把作业的全部页面装入内存,各个页所占的内存块可以不连续,也不必按先后顺序,可以放到不相邻的各个页框中。
这实际是个把作业从地址空间映射到存储空间的过程
页表
页面的划分完全是一种系统硬件的行为,一个逻辑地址放到这种地址结构中,自然就分成了页号和页内单元号两部分。
页面大小为:4KB
在分页系统中,允许将作业(进程)的任一页装入到内存中的任一可用的物理块中,但进程的地址空间本来是连续的,若把他分页后装入到不相邻的物理块中,要保证系统仍能正确运行,就要实现从进程的逻辑地址变换为内存的物理地址。
所以,系统为每个进程建立一张页面映射表,简称页表。
地址映射
在系统中设置地址变换机构,能将用户进程地址空间中的逻辑地址变为内存空间中的物理地址。
由于页面和物理块的大小相等,页内偏移地址和块内偏移地址是相同的。无须进行从页内地址到块内地址的转换。
地址变换机构的任务,关键是将逻辑地址中的页号转换为内存中的物理块号。物理块号内的偏移地址就是页内偏移地址。
页表的作用就是从页号到物理块号的转换,所以地址变换的任务借助于页表来完成的。
如果题目中是用十进制数表示逻辑地址,则:
例题1:有一系统采用页式存储管理,有一作业大小是8KB,页大小为2KB,依次装入内存的第7、9、10、5块,试将虚地址7145,3412转换成内存地址。
虚地址 3412
P=3412 % 2048=1
W=3412 mod 2048=1364
MA=9*2048+1364=19796
虚地址3412的内存地址是19796
虚地址 7145
P=7145 % 2048 =3
W=7145 mod 2048 =1001
MA=5*2048+1001=11241
虚地址7145的内存地址是:11241
快表
因为页表是存放在内存中的,CPU要存取一个数据,需访问主存两次。
第一次:访内存中的页表,找到该页的的物理块号,将此块号与页内地址拼结形成物理地址;
第二次:真正访问该物理地址,存取其中的内容。
这样就把程序的执行速度降低一倍。
为了提高存取速度,在地址变换机构中增设一组寄存器,用来存放访问的那些页表。
快表是一种访存速度比内存快很多的高速缓冲器。
把存放在高速缓冲寄存器中的页表叫快表,这个高速缓冲寄存器又叫联想存贮器(TLB)。与此对应,内存中的页表称为慢表。
当进程访问一页时,系统将页号与快表中的所有项进行并行比较。若访问的页在快表中,即可立即进行地址转换。
当被访问的页不在快表中时,去内存中查询页表,同时将页表找到的内存块号与虚页号填入快表中
例题2:
快表命中率98%,访问时间是10ns,内存访问时间是100ns,平均访问时间?
平均访问时间=98%(10+100)+(1-98%)(10+100+100)
若快表命中
联想寄存器检索时间:10ns
访问内存1次取数据时间:100ns
取数据总时间:110ns
若快表中未命中
联想寄存器检索时间:10ns
访问内存1次检索页表时间:100ns
访问内存1次取数据时间:100ns
取数据总时间:210ns
两级和多级页表
现代的大多数计算机系统,都支持非常大的逻辑地址空间(232~264)。页表就变得非常大,要占用相当大的内存空间。可采用两个方法来解决这一问题:
① 采用离散分配方式来解决难以找到一块连续的大内存空间的问题:
② 只将当前需要的部分页表项调入内存,其余的页表项仍驻留在磁盘上,需要时再调入。
二级页表如何实现地址变换?
页的分配与回收
用一张“位示图”构成主存分配表。位示图的每一位与一个主存块对应,其值为0,表示对应的主存块空闲,其值为1,表示对应的主存块已分配。
位示图优点是占用内存空间小,可常驻内存,加快分配进程,但缺点是不够直观。
内存分配过程:
计算一个作业所需要的总块数N
查位示图,看看是否还有N个空闲块
如果有足够的空闲块,则页表长度设为N,可填入PCB中;申请页表区,把页表始址填入PCB
依次分配N个空闲块,将块号和页号填入页表
修改位示图
存在的问题
为每个进程配置一张页表,进程逻辑空间非常大,带来的问题?
可以引入Inverted page tables(反置页表)
反置页表 – 按物理块号排序
IBM RT; HP Spectrum…
反置页表很大,使用Hash表加快检索
所有在内存中的并发进程只有一张页表
除了Hash表,联想寄存器也被用来存放最近使用过的页表项
分页存储管理方案的评价
优点:
较好地解决了碎片问题
打破了存储分配的连续性要求
提高了主存的利用率
缺点:
页内碎片
动态地址变换、方案实施需耗用额外的系统资源
存储扩充问题没有解决——作业大小受到限制,可用块数小于作业需求时需等待
虚拟页式存储
虚拟存储器
局部性原理(principle of locality)
指程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域。还可以表现为:
- 时间局部性:一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时期内;
- 空间局部性:当前指令和邻近的几条指令,当前访问的数据和邻近的数据都集中在一个较小区域内。
局部性原理的具体体现:
- 程序在执行时,大部分是顺序执行的指令,少部分是转移和过程调用指令。
- 过程调用的嵌套深度一般不超过5,因此执行的范围不超过这组嵌套的过程。
- 程序中存在相当多的循环结构,它们由少量指令组成,而被多次执行。
- 程序中存在相当多对一定数据结构的操作,如数组操作,往往局限在较小范围内。
引入虚拟存储技术的好处
大程序:可在较小的可用内存中执行较大的用户程序;
大的用户空间:提供给用户可用的虚拟内存空间通常大于物理内存(real memory)
并发:可在内存中容纳更多程序并发执行;
易于开发:与覆盖技术比较,不必影响编程时的程序结构
虚拟存储技术的特征
不连续性:物理内存分配的不连续,虚拟地址空间使用的不连续(数据段和栈段之间的空闲空间,共享段和动态链接库占用的空间)
部分交换:与交换技术相比较,虚拟存储的调入和调出是对部分虚拟地址空间进行的;
大空间:通过物理内存和快速外存相结合,提供大范围的虚拟地址空间
虚拟存储技术的种类
- 虚拟页式
- 虚拟段式
- 虚拟段页式
虚拟页式存储管理
基本原理
系统自动地将作业的地址空间分页,将系统的主存空间分块,页与块等大小,在作业运行前,只把初始需要的一部分页面装入内存块里,运行中需要访问自己地址空间中的但当前不在内存的页面时产生缺页中断,由缺页中断服务程序将所需的页面调入内存,若此时内存中没有空闲物理块安置请求调入的新页面,则系统按预定的置换策略自动选择一个或一些在内存的页面,把它们换出到外存。
虚拟页式存储管理实际是实分页技术与虚拟存储技术相结合的产物,其分页思想与实分页是一样的。
这里的请求调入和置换功能都是比实分页存储管理增加的内容,是实现虚拟存储的主要功能。
为实现虚拟页式存储管理:
需要置换技术、请求装入技术和大硬盘支持,另外:
-
页表表目需要增加外存块号、状态位、访问位或访问字段、修改位、存取控制字段等。
-
外存块号指出该页在外存的地址,供调入该页时用;
-
状态位指示该页是否在内存;
-
访问位或访问字段则是该页被访问过的标志或被访问过的次数;
-
修改位表示该页是否被修改过;
-
存取控制字段则是用来限制页面被安全共享的。
作业1在请求分页系统中的存储映像
当执行 “mov r1,[2120]”时
CPU产生的虚地址为2120
分页机构得 p=2,w=72(每页1K)
查页表。该页中断位i=1,发生缺页中断
如主存中有空白块,直接调入
如主存中无空白块,则需淘汰该作业在主存中的一页
主存页面分配策略
在虚拟页式存储管理中,内存分配似实分页方式,但还必须考虑解决下面两个问题:
-
(1)是否对各进程采用平均分配策略?
-
a、平均分配。
-
b、按进程长度比例分配。
-
c、按进程优先级分配。
-
d、按进程长度和优先级别分配。
-
-
(2)发生缺页中断时,如何为所缺的页面分配内存?
- a、固定分配局部置换。
- b、可变分配全局置换。
页面调入策略
(1)请求调入
当发生页面故障时进行调度,即当进程访问不在内存的页面引发缺页中断时,由系统根据这种访问请求把所缺页面装入内存。
优点:由请求调入策略装入的页一定会被访问,再加之比较容易实现,故在目前的虚拟存储器中,大多采用此策略。
缺点:每次仅调入一页,增加了磁盘I/O的启动频率。
( 2)预调入
=>也称先行调度,即一页面被访问前就已经预先置入内存,以减少今后的缺页率。
=>主要适于进程的许多页存放在外存的连续区域中的情况。有的系统结合请求调入使用,即每次缺页时装入多个页面。
优点:提高调页的I/O效率。
缺点:基于预测,若调入的页在以后很少被访问,则效率低。常用于程序装入时的调页。
调入页面的来源:
通常对外存交换区的I/O效率比文件区的高。
进程装入时,将其全部页面复制到交换区,以后总是从交换区调入。执行时调入速度快,要求交换区空间较大。
凡是未被修改的页面,都直接从文件区读入,而被置换时不需调出;已被修改的页面,被置换时需调出到交换区,以后从交换区调入。
存储分配的安全性考虑:
把一个页面分配给进程之前,先要清除页面中的数据(如全部填充为0),以免该进程读取前一进程遗留在页面中的数据;
页面调度算法
由缺页中断服务程序将所需的页面调入内存,若此时内存中没有空闲物理块安置请求调入的新页面,则系统按预定的策略自动选择一个(请求调入策略)或一些(预调入策略)在内存的页面,把它们换出到外存。
a、什么是淘汰策略(置换策略)?
用来选择淘汰哪一页的规则就叫做置换策略,或称淘汰算法。如何决定淘汰哪一页?根据页面在系统中的表现(如:使用的频繁程度、进入系统时间的长短)
b、颠簸
颠簸(thrashing),又称为“抖动”。
简单地说,导致系统效率急剧下降的主存和辅存之间的频繁页面置换现像称为“抖动”。
现象?淘汰的页面恰好是不久又要访问的页面。
缺页率 = (页面置换次数+分配给该进程的物理块数)/要访问的页面总数
(1)最佳淘汰算法——OPT(Optimal)
这是Belady贝莱迪于1966年提出的一种理论上的算法。该算法每次都淘汰以后永不使用的,或者过最长的时间后才会被访问的页面。
显然,采用这种算法会保证最低的缺页率,但它是无法实现的,因为它必须知道页面“将来”的访问情况。不过,该算法仍有一定意义,可作为衡量其他算法优劣的一个标准。
假定系统为某个进程分配了三个物理块,进程的访问顺序为7,0,1,2,0,3,0,4,2,3,0,3,2,1,2
采用OPT淘汰算法:
(2)先进先出淘汰算法——FIFO
这是最早出现的淘汰算法。
总是淘汰最先进入内存的页面。它实现简单,只需把进程中已调入内存的页面,按先后次序链成一个队列,并设置一个所谓的替换指针,使它总是指向内存中最老的页面。
缺点:效率不高,因为它与进程实际的运行规律不相适应,比如常用的全局变量所在的页面或者循环体所在页面都可能被它选为淘汰对象。出现bleady现象。
页面进入主存的先后次序:
2->4->5->1
当要调入第6页时:
置换第2页
将第2页改为6
替换指针指向第4页4->5->1->6
Belady现象:采用FIFO算法时,如果对一个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多,缺页率反而提高的异常现象。
Belady现象的描述:一个进程P要访问M个页,OS分配N个内存页面给进程P;对一个访问序列S,发生缺页次数为PE(S,N)。当N增大时,PE(S, N)时而增大,时而减小。
Belady现象的原因:FIFO算法的置换特征与进程访问内存的动态特征是非常不一致的,即被置换的页面通常并不是进程不会访问的。
采用FIFO淘汰算法:
(3) 最近最久未使用算法(LRU, Least Recently Used)
根据页面调入内存后的使用情况,选择内存中最久未使用的页面被置换。这是局部性原理的合理近似,性能接近最佳算法。
OPT算法使用页面将要被访问的时间,LRU算法使用页面最后一次被访问的时间。二者唯一的差别是:OPT是向前看的,而LRU是向后看的。
下面给出LRU的实现算法:
a、计时法:对于每一页面增设一个访问时间计时器,每当一个页面被访问时,当时的绝对时钟内容被拷贝到对应的访问时间计时器中,这样系统记录了内存中所有页面最后一次被访问的时间。淘汰时,选取访问时间计时器的值最小的页面。
b、堆栈法:每当进程访问某页面时,便将该页面的页号从栈中移出,将它压入栈顶。栈顶始终是最新被访问的页面的编号。栈底则是最近最久未被使用的页面的页面号。
c、多位寄存器法
为每页设置一个R位的寄存器
每次访问一页时,将该页所对应的寄存器最左位置1
每隔时间间隔T,所有寄存器右移一位。
选择R值最小的页淘汰。
例如,r寄存器共有四位,页面P0、P1、P2在T1、T2、T3时刻的r寄存器内容如下:
页面 时刻
T1 T2 T3
P0 1000 0100 1010
P1 1000 1100 0110
P2 0000 1000 0100
给某作业分配了三块主存,该作业依次访问的页号为:4,3,0,4,1,1,2,3,2。当访问这些页时,页面淘汰序列变化情况如下
LRU的开销是很大的,必须有硬件的支持,完全由软件实现其速度至少会减少10倍,因此LRU近似算法更实用些
(4)二次机会淘汰算法——SC(Second Chance)淘汰算法
这是一种LRU的近似算法,是通过对FIFO算法进行简单改造,结合页表中的访问位而得来一种淘汰算法。
该算法首先检查位于FIFO链链首的页,如果它的访问位为0,则选择该页淘汰;如果它的访问位为1,则清除其访问位,将它移至FIFO链的链尾,重复此算法的查找过程,直至遇到新链首页是一个访问位为0的较早进入内存的页为止,把它选为被淘汰的页。
为每一个存储块(存储分块表)或页面(页表)设立一个引用位。
当访问某页时,就将该页引用位置1
页面管理软件周期性地(设周期为T)将所有引用位重新置0
在T内,被访问过的页面引用位为1,否则为0
选择引用位为0的页面淘汰。
(5)时钟(Clock)淘汰算法
二次机会淘汰算法缺点:就是需要把访问位为1的处于链首的页移至链尾,这需要一定的开销。
改进的方法:就是把进程所访问的页面链成一个环形链表,再设一个指针指向最老的页面,于是形成了一种简单实用的LRU近似算法——时钟淘汰算法。
该算法首先检测指针所指的页面,如果它的访问位为0,则淘汰该页,新装入的页插入到此位置,然后指针前进一个位置;如果它的访问位为1,则清除为0,并将指针前进一个位置,继续检查访问位。重复此过程,直到找到访问位为0的页面为止。
访问页号727
引发缺页
(6)最近未用淘汰算法——NRU(Not Used Recently)淘汰算法
它把FIFO算法的思想与页面的访问位和修改位结合起来确定一个接近LRU算法的淘汰对象。
该算法每次都尽量选择最近最久未被写过的页面淘汰,这种干净的页面可以不被写回到磁盘。在实现时,为每一个页面设置初始值0的访问位和修改位。当对某页面执行写操作时,其修改位和访问位均由硬件置成1;当对某页面执行读操作时,只有其访问位被硬件置成1。系统每隔固定时间将所有访问位都清0。
按照下列次序选择被淘汰的页面:
①访问位=0,修改位=0;直接淘汰;
②访问位=0,修改位=1;写回外存后淘汰;
③访问位=1,修改位=0;直接淘汰;
④访问位=1,修改位=1;写回外存后淘汰;
页面请求序列为:2,3,2,1,5,2,4,5,3,2,5,2
内存分配3块
用OPT、LRU、FIFO、Clock算法写出页面置换过程
时钟clock算法中的箭头是当前指针的位置!
影响缺页中断率的因素
(1)页面调度算法不合理
抖动又叫颠簸,是指一段时间里,页面在内存与外存之间频繁地调度或换入换出,以至于系统用于调度页面所需要的时间比进程实际运行所占用的时间还要多。
显然,抖动是由于缺页中断率很高而引起的一种坏现象,它将严重影响系统的效率,甚至可能使系统全面崩溃。
(2)分配给作业的内存块数太少
作业的缺页中断率与作业所占内存块数成反比。分配给作业的内存块数太少是导致抖动现象发生的最主要的原因,实验分析表明:对所有的程序来说,要使其有效地工作,它在内存中的页面数不应少于它的总页面数的一半。
(3)页面大小的选择不合理
虽然缺页中断率与页面尺寸成反比,但页面尺寸却不能一味地求大,它一般在0.5KB~4KB之间,是个实验统计值。因为页面大时,页表较小,占空间少,查表速度快,缺页中断次数少,但页面调度时间长,页内碎片较大。页面小时,恰恰相反。
(4)用户程序编制的方法不合适
作业的缺页中断率与程序的局部化(包括时间局部化和空间局部化)程度成反比。用户程序编制的方法不合适可能导致程序运行的时空复杂度高,缺页次数多。
段式存储管理
分段
- 进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。
- 内存分配规则:以段为单位进行分配,每个段在内存中占连续空间,但各段之间可以不相邻。
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。
段表
每一个程序设置一个段表,放在内存,属于进程的现场信息
地址变换
段的保护
越界中断处理
进程在执行过程中,有时需要扩大分段,如数据段。由于要访问的地址超出原有的段长,所以发越界中断。操作系统处理中断时 ,首先判断该段的“扩充位”,如可扩充,则增加段的长度;否则按出错处理
缺段中断处理
检查内存中是否有足够的空闲空间
- ①若有,则装入该段,修改有关数据结构,中断返回
- ②若没有,检查内存中空闲区的总和是否满足要求,是则应采用紧缩技术,转 ① ;否则,淘汰一(些)段,转①
段的动态连接
为何要进行段的动态链接?
大型程序由若干程序段,若干数据段组成,进程的某些程序段在进程运行期间可能根本不用,互斥执行的程序段没有必要同时驻留内存,有些程序段执行一次后不再用到,静态链接花费时间,浪费空间
在一个程序运行开始时,只将主程序段装配好并调入主存。其它各段的装配是在主程序段运行过程中逐步进行的。每当需要调用一个新段时,再将这个新段装配好,并与主程序段连接。
页式存储管理:难以完成动态链接,其逻辑地址是一维的
信息的保护与共享
这里主要与页式存储管理进行一下对比。
分段比分页更容易实现信息的共享和保护。
只读代码共享
纯代码举例:比如,有一个代码段只是简单的输出“Hello World!”。
页式系统与段式系统的对比
段长是可变的,页的大小是固定的。
分段存储:段内地址W字段溢出将产生越界中断。
分页存储:段内地址W字段溢出会自动加入到页号中。
总结
段页式存储管理
分页、分段的有缺点分析
基本思想
用户程序划分:按段式划分(对用户来讲,按段的逻辑关系进行划分;对系统讲,按页划分每一段)
逻辑地址:
- 内存划分:按页式存储管理方案
- 内存分配:以页为单位进行分配
逻辑地址结构
段表页表
地址转换
评价
优点:
保留了分段和请求分页存储管理的全部优点
提供了虚存空间,能更有效利用主存
缺点:
增加了硬件成本
系统复杂度较大
总结
虚拟内存
这一部分在虚拟页式存储管理讲过了
虚拟内存概述:是操作系统内存管理的关键技术,使得多道程序运行和大程序运行成为现实,把程序使用内存划分,将部分暂时不使用的内存放置在辅存,实际是对物理内存的扩充。
- 局部性原理:指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。
- 虚拟内存的置换算法:先进先出(FIFO)、最不经常使用(LFU)、最近最少使用(LRU)...
虚拟内存的特征:
- 多次性:无需再作业运行时一次性全部装入内存,而是允许被分成多次调入内存;
- 对换性:无需在作业运行时一直常驻内存,而是允许在作业运行过程中,将作业换入、换出;
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存用来,远大于实际的容量;
Linux的存储管理
Buddy内存管理算法:经典的内存管理算法,为解决内存外碎片的问题,算法基于计算机处理二进制的优势具有极高的效率。
Linux交换空间:交换空间(Swap)是磁盘的一个分区,Linux内存满时,会把一些内存交换至Swap空间,Swap空间是初始化系统时配置的。
Swap空间与虚拟内存的对比:
文件管理
文件的逻辑结构:
-
逻辑结构的文件类型:有结构文件(文本文件,文档,媒体文件)、无结构文件(二进制文件、链接库)。
-
顺序文件:按顺序放在存储介质中的文件,在逻辑文件当中存储效率最高,但不适合存储可变长文件。
-
索引文件:为解决可变长文件存储而发明,需要配合索引表存储。
辅存的存储空间分配:
- 辅存的分配方式:连续分配(读取文件容易,速度快)、链接分配(隐式链接和显式链接)、索引分配
- 辅存的存储空间管理:空闲表、空闲链表、位示图。
目录树:使得任何文件或目录都有唯一的路径。
设备管理
基本概念:将数据输入输出计算机的外部设备;
广义的IO设备:
-
按照使用特性分类:存储设备(内存、磁盘、U盘)和交互IO设备(键盘、显示器、鼠标);
-
按照信息交换分类:块设备(磁盘、SD卡)和字符设备(打印机、shell终端);
-
按照设备共享属性分类:独占设备,共享设备,虚拟设备;
-
按照传输速率分类:低速设备,高速设备;
-
IO设备的缓冲区:减少CPU处理IO请求的频率,提高CPU与IO设备之间的并行性。
SPOOLing技术:虚拟设备技术,把同步调用低速设备改为异步调用,在输入、输出之间增加了排队转储环节(输入井、输出井),SPoOLing负责输入(出)井与低速设备之间的调度,逻辑上,进程直接与高速设备交互,减少了进程的等待时间。
实现支持异步任务的线程池
线程池:线程池是存放多个线程的容器,CPU调度线程执行后不会销毁线程,将线程放回线程池重新利用。
使用线程池的原因:
-
线程是稀缺资源 ,不应该频繁创建和销毁;
-
架构解耦,业务创建和业务处理解耦,更加优雅;
-
线程池是使用线程的最佳实践。
实现线程安全的队列Queue
- 队列:用于存放多个元素,是存放各种元素的“池”。
- 实现的基本功能:获取当前队列元素数量,往队列放入元素,往队列取出元素。
- 注意:队列可能有多个线程同时操作,因此需要保证线程安全,如下两种情况:
- 实现基本任务对象Task
实现的基本功能:任务参数,任务唯一标记(UUID),任务具体的执行逻辑 - 实现任务处理线程ProcessThread:任务处理线程需要不断地从任务队列里取任务执行,任务处理线程需要有一个标记,标记线程什么时候应该停止。
实现的基本功能:基本属性(任务队列、标记),线程执行的逻辑(run),线程停止(stop)。 - 实现任务处理线程池Pool:存放多个任务处理线程,负责多个线程的启停,管理向线程池的提交任务,下发给线程去执行。
实现的基本过程:基本属性,提交任务(put,batch_put),线程启停(start,join),线程池大小(size)。 - 实现异步任务处理AsyncTask:给任务添加一个标记,任务完成后,则标记为完成;任务完成时可直接获取任务运行结果;任务未完成时,获取任务结果,会阻塞获取线程。
主要实现的两个函数:设置运行结果(set_result),获取运行结果(get_result)