并发
: 一个处理器同时处理多个任务, 这里的同时是宏观概念, 指的是在某个时间段内这些任务交替执行并行
: 多个处理器或者是多核的处理器同时处理多个不同的任务, 这里的同时就是同一时刻真正的一块儿执行多个任务共享是指系统中的资源可以被多个并发进程共同使用, 有两种共享方式:互斥共享
和同时共享
互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,需要用同步机制来实现对临界资源的访问
虚拟技术把一个物理实体转换为多个逻辑实体, 主要有两种虚拟技术:时分复用技术和空分复用技术
。 例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换
同步
就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。是一种线性执行的方式,执行的流程不能跨越。一般用于流程性比较强的程序,比如用户登录,需要对用户验证完成后才能登录系统。
异步
则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。是一种并行处理的方式,不必等待一个程序执行完,可以执行其它的任务,比如页面数据加载过程,不需要等所有数据获取后再显示页面。
内核态
:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
用户态
:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取
用户态切换到内核态的3种方式: 系统调用、异常、外围设备的中断
进程是一个正在执行程序的示例,拥有自己的程序计数器和内部状态,是系统进行资源分配和调度的一个独立单位
。进程控制块 (Process Control Block, PCB)
描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB
的操作
线程是独立调度的基本单位。 一个进程中可以有多个线程,它们共享进程资源。就拿浏览器来说,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件
进程间通信 (IPC)
需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信就绪态
就是获取了出cpu外的所有资源,只要处理器分配资源就可以马上执行。
运行态
就是获得了处理器分配的资源,程序开始执行。
阻塞态
当程序条件不够时候,需要等待条件满足时候才能执行,如等待i/o操作时候,此刻的状态就叫阻塞态。
新建(New):线程在进程内派生出来,它即可由进程派生,也可由线程派生。
阻塞(Block):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。
激活(Unblock):如果阻塞线程的事件发生,则该线程被激活并进入就绪队列。
调度(Schedule):选择一个就绪线程进入执行状态。
结束(Finish):如果一个线程执行结束,它的寄存器上下文以及堆栈内容等将被释放
在用户空间中实现线程
用户级线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。
在内核中实现线程
内核级线程由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。
在用户空间中实现线程的优势
在用户空间中实现线程的缺点
协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。 最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。 这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。其本质是用户空间下的线程。(即不存在上下文切换,用户与内核地址空间切换,但协程是在单线程中运行)
Lua从5.0版本开始使用协程,通过扩展库coroutine来实现。
Python可以通过 yield/send 的方式实现协程。在python 3.5以后,async/await 成为了更好的替代方案。
Go语言对协程的实现非常强大而简洁,可以轻松创建成百上千个协程并发执行。
Java语言并没有对协程的原生支持,但是某些开源框架模拟出了协程的功能,Kilim框架的源码
等待(Waiting)
状态, 其实只是把阻塞状态更细化分成了BLOCKED
和WAITING
两种, 区别在于等待是主动等待, 阻塞是被动阻塞, 就是说等待的状态下只有主动去唤醒它才能进入就绪状态, 而阻塞的情况下线程本身一直在争抢锁, 一旦抢到了不需要别人主动去唤醒它自己就会切换到就绪状态管道是连接两个一个读进程和一个写进程之间用于实现数据交换的一个共享文件
是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
一种比较复杂的通信方式,用于通知接收进程某个事件已经发生, 是进程间通信机制中唯一的异步通信机制
与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
指两个或多个进程共享一个给定的存储区。
套接字也是一种进程间通信机制,可以实现不同主机间的进程通信。一个套接口可以看做是进程间通信的端点,每个套接口的名字是唯一的,其他进程可以访问,连接和进行数据通信
如果多个进程间存在时序关系,需要协同工作以完成一项任务,则称为同步
;如果不满足协同的条件,而知识因为共享具有排他性资源时所产生的关系称为互斥
, 互斥是一种特殊的同步
信号量
和PV原语操作
是有Dijkstra发明的,它是最为广泛的互斥方法之一
信号量和PV操作原理:
缺陷: 程序的易读性相对较差,对于信号量的管理也分散在各个参与对象中,因此有可能引起死锁,进程饿死等问题
安全性
、互斥性
、共享性
通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问
在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占
互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享
通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
信号量、管程、互斥是进程的同步机制,而信号量、互斥也可用于线程的同步,但管程只在进程同步中被用到;
线程的同步除了信号量、互斥外,还有临界区、事件,没有看到说将这两种方式作为进程的同步方式(不知道);
生产者-消费者问题
哲学家就餐问题
读者-写者问题
具体参考:
死锁是指多个进程在运行过程中,因为争夺资源而造成的一种僵局,如果没有外力推进,处于僵局中的进程就无法继续执行
互斥条件
:进程对所分配的资源进行排他性的使用请求和保持条件
:进程被阻塞的时候并不释放锁申请到的资源不可剥夺条件
:进程对于已经申请到的资源在使用完成之前不可以被剥夺环路等待条件
:发生死锁的时候存在的一个 进程-资源 环形等待链先来先服务FCFS
既可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度;因此对于长作业比较有利。
短作业优先SPF
作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱。
高优先权优先HRRF
既可以作为作业调度也可以作为进程调度算法;调度作业时,从就绪队列中选择优先级最高的作业进行处理;由于涉及到了优先级,因此可以分为抢占式和非抢占式;而且优先级的确定也可以分为静态优先级(事先根据进程类型,进程对资源的需求,用户要求等方面确定一个固定值);动态优先级(随进程的推进或者等待时间而增加或者减少)。
最高响应比优先HRN
FCFS可能造成短作业用户不满,SPF可能使得长作业用户不满,于是提出HRN,选择响应比最高的作业运行。响应比=1+作业等待时间/作业处理时间。
时间片轮转
按到达的先后对进程放入队列中,然后给队首进程分配CPU时间片,时间片用完之后计时器发出中断,暂停当前进程并将其放到队列尾部,循环。
多级反馈队列
目前公认较好的调度算法;设置多个就绪队列并为每个队列设置不同的优先级,第一个队列优先级最高,其余依次递减。优先级越高的队列分配的时间片越短,进程到达之后按FCFS放入第一个队列,如果调度执行后没有完成,那么放到第二个队列尾部等待调度,如果第二次调度仍然没有完成,放入第三队列尾部…。只有当前一个队列为空的时候才会去调度下一个队列的进程
生成装入模块形成完整逻辑地址
三种链接方式:
静态链接
: 装入前链接装入时动态链接
: 运行前边装入边链接运行时动态链接
: 运行时需要才装入并链接目的:确定完整的逻辑地址
装入内存形成物理地址
三种装入方式:
绝对装入
:编译时产生绝对地址,单道程序环境静态重定位
:装入时将逻辑地址转化为物理地址,多道批处理操作系统动态重定位
:运行时将逻辑地址转化为物理地址,需要设置定位寄存器,现代操作系统目的:确定最终的物理地址
单一连续分配
: 内存分为系统区和用户区,只能有一道用户程序(无外部碎片,有内部碎片)固定分区分配
: 将整个用户空间划分若干个固定大小的分区,分区大小可相等也可不相等,每个分区只能装入一道作业,分区说明表进行管理(无外部碎片,有内部碎片)动态分区分配
: 根据进程的大小动态地建立分区,使用空闲分区表或空闲分区链进行管理,动态分区算法选择合适的空闲分区分配(有外部碎片,无内部碎片)首次适应算法(FF)
在分配内存时,从空闲分区表的头开始查找,直到找到第一个大小能够满足的空闲分区为止。
该算法实现简单,优先利用低地址的空闲分区,从而保留了高地址部分的大空闲分区,为后期的大作业分配大的内存空间创造了条件,缺点是低地址部分不断被划分,留下了很多难以利用的,很小的空闲分区,且每次查找都是从地址址部分开始查找,无疑会增加查找的开销
循环首次适应算法(NF)
和首次适应算法所不同,在为进程分配内存空间时,不再是每次都从低地址开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,因此需要设置一个查询指针,用来指示下一次起始查询的空闲分区,如果最后一个空闲分区的大小仍然不能满足要求,则应该返回到第一个空闲分区。该算法使空闲分区二分内部得更均匀,适当减少了查找空闲分区时候的开销,但这样容易拆分大的空闲分区
最佳适应算法(BF)
每次为作业分配内存的时候,总是把满足要求的,但又是最小的空闲分区分配给进程,即寻找一个空闲分区,使得的值最小,这样可以避免“大材小用”,但是容易产生细小的碎片
最坏适应算法(WF)
和最佳适应算法相反,它总是为进程挑选满足要求的,但又是最大的空闲区分配给进程,即寻找一个空闲分区,使得的值最大,这样可以使产生碎片的可能性最小,对中小作业有利
基本分页存储管理中不具备页面置换功能(即没有实现虚拟内存的功能),因此需要整个程序的所有页面都装入内存之后才可以运行。因为程序数据存储在不同的页面中,而页面又离散的分布在内存中,因此需要一个页表来记录逻辑地址和实际存储地址之间的映射关系,以实现从页号到物理块号的映射。由于页表也是存储在内存中的,因此和不适用分页管理的存储方式相比,访问分页系统中内存数据需要两次的内存访问
(一次是从内存中访问页表,从中找到指定的物理块号,加上页内偏移得到实际物理地址;第二次就是根据第一次得到的物理地址访问内存取出数据)。
为了减少两次访问内存导致的效率影响,分页管理中引入了快表(或者联想寄存器)机制(联想到缓存机制)
,包含快表机制的内存管理中,当要访问内存数据的时候,首先将页号在快表中查询,如果查找到说明要访问的页表项在快表中,那么直接从快表中读取相应的物理块号;如果没有找到,那么访问内存中的页表,从页表中得到物理地址,同时将页表中的该映射表项添加到快表中,可能存在快表换出算法
优缺点:没有外部碎片,内存利用率高。但各页中内容没有关联,不利于编程和共享
补充概念:
页表长度
是指这个页表中总共有几个页表项,即总共有几个页;页表项长度
是指每个页表项占多大的存储空间;页面大小
是指一个页面占多大的存储空间把程序按内容或过程(函数)关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应一个二维线形虚拟空间,也就是一个二维虚拟存储器。段式管理程序以段为单位分配内存,然后通过地址影射机构把段式虚拟地址转换为实际内存物理地址
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每段有段名且每段从0开始编址;
逻辑地址结构:段号和段内地址(段内偏移量);
内存分配规则:以段为单位进行分配,每个段在内存中占据连续的空间,各段不相邻 ;
段表中的段表项由段号(隐含的,不占存储空间)、段长、基地址组成;
与页表地址转换的不同之处在于增加了还要根据段表中记录的段长来检查段内地址是否越界
优缺点:
地址变换:由逻辑地址得到短号、页号、页内偏移量-->段号与段表寄存器中的段长度比较,检查是否越界-->由段表始址、段号找到对应段表项-->根据段表中记录的页表长度,检查页号是否越界-->由段表中的页表地址、页号查询页表项-->由块号和页内偏移得到物理地址-->访问目标单元
简单来说, 流程就是3次访存: 查段表 --> 查页表 --> 访问目标单元(设置了快表的话可以做到1次访存)
覆盖技术包含一个固定区(区内程序段在运行过程中不会调入调出)和若干覆盖区(区内程序段在运行过程中需要调入和调出),对用户不透明增加用户编程负担,是在同一个程序或者进程中进行的
交换技术是内存紧张时,换出某些进程以腾出内存空间,再换入某些进程,而外存磁盘分为文件区和对换区,换出的进程放在对换区,是在不同进程(或作业)之间的
局部性原理
(联想到Mysql中利用B树作为索引的数据结构原因)
基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其余部分留在外存,就可以启动程序执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟存储器
虚拟内存的定义:程序不需要全部装入即可运行,运行时动态调入,若内存不够则进行置换
虚拟内存的特征
虚拟内存技术的实现: 请求分页存储管理
,请求分段存储管理
,请求段页式存储管理
使用虚拟内存的代价
最优页面置换算法(OPT)
只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。
先进先出置换算法(FIFO)
由操作系统维护一个所有当前在内存中的页面的链表,最新进入的页面放在表尾,最早进入的页面放在表头;当发生缺页中断时,淘汰表头的页面并将新调入的页面追加到表尾。简单粗暴的一种置换算法,没有考虑页面访问频率信息。
最近最少使用算法(LRU)
算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。
时钟置换算法(也被称为最近未使用算法NRU)
页面设置一个访问位,页面被访问的时候访问位设置为1;并将所有页面保存在一个循环队列中,表针指向最老的页面。页面置换的时候,如果当前指针所指页面访问为为0,那么置换,否则将其置为0,循环直到遇到一个访问为位0的页面。
改进型Clock算法
在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问为何修改位都是0的页面,其次是访问位为0修改位为1的页面。
最少使用算法(LFU)
设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。存在问题是该访问寄存器并不能真正反映当前页面访问次数,因为访问速度比较快,所以在更新寄存器的时间间隔内访问1次和访问100次都是一样的。另外,LFU和LRU是很类似的,支持硬件也是一样的,但是区分两者的关键在于一个以时间为标准,一个以次数为标准(例如对于寄存器 pa 001111 和pb 111000,两个页面,如果采用LRU,那么被淘汰的是pa,如果采用LFU那么被淘汰的是pb)。
页面缓冲算法(PBA)
置换的时候,页面无论是否被修改过,都不被置换到磁盘,而是先暂留在内存中的页面链表(已修改页面链表和未修改页面链表,也可以不区分)里面,当其再次被访问的时候可以直接从这些链表中取出而不必进行磁盘IO,当链表中已修改也难数目达到一定数量之后,进行依次写磁盘操作(相当于将多次IO合并为一次)
驻留集:是指请求分页存储管理中给进程分配的物理块的集合(即是系统给进程分配的物理块数)
固定分配局部置换:进程运行前就分配号驻留集,缺页时只能换出进程自己的某一页
可变分配全局置换:只要缺页就分配新的物理块,可能来自空闲物理块,也可能需换出别的进程页面
可变分配局部置换:根据发生缺页的频率来动态地增加或减少进程的物理块
何时调入页面:预调页策略(运行前);请求调页策略(运行时)
何处调用页面:UNIX方式:第一次使用的页面都从文件区调入,调出的页面都写回对换区,再次使用时从对换区调入
抖动(颠簸)现象
:由于分配给进程的物理块不够导致,刚刚换出的页面马上又换入主存,刚刚换入的页面马上就要换出主存------>如果是因为页面替换策略失误,可以修改替换算法来解决这个问题;如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量;否则终止该进程或者增加驻留集大小
工作集:是指在某段时间间隔里,进程实际访问页面的集合(驻留集大小一般不小于工作集大小来防止抖动现象)
补充:
缺页中断(或页错误)
: 当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失部分装入物理内存并重新执行失败的指令Belady异常
:采用FIFO算法时,如果对一个进程未分配它所要求的全部页面,有时就会出现分配的页面数增多但缺页率反而提高的异常现象先来先服务(FCFS)
最短寻道时间优先(SSTF)
:让离当前磁道最近的请求访问者启动磁盘驱动器,即是让查找时间最短的那个作业先执行,而不考虑请求访问者到来的先后次序,这样就克服了先来先服务调度算法中磁臂移动过大的问题扫描算法(SCAN)或电梯调度算法
:总是从磁臂当前位置开始,沿磁臂的移动方向去选择离当前磁臂最近的那个柱面的访问者。如果沿磁臂的方向无请求访问时,就改变磁臂的移动方向。在这种调度方法下磁臂的移动类似于电梯的调度,所以它也称为电梯调度算法。循环扫描算法(CSCAN)
:循环扫描调度算法是在扫描算法的基础上改进的。磁臂改为单项移动,由外向里。当前位置开始沿磁臂的移动方向去选择离当前磁臂最近的哪个柱面的访问者。如果沿磁臂的方向无请求访问时,再回到最外,访问柱面号最小的作业请求NStepSCAN算法
: N步SCAN算法将磁盘请求队列分成若干个长度为N的子序列,磁盘调度按FCFS算法依次处理这里子队列,而每个子队列的内部是根据SCAN算法进行处理的。当N很大的时候,算法接近于SCAN算法,当N=1时,退化成FCFS算法。N步SCAN算法可以有效避免“磁臂粘着”的现象轮询方式
中断方式
直接内存存取(DMA)访问方式
通道控制方式
阻塞
用户线程调用某些系统函数去内核取数据,直到数据到达内核cache前,该线程处于阻塞状态,等待数据到达
非阻塞
用户线程去取数据,不管内核cache有没有数据,都直接返回,可能拿到数据,也可能拿不到,不会使线程进入阻塞态
IO多路复用
多路就是一个线程管理多路IO,线程还是被阻塞调用,其中一路或几路IO有数据了就返回。需要线程遍历全部IO,判断是哪个IO有数据。
例如 socket 的 select() 函数,线程调用 select() 进入阻塞态,任何一个IO有数据了,线程就退出阻塞态,获得机会继续执行。
信号驱动IO
给一个IO注册一个信号和信号触发的回调函数,一旦信号被触发,回调函数里读取数据。
例如给 socket 注册一个“可读”的信号,当数据来了,可读的时候,信号被触发,执行回调函数从内核cache复制数据到用户空间
异步IO
异步IO中,操作系统完成了数据从内核到用户空间的拷贝后,以信号的方式通知用户线程可以下一步操作。省去了用户线程阻塞下来拷贝数据的过程
select
epoll
然后收到一个IO中断,epoll 把网卡数据拷贝到内核cache,根据中断号在红黑树中查找对应的 fd,把 fd 加入到就绪链表中,准备返回给用户线程。用户直接得到就绪的 fd
kqueue