到了晚饭的时间,一对小情侣肚子都咕咕叫了,于是男生见机行事,就想给女生做晚饭。所以他就在网上找了辣子鸡的菜谱,接着买了一些肌鸡肉,辣椒,香料等材料,然后边看边学做这道菜。
<br />突然,女生说她想喝可乐,那么男生只好把做菜的事情暂停一下,并在手机菜谱标记做到哪一个步骤,把状态信息记录下来。
然后男生听从女生的指令,跑下一楼买了一瓶冰可乐后,由回到厨房继续做菜。
这就体现,CPU可以从一个进程(做菜)切换到另外一个进程(买可乐),在切换前必须要记录当前进程中运行的状态信息,以备下次切换回来的时候可以恢复执行。
所以,可以发现进程有着:运行—暂停—运行 的活动规律。
进程的状态
在上面,我们知道了进程有着运行—暂停—运行 的活动规律。一般来说,一个进程并不是自始至终连续不停地运行的,它的并发执行中的其他进程的执行是相互制约的。
它有时处于运行状态,有时又由于某种原因再暂停运行而处于等待状态。当使它暂停的原因消失后,它又进入准备运行状态。
所以,在一个进程的活动期间至少具备三种基本状态,即运行状态,就绪状态,阻塞状态。
<br />上图中各种状态的意义:
- 运行状态(Running): 该时刻进程占用(CPU)
- 就绪状态(Ready): 可运行,但因为其他进程正在运行而暂停
- 阻塞状态(Blocked): 该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行。
当然,进程也会存在另外两个基本状态:
- 创建状态(new): 进程正在创建时的状态
- 结束状态(Exit): 进程正在从系统中消失时的状态
于是,一个完整的进程状态的变迁如下图所示:
<br />再来详细说明一下进程的状态变迁:
- NULL -> 创建状态:一个新进程被创建时的第一个状态
- 创建状态 -> 就绪状态:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的
- 就绪态 -> 运行状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给CPU正在运行该进程
- 运行状态 -> 结束状态:当进程已经运行完成或者出错时,会被操作系统当作结束状态处理
- 运行状态 -> 就绪状态:处于运行状态的进程在运行过程中,由于分配的时间片用完,操作系统会把该进程变为就绪状态,接着从就绪状态选中另外一个进程运行
- 运行状态 -> 阻塞状态:当进程请求某个事件且必须等待时,例如请求I/O事件
- 阻塞状态 -> 就绪状态:当进程要等待的事件完成时,它从阻塞状态变到就绪状态
如果有大量处于阻塞状态的进程,进程可能会占用物理内存空间,显然不是我们所希望的,毕竟物理内存空间是有限的。
所以,在虚拟内存管理的操作系统中,通常会把阻塞状态进程的物理内存换出到硬盘中,等需要再次运行的时候,再从硬盘换入物理内存。
那么,就需要一个新的状态,来描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态。这跟阻塞状态是不一样的,阻塞状态是等待某个事件的返回。
- 阻塞挂起的状态:进程在外存(硬盘)并等待某个事件的出现
- 就绪挂起状态:进程在外存(硬盘),但只要是进入内存,即刻立即运行
这两种挂起状态加上前面的物种,就变成了七种状态变化,如下图所示:
导致进程挂起的原因不只是因为进程所使用的内存空间不在物理内存,还包括如下情况:
- 通过sleep让进程间歇性挂起,其工作原理是设置一个定时器,到期后唤醒进程
- 用户希望挂起一个进程的执行,比如在linux中用Ctrl + Z挂起进程
进程的控制结构
在操作系统中,是用进程控制快(process control block, PCB)数据结构来描述进程的。
那PCB是什么呢?PCB是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个PCB。如果进程消失,那么,PCB也会随之消失。
那么PCB里面包含什么信息呢?
进程描述信息:
- 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符
- 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务
进程控制和管理信息:
- 进程当前的状态:如new, ready, running, waiting或者blocked等
- 进程优先级:进程抢占CPU时的优先级
资源分配清单:
- 有关内存地址空间或者虚拟内存空间的信息,所打开文件的列表和所使用的I/O设备信息
CPU相关信息:
- CPU中各个寄存器的值,当进程被切换时,CPU的状态信息都会被保存在相应的PCB中,以便进程重新执行,能从断点处继续执行
每个PCB是怎么组织的呢?
通常是通过链表的方式进行组织,把具有相同状态的进程链合在一起,组成各种队列:
比如:
- 将所有处于就绪状态的进程链在一起,称为就绪队列
- 把所有因等待某个事件而处于等待状态的进程链在一起就组成各种阻塞队列
- 另外,对于运行队列在单核CPU系统中则只有一个运行指针,因为单核CPU在某个时间内,只能运行一个程序
那么,就绪队列和阻塞队列链表的组织形式如下图所示:

