0%

操作系统 - 进程与线程

进程,是指运行中的程序,也称为作业。

早期的计算机,只允许运行 一个 程序,这个程序被分配了计算机所能提供的所有资源。
现代的计算机,允许并行运行 多个 程序,系统资源也就被这些程序瓜分。
每一个程序也就成为了一个 工作单元,将这些 工作单元 称呼为:进程
因此,进程 也是计算机系统分配资源的基本单位。

程序,是静态的代码,而进程是动态的实体。

程序,也就是 代码,在硬盘中存放着,经过 主存储器(内存) 的加载,被封装成 PCB 数据结构,在 内核队列 中排队,等待 CPU调用 执行。相同的一份代码,可以被多次加载,形成多个独立的进程,对于系统来说,不过是队列中多了一个成员,当然,资源也是需要被瓜分的。

操作系统通过进程控制块PCB来表示进程。  

PCB

进程控制块,也就是PCB,是操作系统内部的一种用来表示进程数据结构,记载着和进程相关的一些信息。
包括:内存指针、进程状态、进程号、程序计数器、寄存器、内存限制说明、I/O状态信息等。

内存指针,进程内存数据相关的指针。
进程状态,包含:新建、就绪、运行、阻塞、停止。
进程号,用来标识唯一进程的标识符,也就是PID。
程序计数器,用来标识程序要执行的下一条指令的地址。
寄存器,用于发生CPU中断时临时存储信息。
内存限制说明,包含了内存管理系统的一些信息,比如:页表、段表。
I/O状态信息,包含了进程打开的文件列表及分配给进程的I/O设备。

进程的一生都在队列之间徘徊,一旦被CPU调用,那么就出队列,改变状态,读取PCB中记录的信息,恢复现场(也就是CPU上下文切换),相同状态的PCB会形成链表,从程序计数器记录的地址开始执行,直到时间片用完或者被中断,回到队列之中或者执行结束,释放资源。

如果,所有的进程都是CPU繁忙型,那么等待队列(CPU都在忙着)几乎都是空的。
如果,所有的进程都是I/O繁忙型,那么就绪队列(CPU都在闲着)几乎都是空的。

那么,就需要合理分配进程组合,避免内存需求过多或者设备过度空闲。

一个进程,在运行期间可以创建多个子进程。

创建进程的进程称为父进程,通过fork来实现,而子进程也可以再创建子进程,形成一个进程树
创建进程是需要分配系统资源的,子进程所需要的资源可以直接从系统获取,也可以从父进程获取,父进程所拥有的数据也可以传送给子进程,父子进程也可以共享同样的资源。
子进程也不是无限制的创建的,需要受限于父进程,否则会负载过高。

进程之间,可以通过多种方式相互通信。

进程的通信可以分为:直接通信、间接通信。
具体的方式包含:管道、信号、消息队列、Socket、共享内存。

管道,一种半双工的通信方式,数据只能在父子进程之间单向流动。
信号,一种异步通信方式,通过监听、中断来实现,如:SIGINT。
消息队列,是一种保存在内核中的消息的链表,进程之间通过读写消息队列通信。
Socket,通过TCP、UDP协议进行通信。
共享内存,多个进程读取同一块共享的内存。

最后,进程是系统分配资源的基本单位,是执行中的程序,被封装为PCB数据结构,由CPU来进行调度,每次调度将会加载进程数据,切换上下文,通过此方式切换进程,成本也是过高。

因此,诞生了线程

线程,本质上是轻量级进程,且共享了进程所拥有的数据和资源。

线程,建立在进程的基础上,一个进程可以拥有多个线程,每一个线程共享进程的数据和资源,并且可以拥有自己独享的数据,是CPU执行的基本单位。通过线程这种方式,CPU无需进行复杂的进程上下文切换,只需要切换线程即可,内存数据在同一个进程内是共享的,相比切换进程来说,成本明显降低许多。

thread

一个线程,包含:代码、数据、打开文件列表、寄存区、程序计数器、堆栈。
多个线程之间,处于并行状态。

举例,在我们打开浏览器的时候,一个线程负责拉取数据,一个线程负责加载页面,一个线程负责显示图像等,这几个线程之间互不影响,换做进程的话,需要来回切换。

RPC的场景中,每来一个请求,就单独使用一个线程来处理,比起单线程来说,并行的方式显然提高了处理的效率。当然,线程也不是无限开辟的,可创建的线程数受限于所拥有的资源。虽然创建线程的成本比创建进程的成本低,但也不是没有成本的。为了降低这种成本,基于线程池,可以快速创建进程,充分利用资源。

线程的实现方式有:一对一、多对一、多对多。

线程分为用户线程内核线程,用户线程由内核线程来实现。

一对一模型情况下,一个用户线程由一个内核线程来实现。这种方式使得线程之间并行化,不会因为一个用户线程的阻塞导致其它用户线程的阻塞,但是资源消耗就比较大了。

多对一模型情况下,将多个用户线程映射到一个内核线程。这种方式使得线程管理变得方便了,但一次只能执行一个线程,无法并行化,如果一个用户线程阻塞,将导致所有由此内核线程实现的用户线程阻塞。

多对多模型情况下,多个用户线程映射到多个内核线程上,处于交叉状态,做到多路复用,一个内核线程阻塞的情况下,可以切换到其它的内核线程,避免了上面两种模型的缺点。