文章目录
- 一 基本概念
- 1 操作系统的特征
- 2 操作系统的位置
- 3 计算机的硬件组成
- 4 中断与异常
- 5 系统调用
- 二 进程管理
- 1 进程控制块 PCB(Process Control Block)
- 2 进程的状态与转换
- 3 进程间的通信
- 4 线程
- 5 调度算法
- 6 死锁
- 7 PV 操作
- 三 内存管理
- 1 内存的非连续分配
- 2 虚拟内存的概念
- 3 虚拟内存的实现——局部性原理
- 4 虚拟内存的页面置换算法
- 5 抖动和颠簸
- 四 文件管理
- 五 I/O管理
- 1 同步与异步
- 2 阻塞与非阻塞
- 3 I/O模型:同步阻塞I/O & 同步非阻塞I/O
- 4 I/O模型:I/O多路复用
一 基本概念
1 操作系统的特征
并发
:两个或多个事件,在同一时间间隔内发生共享
:系统中的资源可供内存中多个并发执行的进程共同使用,分为互斥共享方式 / 同时访问方式虚拟
:将一个物理上的实体变为若干逻辑上的对应物异步
:进程的执行不是一贯到底,而是以不可预知的速度向前推进
2 操作系统的位置
- 操作系统是在硬件的基础之上安装的 软件,能够根据用户输入的指令达到 控制硬件 的效果
- 操作系统相当于是一个中间层,为用户层和硬件提供各自的接口,屏蔽了不同应用和硬件之间的差异,达到统一标准的作用
- 大部分计算机有两种运行模式:内核态 和 用户态
- 软件中最基础的部分是操作系统,它运行在 内核态 中,操作系统具有硬件的访问权,可以执行机器能够运行的任何指令;软件的其余部分运行在 用户态 下
3 计算机的硬件组成
- 冯诺伊曼体系结构:运算器、控制器、存储器(内存与外存)、输入设备、输出设备
总线(Buses)
:总线在组件之间来回传输字节信息。通常总线被设计成传送定长的字节块,也就是 字(word)。字中的字节数是一个基本的系统参数,常见的有 4 个字节(32 位)或者 8 个字节(64 位)
4 中断与异常
- 操作系统内核工作在核心态,用户程序工作在用户态,但用户程序需要使用核心态的功能。中断和异常实现了从用户态到核心态的切换,是用户态转为核心态的唯一途径。发生中断或异常时,运行在用户态的 CPU 会立刻切换成核心态
- 中断的定义:也称外中断,指来自 CPU 指令以外的事件发生(I/O结束中断、时钟中断等),该类中断通常是与当前指令无关的事件,即它们与当前处理机运行的程序无关
- 异常的定义:也称内中断或 陷入(trap),指源自 CPU 执行指令内部的事件(程序的非法操作码、地址越界、算术溢出等),对异常的处理一般要依赖于当前程序的运行现场
5 系统调用
- 用户在程序中调用操作系统提供的一些子功能
- 凡是与资源有关的操作,都必须通过系统调用的方式向操作系统提出服务请求,并由操作系统的内核程序代为完成
- 发起系统调用需要用户程序执行陷入(trap)指令(该指令不是特权命令,因为是在用户态使用),即通过异常的方式切换为核心态
- 从核心态转为用户态需要执行中断返回指令,该指令是特权指令
- 和网络模型的对应关系:系统调用发生在5->4时
二 进程管理
1 进程控制块 PCB(Process Control Block)
- 程序段 + 相关数据段 + PCB = 进程实体;PCB 是一种数据结构,是标志进程存在的唯一标志
- 创建进程,实际上指创建进程实体中的 PCB;撤销进程,实际上指撤销 PCB
- PCB 是有限的,如果创建进程时申请 PCB 失败,则创建失败
2 进程的状态与转换
状态 | 含义 |
---|---|
创建 | 进程正在被创建,尚未转到就绪态 |
就绪 | 进程获得了除了处理机之外的一切所需资源,一旦得到处理机就可以运行。通常处于就绪态的进程组成就绪队列 |
运行 | 进程正在处理机上运行 |
阻塞 | 进程正在等待某一事件而暂停运行,如等待某些资源(不包括处理机)或等待I/O完成等,即使处理机空闲也不能运行 |
结束 | 进程正在从系统中消失,进程先进入结束态,然后回收释放资源 |
- 从运行态变为阻塞态是主动行为,从阻塞态变为就绪态是被动行为(需要其它进程协助)
- 进程在切换时,运行信息存放在 PCB 中
3 进程间的通信
-
共享存储:进程间存在一块可直接访问的共享的空间
-
消息传递:进程间的数据交换以格式化的消息为单位
-
管道通信:消息传递的一种特殊方式,管道指 pipe 文件,连接一个读进程和一个写进程
4 线程
- 线程是轻量级的进程,是最基本的 CPU 执行单元
- 除了少量的运行必备的资源,线程几乎不拥有系统资源,但可以和同属一个进程的其它线程共享进程拥有的资源
- 为什么引入线程可以提高系统的并发性:同一进程内的线程相互切换的开销很小,不进行进程的切换;即使线程切换时需要发生进程切换,总体而言进程切换的次数变少了
5 调度算法
- 衡量调度的一些基本指标:
周转时间
:从作业提交到完成经历的时间
带权周转时间
:作业周转时间和作业实际运行时间的比值
-
先来先服务(FCFS)调度算法
对长作业有利,短作业不利
-
短作业优先(SJF)调度算法
对短作业有利,长作业不利
-
优先级调度算法
为作业分配优先级,每次选择优先级最高的一个或几个作业执行
可以分为 剥夺式 / 非剥夺式,静态优先级 / 动态优先级 -
高响应比优先调度算法
响应比 = (等待时间+要求服务时间) / 要求服务时间
是对 FCFS 和 SJF 的综合平衡,每次选择相应比最高的作业投入运行 -
时间片轮转调度算法
时间片的大小对系统性能影响很大:时间片足够大时,退化成先来先服务;时间片太小时,进程切换过于频繁,CPU 利用率降低 -
多级反馈队列调度算法*
是对优先级调度算法和时间片轮转调度算法的综合
优先级越高的队列,时间片越小
高优先级队列可以抢占低优先级队列的 CPU,只有在高优先级队列为空时,CPU 才分配给低优先级队列的进程
每隔一段时间,将所有的进程重置到最高优先级的队列中,防止低优先级的进程饥饿,以及进程类型变化导致的不公平对待
相同优先级的进程轮转运行
6 死锁
- 产生的必要条件
条件 | 解释 |
---|---|
互斥条件 | 在某段时间内,资源仅被一个进程占有,其它进程请求该资源只能等待 |
不剥夺条件 | 进程获得的资源只能主动释放,不能被其它进程强行夺走 |
请求并保持条件 | 进程已经持有至少一个资源,再请求新的资源时,对已持有的资源保持不放 |
循环等待条件 | 存在进程资源的循环等待链 |
- 处理死锁的策略
- 死锁预防:破坏产生死锁的产生条件之一
- 死锁避免:银行家算法
进程运行前,先声明对各种资源的最大需求量
操作系统先测试该进程已占用的资源数与本次申请的资源数是否超过声明的最大需求量,如果超出则拒绝分配
如果不超出则测试 系统现存的资源能否满足进程需要的最大资源,如果满足则可以分配,否则推迟分配 - 死锁检测和解除:资源剥夺、撤销进程、进程回退
7 PV 操作
- PV 操作是一种实现进程互斥和同步的方法
P(S):使 S=S-1 ,若 S>=0 ,则该进程继续执行,否则排入等待队列
V(S):使 S=S+1 ,若 S>0 ,唤醒等待队列中的一个进程 - 信号量根据作用可以分为 互斥信号量 和 同步信号量
- 实现互斥的 P 操作一定要放在实现同步的 P 操作之后(先获取资源再获取锁),否则可能会造成死锁;V 操作的顺序可以交换
- 生产者-消费者问题
semaphore mutex = 1; //互斥信号量
semaphore empty = n; //同步信号量,空闲缓冲区的数量
semaphore full = 0; //同步信号量,非空缓冲区的数量
producer(){while(1){生成一个产品;P(empty); //消耗一个空闲缓冲区P(mutex);把产品放入缓冲区;V(mutex);V(full) //增加一个产品}
}
consumer(){while(1){P(full); //消耗一个产品P(mutex);从缓冲区取出一个产品;V(mutex);V(empty); //增加一个空闲缓冲区使用产品;}
}
- 吃水果问题
- 桌上有一个盘子,每次能放 n 个水果,妈妈向盘中放苹果,爸爸向盘中放橘子,儿子专等吃盘里的橘子,女儿专等吃盘里的苹果。只要盘子空,妈妈可向盘中放水果,仅当盘中有自己需要的水果时,儿子或女儿可从中取出
- 因为爸爸和妈妈向盘中放水果需要消耗同一个信号量 empty,所以要加互斥锁
- 如果仅有一个儿子和一个女儿,在吃水果的时候不需要加互斥锁(消耗的不是同一种水果);如果是多个则需要加
- 加不加互斥锁,取决于需要进行 P 操作的资源是否存在竞争
mutex = 1 // 互斥量
empty = n // 同步量
apple = 0 // 同步量
orange = 0 // 同步量mother(){while(1){P(empty);P(mutex);放苹果;V(mutex);V(apple);}
}
father(){while(1){P(empty);P(mutex);放橘子;V(mutex);V(orange);}
}
son(){while(1){P(orange);// P(mutex);吃橘子;// V(mutex);V(empty);}
}
daughter(){while(1){P(apple);// P(mutex);吃苹果;// V(mutex);V(empty);}
}
- 抽烟者问题
semaphore offer1=0; // 为第一个吸烟者提供卷纸和火柴
semaphore offer2=0; // 为第二个吸烟者提供火柴和烟草
semaphore offer3=0; // 为第三个吸烟者提供卷纸和烟草
semaphore finish=1; // 通知供应者继续提供材料provider(){int rand;while(1){P(finish);rand = getRandom()%3;if(rand == 0)V(offer1);else if(rand == 1)V(offer2);elseV(offer3);}
}somker1(){while(1){P(offer1);制作香烟并吸掉;V(finish);}
}
somker2(){while(1){P(offer2);制作香烟并吸掉;V(finish);}
}
somker3(){while(1){P(offer3);制作香烟并吸掉;V(finish);}
}
三 内存管理
1 内存的非连续分配
非连续分类允许一个程序分散地装入不相邻的内存分区,但需要额外的空间存放索引,导致非连续分配的存储密度低于连续分配。非连续分配管理方式可以根据 分区的大小是否固定,分为分页存储管理方式和分段存储管理方式
- 分页存储管理方式
- 将内存空间划分为大小相等且固定的页(页相对于固定分区分配的划分更小),以页作为内存的基本单位;这种思想类似于固定分区分配,但产生的内部碎片(页内碎片)要小得多
- 使用页表完成逻辑地址到物理地址的映射,页的逻辑地址 = 页号 + 页内偏移量(也可以只给出总偏移量,通过除法和求余得到页号、页内偏移量)
- 通过快表(TLB)作为缓存的方式解决了每次访问内存时都需要进行逻辑地址到物理地址的转换,转换速度需要足够快的问题;通过多级页表的方式解决了 页表不能太大,否则利用率会降低的问题(但多级页表会增加查询时间)
分页可能产生少量的内部碎片
对于每个页都需要在页表中记录虚拟地址到物理地址的映射,无论该页是否有数据
- 分段存储管理方式
- 使用段表完成逻辑地址到物理地址的映射,段的逻辑地址 = 段号 + 偏移量
- 和分页存储相比,段的大小是不固定的。因此不能通过只给出偏移量,通过整数除法得到段号,或通过求余得到段内偏移,即 分段管理的地址空间是二维的
分段的问题是内存空间被划分为不规整的部分,容易出现外部碎片问题
需要基址寄存器和界限寄存器记录内存的起始位置和长度
- 段页式管理方式
- 作业的地址空间首先被划分为若干逻辑段,每段再划分为大小相等的页
- 逻辑地址 = 段号 + 页号 + 页内偏移量
每个段维护一个页表(避免了纯分页导致的问题:未分配的页都出现在页表里)
段的基址寄存器和界限寄存器记录页表的起始位置和长度
2 虚拟内存的概念
- 虚拟内存的实现基于非连续分配的内存管理方式
- 虚拟内存让每个程序都认为自己“独占内存”,无需考虑覆盖其它程序内存的情况,同时每个程序的内存(虚拟内存)都对应一个很大的字节数组
- 每个程序的虚拟内存由两部分空间组成:给这个程序分配的 内存空间,以及分配的 磁盘空间
- 请求的页在内存中命中时可以直接使用;不命中时通过页面置换算法,将请求页从硬盘空间换入内存空间
- 操作系统完成由虚拟内存地址到 真实内存地址 或者 磁盘地址 之间的映射工作
3 虚拟内存的实现——局部性原理
- 局部性原理:空间局部性、时间局部性
- 虚拟内存基于局部性原理实现:程序装入内存时,将程序的一部分装入内存即可启动程序。程序执行时访问到不在内存中的信息,则操作系统将所需要的部分调入内存,然后执行程序;同样地,将暂时不使用的内容换出到外存。这样用户就能获得比实际内存更大的内存体验,可以运行所需内存大于物理内存的程序
4 虚拟内存的页面置换算法
用于决定虚拟内存 换出 哪页,即内存满但是需要调入新页时,决定从 内存 中调出哪一页到 磁盘的对换区
- 最佳置换算法
选择被淘汰的页面是以后永不使用的页面,或是最长时间内不再被访问的对象
无法实现,但可以用于评价其它算法 - 先进先出(FIFO)页面置换算法
- 最近最久未使用(LRU)置换算法
为每个页面设置一个访问字段,记录页面上次被访问以来经历的时间,每次淘汰经过时间最久的页面 - 时钟置换算法(CLOCK)
简单的 CLOCK 算法仅使用 1 位标志当前页的状态,一种改进做法是引入另外的位标志该页是否是脏页,从而延迟替换脏页的内容(因为替换脏页需要将数据写入外存,代价比较大)
5 抖动和颠簸
- 刚换出的页面马上又要换入主存,刚换入的页面马上又要换出主存,这种频繁的页面调度行为成为抖动
- 如果一个进程换页使用的时间多于执行时间,则这个进程处于颠簸
- 发生抖动的主要原因是,某个进程频繁访问的页面数,高于主存可用的物理页数目
四 文件管理
- 文件是以计算机硬盘为载体的,存储在计算机上的信息集合。系统运行时,计算机以进程为基本单位进行资源的调度和分配;在用户进行输入、输出时,以文件为基本单位
- 文件系统用于实现用户对文件的访问、修稿、保存、维护管理等要求
五 I/O管理
解读I/O多路复用技术
100%弄明白5种IO模型
I/O多路复用详解
1 同步与异步
- 同步是指用户发起IO请求后,需要等待或者轮询内核IO操作完成后才能继续执行
- 异步是指用户线程发起IO请求后继续执行,当内核操作完成后会通知线程或者调用用户线程注册的回调函数
2 阻塞与非阻塞
- 阻塞是指IO操作需要彻底完成后才返回到用户空间
- 非阻塞是指IO操作被调用后立即返回给用户一个状态值
3 I/O模型:同步阻塞I/O & 同步非阻塞I/O
同步阻塞I/O
- 在应用调用 recvfrom 读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回
- 整个请求过程中,用户线程是被阻塞的,对CPU资源利用不够
- 服务器单线程情况下,无法处理并发的请求;服务器多线程时,为每个请求创建一个线程,造成资源浪费
同步非阻塞I/O
- 如果缓冲区没有数据的话,就会直接返回一个
EWOULDBLOCK
错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,意味着如果应用要读取数据就需要不断的调用recvfrom
请求,直到读取到它要的数据为止 - 用户线程的轮询操作消耗了大量 CPU 资源
4 I/O模型:I/O多路复用
- I/O = 网络I/O,多路 = 多个TCP连接,复用 = 复用一个或少量线程(很多个网络I/O,复用一个或少量的线程来处理这些文件描述符fd)
- 实现思路是,由一个线程监控多个网络请求(fd,文件描述符,Linux 系统把所有网络请求以 fd 来标识),这样只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,节省大量的线程资源
- 系统提供了一种函数可以同时监控多个 fd 的操作,避免为每个 fd 创建一个对应的监控线程。在 Linux 下包括三种函数:
select, poll, epoll
- 进程将一个或多个 fd 传递给
select
,阻塞在select
操作上,由select
侦测多个fd是否准备就绪,当有 fd 准备就绪时,select
返回数据可读状态,应用程序再调用recvfrom
读取数据
select poll epoll
select & poll
- 都采用轮询的方式,时间复杂度O(n),随着连接数的增加性能急剧下降
- select 底层采用数组,有最大 fd 数限制1024;poll 底层使用链表,无最大 fd 限制
epoll
- 采用回调 callback 而非轮询(被动通知而非主动轮询),时间复杂度O(1)
- 具有
EPOLLLT(默认)
和EPOLLET
两种模式EPOLLLT
,系统中一旦有大量无需读写的就绪文件描述符,它们每次调用epoll_wait
都会返回,这大大降低处理程序检索关心的就绪文件描述符的效率EPOLLET
,当被监控的文件描述符上有可读写事件发生时,epoll_wait
会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait
时,它不会通知,即只会通知一次,直到该文件描述符上出现第二次可读写事件才会通知。这样系统不会充斥大量不关心的就绪文件描述符