面临进程创建,销毁等调度导致进程状态放生变化时,链表能够更加灵活插入和删除。
进程的控制
我们熟知了进程状态变迁和进程数据结构PCB后,再来看看进程的创建,终止,阻塞,唤醒的过程,这些过程也就是进程的控制。
创建进程
操作系统运行一个进程创建另外一个进程,而且允许子进程继承父进程所拥有的资源。当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程同时也会终止其所有的子进程。
注意,Linux操作系统对于终止有子进程的父进程,会把子进程交给1号进程接管。本文所指出的进程终止的概念是宏观系统的一种观点,下面看看具体的操作系统是怎么实现的:
- 为新进程分配一个唯一的进程标识号,并申请一个空白的PCB, PCB是有限的,若申请失败则创建失败。
- 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源
- 初始化PCB
- 如果进程的调度队列能够接纳新进程,那就将进程插入到就绪队列,等待被调度执行
终止进程
进程可以有3种终止方式,正常结束,异常结束,以及外界干预(信号kill掉):
- 查找需要终止的进程PCB
- 如果处于执行状态,则立即终止该进程的执行,然后将CPU资源分配给其他进程
- 如果还有子进程,则应将其所有子进程终止
- 将该进程所拥有的全部资源对归还给父进程或者操作系统
- 将其从PCB所有队列中删除
阻塞队列
当进程需要等待某一个时间完成时,它可以调用阻塞语句把自己阻塞等待,而一旦被阻塞等待,就只能有用另外一个进程唤醒。
- 找到将要被阻塞进程标示号对应的PCB
- 如果该进程为运行状态,则保护其现场,将其状态转为阻塞状态,停止运行
- 将该PCB插入到阻塞队列中去
唤醒进程
进程由运行转变为阻塞状态由于进程必须等待某一事件的完成,所以处于阻塞状态的进程是绝对不可以叫醒自己的。
如果某进程正在等待I/O事件,需要由别的进程发送给它,则只有当该进程所期待的事件出现时,才由发现者进程用唤醒语句叫醒它。
进程唤醒的过程如下:
- 在该事件的阻塞队列中找到相应进程的PCB
- 将其从阻塞队列中移出,并置其状态为就绪状态
- 把该PCB插入到就绪队列中,等待调度程序调度
进程的阻塞和唤醒是一对功能相反的语句,如果某个进程调用了阻塞语句,则必须有一个与之相对应的唤醒语句。
线程
在早期的操作系统中都是以进程作为独立运行的基本单位,直到后面,计算机科学家们有提出了更小的能够独立运行的阻塞单位,那就是线程。
为什么要使用线程?
我们举个例子,假如你要编写一个视频播放器软件,那么该软件功能的核心模块有三个:
- 从视频文件当中读取数据
- 对读取的数据进行解压缩
- 把解压缩后的视频数据播放出来
对于当进程的实现方式,我想大家都会是一下这种方式:

对于单进程的实现方式,存在一下问题:
- 播放出来画面和声音会不连贯,因为I/O时间过长的时候,read的时候进程就可能阻塞。这样就会导致等半天才能进入数据的解压和播放
- 各个函数之间不是并发执行,影响资源的使用效率
对于多进程的这种方式,依然会存在问题:
- 进程之间如何通信,共享数据?
- 维护进程的系统开销较大,如创建进程时,分配资源,建立PCB;终止进程时,回收资源,撤销PCB; 进程切换时,保存当前进程的状态信息
那到底如何解决?需要有一种新的实体,满足以下特征:
- 实体之间可以并发运行
- 实体之间共享相同的地址空间
这个新的实体,就是线程(thread)。 线程之间可以并发运行且共享相同的地址空间。
什么是线程?
线程是进程当中的一条执行流程。
同一个进程内多个线程之间可以共享代码段,数据段,打开好的文件资源,但是每一个线程都有独立的一套寄存器和栈,这样可以确保线程的控制流是相对独立的。

线程的优缺点?
线程的优点:
- 一个进程中可以同时存在多个线程
- 各个线程之间可以并发执行
- 各个线程之间可以共享地址空间和文件等资源
线程的缺点:
- 当进程中的一个线程崩溃时,会导致其他所属进程的所有线程崩溃
举个例子,对于游戏的用户设计,则不应该使用多线程的方式,否则一个用户挂了,会影线其他同个进程的线程。
线程和进程的比较
线程与进程的比较如下:
- 进程是资源(包括内存,打开的文件等)分配的单位,线程是CPU调度单位
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈
- 线程同样具有就绪,阻塞,执行三种基本状态,同样具有状态之间的转换关系
- 线程能够减少并发执行的时间和空间开销
对于线程的减少开销,体现在:
- 线程的创建时间比进程快,因为进程创建的过程中,还需要资源的管理信息,比如内存管理信息,文件管理信息,文件管理信息,而线程在创建的过程中,不会涉及这些资源管理信息,而是共享它们
- 线程的终止时间比进程快,因为线程释放的资源相比进程少很多
- 同一个进程内的线程切换比进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同一个进程的线程都具有同一个页表,那么在切换的时候不需要切换页表。而对于进程之间的切换,切换的时候要把页表给切换掉,而页表的切换过程开销是比较大的。
- 由于同一进程的各线程间共享内存和文件资源,那么在线程之间进行数据传递的时候,就不需要进过内核了,这就使得线程之间的数据交换效率更高了。
所以,在时间效率和空间效率上, 线程都比进程的效率要高。
线程的实现
主要有三种线程的实现方式:
- 用户线程(User Thread): 在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理
- 内核线程(Kernel Thread): 在内核中实现的线程,是由内核管理的线程
- 轻量级进程(Light Weight Process):在内核中支持用户线程
那么,这还需要考虑一个问题,用户线程和内核线程的对应关系:
多对一
第一种关系是多对一的关系,也就是多个用户线程对应同一个内核线程:
一对一

