进程和线程相关
进程
- 进程是程序的一次执行过程,是系统进行资源分配的单位
进程控制块(PCB)
系统为每个运行的程序配置一个数据结构,称为进程控制块(PCB),用来描述进程的各种信息(如程序代码存放位置),操作系统通过PCB管理进程。创建进程实际就是创建PCB,撤销进程实际就是撤销PCB。
PCB是进程存在的唯一标志。存操作系统管理进程的时候需要的数据
内容:
- 进程描述信息:进程标识符PID、用户标识符UID(该进程所属用户)
- 进程控制和管理信息:进程当前状态、进程优先级
- 资源分配清单:程序段指针、数据段指针、键盘、鼠标等
- 处理机相关信息
- 各种寄存器值(进程切换的时候需要把进程当前运行情况记录下来保存在PCB中,也就是运行的时候是在寄存器里、计数器里,但是要进程切换的时候,要保存下来的时候就保存到PCB中)
组织方式:
- 链接方式
- 按进程状态分成多个队列
- 持有指向各个队列的指针,队列里存的本身就是PCB
- 索引方式
- 根据进程状态分成多个索引表
- 持有指向各个索引表的指针,然后索引表里又会有指向各个PCB的指针
(反正就都是根据进程状态进行划分)
进程状态:运行态(当前正在被执行,单CPU里只有一个进程)、就绪态(等待被调度,优先级高的在队头)、阻塞态(可能会因为阻塞原因的不同再分多个队列)
状态和转换
-
运行态(当前正在被执行,占用CPU)
-
就绪态(已经具备运行条件,只需要等CPU,等待被调度)
-
阻塞态(因等待某一事件而暂时不能运行,比如等待IO)
切换:
- 运行态-》就绪态:时间片到了,或者被抢占,就被调度
- 就绪态-》运行态:被调度执行
- 运行态-》阻塞态:用系统调用的方式申请某种系统资源,或者请求等待某个事件发生,要等待事件完成。(进程主动发出转化的请求,所以只能从运行变为阻塞,就绪里什么都没在做,不可能调用什么)
- 阻塞态-》就绪态:申请的资源被分配,或者等待的事件完成,就所有准备都处理好了(只有在就绪态的才可能被调度,所以哪怕等待的已经做完了也不能直接从阻塞态变到运行态)(进程被动,要申请的和要等的都不是该进程本身能控制的)
- 创建态-》就绪态
- 运行态-》终止态
(PCB就会在各个队列里转移,且PCB内部存状态也要有相应改变)
线程
一个进程内部本身可能需要同时做很多事,但进程只能进行进程间的并发。所以引入线程,增加并发度,进程内部操作也能并发。
-
轻量级进程
-
基本的CPU执行单元,程序执行流的最小单位,调度的最小单位
-
也有线程ID、线程控制块(TCB)(类似PCB)等,也有就绪、阻塞、运行这几种状态。
-
几乎不拥有系统资源。同一进程的不同线程共享该进程的资源。
-
如果是同一进程内的线程切换,不需要切换进程环境,系统开销小。(因为都是用的同一进程的资源)
对比进程和线程
进程 | 线程 | |
---|---|---|
本质上 | 进程是对运行时程序的封装,是资源分配的最小单位 | 线程是进程的子任务,实现进程内部的并发。线程是CPU调度的最小单位 |
之间的关系 | 一个进程可以有多个线程,但至少有一个线程。 | 一个线程只能属于一个进程,线程依赖于进程而存在 |
资源 | 资源分配给进程,进程在执行过程中拥有独立的内存单元。 | 同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,用来存放所有局部变量和临时变量。(每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。)每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。 |
切换/创建或撤销 | 在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。 | 属于同一进程的线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作 |
切换 | 进程切换需要分两步:切换页目录、刷新TLB以使用新的地址空间;切换内核栈和硬件上下文(寄存器); | 而同一进程的线程间逻辑地址空间是一样的,不需要切换页目录、刷新TLB。 |
通信 | 主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。 | 同一进程中的多个线程具有相同的地址空间,可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。 |
之间的关系 | 进程间不会相互影响 | 一个线程挂掉将导致整个进程挂掉 |
总的来说 | 多进程间拥有各自独立的运行地址空间,进程间不会相互影响,程序可靠性强。但是进程创建、销毁和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步简单,适用于多核、多机分布。 | 多线程之间共享同一个进程的地址空间,线程间通信简单,同步复杂,线程创建、销毁和切换简单,速度快,占用内存少,适用于多核分布式系统,但是线程间会相互影响,一个线程意外终止会导致同一个进程的其他线程也终止,程序可靠性弱。 |
- 根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
- 资源 / 切换开销方面:每个进程都有独立的地址空间,程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
- 进程切换开销更大是因为每个进程都有自己的虚拟地址空间,对应了不同的页表。进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,虚拟地址转物理地址就会变慢。而线程是公用一个地址空间,切换不会导致TLB失效
- 包含关系: 操作系统中的每一个进程中都至少存在一个线程,一个进程可拥有多个线程。一个线程只属于一个进程,线程也被称为轻权进程或者轻量级进程。
- 进程间不会互相影响;一个线程挂掉后整个进程都会挂掉
- 通信:进程间通信复杂;线程间简单(可以通过共享内存)
为什么要区分线程和进程的概念
一个进程内部本身可能需要同时做很多事,但进程只能进行进程间的并发。所以引入线程,增加并发度,进程内部操作也能并发。
提高资源利用率(防止阻塞);提高并发度,减少并发执行时的切换开销
引入多线程最主要的原因是为了提高资源的利用率。现在的cpu都是多核心的这意味着多个线程可以同时运行,从而可以减少了线程上下文切换的开销。第二点是可以防止阻塞。如果单核cpu使用单线程,那么只要这个线程被阻塞了,那么整个程序也就被阻塞。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
操作系统引入了比进程粒度更小的线程,作为并发执行的基本单位,从而减少程序在并发执行时所付出的时空开销,提高并发性。
线程的好处:
-
易于调度,切换效率高,减少上下文切换的开销。
-
提高并发性,提高资源利用率。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
-
通信方便
切换的开销
进程切换分两步:
-
切换页目录以使用新的地址空间
-
切换内核栈和硬件上下文
线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
进程之间通讯方式
-
管道:管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。管道只能承载无格式字节流以及缓冲区大小受限
- 普通管道PIPE:
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
- 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
- 流管道s_pipe: 去除了第一种限制,可以双向传输.
- 命名管道FIFO:
- FIFO可以在无关的进程之间交换数据
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
- 普通管道PIPE:
-
消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。 (消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;
- 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
-
信号量:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
- 支持信号量组。
-
信号:用于通知接收进程某个事件已经发生。
-
共享内存:它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
- 共享内存是最快的一种IPC,因为进程是直接对内存进行存取
- 因为多个进程可以同时操作,所以需要进行同步
- 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
-
套接字SOCKET: socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。可以将套接字看作不同主机间的进程进行双间通信的端点。套接字Socket =(IP地址:端口号)。
线程同步方式
线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系。
-
临界区、互斥对象:主要用于互斥控制;都具有拥有权的控制方法,只有拥有该对象的线程才能执行任务,所以拥有,执行完任务后一定要释放该对象。
-
信号量、事件对象:事件对象是以通知的方式进行控制,主要用于同步控制
-
临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
- 在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。它并不是核心对象,不是属于操作系统维护的,而是属于进程维护的。
- 临界区对应着一个Critical Section对象,当线程需要访问保护数据时,调用Enter Critical Section函数;当对保护数据的操作完成之后,调用Leave Critical Section函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据
-
互斥对象:互斥对象和临界区很像,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。
- 由于互斥量是内核对象,因此其可以进行进程间通信,同时还具有一个很好的特性,就是在进程间通信时完美的解决了"遗弃"问题
- 互斥与临界区很相似,但是使用时相对复杂一些(互斥量为内核对象),不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。
-
信号量:信号量也是内核对象。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,PV操作
-
事件对象:是内核对象,通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
- 包含一个使用计数,一个用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。
- 有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。
- 当一个人工重置的事件对象处于有信号状态时,等待该事件对象的所有线程均变为可以调度状态;当一个线程得到该事件对象后,操作系统不会自动地将事件对象设置为无信号状态,需要显示地调用Reset Event函数才会将其设置为无信号状态,否则状态不变;
- 当一个自动重置的事件对象处于有信号状态时,等待该事件对象的所有线程中只有一个线程处于可以调度线程;同时操作系统会将该事件对象自动设置为无信号状态;当执行完保护代码后,需要显示调用下Set Event函数将该事件对象设置为有信号状态;
进程调度算法
-
批处理系统:没有太多用户操作,目标是保证吞吐量和周转时间(没什么交互的)
- 先来先服务FCFS:可以用一个队列来维持。可能导致短作业等待时间过长
- 短作业优先SJF:长作业可能会挨饿
- 最短剩余时间优先
-
交互式系统:有大量用户交互操作,目标是快速进行响应
- 时间片轮转:每个进程执行一个时间片,用完后由计时器发出中断。当正在运行的进程用完了时间片后,返回到就绪队列的末尾,等待再次被调度。
- 优先级调度:可随时间推进增加等待进程的优先级
- 多级反馈队列:每个队列时间片大小不同,优先级不同。同优先级的按时间片来。