基础篇
注:本文内容总结自《计算机操作系统》第四版(汤小丹等人编著)。本文由博主同步发布于:https://blog.csdn.net/donotgogentle/article/details/106930437
第一章 引论
1.1 什么是操作系统?
操作系统是配置在计算机硬件上的第一层软件。其作用是:
- 管理好硬件设备,提高它们的利用率和吞吐量
- 为用户和应用程序提供一个简单接口,便于用户使用
具体为:
- 处理机管理
- 进程控制:在多道程序环境下,使作业能并发执行。为若干作业(在外存)创建、撤销进程,以及进程状态的切换(在内存)
- 进程同步:使多个进程有条不紊地进行
进程互斥:为临界资源创建互斥锁
进程同步:信号量
-
- 进程通信:
- 调度:
作业调度:从后备队列中按照算法挑选出若干作业,为它们分配资源,调入内存后创建进程,将之插入进程就绪队列
进程调度:从进程就绪队列中,按照一定算法挑选出一个进程,将处理机分配给它,使其执行
- 存储器管理
- 内存分配:为每道程序分配内存空间;允许正在运行的程序动态申请内存空间;提高存储器的利用率,减少内存碎片
- 内存保护:确保每个进程在自己的内存空间中执行,互不干扰(两个界限寄存器,规定执行程序的上界和下界,执行指令时,系统对要访问的地址进行检查,若越界,则产生越界中断);严禁用户程序访问操作系统的程序和数据
-
- 地址映射:每个程序的地址都是从0开始的,这是它们的逻辑起始地址,存储器管理必须将逻辑地址映射为内存空间中与之对应的物理地址
- 内存扩充:虚拟存储技术
请求调入:系统允许在仅装入部分用户程序和数据的情况下,便能启动改程序运行
置换功能:若发现内存已无足够空间装入需要调入的用户程序和数据时,按照一定的算法,将已装入内存的一部分程序和数据调至硬盘,再将需要的部分调入内存
- 设备管理
完成用户提出的I/O请求,为用户进程分配所需I/O设备,并完成指定操作
提高I/O设备与CPU之间的利用率
- 文件管理
文件存储空间管理
目录管理
文件的读写保护
1.2 操作系统的基本特性
- 并发:并行指两个及以上时间在同一时间发生,并发指指两个及以上事件在同一时间间隔发生
- 共享(Sharing):指资源共享,或者资源复用,是指系统中的资源可供内存中多个并发执行的进程共同使用。分为:互斥共享,同时访问
- 虚拟:通过某种技术将一个物理实体变为若干个逻辑上的对应物
- 异步:进程以人们不可预知的速度向前推进,但能保证多次执行也能得到相同的结果。
第二章 进程与线程
2.1 进程
进程是指在系统中能独立运行并作为资源分配的基本单位,是程序的一次执行。它是由程序段、相关的数据段、进程控制块(Process Control Block,PCB)组成的。
2.1.1 进程的特征
- 动态性D:进程实体由一定的生命周期,由创建而产生,由调度而执行,由撤销而消亡
- 并发性C:并发执行
- 独立性I:进程是能独立运行、独立获得资源、独立接受调度的基本单位
- 异步性A:进程按照异步方式执行
2.1.2 进程的五种状态
- 创建
- 就绪(Ready):已得到资源,只要获得CPU即可执行
- 执行(Running)
- 阻塞(Block):正在执行的进程由于突发事件而暂时无法继续执行。此时引起进程调度,OS将处理机分配给另一个就绪进程
- 终止
2.1.3 状态转换
活动阻塞通过挂起进入静止阻塞状态,活动就绪通过挂起进入静止就绪状态,而挂起这一操作是由OS将暂时不能运行的进程调入外存。也就是说这两个状态下,进程并没有在内存中,而是在外存中。
一项作业被调度时,是OS将该作业调入内存,并为之创建进程,分配资源,进入就绪队列。
2.1.4 进程控制块
操作系统为每个进程定义了一个进程控制块。进程控制块作为进程实体的一部分,记录了操作系统所需要的,用于描述进程当前情况以及管理进程运行的全部信息。
PCB里有什么?
- 进程标识符
- 处理机状态(上下文),包括:通用寄存器、指令计数器、程序状态字、栈指针
- 进程调度信息,包括:进程状态、进程优先级等
- 进程控制信息,程序和数据的地址,进程同步和通信机制,如:消息队列指针、信号量等,进程拥有的系统资源等
2.1.5 进程通信类型
(1)管道
pipe 匿名管道
本质:
内核缓冲区
特点:
读端流出,写端流入,分别对应两个文件描述符
操作管道的进程被销毁后,管道自动释放
管道读写两端默认是阻塞的
原理:
使用环形队列实现,缓冲区大小默认4k
局限:
使用队列作为数据结构数据,因此数据只能读一次
半双工机制,数据只能单向流动
仅仅适用于有血缘关系的进程
创建:
<unistd>
int pipe(int fd[2]);
0读1写
读写情况:
读端有数据,读:使用read正常读,返回读到的字节数
读端无数据,读:当写端全关时,read解除阻塞,返回0,相当于读到末尾EOF;当写端未关时,read阻塞
读端全关,写:管道破裂,进程终止,内核给当前进程发送SIGPIPE=13
读端未全关,写:当缓冲区满了,write阻塞;当缓冲区不满,正常写
查看管道缓冲区大小:
ulimit -a,使用此命令还可查看用户空间各部分的大小
如何设置非阻塞?
通过fcntl
int flags = fcntl(fd[0], F_GETFL); flags |= O_NONBLOCK; fcntl(fd[0], F_SETFL, flags);
父子进程是否需要sleep以等待读写?
不需要,管道读写端默认是阻塞的。
fifo 有名管道
特点:
磁盘上存在改文件,但是占磁盘空间大小为0
在内核中有对应的缓冲区
半双工
可用于无血缘关系的进程间通信
函数:
mkfifo
实例:
//fifo 文件 “myfifo” //进程 a 读 int fd = open("myfifo", O_RDONLY); read(fd, buf, sizeof(buf)); close(fd); //进程 b 写 int fd2 = open("myfifo", O_WRONLY); write(fd2, buf, sizeof(buf)); close(fd);
(2) 共享映射区
共享映射区将磁盘文件映射到内存
创建:
void* mmap(
void* addr, //首地址,一般传null即可
size_t length, //映射区大小,必须是4k整数倍,且不能为0
int prot, //映射区权限,PROT_READ读,PROT_WRITE写
int flags, //标志位,MAP_SHARED数据同步到磁盘,MAP_PRIVATE数据不同步到磁盘
int fd, // 待映射文件
off_t offset //映射时文件指针偏移量,必须是4k整数倍,一般设0
)
成功则返回映射区首地址。失败返回(void*)-1 == MAP_FAILED
释放:
munmap(void* addr, size_t length);
若对mmap返回值pos,做++操作,munmap(pos)能成功吗?
不能
对于传入的带映射文件参数fd,如果在mmap之前,open时,指定权限O_RDONLY,mmap时,指定PROT_READ|PROT_WRITE会怎样?
mmap失败,open文件指定的权限决定着mmap指定的权限,mmap指定的权限必须小于等于open指定
offset不是4096的整数倍会如何?
mmap失败
mmap什么时候会失败?
length==0
prot权限大于fd被open指定的权限
offset不是4096的倍数
可以open时使用O_CREAT创建一个文件来创建映射区吗?
可以,但是需要做文件大小的拓展,可使用ftrucate(fd, length)
超出映射区范围的写操作不会被同步到磁盘。
(3)信号
(4)socket
2.2 临界资源和临界区
- 临界资源:打印机等独占设备,进程间采用互斥方式,实现对该资源的共享
- 临界区:进程中访问临界资源的代码段
2.2.1 信号量
mutex为互斥信号量,初值为1,范围(-1,0,1)。
- mutex=1:两个进程未进入临界区
- mutex=0:有一个进程进入临界区运行,另一个必须等待,挂入阻塞队列
- mutex=-1:有一个进程进入临界区,另一个进程阻塞,需要被当前已经在临界区运行的进程退出时唤醒
2.3 管程
一个管程定义了一个数据结构,和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
管程被请求和释放资源的进程所调用。
2.4 操作系统内核
通常,将一些与硬件紧密相关的模块、各种设备的驱动程序以及运行频率较高的模块,安排在紧靠硬件的软件层次中,将它们常驻内存,即,OS内核。
2.4.1 内核态和用户态
为防止OS本身及关键数据遭到应用程序的破坏,通常将处理机的执行状态分为两种:
- 系统态(又叫管态,又叫内核态):具有较高的权限,能执行一切指令,访问所有寄存器和存储区
- 用户态(又称目态):具有较低的权限,仅能执行规定的指令,访问指定的寄存器和存储区。一般情况下,应用程序只能在用户态运行,以防止破坏。
原语:由若干条指令构成,这组指令要不全部完成,要不全部不完成。因此在执行过程中不允许被中断。原子操作在系统态下执行,常驻内存。
2.5 线程
线程又程轻型进程。
线程与进程的区别可从以下5个方面分析:
- 调度的基本单位:是能独立运行的基本单位。进程是分配系统资源的基本单位,也可独立运行。同一进程中线程的切换不会引起进程的切换,但切换到另一进程中的线程会引起进程切换
- 并发性:一个进程中的所有线程可以并发执行
- 拥有资源:线程本身并不拥有系统资源。同属于一个进程的所有线程有相同的地址空间,这意味着线程可以访问该地址空间中的每个虚拟地址,还可以访问进程所拥有的资源,如:I/O,打开的文件等
- 独立性:线程间的独立性弱于进程间独立性,因为线程共享进程地址空间中的所有地址
- 系统开销:创建、撤销和切换线程的消耗,远小于创建、撤销和切换进程的消耗。线程的同步和通信机制也比进程简单
- 支持多处理机系统:在多处理机系统中,一个进程只能运行在一个处理机上。而若是一个进程拥有多个线程,可以把这些线程分配到多个处理机上,加速进程的完成
线程拥有什么?
- 线程控制块TCB
- 通用寄存器、状态寄存器
- 程序计数器
- 栈指针
TCB里有什么?
- 线程标识符
- 一组寄存器,包括:通用寄存器、程序计数器、状态寄存器
- 线程运行状态
- 线程优先级
- 线程专有存储区,用于线程切换时保存现场信息
- 信号屏蔽
- 栈指针
2.5.1 线程实现
(1)内核支持线程(Kernel Supported Threads)
内核支持线程的创建、撤销、切换、阻塞都是在内核空间进行,由内核对每个线程设置的线程控制块对其进行控制。
优点:
- 在多处理器系统中,内核能同时调度同一进程中的多个线程并行执行
- 如果进程中的一个线程被阻塞,内核还能调度该进程中其他线程占有处理机
- 内核支持线程具有很小的数据结构和堆栈,切换速度快,开销小
缺点:
- 对于用户的线程切换而言,开销大,在同一进程中,线程切换会导致用户态转到核心态进行,这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核上实现的。
(2)用户级线程(User Level Threads)
在用户空间实现,对线程的创建和撤销无需内核帮助,内核甚至不知道它的存在。但是,调度是以进程为单位的。
优点:
- 线程切换不需要转到内核空间
- 调度算法可以是进程根据自身需要而选择不同的调度算法,与OS的低级调度算法无关
- 实现与OS平台无关,因为线程管理代码属于用户程序的一部分
缺点:
- 大多数系统调用将使进程阻塞,进而使进程中的线程阻塞,内核则不然
- 在多处理机系统中,内核每次只分配给用户级进程一个CPU,因此进程中的线程只能并发而不是并行执行,内核则不然
(3)组合方式
内核支持多个内核支持线程的创建、调度和管理等,同时允许应用程序创建、调度、管理用户级线程。一些内核线程对应多个用户级线程,即,用户级线程对部分或者全部内核支持线程进行时分多路复用。
分为多对一、一对一、多对多(用户级线程数 >= 内核支持线程)。
优点(多对多):
- 在多处理机系统中,同一进程中多个线程可以在多个处理机中并行执行
- 减少了线程管理的开销,提高了效率
第三章 处理机调度与死锁
3.1 处理机调度的层次
- 高级调度
又称作业(长程)调度,调度对象是作业。根绝算法,决定将外存上处于后备队列的哪些作业调入内存,为其创建进程、分配资源,放入就绪队列。
- 低级调度
又称进程(短程)调度,调度的对象是进程或内核支持线程。根据调度算法,决定哪个进程获得处理机。
- 中级调度
又称内存调度。把暂时不能运行的进程调入外存,即改为挂起状态,当这些进程具备运行条件时,再把他们重新调入内存,即改为就绪状态。
3.1.1 作业调度
作业的三种状态:
- 后备状态(将用户提交的作业输入硬盘,为其建立JCB,放入作业后备队列)
- 运行状态(运行)
- 完成状态(回收资源)。
调度算法:
(1)先来先服务(FCFS)调度算法
first-come first-served,优先级 == 作业等待时间
(2)短作业优先算法
优先级 == 作业需要时间短的优先级高
需要预知作业工作时间,对长作业不利,没考虑到紧迫性作业
(3)高响应比优先级调度算法
优先级 == (等待时间+要求服务时间)/要求服务时间
3.1.2 进程调度基本概念
进程调度的任务:
- 保存处理机现场信息
- 按某种调度算法选取进程
- 把处理机分配给进程
进程调度方式:
- 非抢占方式
将处理机分配给某进程后,就让其运行到结束。
- 抢占方式
基于优先原则,去暂停某个正在运行的进程,将其分配给另一个优先级更高的进程。
3.1.3 进程调度算法
(1)轮转调度法:
在轮转法(round robin,RB)中,系统根据FCFS策略,将所有的就绪进程排成一个就绪队列,并设置每隔一定时间间隔即产生一次中断,激活系统中的进程调度程序,完成一次调度,将CPU分配给队首进程(或新的紧急进程),令其执行,当该进程的时间片耗尽或者运行完毕后,系统将再次将CPU分配给新的首进程。
时间片的大小对系统性能有很大影响。时间上一般取略大于一次典型的交互所需要的时间,使大多数交互式进程能在一个时间片内完成。
(2)优先级调度算法:
- 静态优先级:创建进程时就确定,在整个运行期间保持不变
- 动态优先级:优先级随进程推进或等待时间而改变
3.2 死锁
3.2.1 死锁定义
如果一组进程中的每一个进程都在等待,该组进程中的其他进程释放所占有的资源后,才能运行,那么该组进程是死锁的。
3.2.2 产生死锁的必要条件
- 互斥条件:进程对所分配到的资源进行排他性使用
- 请求和保持:请求和保持条件:进程已经获得了一个资源,但还需要其他资源才能进入就绪状态,而该资源被其他进程占有,此时该进程被阻塞,但不释放自己获得的资源
- 不可抢占:进程获得的资源在未使用完成之前不能被抢占
- 循环等待:发生死锁时存在一个进程-资源循环链,该链上的进程循环等待该链上的其他进程释放资源
3.2.3 处理死锁的方法
避免死锁-银行家算法:
用到的数据结构:
- 可利用资源向量 Available
- 最大需求矩阵Max
- 分配矩阵Allocated
- 需求矩阵Need
银行家算法首先执行安全性检测,若能确定分配资源后系统处于安全状态,则分配,否则让进程等待。
安全性检测:当检测到进程 P 对资源的请求,算法尝试将资源分配(不是真的分配,仅用于计算安全性)给 P,若 Available能满足 P 的需求(P 的需求必须小于 Need,否则认为出错),直接分配,并回收 Max
资源 进程 |
Max |
Allocation |
Need |
Available |
A B C |
A B C |
A B C |
A B C | |
P0 |
7 5 3 |
0 1 0 |
7 4 3 |
3 3 2 |
P1 |
3 2 2 |
2 0 0 |
1 2 2 |
|
P2 |
9 0 2 |
3 0 2 |
6 0 0 |
|
P3 |
2 2 2 |
2 1 1 |
0 1 1 |
|
P4 |
4 3 3 |
0 0 2 |
4 3 1 |
检测此时刻安全性(可能的序列):
(1)Available(3, 3, 2)分配给P3(0, 1, 1), P3完成,归还资源,Available(5, 4, 3)
(2)此时P1,P4均可完成,设分给P1,故分配+归还,Available(7, 4, 3)
(3)此时剩余进程均可完成,继续按照上述步骤,得到存在的可能的安全序列:{P3, P1, P4, P2, P0}
(4)判定分配资源后系统安全,可以分配资源
第四章 存储器
4.1 存储器的多层结构
- 寄存器(CPU寄存器)
- 高速缓存Cache(主存)
主要用于备份主存中常用的数据,以减少处理机对主存的访问次数,从而提高程序执行速度。基于程序执行的局部性原理:一段较短时间内,程序执行局限于某部分。
Cache一般使用SRAM(静态随机读写存储器),存储容量小,速度快,断电则信息丢失。
- 主存储器(主存)
简称内存或主存,用于保存进程运行时的程序和数据。为缓和 CPU 指令执行速度与主存储器访问速度的矛盾,引入了高速缓存和寄存器。
内存条用于提供主存,一般使用DRAM(动态随机读写存储器),速度低于SRAM但远高于磁盘,容量较SRAM大,需要定时刷新以维持存储的信息。
- 硬盘缓存(主存)
磁盘 I/O 速递远低于主存访问速度,为缓和这个矛盾,设置磁盘缓存。它是利用主存中的存储空间暂时存放从磁盘中读取的信息。
- 固定磁盘(辅存)
- 可移动存储介质(辅存)
4.1.1高速缓存Cache
原理:主要用于备份主存中常用的数据,以减少处理机对主存的访问次数,从而提高程序执行速度。基于程序执行的局部性原理:一段较短时间内,程序执行局限于某部分。
种类:Cache一般使用SRAM(静态随机读写存储器),存储容量小,速度快,断电则信息丢失。
构成:Cache中的地址映射需要专门的硬件来处理,就是相联存储器,它依据内容,去查询内容对应的地址。
工作过程:当CPU执行程序需要从主存读取指令或者数据时,将存储器地址加载到主存也加载到地址映射与变换模块上(相联存储器),若地址映射表中检索到要读写的信息在Cache中,则通过地址映射表,将地址转换为cache地址,CPU从Cache中读出信息,这种情况是为“命中”,若检索不到,称为“未命中”,此时,CPU从主存中读出指令或数据,同时将包括读出信息在内的存储块替换Cache中的某一存储块。
地址映射:在Cache工作过程中,需要将主存信息拷贝到Cache中,需要建立主存地址与Cache地址之间的映射关系,并将该关系存储在地址映射表中,这就是主存地址到Cache地址的地址映射。
地址变换:如果命中,则CPU从Cache中读取信息,这需要将主存地址转换为Cache地址,才能对Cache进行访问,这就是主存地址转换为Cache地址的地址变换。地址变换有3种方式:
(1)全相联
将Cache和主存分成若干容量相等的存储块,如此,主存中的任意一块可以装入Cache块中。例如:主存为64MB,Cache为32KB,若以4KB分块,Cache被分为快好0-7的8个块,主存被分为0-16383 块。
地址映射法则是:主存地址由主存块号和块内地址组成,Cache地址也由Cache块号和块内地址(与主存块内地址相等)组成,相联存储器的地址映射表中存储主存块号对应的Cache块号,如果主存地址块号在地址映射表中被找到,那么久可以得到Cache块号,由Cache块号和块内地址就可以索取cache中存储的主存中对应位置内容的副本。
优缺点:主存装入Cache十分灵活,可以装入Cache的任意一块中。但是,它要求相联存储器非常大,地址变换也相对复杂。
(2)直接映射
将主存先按照Cache总大小分区,再按照规定的块大小分块。例如:主存为64MB,Cache为32KB,若以4KB分块,Cache被分为快好0-7的8个块,主存被分为0-2047区,每个区内都有一个类似Cache的0-7个块。
地址映射法则是:对于主存各区中的块,只有它们的块号与Cache块号相同,才能装入Cache。例如,主存所有区中的0号块,只能装入Cache中的0号块。主存地址由区号+区内块号+块内地址组成,对于Cache来说,Cache块号和块内地址都是等同的,因此,相联存储器的地址映射表中存储的是主存区号到Cache块号的映射,如果找到了,则就可以得到Cache中存储的内容。
优缺点:存取简单。但是,因为限制了块号相等才能存入Cache,当这个Cache块正在使用时,等待的主存块号相等块迟迟不能装入Cache,造成效率低。
(3)组相联
先将Cache先分组,组内再分块;主存先以Cache容量分区,区内按Cache的方法分组,组内再分块。
地址映射法则:组间直接映射,组内全相联映射。主存组号相等才能存入Cache中。主存地址由主存区号+区内组号+组内块号+块内地址,Cache地址由组号+组内块号。地址映射时,先根据主存组号确定Cache组号,地址映射表中存储主存区号+块号对应Cache组内块号,然后根据主存块内地址确定Cache块内地址,如此便存取到了Cache对应内容。
注意:Cache中存储的内容与主存对应存储块的内容完全一样,是主存内容的拷贝(一小部分),只是为了提高运行速度才将这部分内存放在Cache中。
4.1.2 替换算法(主存-Cache)
(1)LRU近期最少使用算法
该算法将近期最少使用的块替换出去。对每个Cache块设置一个计数器,如果命中某Cache,就把该块计数值清零,而其他块+1。当需要进行页面替换时,把计数值最大的块替换出去。
(2)LFU最不经常使用算法
该算法将一段时间内访问最少的Cache块替换出去。对每个Cache块设置一个计数器,如果命中某Cache,就把该块计数值+1。当需要进行页面替换时,把计数值最小的块替换出去,并把其他块的计数值置0(就是这一步造成此算法不能反映近期使用情况)
4.1.3 一致性(主存-Cache)
(1)写回法
当CPU命中Cache时,只将数据写入Cache,而不立即写入主存(在这段期间,会造成主存与Cache的内容不一致问题)。只有当被改动过的Cache块需要被替换出Cache的时候,才把改动写回内存。
(2)全写法
当CPU命中Cache时,将数据写入Cache的同时,也立即写入主存,较好地保证了主存与Cache的内容一致。但是降低了CPU的运行效率。
4.2 存储管理方式
4.2.0 程序的装入与链接
用户程序要在系统中运行,必须将它装入内存,然后再将它变为一个可执行程序。一般需要经过:
- 编译:由编译模块对源程序进行编译,形成若干目标模块
- 链接:由链接程序将一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的装入模块
- 装入:由装入模块将模块装入内存
(1)链接
静态链接:
在程序运行前,将各个目标模块以及它们需要的库函数链接成一个完成的装入模块,以后不再拆开。
装入时,需要解决连个问题:
a. 对相对地址的修改:各个模块的起始地址都是0,模块中的地址都需要相对于起始地址来计算。在链接时,需要把若干个模块链接为一个起始地址为0的完整模块,因此,需要对各个模块的相对地址进行修改。
例子:有目标模块ABC,长度分别为LMN,模块A调用模块B,模块B调用模块C。此时,模块B,C都属于外部调用符号。在链接为一个模块时,B和C的起始地址不再是0,B的起始地址应该是0+L,C的起始地址应该是0+L+M。因此,需要修改B和C中的所有相对地址,对于B,把这些地址加上L,对于C,把这些地址加上L+M。
b. 变换外部调用符号:将外部调用符号(B和C)都变换为相对地址,如:B的起始地址替换为L,C的起始地址替换为L+M。如此形成了一个完整的装入模块,是为只执行程序。
装入时动态链接:
在装入一个目标模块时,若发生一个外部模块调用时间,装入程序将去寻找对应的外部模块,并将它装入内存。它也需要像静态链接那样修改模块的相对地址。
优点:便于修改和更新,只需要修改需要更新的模块即可。
注:此方法是在装入时把每个目标及对应外部调用模块链接在一起装入内存,而实际上程序执行过程中,一些外部调用模块可能是用不上的。
运行时动态链接:
是对装入时动态链接的改进。在执行过程中,当发现一个模块还未装入内存时,由OS立即寻找该模块,并将之装入内存,将其链接到调用者模块上。凡是在执行过程中未用到的目标模块,都不会被调入内存以及链接到装入模块。
优点:加速程序的装入过程,节省大量内存空间。
(2)装入
绝对装入:单道程序系统采用,事先知道程序将驻留在内存中的位置,用户编译程序后,产生绝对地址(与实际物理地址相同)的目标代码。
可重定位装入:事先不知道程序将驻留在内存中的位置,对于用户编译产生的目标模块,它们的起始地址都从0开始(是为逻辑地址),需要将这些逻辑地址修改为实际对应的物理地址,这些地址变换在进程装入时一次性完成,以后不再改变。
动态运行时装入:运行过程中,程序在内存中的位置可能发生改变。这种方法先把模块装入内存,随后并不立即把逻辑地址转换为物理地址,而是把这种转换推迟到程序运行时才进行。这种方式需要可重定位寄存器的支持。
4.2.1 连续分配存储管理
连续分配方式为用户程序分配一个连续的内存空间,即,程序中代码或者数据的逻辑地址相邻,体现在内存空间分配时物理地址的相邻。
为了将用户程序装入内存,必须为它分配一定大小的内存空间。有四种分配方式:
- 单一连续分配
- 固定分区分配
- 动态分区分配
- 动态可重定位分区分配
(1)单一连续分配
单道程序系统使用此方式。内存分为系统区和用户区,用户区内存中仅装有一道用户程序。
(2)固定分区分配
将用户空间划分为若干个固定大小的区域,每个分区中之装入一道作业。分区大小的划分有大小相等和大小不等两种方法,后者把分区分为小分区、中分区、大分区等,相比于方式一能够减少内存碎片。
将分区按大小排序,并建立一张分区使用表。如下所示:
(3)动态分区分配
根据进程的需要动态分配内存空间。为了实现该分配方法,总是把系统中的空闲分区链接成一个链表。然后按照此空闲分区链,顺序查找合适的分区。
基于该分配方法,有以下几种分区分配方法:
首次适应(first fit,FF)算法:从空闲分区链头部顺序找起,找到第一个能满足进程要求的分区大小,在该分区中动态为其分配一块内存空间,用不完的剩余部分仍然留在空闲分区链。
循环首次适应(next fit,NF)算法:从上次分配分区的地方开始找起。若找到链表尾部还没找到,则返回链表头部,从头找起。
最佳适应(best fit,BF)算法:能满足要求,且大小是最小的分区被优先分配。
最坏适应(worst fit,WF)算法:能满足要求,且大小是最大的分区被优先分配。
回收时:
如果回收区与前一个空闲分区F1相邻,则回收时把二者合并,不用创建新表项,只把F1表项的分区大小调整+回收区大小即可
如果回收区与后一个空闲分区F2相邻,则回收时把二者合并,不用创建新表项,只把F1表项的分区大小调整+回收区大小,另外把起始地址调整为回收区起始地址即可
如果回收区与前后空闲分区F1,F2相邻,则回收时把三者合并,不用创建新表项,只把F1表项的分区大小调整+回收区大小+F2大小即可
如果回收区与前后空闲分区都不相邻,则回收时创建新表项,设置分区大小为回收区大小,设置起始地址为回收区起始地址,并插入分区表合适位置
4.2.2 分页分配存储管理
连续分配存储往往导致碎片,浪费存储。如果允许进程直接分散地装入到许多不相邻接的分区中,便可充分地利用内存空间。基于该思想有以下三种方法:
- 分页存储管理方式:将用户程序的地址空间分为若干个大小固定的区域,称为“页”,典型大小1KB。相应地,将内存空间也分为若干物理块,块大小与相同。
- 分段存储管理方式:为满足用户要求而形成,把用户程序的地址空间分为若干个大小不同的段,在存储器分配时,以段为单位。
- 段页式存储管理方式:结合上述二者优点。
系统为每个进程建立一张页表,记录了相应页在内存中对应的物理块号。
分页地址=页号+页内地址
快表:
页表存放在内存中,这使得CPU每次存取一个数据都要访问两次内存,第一次根据页号在页表中查找得到物理块号,将该物理块号与页内偏移地址拼接才能形成物理地址,第二次才从物理地址读写数据。为提高地址变换速度,出现了快表。在CPU给出有效地址后,由地址变换机构将页号送入高速缓存,将此页号与其中的所有页号进行比较,若匹配,直接在该快表上得到页号对应的Cache块地址,并将该地址与业内地址进行拼接得到Cache地址,送入物理地址寄存器,从而在相应物理地址上读写。若未命中,才访问内存中的页表。
注意:此处跟上面讲的Cache部分实质上是一样的,联想寄存器就是上面的相联存储器。Cache块的大小划分可理解为分页对应的物理块。根据主存地址变换到Cache地址的过程,与上述通过页号+页内偏移地址找到Cache物理块地址的过程是一样的。
4.2.3 分段
作业的地址空间被划分为若干个段,每个段定义了一组逻辑信息。
分段地址=段号+段内地址
为什么要引入分段?
- 程序通常分为若干个段,如:主程序段、子程序段A、数据段、栈段等,每个段是一个相对独立的逻辑单位
- 使用段可以方便地实现存储空间动态增长、动态链接等
你可以把主函数所调用的一个函数理解为一个段,这个段有一组完成特定功能的指令,以及起始地址,主函数可以方便地通过起始地址调用该段的功能。
分段和分页的区别:
页是信息的物理单位,分页是系统的行为,用户不可见。分段中的段是逻辑单位。
页大小固定,由系统决定。段的长度不固定,由用户编写的程序决定。
两次访存。
4.2.4 段页式
先将程序分段,再将每个段分成若干个页。由段号+段表起始地址指出对应的页表起始地址,由段内页号和对应的页表定位块号,由块号和页内地址构成物理地址。
段页式地址=段号+段内页号+页内地址
为了获得一条指令或者数据,必须三次访问内存。第一次访问内存中的段表,从中取得页表起始地址;第二次访问内存中的页表,从中取出该页所在的物理块号,将该块号与页内地址拼接在一起形成物理地址;第三次才是从地址中取得指令或数据。
第五章 虚拟存储器
首先要区分开来虚拟存储器和Cache。Cache位于CPU与主存之间,用于缓和CPU与主存速度不一致的问题。Cache系统由Cache和主存以及对应的硬件构成。
而虚拟存储器由主存和磁盘(辅助存储器)构成。主要的任务是对内存进行扩展,使计算机能运行体积庞大的若干程序
5.1 虚拟存储基本原理
有的作业很大,所要求的内存空间超过了内存总容量,虚拟存储器便是为此而生。
虚拟存储器是基于程序运行的局部性原理,应用程序在运行前并不需要全部装入内存,仅仅将当前需要运行的少数页面或者段装入内存便可运行。
程序运行时,如果它所需要的段(页)已经在内存,便能继续执行,否则,发出缺页(段)中断请求,此时OS利用请求调页(段)功能将这些页(段)调入内存,以使进程继续运行下去。如果此时内存已满,OS再利用页(段)的置换功能,将内存中暂时不用的页(段)调至盘上,腾出内存空间后,再将要访问的页(段)调入内存。
5.2 页面调度策略
5.2.1 最佳置换算法
选择的淘汰页是以后永久不使用或者最长时间内不再被访问的页面。
注:和上面所讲的策略相同。
5.2.2 先进先出(FIFO)页面置换算法
总是淘汰最先进入的页。
5.2.3 LRU(Least Recently Used)置换算法
淘汰最近最久未使用的页。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来经历的时间t,淘汰时选择t最大的页淘汰。
5.2.4 最少使用(Least Frequently Used,LFU)置换算法
记录每个页面被访问的频率,选择在最近时期使用最少的页淘汰。
5.3 抖动
5.3.1 什么是抖动?
同时运行在系统中的进程太多,造成分配给每个进程的物理块太少,不能满足程序正常运行的需求,致使每个进程频繁地出现缺页,从而每个进程大部分时间都用于页面的换进/换出,几乎不能做有效的工作,从而导致处理机的利用率急剧下降并趋近于0。
5.3.2 什么是工作集
工作集是某段时间间隔内,进程实际要访问页面的集合。在不同时间,工作集所含页面数不同。
5.3.3 预防抖动的方法
L=S准则:
L为缺页之间的平均时间,S为平均缺页服务时间,如果L>S,说明很少缺页,但处理机利用率低,如果L<S,说明频繁缺页。只有当L=S时,磁盘和处理机能达到它们最大的利用率。