多对多

怎么理解用户线程?存在什么优势和劣势?
用户线程是基于用户态的线程管理库来实现的,那么线程控制块(thread control block,TCB)也是在库里面来实现的。对于操作系统而言是看不到这个TCB的,它只能看到整个进程的PCB。
所以,用户线程的整个线程管理和调度,操作系统是不直接参与的,而是由用户线程库函数来完成线程的管理,包括线程的创建,终止,同步和调度。
用户级线程的模型,也就是类似前面提到的多对一的关系,即多个用户组线程对应同一个内核线程,如下图所示:

用户线程的优点:
- 每个进程都需要它私有都线程控制块(TCB)列表,用来跟踪记录它各个线程状态信息(PC, 栈指针,寄存器), TCB由用户级线程库来维护,可用于不支持线程技术的操作系统
- 用户线程的切换也是由线程库函数完成的,无需用户态与内核态切换,所以速度特别快
用户线程的缺点:
- 由于操作系统不参与线程的调度,如果一个线程发起了系统调用而阻塞,那进程所包含的用户线程都不能执行了
- 当一个线程开始运行后,除非它主动地交出CPU的使用权,否则它所在的进程当中的其他线程无法运行。因为用户态的线程没有办法打断当前运行,因为用户态的线程没法打断当前运行的线程,它没有这个特权,只有操作系统有。但是用户线程不是由操作系统管理的。
- 由于时间片分配给进程,故其与其他进程比较;在多线程执行时,每个线程得到的时间片较少,执行会比较慢
内核线程如何理解?存在什么优势和缺陷?
内核线程是由操作系统管理的,线程对应的TCB自然是放在操作系统里面的,这些线程的创建,终止和管理都是由操作系统负责。
内核线程的模型,也就是类似前面提到的一对一的关系,即一个用户线程对应一个内核线程,如下图所示:

内核线程的优点:
- 在一个进程当中,如果某个内核线程发起系统调用而被阻塞,并不会影响其他内核线程运行
内核线程的缺点:
- 在支持内核线程的操作系统中,由内存来维护进程和线程的上下文信息,如PCB和TCB
- 线程的创建,终止和切换都是通过系统调用的方式来进行,因此对于系统来说,系统开销比较大
轻量级的进程如何理解?
轻量级进程(Light-weight process,LWP)是内核支持的用户线程,一个用户进程可有一个或多个 LWP,每个 LWP 是跟内核线程一对一映射的,也就是 LWP 都是由一个内核线程支持。
LWP只能由内核管理并像普通进程一样被调度,linux内核是支持LWP的典型例子。在大多数系统中,LWP与普通进程的最大区别也就是在于它只有一个最小的执行上下文和调度程序所需要的统计信息。一般来说,一个进程代表进程的一个实例,而LWP代表程序的执行线程,因为一个执行线程不像进程那样需要那么多状态信息,所以LWP也不带有这样的信息。
在LWP之上也是可以使用用户线程的,那么LWP与用户线程的对应关系就有三种:
- 1:1,即一个LWP对应一个用户线程
- N:1, 即一个LWP对应多个用户线程
- M:N,即一个LMP对应多个用户线程
LWP模型
1:1 模式
一个线程对应一个LWP再对应一个内核线程,如上图的进程4属于这种模型:
- 优点: 实现并行,当一个LWP阻塞,不会影响其他LWP
缺点:每一个用户线程,就产生一个内核线程,创建线程的开销较大
N:1模式
多个用户线程对于一个LWP再对应一个内核线程,如上图的进程2线程管理实在用户空间完成的,此模式中用户的线程对其他操作系统不可见。
优点: 用户线程要开几个都没问题,而且上下文切换发生用户空间,切换的效率较高
缺点:一个用户线程如果阻塞了,则整个进程都将会阻塞。这种方式,在多核CPU, 是没有办法充分利用CPU的
M:N模式
根据前面的两个模型混搭一起,就形成M:N模型,该模型提供了两级控制,首先多个用户线对应到多个LWP,LWP再一一对应到内核线程。
优点:综合了前两种优点,大部分的线程上下文发生在用户空间,且多个线程又可以充分利用多核CPU的资源
组合模式
如上图的进程5,此进程结合1:1模型和M:N模型,开发人员可以针对不同的应用特点调节内核线程的数目来达到物理并行和逻辑并行的最佳方案。
问题来了?
组合模式究竟是怎么使用的呢?我们需要对linux的参数做怎么样的设置?
https://www.cnblogs.com/still-smile/p/11656591.html 这篇文章或许可以为我们解决部分疑惑
