进程同步的主要任务:是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合做,从而使程序的执行具备可再现性。html
同步机制遵循的原则:linux
(1)空闲让进;c++
(2)忙则等待(保证对临界区的互斥访问);程序员
(3)有限等待(有限表明有限的时间,避免死等);web
(4)让权等待,(当进程不能进入本身的临界区时,应该释放处理机,以避免陷入忙等状态)。算法
进程通讯,是指进程之间的信息交换(信息量少则一个状态或数值,多者则是成千上万个字节)。所以,对于用信号量进行的进程间的互斥和同步,因为其所交换的信息量少而被归结为低级通讯。windows
所谓高级进程通讯指:用户能够利用操做系统所提供的一组通讯命令传送大量数据的一种通讯方式。操做系统隐藏了进程通讯的实现细节。或者说,通讯过程对用户是透明的。数组
高级通讯机制可归结为三大类:缓存
(1)共享存储器系统(存储器中划分的共享存储区);实际操做中对应的是“剪贴板”(剪贴板其实是系统维护管理的一块内存区域)的通讯方式,好比举例以下:word进程按下ctrl+c,在ppt进程按下ctrl+v,即完成了word进程和ppt进程之间的通讯,复制时将数据放入到剪贴板,粘贴时从剪贴板中取出数据,而后显示在ppt窗口上。安全
(2)消息传递系统(进程间的数据交换以消息(message)为单位,当今最流行的微内核操做系统中,微内核与服务器之间的通讯,无一例外地都采用了消息传递机制。应用举例:邮槽(MailSlot)是基于广播通讯体系设计出来的,它采用无链接的不可靠的数据传输。邮槽是一种单向通讯机制,建立邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。
(3)管道通讯系统(管道即:链接读写进程以实现他们之间通讯的共享文件(pipe文件,相似先进先出的队列,由一个进程写,另外一进程读))。实际操做中,管道分为:匿名管道、命名管道。匿名管道是一个未命名的、单向管道,经过父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上两个进程之间的通讯,而不能实现跨网络的通讯。命名管道不只能够在本机上实现两个进程间的通讯,还能够跨网络实现两个进程间的通讯。
管道:管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另外一个进程的标准输入链接在一块儿。写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。一样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据以前,写进程将一直阻塞。
注1:无名管道只能实现父子或者兄弟进程之间的通讯,有名管道(FIFO)能够实现互不相关的两个进程之间的通讯。
注2:用FIFO让一个服务器和多个客户端进行交流时候,每一个客户在向服务器发送信息前创建本身的读管道,或者让服务器在获得数据后再创建管道。使用客户的进程号(pid)做为管道名是一种经常使用的方法。客户能够先把本身的进程号告诉服务器,而后再到那个以本身进程号命名的管道中读取回复。
信号量:信号量是一个计数器,能够用来控制多个进程对共享资源的访问。它常做为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。所以,主要做为进程间以及同一进程内不一样线程之间的同步手段。
消息队列:是一个在系统内核中用来保存消 息的队列,它在系统内核中是以消息链表的形式出现的。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存:共享内存容许两个或多个进程访问同一个逻辑内存。这一段内存能够被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,能够被其余使用这个共享内存的进程,经过一个简单的内存读取读出,从而实现了进程间的通讯。若是某个进程向共享内存写入数据,所作的改动将当即影响到能够访问同一段共享内存的任何其余进程。共享内存是最快的IPC方式,它是针对其它进程间通讯方式运行效率低而专门设计的。它每每与其它通讯机制(如信号量)配合使用,来实现进程间的同步和通讯。
套接字:套接字也是一种进程间通讯机制,与其它通讯机制不一样的是,它可用于不一样机器间的进程通讯。
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换(Context Switch)是一种将CPU资源从一个进程分配给另外一个进程的机制。从用户角度看,计算机可以并行运行多个进程,这偏偏是操做系统经过快速上下文切换形成的结果。在切换的过程当中,操做系统须要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,而后执行此进程。
进程和线程的关系
(1)一个线程只能属于一个进程,而一个进程能够有多个线程,但至少有一个线程。线程是操做系统可识别的最小执行和调度单位。
(2)资源分配给进程,同一进程的全部线程共享该进程的全部资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。可是每一个线程拥有本身的栈段,栈段又叫运行时段,用来存放全部局部变量和临时变量。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程当中,须要协做同步。不一样进程的线程间要利用消息通讯的办法实现同步。
进程与线程的区别?
(1)进程有本身的独立地址空间,线程没有
(2)进程是资源分配的最小单位,线程是CPU调度的最小单位
(3)进程和线程通讯方式不一样(线程之间的通讯比较方便。同一进程下的线程共享数据(好比全局变量,静态变量),经过这些数据来通讯不只快捷并且方便,固然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通讯只能经过进程通讯的方式进行。)
(4)进程上下文切换开销大,线程开销小
(5)一个进程挂掉了不会影响其余进程,而线程挂掉了会影响其余线程
(6)对进程进程操做通常开销都比较大,对线程开销就小了
为何进程上下文切换比线程上下文切换代价高?
进程切换分两步:
1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文
对于linux来讲,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不须要作的,第2是进程和线程切换都要作的。
切换的性能消耗:
一、线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,可是进程切换是不一样的。这两种上下文切换的处理都是经过操做系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
二、另一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中全部已经缓存的内存地址一瞬间都做废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor's Translation Lookaside Buffer (TLB))或者至关的神马东西会被所有刷新,这将致使内存的访问在一段时间内至关的低效。可是在线程的切换中,不会出现这个问题。
转自知乎:进程和线程的区别
首先来一句归纳的总论:进程和线程都是一个时间段的描述,是CPU工做时间段的描述。
下面细说背景:
CPU+RAM+各类资源(好比显卡,光驱,键盘,GPS, 等等外设)构成咱们的电脑,可是电脑的运行,实际就是CPU和相关寄存器以及RAM之间的事情。
一个最最基础的事实:CPU太快,太快,太快了,寄存器仅仅可以追的上他的脚步,RAM和别的挂在各总线上的设备彻底是望其项背。那当多个任务要执行的时候怎么办呢?轮流着来?或者谁优先级高谁来?无论怎么样的策略,一句话就是在CPU看来就是轮流着来。
一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当获得CPU的时候,相关的资源必须也已经就位,就是显卡啊,GPS啊什么的必须就位,而后CPU开始执行。这里除了CPU之外全部的就构成了这个程序的执行环境,也就是咱们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工做就是保存程序上下文,由于这个是下次他被CPU临幸的运行环境,必须保存。
串联起来的事实:前面讲过在CPU看来全部的任务都是一个一个的轮流执行的,具体的轮流方法就是: 先加载程序A的上下文,而后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,而后开始执行B,保存程序B的上下文。。。。线程是什么呢?
进程的颗粒度太大,每次都要有上下的调入,保存,调出。若是咱们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不多是一条逻辑执行的,一定有多个分支和多个程序段,就比如要实现程序A,实际分红 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:
程序A获得CPU =》CPU加载上下文,开始执行程序A的a小段,而后执行A的b小段,而后再执行A的c小段,最后CPU保存A的上下文。
这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这 里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。进程(process)与线程(thread)最大的区别是进程拥有本身的地址空间,某进程内的线程对于其余进程不可见,即进程A不能经过传地址的方式直接读写进程B的存储区域。进程之间的通讯须要经过进程间通讯(Inter-process communication,IPC)。与之相对的,同一进程的各线程间之间能够直接经过传递地址或全局变量的方式传递信息。
进程做为操做系统中拥有资源和独立调度的基本单位,能够拥有多个线程。一般操做系统中运行的一个程序就对应一个进程。在同一进程中,线程的切换不会引发进程切换。在不一样进程中进行线程切换,如从一个进程内的线程切换到另外一个进程中的线程时,会引发进程切换。相比进程切换,线程切换的开销要小不少。线程于进程相互结合可以提升系统的运行效率。
线程能够分为两类:
用户级线程(user level thread):对于这类线程,有关线程管理的全部工做都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操做系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序一般先在一个线程中运行,该线程被成为主线程。在其运行的某个时刻,能够经过调用线程库中的函数建立一个在相同进程中运行的新线程。用户级线程的好处是很是高效,不须要进入内核空间,但并发效率不高。
内核级线程(kernel level thread):对于这类线程,有关线程管理的全部工做由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每一个线程,调度也由内核基于线程架构完成。内核级线程的好处是,内核能够将不一样线程更好地分配到不一样的CPU,以实现真正的并行计算。
事实上,在现代操做系统中,每每使用组合方式实现多线程,即线程建立彻底在用户空间中完成,而且一个应用程序中的多个用户级线程被映射到一些内核级线程上,至关因而一种折中方案。
调度种类
高级调度:(High-Level Scheduling)又称为做业调度,它决定把后备做业调入内存运行;
低级调度:(Low-Level Scheduling)又称为进程调度,它决定把就绪队列的某进程得到CPU;
中级调度:(Intermediate-Level Scheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
非抢占式调度与抢占式调度
非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另外一个进程。
抢占式:操做系统将正在运行的进程强行暂停,由调度程序将CPU分配给其余就绪进程的调度方式。
调度策略的设计
响应时间: 从用户输入到产生反应的时间
周转时间: 从任务开始到任务结束的时间
CPU任务能够分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽量短,用户不至于感到延迟,同时使得批处理任务的周转时间尽量短,减小用户等待的时间。
调度算法:
FIFO或First Come, First Served (FCFS)先来先服务
调度的顺序就是任务到达就绪队列的顺序。
公平、简单(FIFO队列)、非抢占、不适合交互式。
未考虑任务特性,平均等待时间能够缩短。
Shortest Job First (SJF)
最短的做业(CPU区间长度最小)最早调度。
SJF能够保证最小的平均等待时间。
Shortest Remaining Job First (SRJF)
SJF的可抢占版本,比SJF更有优点。
SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法。
优先权调度
每一个任务关联一个优先权,调度优先权最高的任务。
注意:优先权过低的任务一直就绪,得不到运行,出现“饥饿”现象。
Round-Robin(RR)轮转调度算法
设置一个时间片,按时间片来轮转调度(“轮叫”算法)
优势: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;
时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
多级队列调度
按照必定的规则创建多个进程队列
不一样的队列有固定的优先级(高优先级有抢占权)
不一样的队列能够给不一样的时间片和采用不一样的调度方法
存在问题1:无法区分I/O bound和CPU bound;
存在问题2:也存在必定程度的“饥饿”现象;
多级反馈队列
在多级队列的基础上,任务能够在队列之间移动,更细致的区分任务。
能够根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。
多级反馈队列调度算法描述:
进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
对于同一个队列中的各个进程,按照时间片轮转法调度。好比Q1队列的时间片为N,那么Q1中的做业在经历了N个时间片后若尚未完成,则进入Q2队列等待,若Q2的时间片用完后做业还不能完成,一直进入下一级队列,直至完成。
在低优先级的队列中的进程在运行时,又有新到达的做业,那么在运行完这个时间片后,CPU立刻分配给新到达的做业(抢占式)。
一个简单的例子
假设系统中有3个反馈队列Q1,Q2,Q3,时间片分别为2,4,8。如今有3个做业J1,J2,J3分别在时间 0 ,1,3时刻到达。而它们所须要的CPU时间分别是3,2,1个时间片。
时刻0 J1到达。 因而进入到队列1 ,运行1个时间片 ,时间片还未到,此时J2到达。
时刻1 J2到达。 因为时间片仍然由J1掌控,因而等待。J1在运行了1个时间片后,已经完成了在Q1中的2个时间片的限制,因而J1置于Q2等待被调度。如今处理机分配给J2。
时刻2 J1进入Q2等待调度,J2得到CPU开始运行。
时刻3 J3到达,因为J2的时间片未到,故J3在Q1等待调度,J1也在Q2等待调度。
时刻4 J2处理完成,因为J3,J1都在等待调度,可是J3所在的队列比J1所在的队列的优先级要高,因而J3被调度,J1继续在Q2等待。
时刻5 J3通过1个时间片,完成。
时刻6 因为Q1已经空闲,因而开始调度Q2中的做业,则J1获得处理器开始运行。 J1再通过一个时间片,完成了任务。因而整个调度过程结束。
定义:若是一组进程中的每个进程都在等待仅由该组进程中的其余进程才能引起的事件,那么该组进程就是死锁的。或者在两个或多个并发进程中,若是每一个进程持有某种资源而又都等待别的进程释放它或它们如今保持着的资源,在未改变这种状态以前都不能向前推动,称这一组进程产生了死锁。通俗地讲,就是两个或多个进程被无限期地阻塞、相互等待的一种状态。
产生死锁的必要条件:
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经获得资源的进程能够再次申请新的资源。
非抢占条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每一个进程都在等待相邻进程正占用的资源。
如何处理死锁问题:
忽略该问题。例如鸵鸟算法,该算法能够应用在极少发生死锁的的状况下。为何叫鸵鸟算法呢,由于传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟以为看不到危险也就没危险了吧。跟掩耳盗铃有点像。
检测死锁而且恢复。
仔细地对资源进行动态分配,使系统始终处于安全状态以避免死锁。
经过破除死锁四个必要条件之一,来防止死锁产生。
在操做系统中,进程是占有资源的最小单位(线程能够访问其所在进程内的全部资源,但线程自己并不占有资源或仅仅占有一点必须资源)。但对于某些资源来讲,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源好比物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(若是这类资源不被当作临界资源加以保护,那么颇有可能形成丢数据的问题)。
对于临界资源的访问,必须是互斥进行。也就是当临界资源被占用时,另外一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。
一、预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。
二、编译:将预处理后的文件转换成汇编语言,生成.s文件
三、汇编:汇编变为目标代码(机器代码)生成.o的文件
四、连接:链接目标代码,生成可执行程序
首先介绍一个概念“池化技术 ”。池化技术就是:提早保存大量的资源,以备不时之需以及重复使用。池化技术应用普遍,如内存池,线程池,链接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。
因为在实际应用当作,分配内存、建立进程、线程都会设计到一些系统调用,系统调用须要致使程序从用户态切换到内核态,是很是耗时的操做。所以,当程序中须要频繁的进行内存申请释放,进程、线程建立销毁等操做时,一般会使用内存池、进程池、线程池技术来提高程序的性能。
线程池:线程池的原理很简单,相似于操做系统中的缓冲区的概念,它的流程以下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当须要一个开辟一个线程去作具体的工做时,就会唤醒线程池中的某一个睡眠线程,让它去作具体工做,当工做完成后,线程又处于睡眠状态,而不是将线程销毁。
进程池与线程池同理。
内存池:内存池是指程序预先从操做系统申请一块足够大内存,此后,当程序中须要申请内存的时候,不是直接向操做系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操做系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将以前申请的内存真正释放。
静态库
静态库是一个外部函数与变量的集合体。静态库的文件内容,一般包含一堆程序员自定的变量与函数,其内容不像动态连接库那么复杂,在编译期间由编译器与连接器将它集成至应用程序内,并制做成目标文件以及能够独立运做的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态建立(static build)。
动态库
静态库很方便,可是若是咱们只是想用库中的某一个函数,却仍然得把全部的内容都连接进去。一个更现代的方法则是使用共享库,避免了在文件中静态库的大量重复。
动态连接能够在首次载入的时候执行(load-time linking),这是 Linux 的标准作法,会由动态连接器ld-linux.so 完成,比方标准 C 库(libc.so) 一般就是动态连接的,这样全部的程序能够共享同一个库,而不用分别进行封装。
动态连接也能够在程序开始执行的时候完成(run-time linking),在 Linux 中使用 dlopen()接口来完成(会使用函数指针),一般用于分布式软件,高性能服务器上。并且共享库也能够在多个进程间共享。
连接使得咱们能够用多个对象文件构造咱们的程序。能够在程序的不一样阶段进行(编译、载入、运行期间都可),理解连接能够帮助咱们避免遇到奇怪的错误。
区别:
定义:具备请求调入功能和置换功能,能从逻辑上对内存容量加以扩充得一种存储器系统。其逻辑容量由内存之和和外存之和决定。
与传统存储器比较虚拟存储器有如下三个主要特征:
虚拟内存的实现有如下两种方式:
操做系统将内存按照页面进行管理,在须要的时候才把进程相应的部分调入内存。当产生缺页中断时,须要选择一个页面写入。若是要换出的页面在内存中被修改过,变成了“脏”页面,那就须要先写会到磁盘。页面置换算法,就是要选出最合适的一个页面,使得置换的效率最高。页面置换算法有不少,简单介绍几个,重点介绍比较重要的LRU及其实现算法。
1、最优页面置换算法
最理想的状态下,咱们给页面作个标记,挑选一个最远才会被再次用到的页面调出。固然,这样的算法不可能实现,由于不肯定一个页面在什么时候会被用到。
2、先进先出页面置换算法(FIFO)及其改进
这种算法的思想和队列是同样的,该算法老是淘汰最早进入内存的页面,即选择在内存中驻留时间最久的页面予淘汰。实现:把一个进程已调入内存的页面按前后次序连接成一个队列,而且设置一个指针老是指向最老的页面。缺点:对于有些常常被访问的页面如含有全局变量、经常使用函数、例程等的页面,不能保证这些不被淘汰。
3、最近最少使用页面置换算法LRU(Least Recently Used)
根据页面调入内存后的使用状况作出决策。LRU置换算法是选择最近最久未使用的页面进行淘汰。
1.为每一个在内存中的页面配置一个移位寄存器。(P165)定时信号将每隔一段时间将寄存器右移一位。最小数值的寄存器对应页面就是最久未使用页面。
2.利用一个特殊的栈保存当前使用的各个页面的页面号。每当进程访问某页面时,便将该页面的页面号从栈中移出,将它压入栈顶。所以,栈顶永远是最新被访问的页面号,栈底是最近最久未被访问的页面号。
连接:分页内存管理(把虚拟内存空间和物理内存空间均划分为大小相同的页面等内容)
所谓的中断就是在计算机执行程序的过程当中,因为出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完以后再回去执行以前的程序。中断通常分为三类:
由计算机硬件异常或故障引发的中断,称为内部异常中断;
由程序中执行了引发中断的指令而形成的中断,称为软中断(这也是和咱们将要说明的系统调用相关的中断);
由外部设备请求引发的中断,称为外部中断。简单来讲,对中断的理解就是对一些特殊事情的处理。
与中断紧密相连的一个概念就是中断处理程序了。当中断发生的时候,系统须要去对中断进行处理,对这些中断的处理是由操做系统内核中的特定函数进行的,这些处理中断的特定的函数就是咱们所说的中断处理程序了。
另外一个与中断紧密相连的概念就是中断的优先级。中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也代表了中断须要被处理的紧急程度。每一个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断能够被处理器接受而且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
典型的中断优先级以下所示:
机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
在讲系统调用以前,先说下进程的执行在系统上的两个级别:用户级和核心级,也称为用户态和系统态(user mode and kernel mode)。
用户空间就是用户进程所在的内存区域,相对的,系统空间就是操做系统占据的内存区域。用户进程和系统进程的全部数据都在内存中。处于用户态的程序只能访问用户空间,而处于内核态的程序能够访问用户空间和内核空间。
用户态切换到内核态的方式以下:
系统调用:程序的执行通常是在用户态下执行的,但当程序须要使用操做系统提供的服务时,好比说打开某一设备、建立文件、读写文件(这些均属于系统调用)等,就须要向操做系统发出调用服务的请求,这就是系统调用。
异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换处处理此异常的内核相关程序中,也就转到了内核态,好比缺页异常。
外围设备的中断:当外围设备完成用户请求的操做后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,若是先前执行的指令是用户态下的程序,那么这个转换的过程天然也就发生了由用户态到内核态的切换。好比硬盘读写操做完成,系统会切换到硬盘读写的中断处理程序中执行后续操做等。
用户态和核心态(内核态)之间的区别是什么呢?
权限不同。
用户态的进程能存取它们本身的指令和数据,但不能存取内核指令和数据(或其余进程的指令和数据)。
核心态下的进程可以存取内核和用户地址某些机器指令是特权指令,在用户态下执行特权指令会引发错误。在系统中内核并非做为一个与用户进程平行的估计的进程的集合。
当有多个线程的时候,常常须要去同步(注:同步不是同时刻)这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另外一个线程用于统计文件中的字符数。固然,在把整个文件调入内存以前,统计它的计数是没有意义的。可是,因为每一个操做都有本身的线程,操做系统会把两个线程看成是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工做。
所谓同步,是指在不一样进程之间的若干程序片段,它们的运行必须严格按照规定的某种前后次序来运行,这种前后次序依赖于要完成的特定的任务。若是用对资源的访问来定义的话,同步是指在互斥的基础上(大多数状况),经过其它机制实现访问者对资源的有序访问。在大多数状况下,同步已经实现了互斥,特别是全部写入资源的状况一定是互斥的。少数状况是指能够容许多个访问者同时访问资源。
所谓互斥,是指散布在不一样进程之间的若干程序片段,当某个进程运行其中一个程序片断时,其它进程就不能运行它们之中的任一程序片断,只能等到该进程运行完这个程序片断后才能够运行。若是用对资源的访问来定义的话,互斥某一资源同时只容许一个访问者对其进行访问,具备惟一性和排它性。但互斥没法限制访问者对资源的访问顺序,即访问是无序的。
线程间的同步方法大致可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时须要切换内核态与用户态,而用户模式就是不须要切换到内核态,只在用户态完成操做。
用户模式下的方法有:原子操做(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量。
一、临界区:经过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
二、互斥量:为协调共同对一个共享资源的单独访问而设计的。
三、信号量:为控制一个具备有限数量用户资源而设计。
四、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
所谓的逻辑地址,是指计算机用户(例如程序开发者),看到的地址。例如,当建立一个长度为100的整型数组时,操做系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。因为整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。事实上,逻辑地址并不必定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并不是是连续的,只是操做系统经过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思惟。
另外一个重要概念是虚拟内存。操做系统读写内存的速度能够比读写磁盘的速度快几个量级。可是,内存价格也相对较高,不能大规模扩展。因而,操做系统能够经过将部分不太经常使用的数据移出内存,“存放到价格相对较低的磁盘缓存,以实现内存扩展。操做系统还能够经过算法预测哪部分存储到磁盘缓存的数据须要进行读写,提早把这部分数据读回内存。虚拟内存空间相对磁盘而言要小不少,所以,即便搜索虚拟内存空间也比直接搜索磁盘要快。惟一慢于磁盘的多是,内存、虚拟内存中都没有所须要的数据,最终还须要从硬盘中直接读取。这就是为何内存和虚拟内存中须要存储会被重复读写的数据,不然就失去了缓存的意义。现代计算机中有一个专门的转译缓冲区(Translation Lookaside Buffer,TLB),用来实现虚拟地址到物理地址的快速转换。
与内存/虚拟内存相关的还有以下两个概念:
1) Resident Set
当一个进程在运行的时候,操做系统不会一次性加载进程的全部数据到内存,只会加载一部分正在用,以及预期要用的数据。其余数据可能存储在虚拟内存,交换区和硬盘文件系统上。被加载到内存的部分就是resident set。
2) Thrashing
参考连接:https://blog.csdn.net/newcong0123/article/details/52792070
在内存管理中,内部碎片是已经被分配出去的的内存空间大于请求所需的内存空间。
外部碎片是指尚未分配出去,可是因为大小过小而没法分配给申请空间的新进程的内存空间空闲块。
固定分区存在内部碎片,可变式分区分配会存在外部碎片;
页式虚拟存储系统存在内部碎片;段式虚拟存储系统,存在外部碎片
为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页每每装不满,因而造成了内部碎片。
为了共享要分段,在段的换入换出时造成外部碎片,好比5K的段换出后,有一个4k的段进来放到原来5k的地方,因而造成1k的外部碎片。
当有多个线程的时候,常常须要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另外一个线程用于统计文件中的字符数。固然,在把整个文件调入内存以前,统计它的计数是没有意义的。可是,因为每一个操做都有本身的线程,操做系统会把两个线程看成是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工做。
所谓同步,是指散步在不一样进程之间的若干程序片段,它们的运行必须严格按照规定的某种前后次序来运行,这种前后次序依赖于要完成的特定的任务。若是用对资源的访问来定义的话,同步是指在互斥的基础上(大多数状况),经过其它机制实现访问者对资源的有序访问。在大多数状况下,同步已经实现了互斥,特别是全部写入资源的状况一定是互斥的。少数状况是指能够容许多个访问者同时访问资源。
所谓互斥,是指散布在不一样进程之间的若干程序片段,当某个进程运行其中一个程序片断时,其它进程就不能运行它们之中的任一程序片断,只能等到该进程运行完这个程序片断后才能够运行。若是用对资源的访问来定义的话,互斥某一资源同时只容许一个访问者对其进行访问,具备惟一性和排它性。但互斥没法限制访问者对资源的访问顺序,即访问是无序的。
若是多线程的程序运行结果是可预期的,并且与单线程的程序运行结果同样,那么说明是“线程安全”的。
系统调用(System call)是程序向系统内核请求服务的方式。能够包括硬件相关的服务(例如,访问硬盘等),或者建立新进程,调度其余进程等。系统调用是程序和操做系统之间的重要接口。
库函数:把一些经常使用的函数编写完放到一个文件里,编写应用程序时调用,这是由第三方提供的,发生在用户地址空间。
在移植性方面,不一样操做系统的系统调用通常是不一样的,移植性差;而在全部的ANSI C编译器版本中,C库函数是相同的。
在调用开销方面,系统调用须要在用户空间和内核环境间切换,开销较大;而库函数调用属于“过程调用”,开销较小。
守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。
僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有wait/waitpid子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工做)
当用户创立多个线程/进程时,若是不一样线程/进程同时读写相同的内容,则可能形成读写错误,或者数据不一致。此时,须要经过加锁的方式,控制临界区(critical section)的访问权限。对于semaphore而言,在初始化变量的时候能够控制容许多少个线程/进程同时访问一个临界区,其余的线程/进程会被堵塞,直到有人解锁。
Mutex至关于只容许一个线程/进程访问的semaphore。此外,根据实际须要,人们还实现了一种读写锁(read-write lock),它容许同时存在多个阅读者(reader),但任什么时候候至多只有一个写者(writer),且不能于读者共存。
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用以下场合:
当客户处理多个描述字时(通常是交互式输入和网络套接口),必须使用I/O复用。
当一个客户同时处理多个套接口时,而这种状况是可能的,但不多出现。
若是一个TCP服务器既要处理监听套接口,又要处理已链接套接口,通常也要用到I/O复用。
若是一个服务器即要处理TCP,又要处理UDP,通常要使用I/O复用。
若是一个服务器要处理多个服务或多个协议,通常要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优点是系统开销小,系统没必要建立进程/线程,也没必要维护这些进程/线程,从而大大减少了系统的开销。
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来讲是原子操做或者多个线程之间的切换不会致使该接口的执行结果存在二义性,也就是说咱们不用考虑同步的问题。
一个进程中的全部线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不一样,通常是一个进程有一个C运行时堆,这个堆为本进程中全部线程共享,windows进程还有所谓进程默认堆,用户也能够建立本身的堆。
用操做系统术语,线程切换的时候实际上切换的是一个能够称之为线程控制块的结构(TCB),里面保存全部未来用于恢复线程环境必须的信息,包括全部必须保存的寄存器集,线程的状态等。
堆: 是你们共有的空间,分全局堆和局部堆。全局堆就是全部没有分配的空间,局部堆就是用户分配的空间。堆在操做系统对进程初始化的时候分配,运行过程当中也能够向系统要额外的堆,可是记得用完了要还给操做系统,要否则就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每一个线程的栈互相独立,所以,栈是 thread safe的。操做系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不须要在高级语言里面显式的分配和释放。