什么是操作系统(OS)?
- 本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源
- 操作系统的存在屏蔽了硬件层的复杂性。
- 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统内存的管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
1、进程与线程
1.1 进程与线程的联系与区别
进程:进程是一个有独立功能的程序在某个数据集合上的一次运行。
线程:线程是系统调度的最小单位,包含在进程之中。
联系
进程可以有多个线程,最少包含一个线程,即主线程。一个线程只能属于一个进程。线程是进程执行的实体。
区别
- 进程是资源分配的最小单位。线程是系统调度的最小单位。进程拥有资源,而线程不拥有资源,但是进程中的多个线程可以共享所属进程中的资源。
- 进程的创建或撤销,系统需要为它分配或者回收资源,开销远大于线程的创建或撤销。进程间的切换需要保存当前的CPU环境和配置新的CPU环境,而线程间的切换只需要保存和设置少量寄存器,开销较小。
- 进程的崩溃不会引起其他进程的崩溃,而线程的崩溃会引起整个进程的崩溃。
- 每个独立的进程都有一个程序运行的入口、顺序执行的序列,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
线程占有的不共享的,其中包括:栈、寄存器、状态、程序计数器;
线程间共享的有:堆、全局变量、静态变量;
进程占有的资源有:地址空间、全局变量,打开的文件、子进程、信号量、账户信息。
进程 | 线程 | 协程 | |
---|---|---|---|
定义 | 资源分配的基本单位 | 系统调度的基本单位 | 用户态的轻量级线程,线程内部调用的基本单位 |
切换情况 | 进程CPU环境(栈、寄存器、页表、文件句柄等)的保存以及新调度进程CPU环境的设置 | 保存和设置程序计数器、少量寄存器和栈的内容 | 先将寄存器上下文和栈保存,等切换回来的时候再进行恢复 |
切换者 | 操作系统 | 操作系统 | 用户 |
切换过程 | 用户态->内核态->用户态 | 用户态->内核态->用户态 | 用户态 |
调用栈 | 内核栈 | 内核栈 | 用户栈 |
拥有资源 | CPU资源、内存资源、文件资源和句柄等 | 程序计数器、寄存器、栈和状态字 | 拥有自己的寄存器上下文和栈 |
并发性 | 不同进程之间切换实现并发,各自占有CPU实现并行 | 一个进程内部的多个线程并发执行 | 同一时间只能执行一个协程,而其他协程处于休眠状态,适合对任务 |
系统开销 | 切换虚拟地址空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换,开销很大 | 切换时只需保存和设置少量寄存器内容,因此开销很小 | 直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快 |
通信方面 | 进程间通信需要借助操作系统 | 线程间可以直接读写进程数据段(如全局变量)来进行通信 | 共享内存、消息队列 |
1.2 为什么需要有线程
- 因为进程执行过程中会因为阻塞而导致整个进程挂起,比如等待输入,即使进程中有些不依赖这个资源的工作,仍然不会执行。线程的引入能减少时空开销,更好的实现并发。
- 线程的创建和销毁,只需要保留线程自己的栈区和少量寄存器,而进程的创建和销毁远远大于线程的开销。
1.3 线程的类型
用户级线程
这类线程的管理的所有工作都由应用程序完成。执行一个应用程序,操作系统会为该应用程序分配进程号、内存空间等资源。然后在一个线程上运行这个应用程序,这个线程就是主线程。
优点是非常高效,因为不需要进入内核空间,但是不能很好的实现并发。
内核级线程
这类线程的管理的所有工作都由内核完成。应用程序不能进行线程管理,只能调用内核对外开放的该线程的接口。
优点是内核可以将不同线程分配给不同CPU,更好的实现并发,但是效率不高,需要在用户态和系统态之间不断切换。
1.4 并发和并行
-
并发是指在一段时间内,多个任务都在执行,也就是宏观上看是同时进行的,但是微观上同一时间只有一个任务在执行。多个任务交替执行,交替速度非常快,所以宏观上看是同时的。
-
并行是真正的实现了物理上的同时执行。在同一时刻多个任务同时执行。
程序并发执行可以使系统资源的利用率得到提高,从而提高系统的处理能力
并发和并行的区别
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。
并发的多个任务之间是相互抢占资源的。
并行的多个任务之间是不相互抢占资源的。
只有在多CPU系统的情况下,才会发生并行,否则,看似同时发生的事情,其实都是并发执行的。
顺序执行程序的特征
- 顺序性
- 环境的封闭性。独占系统全部资源,不受外界因素的影响。
- 结果的可再现性。程序初始条件保持不变,无论在什么情况下运行,执行结果都是一样的。
并发执行程序的特征
- 并发性
- 共享性
- 制约性
Bernstein条件为了保持程序的可再现性。
1.5 进程的状态
新建、就绪、阻塞、运行、消亡
- 新建:刚刚创建的进程,通常是还没有加载到主存中的新进程。
- 新建->就绪:已具备运行条件,通常将该进程插入就绪队列成为就绪态
- 新建->运行:紧急事件需要立即处理,直接投入运行,称为抢先
- 就绪:进程做好了运行准备,只要获得CPU就可以开始执行。
- 就绪->运行:在就绪队列中等待进入运行态
- 阻塞:进程在等待某些事件,如I/O操作。
- 阻塞->就绪:等待的条件已经满足
- 阻塞->运行:等待事件后需要立即处理
- 运行:进程正在被执行。
- 运行->就绪:时间片用完了,进入就绪
- 运行->阻塞:因为一些资源等待阻塞了,如等待IO输入
- 运行->消亡:进程全部结束成为消亡状态
- 消亡:操作系统从可执行进程组中撤销了进程,或者自身停止,或者因为某种原因被撤销。
进程控制块(PCB)是进程存在的唯一标识,是操作系统对进程进行控制、管理和调度的依据,它在创建进程时产生,在撤销进程时消亡。
1.6 进程间通信(IPC)
临界资源:一次只允许一个进程访问的资源。
临界区:访问临界资源的程序。
对临界资源的访问原则:
- 有空让进
- 忙着等待
- 有限等待
- 让权等待
进程之间的关系
-
进程互斥:多个进程因不能同时访问临界资源而产生的制约关系。
-
进程同步:系统中多个进程中发生的事件存在某种时序关系,需要互相合作,共同完成一项任务。
-
通信:进程之间需要交换数据
信号量:管理相应临界区的公有资源,代表可用资源的实体数。
- 公用信号量:一组互斥关系的进程间共享的信号量。
- 私用信号量:仅供具有同步关系的并发进程间各自独自使用的信号量。
根据信息量的大小可以分为
- 低级通信
- 高级通信
常见的通信方式
无名管道、有名管道、消息队列、信号量、信号、共享内存、套接字
-
无名管道pipe:无名管道没有名字标识,是特殊文件只存在于内存,不存在于文件系统,无格式流并且大小受限,是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
-
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
-
消息队列MessageQueue:消息队列是一个消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限制等缺点。每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
-
共享内存SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
-
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同进程内不同进程之间的同步手段。
-
信号Sinal:用于通知接收进程某个事件已经发生。
Ctrl+C 产生SIGINT信号,表示终止该进程
Ctrl+Z 产生SIGTSTP信号,表示停止该进程,但还未结束
kill -9 1050 给pid为1050的进程发送SIGKILL信号,用来立即结束该进程
-
套接字Socket:套接字也是以一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机的通信。
共享内存是最快的通信方式,套接字是最常用的通信方式,它可以实现不同主机的进程通信。
数据通常是在两个站(点对点)之间进行传输,按照数据流的方向可分为三种传输模式:
- 单工通信:只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。
- 半双工通信:允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输(也可理解为可切换方向的单工通信)。
- 全双工通信:允许数据同时在两个方向上传输,即有两个信道。
1.7 死锁
死锁
一个进程集合中的每个进程,都在等待该集合中的其他进程释放资源所形成的僵局,若无外力推进将无法继续执行。
根本原因
系统资源不足,导致资源不够分配
直接原因
进程推进不当
四个必要条件
- 互斥。资源要么被分配给一个进程,要么未分配。
- 不可抢占。进程所占有的资源在未使用完之前不会被其他进程抢占。
- 占有和等待。进程占有资源,并且等待剩下所需要的资源。
- 环路等待。每个进程都在等待其他进程释放资源为自己所用。
解决方法
-
鸵鸟策略:忽略死锁的发生,当死锁发生的概率很低或者不会造成什么影响的时候选择忽略它。因为解决死锁的成本可能很高。(知道就好,面试别说)
-
死锁的预防:破坏死锁的必要条件
- 破坏互斥,让多个进程同时访问资源
- 破坏不可抢占,抢占式占有资源
- 破坏占有和等待,一次性申请所需要的全部资源
- 破坏环路等待,必须按顺序请求资源
互斥是资源的固有特性,是很难改变和破坏的
-
死锁的避免
银行家算法:假设将资源分配给进程P1,最后所有的进程都能够得以释放资源,那么就分配给它
-
死锁的检测与修复
- 撤销产生死锁的所有进程
- 逐个撤销死锁的进程,直到死锁解除为止
- 逐个剥夺死锁进程所占的资源,重新分配给其他进程
1.8 处理机调度
进程调度算法
-
先来先服务 FCFS
按照进程进入就绪队列的顺序,从队列头开始处理进程,利于长作业,不利于短作业。短作业可能会因为前面长作业运行时间太长而饿死。非抢占式。
-
短作业优先 SJF
按照运行时间最短排序,然后开始执行,利于短作业,长作业可能会被饿死。非抢占式。
-
最短剩余时间优先算法 SRTN
当一个进程进入就绪队列时,和当前运行的进程进行剩余时间比较,剩余时间短的先执行,所以只要新进程就绪,调度程序就可能抢占当前正在运行的进程,是一种抢占式的调度算法。
-
优先级算法 PS
对进程进行优先级编号,优先级高的优先,对于一直等待的进程,可以适当提高优先级,防止被饿死。非抢占式。
-
最高响应比优先算法 HRRN
FCFS+SJF的综合运用。响应比最高的作业开始运行。非抢占式。
响应比=周转时间/运行时间
即:R=(W+T)/T=1+W/T
W为作业等待时间,T为作业执行时间
-
时间片轮转算法 RR
将就绪进程按照FCFS算法排成一个队列,每个进程都统一执行一个时间片,没执行完的进程加入队尾。属于抢占式调度。
当时间片太大,大到超过就绪队列中进程的的最大执行时间,就退化成FCFS
当时间片太小,那么需要不断进行进程间的切换,开销非常大。
-
多级反馈队列算法 RRMF
CPU处理机调度算法,被公认的一种较好的进程调度算法。
算法描述:
- 设置多个就绪队列,并赋予不同的优先级,每个就绪队列分配不同的时间片,同一队列的进程具有相同的时间片。队列级别越高,时间片越短,级别越小,时间片越长,最后一级采用时间片轮转,其他采用先进先出。时间片:1,2,4,8…
- 调度时从最高优先级队列中运行队首进程,当进程用完所在队列对应的时间片,移到低一级队列尾部。
- 高优先级队列为空才调度低一级队列中的进程。
- 等待进程被唤醒时进入原来的就绪队列。
- 当进程第一次就绪时,则进入最高优先级队列。
- 如果处理机正在处理第i队列中的某个进程,又有新进程进入优先级较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。
特点:为提高系统吞吐量(单位时间处理事物的个数)和缩短平均周转时间而照顾短进程;不必估计进程的执行时间,可动态调节。
衡量指标:CPU利用率、吞吐量、周转时间、等待时间、响应时间。
多级调度
处理机调度:
- 高级调度(作业调度,宏观调度):按一定原则对外存输入井中的大量后备作业进行选择,给选出的作业分配内存、输入输出设备等资源,并建立相应进程,以使该作业的进程获得竞争CPU的权力。(就是将外存里的作业调入内存)
- 中级调度(交换调度,平衡负载调度,中程调度):根据存储资源量和进程的当前状态来决定辅存和内存中进程的对换。(就是将进程调出和调回内存)
- 低级调度(进程调度,微观调度):执行分配CPU的任务。
- 线程调度(存在于内存支持线程的系统):处理器的分派与管理等调度均以线程为单位进行。
1.9 线程间同步方式
-
临界区(Critical Section)
通过多线程的串行化来访问公共资源或一段代码。
-
互斥量(Mutex)
采用互斥对象机制,只有拥有互斥对象的线程才具有访问资源的权限。因为互斥对象只有一个,所以保证公共资源不会被多个线程同时访问。
-
信号量(Semaphore )
为控制一个具有有限数量用户资源而设计的,允许多个线程在同一时刻去访问同一个资源,但限制最大线程数目。当信号量的最大资源=1就是互斥量
-
事件(Event)
用来通知线程有一些事件发生,从而启动后继任务的开始。
1.10 僵尸进程和孤儿进程
产生原因
-
一般进程
正常情况下:子进程由父进程创建,子进程再创建新的进程。父子进程是一个异步过程,父进程永远无法预测子进程的结束,所以,当子进程结束后,它的父进程会调用wait()或waitpid()取得子进程的终止状态,回收掉子进程的资源。
-
孤儿进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
-
僵尸进程
僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符(包括进程号 PID,退出状态,运行时间等)仍然保存在系统中,这种进程称为僵尸进程。
问题危害
unix提供了一种机制保证父进程知道子进程结束时的状态信息。
这种机制是:在每个进程退出的时候,内核会释放所有的资源,包括打开的文件,占用的内存等。但是仍保留一部分信息(进程号PID,退出状态,运行时间等)。直到父进程通过wait或waitpid来取时才释放。
但是这样就会产生问题:如果父进程不调用wait或waitpid的话,那么保留的信息就不会被释放,其进程号就会被一直占用,但是系统所能使用的进程号是有限的,如果大量产生僵尸进程,将因没有可用的进程号而导致系统无法产生新的进程,这就是僵尸进程的危害。
孤儿进程是没有父进程的进程,它由init进程循环的wait()回收资源,init进程充当父进程。因此孤儿进程并没有什么危害。
补充:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程去处理。如果父进程在子进程exit()之后,没有及时处理,出现僵尸进程。
解决办法
-
杀死父进程
将其父进程杀死,那么子进程就会从僵尸进程变成孤儿进程,由init充当父进程并回收资源
-
通过信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait处理僵尸进程。
-
fork两次
将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
2、内存管理
存储管理的主要对象是内存,程序和数据只用调入内存才能被CPU调用
主存储空间:
- 存放系统程序
- 存放用户程序及数据
2.1 内存管理分类
-
连续分配管理方式
连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 分区式管理(块式) 。
-
非连续分配管理方式
非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理 、 段式管理、段页式管理。
2.2 内存管理功能
-
内存的分配与回收(malloc,free)
-
地址转换
- 逻辑地址(相对地址)->物理地址(绝对地址)
-
内存的共享与保护
代码和数据共享,提高存储空间的利用率
- 硬件保护:设置上下界寄存器,限定该程序可访问的内存空间范围。
- 软件保护:在程序状态寄存器中设定该程序可访问的内存范围及访问权限
- 将上述两种方法进行配合
-
内存扩充
-
虚地址等
2.3 连续分配管理方式
2.3.1 单一连续存储管理
适用于单用户、单任务操作系统
内存分为两个区域:
- 系统区:存放操作系统内核
- 用户区:装入应用程序
优点:易于管理
缺点:内存空间小的程序造成内存浪费,处理器运行效率低,作业大小受主存容量限制。
2.3.2 分区式存储管理
为了支持多道程序系统和分时系统,支持多个程序并发执行,引入分区式存储管理。分区式存储管理是把内存分为一些大小相等或不等的分区,操作系统占用其中一个分区,其余的分区由应用程序使用,每个应用程序占用一个或几个分区。
特点:全部调入内存+占用连续存储空间
优点:
- 实现多作业或进程的存储,实现多道程序系统,提高了系统资源的利用率。
- 所需硬件较少,易于实现
缺点:
- 内碎片、外碎片、全部调入(内存包含未使用过的信息)导致内存利用率不高。
- 内存需求较大的进程无法执行
- 占用连续存储空间,装入后无法移动,导致难以实现虚拟存储技术和信息共享。
分区式存储管理常采用内存紧缩
固定式分区
将内存分为若干固定大小的连续分区。
-
分区大小相等:只适合与处理多个类型相同的对象
-
分区大小不等:根据程序大小分配适当的分区
产生内碎片:占用分区内未被利用的空间
动态式分区
又称可变式分区,分区的建立是在作业的处理过程中进行的,系统初启时内存只有一个空闲区。相邻的空闲分区合并成一个大的空闲分区。
无内碎片,但产生外碎片:占用分区之间难以利用的空闲分区(通常是小空闲分区)
可变分区分配算法:
- 最先适应算法(首次适应算法):将空闲空间按地址由低到高排序形成空闲空间表。每次分配从头(下次适配法{从上次分配的分区开始})开始查找空闲空间表,在回收空间时查找效率较高,从低地址开始找,高地址尽可能少用。
- 最佳适应算法:将空闲空间按大小由小到大排序形成空闲空间表。每次分配从头开始查找空闲空间表,找到的空闲区必然是最接近进程长度的,所以空间利用率高。
- 最坏适应算法:将空闲空间按大小由大到小排序形成空闲空间表。每次分配只比对第一个分区,若满足则将该分区剩余的作为新的区放回空闲空间表中该有的位置以便下次分配,否则所有空闲区无法满足。该算法查找效率很高。
2.3.3 伙伴系统
分为2的k次幂,k为整数(1<=k<=m)
2^1表示分配的最小分区的大小
2^m表示分配的最大分区的大小
通常2^m是整个可分配内存的大小
2.3.4 内存紧缩
内存紧缩:将各个占有分区向内存一端移动,然后将各个空闲分区合并成为一个空闲分区。
紧缩时机:每个分区释放后,或者内存分配找不到满足条件的空闲分区。
堆结构的存储管理分配算法:
A、B、C、D四串,长度分别为12,6,10,8,堆指针free的初值为0,堆指针向高地址移动对应长度个单位,初始地址分别为0,12,18,28,分配后堆指针为36。这种堆结构的存储管理的分配算法非常简单。
释放内存空间执行内存紧缩:
-
有用户释放存储块即进行回收紧缩
-
执行过程中不回收,直到可利用空间不够分配或者堆指针指向最高地址才进行紧缩。将所有占有块集中到低地址区,剩余的的高地址区成为一整个地址连续的空闲块。
实现步骤:
- 计算占用块的新地址
- 修改用户初始变量表
- 检查每个占有块中存储的数据
- 将所有占有块迁移到新地址
内存紧缩十分复杂,开销很大,属于系统操作,最好不用
2.4 非连续分配管理方式
2.4.1 分页存储管理
基本原理
将每个进程的逻辑内存划分成同样大小的页,物理内存划分成与页相同大小的页面。页面内地址连续,页面间可能不连续。
需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。
优点:
- 无外碎片
- 一个程序不必连续存放
- 便于改变程序占用空间的大小
缺点:要求全部装入内存,没有足够内存,程序不能执行。
分页存储管理的数据结构
-
页表
实现从逻辑页号到物理页面号的地址映射。
-
物理页面表
整个系统有一个物理页面表,描述物理内存空间的分配使用状况,其数据结构可采用位示图和空闲页链表。
-
位示图也称存储页面表,用二进制表示一个存储块,0表示存储块未分配,1表示存储块已分配。
-
空闲链利用空闲块内部单元存放的空闲链指针。
-
-
请求表
整个系统有一个请求表,描述系统内各个进程页表的位置和大小,用于地址转换也可以结合到各进程的PCB里。
页式地址结构及转换
虚拟地址=页号P+页内位移W
原理:逻辑页号、页内偏移地址->查进程页表得到物理页号->物理地址
-
二进制:先求页号二进制,找页面号,页面号二进制+页内位置二进制=物理地址二进制
-
十进制:页号P=虚拟地址/页大小
页内位移W=虚拟地址 mod 页大小
根据页号找页面号
物理地址=页面号B*页大小+页内位移
2.4.2 分段存储管理
基本原理
将程序地址空间划分为若干段,每个进程就有一个二维的地址空间。分段存储管理中,每个段分配一个连续的存储空间,各个段可以不连续地存放在内存的不同分区中。
在回收某个段所占用的空间时,将收回的空间与其相邻的空闲空间合并。
需要硬件支持,实现逻辑地址到物理地址的映射。
优点:无内碎片,外碎片可以通过内存紧缩消除,便于实现内存共享。
缺点:进程必须全部装入内存,造成空间浪费。
分段存储管理的数据结构
段表:每个段的长度不一致
包含段号、段长、段的起始地址等信息。
分段存储管理的地址变换
段与段之间没有前后顺序关系,段与段之间只有互相调用关系,段号是为了区别各段而存在的。因此,段与段之间的关系不再是线性的一维结构,而是二维平面结构。每个段首地址为0,段内拥有连续的一维线性地址空间。
- 越界中断:段号>=段表长度
- 越段中断:段内位移>=段长
页式和段式管理的联系和区别
联系:
- 分页机制和分段机制都是为了提高内存利用率,减少内存碎片。
- 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
区别:
- 应用需求。分页是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
- 大小。页大小固定且由系统决定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的。段的长度不固定,取决于用户所编写的程序。一般情况下,段比页大,段表就比页表短,可以缩短查找时间,提高访问速度。
- 逻辑地址的表示。页式系统地址空间是一维的,即单一的线性地址空间,程序员只需一个标识符即可表示一个地址。分段的作业地址空间是二维的,程序员在标识一个地址时,需要给出段名和段内地址。
2.4.3 段页式存储管理
段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 段页式管理机制 中段与段之间以及段的内部的都是离散的。
2.5 内存扩充技术
为提高内存空间的利用率进行空间的扩展,可以通过覆盖技术、交换技术、虚拟存储技术。
交换技术
交换技术:将处于等待状态或就绪状态的进程从内存换出到外存,把将要执行的进程移入内存。
换入、换出技术是基于优先级的调度,高优先级可以通过交换技术交换出内存中的低优先级进程,然后高优先级进程执行完之后低优先级进程可以交换回内存继续执行。在外存需要设立交换区
优点:增加并发运行的程序数目,并给用户提供适当的响应时间,不影响程序结构。
缺点:对换入和换出的控制增加处理器开销,操作中需要耗费较多的系统时间。程序整个地址空间都进行对换,没有考虑执行过程中地址访问的统计特性。
什么时候进行内存交换?
内存交换通常在许多进程运行且内存紧张时进行,而系统负荷降低就停止。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。
覆盖技术
覆盖技术:一个程序不需要一开始就把它的全部指令和数据都装入内存后再执行。
实现过程:将程序划分为若干功能上相对独立的程序段,将那些不会同时执行的程序段共享同一块内存区域。程序段先保存在磁盘中上,当有关程序段前一部分执行结束,后续程序段调入内存,覆盖前面的程序段。
同一时刻,CPU只能执行B、C中某一个,B、C之间可以覆盖。
![](.\photo\1350357539_7229.png)
优点:减少程序在内存中所占存储空间大小
缺点:编程时必须划分程序模块和确定程序模块之间的覆盖关系,增加编程复杂度。以时间延长换空间节省。
实现方式:
- 函数库方式
- 操作系统支持
交换和覆盖比较
- 与覆盖技术相比,交换不要求程序员给出程序段之间的覆盖结构。
- 交换主要是在进程与作业之间进行,而覆盖则主要在同一作业或进程内进行。 另外覆盖只能覆盖那些与覆盖程序段无关的程序段。
3、页面置换算法
当需要调入某页信息而内存空间不够时,则必须将内存中的某页淘汰掉。用来选择淘汰那一页的规则叫做置换算法(淘汰算法)。
-
随机页面置换算法
要淘汰出去的页面是由一个随机数产生程序随机确定的。
-
先进先出页面置换算法 FIFO
将在内存中最久的页面淘汰。
-
最近最久未使用页面置换算法 LRU
最久未使用的淘汰。
-
最近未使用页面置换算法 UNR
将第1个最近未被访问的页淘汰。
-
最不经常使用页面算法 LFU
将内存中页面使用次数最少的淘汰。
-
最佳页面置换算法
将以后不再使用的或者相当长的时间内不会使用的淘汰。理想算法,难以实现。
4、上下文切换
4.1 上下文
每个任务运行前,CPU都需要知道任务从哪里加载,从哪里开始运行,这就涉及到:
- CPU寄存器:CPU内置的容量小,速度极快的内存。
- 程序计数器(PC):存储CPU正在执行的指令位置,或者即将执行的指令位置。
这两个是CPU运行任何任务前都必须依赖的环境,叫做CPU上下文
4.2上下文切换
CPU上下文切换需要履行的步骤:
- 将前一个CPU的上下文(也就是CPU寄存器和程序计数器里的内容)保存起来;
- 然后加载新任务的上下文到CPU寄存器和程序计数器;
- 最后跳转到程序计数器所指的新位置,运行新任务。
被保存起来的上下文会存储到系统内核中,等待任务重新调度执行时再次加载起来。
CPU上下文切换分三种:进程上下文切换、线程上下文切换、中断上下文切换。
4.2.1 进程上下文切换
进程是由内核管理和调度的,进程的切换只能发生在内核态。
保存上下文和恢复上下文需要内核在CPU上运行才能完成
进程的上下文不但包括虚拟内存、栈、全局变量等用户空间资源,还包括内核堆栈,寄存器等内核空间状态。
进程切换的场景有:
- 时间片用完
- 系统资源不足
- 通过睡眠函数sleep把自己挂起来
- 运行高优先级进程
- 发生硬中断,进程被挂起,执行内核中的中断服务进程
4.2.2 线程上下文切换
线程上下文切换时,共享相同的虚拟内存和全局变量等资源不需要修改。而线程自己的私有数据,如栈和寄存器等,上下文切换时需要保存。
线程切换分两种情况:
- 前后两个线程属于不同进程
- 前后两个线程属于同一个进程(速度更快,消耗更少资源)
4.2.3 中断上下文
中断上下文,只包含内核态中断服务程序执行所必须的状态,也就是CPU寄存器、内核堆栈、硬件中断参数等。
4.3 系统调用
进程的运行空间分为用户空间和内核空间
- 用户态:在用户空间运行的进程叫用户态,用户态运行的进程可以直接读取用户程序的数据。
- 系统态:在内核空间运行的进程叫系统态,系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
从用户态到内核态需要经过系统调用,也就是我们运行的用户程序中,凡是与系统态级别的资源有关的操作,都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
系统调用大致分为:
- 设备管理。完成设备的请求或释放,以及设备启动等功能。
- 文件管理。完成文件的读、写、创建及删除等功能。
- 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
- 进程通信。完成进程之间的消息传递或信号传递等功能。
- 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
系统调用会发生CPU上下文切换,先保存用户态状态,然后加载内核态内容。系统调用结束后,再加载回用户态状态。一次系统调用,会有两次CPU上下文切换(用户态-内核态-用户态)
与进程上下文切换区别:
- 进程上下文切换是从一个进程切换到另一个进程;
- 系统调用过程中一直是一个进程在运行。
线程上下文切换为什么比进程快?
- 进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间。
- 虚拟地址转化为物理地址需要查找页表,而使用Cache来缓存常用的地址映射可以加速页表查找。每个进程有自己的页表,当进程切换后页表也要进行切换,页表切换后cache就失效了,cache失效导致命中率降低,程序变慢。
系统调用和一般过程调用的区别:
- 运行的状态不同。系统程序和用户程序。
- 进入的方式不同。系统调用不允许由调用过程直接转向被调用过程,通常是通过访问管中断(即软中断)进入,先进入操作系统,经分析后,才能转向相应的命令处理程序。一般的过程调用可以直接由调用过程转向被调用的过程。
- 返回方式的不同。
- 代码层次的不同。系统级程序和用户及程序。
5、磁盘调度算法
新的服务请求会添加到磁盘驱动器的待处理请求队列,磁盘I/O调度主要目标是减少请求队列中对应的平均柱面定位时间。
访问时间:
- 寻道时间:磁臂移动磁头到包含目标扇区柱面的时间
- 旋转延迟:磁盘旋转目标扇区到磁头下的额外时间
-
先来先服务算法 FCFS
磁盘队列按顺序处理
-
最短寻道时间优先算法 SSTF
选择最接近磁头位置的待处理请求
-
扫描算法(电梯算法)SCAN
磁臂从磁头一端开始,向另一端移动,到达另一端时,磁头移动方向翻转,在移过每个柱面时,处理请求。
-
循环扫描算法 C-SCAN
磁臂从磁头一端开始,向另一端移动,到达另一端时,立即返回到磁盘开头。
-
LOOK调度和C-LOOK调度
对应SCAN和C-SCAN的改进,磁头不用走到尽头,到达该方向的最后一个请求后即可返回。
对于任何调度算法,性能很大程度上取决于请求的数量和类型。
5、设备管理
设备分类(按共享属性分类)
-
独占设备
-
共享设备
-
虚拟设备
假脱机技术:将独占型设备改造为可供多个进程使用的共享设备,以提高设备的利用率。
设备管理的功能
- 完成设备的分配与回收
- 实现设备的启动
- 实现虚拟设备
5.1 设备控制器
CPU不是直接与I/O设备打交道。而是通过设备控制器。设备控制器接收来自CPU的指令,完成对设备的控制。
一般分为两类
- 控制字符设备的控制器
- 控制块设备的控制器
基本功能:
- 接收和识别命令
- 数据交换
- 标识和报告设备状态
- 地址识别
- 数据缓冲
- 差错控制
组成:
- 设备控制器与处理机的接口
- 设备控制器与设备的接口
- I/O逻辑
数据传送控制方式:
-
程序直接控制方式
由用户进程直接控制内存或CPU和外围设备之间进行信息传送的方式。
-
中断控制方式
当外设需要与CPU进行信息交换时,由外设向CPU发送请求信号,使CPU暂停正在执行的程序,转去执行数据的输入/输出操作,数据传送结束后,CPU再继续执行被暂停的程序。
-
DMA方式
又称直接存取方式,在外设和内存之间开辟直接的数据交换通路,传送无需CPU介入。
-
通道控制方式
内存和设备通过通道(专门负责输入/输出的硬件)直接进行数据交换。
5.2 中断
中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保护现场后自动去执行相应的处理程序,处理完该事件后在返回中断处继续执行原来的程序。
中断一般三类:
- 外中断:CPU外部引起的,如:I/O中断(输入输出已完成,发出下一个请求),时钟中断(时间片已到)
- 内中断:CPU内部事件或程序执行引起的,也叫异常,如:程序非法操作,地址越界,浮点溢出,缺页中断
- 系统调用:程序中使用了系统调用引起的
中断处理两个步骤:
- 中断响应:由硬件实施
- 中断处理:由软件实施
对中断的处理方式
-
屏蔽(禁止中断)
当处理机正在处理一个中断时,屏蔽掉所有的中断,直到处理机完成本次中断,再去检查是否有中断发生,有则处理新的中断,若无,则返回被中断的程序。所有中断按顺序依次执行。
优点是简单,不适合实时性要求较高的中断请求
-
嵌套中断
设置中断的优先级
- 多个不同优先级的中断请求时,CPU优先响应最高优先级的中断请求;
- 高优先级中断请求可以抢占正在运行的低优先级中断的处理机。
中断处理程序的处理过程
- 测定是否有未响应的中断信号
- 保护被中断进程的CPU环境
- 转入相应的设备处理程序
- 中断处理
- 恢复CPU的现场并退出中断
6、虚拟内存
6.1 虚拟内存概念
虚拟内存:虚拟内存使用部分加载的技术,让一个进程或者资源的某些页面加载进内存,从而能够加载更多的进程,甚至能加载比内存大的进程,这样看起来好像内存变大了,这部分内存其实包含了磁盘或者硬盘,并且就叫做虚拟内存。
现代操作系统通过虚拟内存技术来扩大物理内存,虚拟内存每一页都映射在物理内存或磁盘上所以虚拟内存会比物理内存大,程序里访问的是虚拟地址,当程序访问页映射在磁盘上时,就会发生缺页中断,调用中断处理程序将页载入物理内存。例如:32位Linux的每个用户进程都可以访问4GB的线性地址空间, 而实际的物理内存可能远远少于4GB,采用分页机制 ,Linux仅把可执行映像的一小部分装入物理内存。当需要访问未装入的页面时,系统产生一个缺页中断,把需要的页读入物理内存并重新执行失败的指令。
虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。
虚拟内存的基本思想:每个程序拥有自己的地址空间,这个空间被分割成多个页面,每一页有连续的地址范围。这些页被映射到物理内存或者外部磁盘存储器。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射;当程序引用到一部分不在物理内存中的地址空间(缺页中断)时,由操作系统负责将缺失的部分装入物理内存(页面置换)并重新执行失败的指令。
虚拟内存很适合在多道程序系统中使用。但一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。
6.2 局部性原理
局部性原理:在某个较短的时间段内,程序执行局限于某一部分,访问的存储空间也局限于某个区域。
虚拟内存技术实际上就是建立了“内存—外存”的两级存储器的结构,利用局部性原理实现高速缓存。正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。
表现在以下两个方面:
- 时间局部性:某条指令执行或者某个数据被访问过不久可能会再被执行或访问。(因为程序中存在大量的循环操作)
- 空间局部性:某个存储单元被访问过不久其附近单元也将被访问。(因为指令是顺序存放和执行的,数据一般是以向量、数组、表等形式簇聚存储的)
6.3 虚拟存储器
定义:具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统。
即:程序在运行之前,没必要全部装入内存,仅把当前要运行的页面装入即可,当程序运行时,如果需要其他页面,在进行页面调入或置换。
虚拟内存指能实际分配的磁盘空间,而虚拟存储器是一种机制,是整个CPU访问内存过程的体现。
特征:
- 多次性。允许作业被分成多次调入内存运行。
- 对换性。允许作业在内存运行过程中进行换进和换出。
- 虚拟性。用户看到的内存容量远大于实际内存容量。
6.4 虚拟内存技术的实现
虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。
-
请求分页存储管理:建立在分页管理之上,增加了请求调3页功能和页面置换功能。
-
请求分段存储管理:建立在分段存储管理之上,增加了请求调段功能、分段置换功能。
-
请求段页式存储管理:程序先分段再分页,进程分配一张段表,每个段再分配一个页表。
逻辑地址=段号S+段内页号P+页内位移W
不管哪种方式,都需要一定的硬件支持。一般需要以下支持:
- 一定容量的内存和外存。
- 页表机制或段表机制。作为主要的数据结构。
- 中断机构。当用户要访问的部分尚未调入内存,则发生中断。
- 地址变换机构。逻辑地址到物理地址的变换。
同步/异步、阻塞/非阻塞
一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪” 和 “数据读写”。
数据就绪阶段分为阻塞和非阻塞。阻塞表示调用IO方法,数据没到达之前,当前线程会被挂起,只有得到结果才返回;非阻塞表示不能立刻得到结果之前,该调用不会阻塞当前线程。
数据读写阶段分为同步和异步。同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞);异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。
四种组合方式
-
同步阻塞
发送方发送请求就一直等待响应,接收方不能马上得到结果就一直等到结果才响应,之后发送方才去做其他事。
-
同步非阻塞(实际不应用)
发送方发送请求就一直等待响应,接收方不能马上得到结果就立即返回去做其他事,发送方没有得到处理结果就一直等,直到接收方得到结果响应后才去做其他事。
-
异步阻塞(实际不应用)
发送方发送请求不等待响应就去做其他事,接收方不能马上得到结果就一直等到结果才响应,期间不能做其他事。
-
异步非阻塞(效率最高)
发送方发送请求不等待响应就去做其他事,接收方不能马上得到结果就立即返回去做其他事,直到接收方得到结果再去响应发送方。
协程
协程又称微线程,是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的上下文(寄存器和栈)。协程调度时,将寄存器上下文和栈保存到其他地方,切回来时,恢复先前保存的上下文,这种切换完全在用户态进行,基本没有内核切换的开销。
协程的优点
- 无需系统内核的上下文切换,减小开销;
- 无需原子操作锁定及同步的开销,不用担心资源共享的问题
- 单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理
协程的缺点
- 协程的本质是个单线程,所以无法使用 CPU 的多核
- 进行阻塞操作会阻塞整个程序
线程和协程的区别
- 线程是抢占式的,协程是非抢占式的
- 一个线程可以多个协程,一个进程也可以单独拥有多个协程
- 线程进程都是同步机制,而协程则是异步
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
对称协程和非对称协程
- 对称协程:所有协程都只有一种控制转移语义,将控制转移至其他协程
- 非对称协程:调用者将控制转移到被调用者,被调用者运行完毕后只能返回到调用者,不能返回到其他协程
有栈协程(stackfull)和无栈协程(stackless)
- 有栈协程:每个协程保存单独的上下文,协程的唤醒和挂起就是拷贝、上下文切换
- 无栈协程:单个线程内所有协程都共享同一个执行栈,协程的切换就是简单的函数返回
页缓存(Page Cache)
为什么需要页缓存?
文件一般存放在硬盘,CPU不能直接访问硬盘的数据,而是需要先将硬盘中的数据读入内存中,然后才能被CPU访问。由于硬盘读写速度比内存慢,所以为了避免每次读写文件都对硬盘进行操作,使用了页缓存机制来对文件的数据进行缓存。
什么是页缓存?
用户对文件中的某个数据块进行读写操作时,内核首先会申请一个内存页(页缓存)与文件中的数据块进行绑定,实际上就是对文件的页缓存进行读写。
- 当从文件读取数据时,如果数据所在的页缓存已存在就直接拷贝给用户,否则内核申请一个空闲的内存页,然后从文件读取数据到页缓存再拷贝给用户
- 当向文件写入数据时,如果数据所在的页缓存已存在就直接把新数据写入页缓存,否则内核申请一个空闲的内存页,然后从文件读取数据到缓存页再把新数据写入到页缓存中。对于被修改的页缓存,内核会定时把这些页缓存刷新到文件中。
如何把逻辑地址转化为物理地址
十六进制
逻辑地址 = 页号 + 页内地址
物理地址 = 块号 + 页内地址
举例:设某用户的编程空间共32个页面,每页为1KB,内存容量为16KB。假定用户程序的页表如下所示。请计算逻辑地址0A5CH所对应的物理地址。
页号 块号 0 5 1 10 2 4 3 7 0A5CH = 0000 1010 0101 1100B
1KB = 2^10B
页内地址 10 0101 1100,页号 0000 10 = 2,块号4 = 0001 00
物理地址 = 0001 0010 0101 1100 = 125CH
非十六进制
物理地址 = 块号 * 页内大小 + 页内地址
页号 = 逻辑地址 / 页内大小
页内地址 = 逻辑地址 % 页内大小
举例:在采用分页存储管理的系统中,某作业J的逻辑地址空间为4页(每页2KB),且已知该作业的页面映像表(即页表)如下:试借助地址变换图求出有效逻辑地址4865所对应的物理地址。
页号 块号 0 2 1 4 2 6 3 8 页号 = 4865 / 2048 = 2,页内地址 = 4865 % 2048 = 769,块号 = 6
物理地址 = 6 * 2048 + 769 = 13057
虚拟地址空间有哪些部分
32位系统中,虚拟地址空间为4G,高地址1G为内核空间,低地址3G为用户空间。
-
内核空间
存放内核的代码和数据,所有进程的内核代码段都映射到同样的物理内存,并在内存中持续存在。内核空间不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
-
用户空间
给各个进程使用,只允许访问部分资源,不能直接访问内核空间,一般分为
- 栈空间
- 共享区:内存映射和动态库所在的内存
- 堆空间:存放进程运行中被动态分配的内存段,大小并不固定,可动态扩张和缩减。
- BSS段(未初始化数据段)
- DATA段(已初始化数据段)
- TEXT段(代码段):存放程序的二进制代码。
- 保留区:位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。
![图片说明](https://i-blog.csdnimg.cn/blog_migrate/adb60834f6aafc178a91d05154e9eb0c.png)
线程间的通信方式
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
- 锁机制:互斥锁、条件变量、读写锁、自旋锁
- 信号量机制:包括无名线程信号量和命名线程信号量
- 信号机制
cache(缓存)和buffer(缓冲)的区别
- 缓冲:为了提高内存和I/0设备之间数据交换的速度,比如每秒写100次硬盘,可以使用buffer将数据暂存起来,变成每秒写10次,减少对系统的冲击。
- 缓存:为了提高cpu和内存之间的数据交换速度,将内存的数据读到cache,下次再访问同样数据就可以直接读取cache的数据。
软中断和硬中断的区别
- 硬中断是由外设引发的,软中断是执行中断指令产生的
- 硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需中断控制器
- 硬中断是可屏蔽的,软中断不可屏蔽
- 硬中断处理程序要确保能快速完成任务,这样程序执行时不会等待较长时间,称为上半部,软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部
为什么共享内存是最快的
将同一块内存区域映射到共享它的不同进程地址空间中,使得这些进程间的通信不需要再经过内核,只需对该共享的内存区域进行操作就可以,但是它需要用户自己进行同步操作。
多线程和多进程的选择
- 频繁修改:需要频繁创建和销毁的优先使用多线程
- 计算量:需要大量计算的优先使用多线程,因为需要消耗大量CPU资源且切换频繁,所以多线程好一点
- 相关性:任务间相关性比较强的用多线程,因为线程之间的数据共享和同步比较简单,相关性比较弱的用多进程,因为一个进程的崩溃不会引起其他进程崩溃。
- 多分布:可能要扩展到多机分布的用多进程,多核分布的用多线程。
局部性原理的应用场景
- 虚拟内存
- CPU的多级缓存技术
- 多级页表
- 页面置换算法
- copy-on-write:子进程修改数据时不是所有的页面都复制,只将修改的页进行复制,代码段和只读数据段不被允许修改,所以无需复制。
- 计算机网路中的CDN。由于同一个用户,甚至同一个地区的用户,在访问的数据上都存在局部性。大型的网站都引入了CDN来帮助分散主机的压力。CDN可以看作是服务器的一个cache。
快表和多级页表
在分页内存管理中,很重要的两点是:
- 加快虚拟地址到物理地址的转换速度
- 解决虚拟内存空间大的问题
快表(TLB)
快表用来加速虚拟地址到物理地址的转换。可以把快表理解为一种特殊的高速缓冲存储器,其中的内容是页表的一部分或者全部内容。访问快表的速度比访问内存的速度快很多,作用与页表相似,但提高了访问速率。如果采用页表做地址转换,读写内存数据时CPU要访问两次主存,使用快表需要访问一次高速缓冲,一次主存,可加快查找并提高指令执行速度。
使用快表地址转换的流程:
- 根据虚拟地址中的页号查找快表
- 如果该页在快表中,直接从快表中读取对应的物理地址
- 如果该页不在快表中,就访问主存的页表,从页表中获得物理地址,同时将页表中的映射表项添加到快表中
- 当快表填满后,又要登记新页时,就按照一定的策略淘汰掉快表中的一个页
多级页表
多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表需要将所有的页表项加载到内存,分级页表只需将较小的一级页表项加载到内存,二级页表基于局部性原理,在需要时再创建。多级页表属于时间换空间的典型场景。
打开文件过程
操作系统根据文件名a,在系统文件打开表中查找
第一种情况:
如果文件a已经打开,则在进程文件打开表中为文件a分配一个表项,将该表项的指针指向系统文件打开表中文件a对应的表项,然后在PCB为文件分配一个文件描述符fd,指向进程文件打开表对应的表项,文件打开完成。
第二种情况:
如果文件a未打开,查看含有文件a信息的目录表是否在内存中,如果不在就将目录表装入到内存,作为cache。根据目录表中文件a对应项找到文件控制块FCB在磁盘的位置,将FCB装入到内存的Active-inode中,然后在进程文件打开表中为文件a分配一个表项,将该表项的指针指向系统文件打开表中文件a对应的表项,在PCB为文件分配一个文件描述符fd,指向进程文件打开表对应的表项,文件打开完成。
中断处理流程
- 某一中断源向CPU发起中断请求
- CPU响应中断后,将状态标志寄存器压入堆栈保护
- 将其中的中断标志位清除从而关闭中断
- CPU将当前CS(代码段地址)和IP(将要执行的下一条地址)压入堆栈保护断点
- CPU确定提出请求的中断源,获得中断向量号,在对应的中断向量表获得中断入口地址,装入CS和IP中
- 将断点处各寄存器的内容压入堆栈保护现场
- 此时程序跳转至中断服务子程序执行
- 中断处理完毕,将堆栈各寄存器内容弹栈,恢复断点处各寄存器的值
- 在中断服务子程序最后安排一条返回指令,执行该指令将堆栈中CS和IP的值弹出,恢复主程序断点处地址值,同时恢复标志寄存器的内容。程序转至被中断的程序继续执行。
虚拟地址和物理地址如何映射?
分段
虚拟地址由段选择因子和段内偏移量组成,段选择因子里有段号,作为段表的索引。通过段号找到段表保存的段基地址、段界限和特权等级。段内偏移量应该位于段界限之内,如果合法,将段基地址+段内偏移量得到物理内存地址。
分页
虚拟地址由页号和页内偏移量组成,根据页号,从页表里找到对应的物理页号,将物理页号+页内偏移量得到物理内存地址。
父子进程共享的资源
经过fork()以后,父进程只复制了自己的PCB块。而代码段,数据段和用户堆栈内存空间并没有复制一份,而是与子进程共享。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。
子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
子进程与父进程的区别在于:
1、父进程设置的锁,子进程不继承(因为如果是排它锁,被继承的话,矛盾了)
2、各自的进程ID和父进程ID不同
3、子进程的未决告警被清除;
4、子进程的未决信号集设置为空集。
多级缓存
- L1 Cache:分为数据缓存和指令缓存,逻辑核独占(不到1MB)
- L2 Cache:物理核独占,逻辑核共享
- L3 Cache:所有物理核共享(10MB左右)
存储器存储空间大小:内存>L3>L2>L1>寄存器
存储器速度快慢排序:寄存器>L1>L2>L3>内存
当CPU运作时,它首先去L1寻找它所需要的数据,然后去L2,然后去L3。如果三级缓存都没找到它需要的数据,则从内存里获取数据。
![img](https://i-blog.csdnimg.cn/blog_migrate/0bfedbb7bd242eb6ed233c62637f4b4a.jpeg)
零拷贝技术
普通模式数据交互
- 读数据涉及2次空间切换、1次DMA拷贝、1次CPU拷贝
- 写数据涉及2次空间切换、1次DMA拷贝、1次CPU拷贝
可见传统模式下涉及多次空间切换和数据冗余拷贝,效率并不高
![img](https://i-blog.csdnimg.cn/blog_migrate/efe6d0aeb945e93976b1ec625f2a6f10.jpeg)
零拷贝技术
出现原因
传统模式下两次数据拷贝都需要CPU的参与,并且涉及用户态与内核态的多次切换,加重了CPU负担。使用零拷贝技术可以降低冗余数据拷贝、解放CPU。
mmap方式
mmap是Linux提供的一种内存映射文件的机制,将内核读缓冲区地址与用户空间缓冲区地址进行映射,从而实现内核缓存区与用户缓冲区的共享。这样状态切换没有减少,但减少了一次CPU拷贝。
![img](https://i-blog.csdnimg.cn/blog_migrate/cab0042bfe6c584d33fb4d7b0b102147.jpeg)
sendfile方式
直接从内核缓冲区复制数据到socket缓冲区。数据不经过用户缓冲区,所以不能修改。2次状态切换、1次CPU拷贝、2次DMA拷贝。
![img](https://i-blog.csdnimg.cn/blog_migrate/07a97e1c9ba2e28e75a3bbcff5378f67.jpeg)
sendfile+DMA收集
将内核缓冲区中对应的数据描述信息(文件描述符、地址偏移量等信息)记录到socket缓冲区中。DMA控制器根据socket缓冲区中的地址和偏移量将数据从内核缓冲区拷贝到网卡中,从而省去了内核空间中仅剩1次CPU拷贝。数据不可修改。2次状态切换、0次CPU拷贝、2次DMA拷贝。
![img](https://i-blog.csdnimg.cn/blog_migrate/a8f67d7ee7a03967f288dd9979683c28.jpeg)
splice方式
在内核缓冲区和socket缓冲区之间建立管道来传输数据,避免了两者之间的 CPU 拷贝操作。
局限:两个文件描述符参数中有一个必须是管道设备。
![img](https://i-blog.csdnimg.cn/blog_migrate/6b6b62869167e5c6521a3ce620d35552.jpeg)
进程同步的方式
-
信号量
用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。基本原理是两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。
-
管程
管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块。通过使用条件变量提供对同步的支持,这些条件变量包含在管程中,并且只有在管程中才能被访问。
-
消息传递
进程消息传递所需要的最小操作集。一个进程以消息的形式给另一个指定的目标进程发送消息;进程通过执行receive原语接收消息,receive原语中指明发送消息的源进程和消息。
多进程和多线程的运用场景
多进程
- nginx主流的工作模式是多进程(也支持多线程)
- 几乎所有的web server服务器都有多进程,至少有一个守护进程配合一个worker进程
- chrome浏览器是多进程方式。
- 可能存在一些网页不符合编程规范,容易崩溃,采用多进程一个网页崩溃不会影响其他网页
- 网页之间相互隔离,保证安全,不必担心某个网页中的恶意代码会取得存放在其他网页中的敏感信息
- redis采用“多进程单线程”模型(平时工作是单个进程,涉及耗时操作如持久化或aof重写用到多进程)
多线程
- 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时)
- 提供非均质的服务(有优先级任务处理)事件响应有优先级
- 单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延
- 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
线程的状态有哪些
- 新建状态:新创建了一个线程对象
- 就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,除CPU之外,其它运行所需资源都已全部获得
- 运行状态:就绪状态的线程获取了CPU,执行程序代码
- 阻塞状态:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
- 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,线程放入“**等待池”**中
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,把该线程放入**“锁池”**中
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
- 消亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期