操作系统—— 进程管理:进程
Posted 大彤小忆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统—— 进程管理:进程相关的知识,希望对你有一定的参考价值。
早期的计算机一次只能执行一个程序。这种程序完全控制系统,并访问所有系统资源。现代操作系统允许加载多个程序到内存,以便并发执行。这些需求导致了进程概念的产生,即进程为执行中的程序。
进程是现代分时操作系统的工作单元。
通过CPU的多路复用,所有进程可以并发执行。通过在进程之间切换CPU,操作系统能使计算机更为高效。
1. 进程
1.1 进程概念
- 在讨论操作系统时,有个问题是关于如何称呼所有CPU活动。批处理系统执行作业(job),而分时系统使用用户程序或任务(task)。即使单用户系统,用户也能同时运行多个程序(如文字处理、网页浏览等)。
- 即使用户一次只能执行一个程序,操作系统也需要支持本身的内部活动,如内存管理。所有这些活动在许多方面都相似,因此称为进程(process)。
1.1.1 进程
- 程序本身不是进程,程序只是被动实体。如存储在磁盘上的包含一系列指令的文件(通常称为可执行文件)。而进程是活动实体。当一个可执行文件被加载到内存时,这个程序就成为进程。
- 进程是执行中的程序实体。
- 进程是操作系统进行资源分配和调度的基本单位。
- 进程是执行的程序,这是一种非正式的说法。
- 进程不只是程序代码,进程还包括当前活动,如程序计数器的值和寄存器的内容等,另外,进程还包括堆、栈、数据等。
内存中的进程如下图所示。
1.1.2 进程状态
- 进程在执行时会改变状态。进程状态,部分取决于进程的当前活动。每个进程可能处于以下状态:
- 新的(new):进程正在创建。
- 运行(running):指令正在执行。
- 等待(waiting):又称阻塞状态,进程等待发生某个事件(如IO完成)。
- 就绪(ready):进程等待分配处理器。
- 终止(terminated):进程已经结束。
进程状态图如下图所示。
1.1.3 进程控制块
- 操作系统内的每个进程采用进程控制块(Process Control Block,PCB)来表示。
- PCB包含许多与某个特定进程相关的信息:
- 进程状态: 包括新的 、就绪、运行、等待、停止等。
- 程序计数器: 存储进程将要执行的下一个指令的地址。
- CPU寄存器: 在发生中断时,这些状态信息与程序计数器一起需要保存,以便进程以后能正确的继续执行。
- CPU调度信息: 包括进程优先级、调度队列的指针和其他调度参数。
- 内存管理信息: 包括基地址和界限寄存器的值、页表和段表等。
- 记账信息: 包括CPU时间、实际使用时间、时间期限、记账数据、进程数等。
- I/O状态信息: 包括分配给进程的IO设备列表、打开文件列表等。
进程控制块(PCB)如下图所示。
简而言之,PCB简单地作为这些信息的仓库,随着进程的不同而不同。进程间的CPU切换如下图所示。
Linux的进程表示: Linux操作系统的进程控制块采用C语言结构task_struct来表示,它位于内核源代码目录内的头文件<linux/sched.h>。这个结构包含用于表示进程的所有必要信息,包括进程状态、调度和内存管理信息、打开文件列表、指向父进程的指针及指向子进程和兄弟进程列表的指针等。(父进程( parent process)为创建它的进程,子进程(childprocess)为它本身创建的进程,兄弟进程(sibling process)为具有同一父进程的进程。)
1.1.4 线程
- 每个进程是一个只能执行单个线程(thread)的程序。这种单一控制线程使得进程一次只能执行一个任务。
1.2 进程调度
- 多道程序设计的目的是无论何时都有进程运行,从而最大化CPU利用率。分时系统的目的是在进程之间快速切换CPU,以便用户在程序运行时能与其交互。
- 为了满足这些目标,进程调度器(process scheduler)选择一个可用进程(可能从可用进程集合中)到CPU上执行。单处理器系统不会具有多个正在运行的进程。如果有多个进程,那么余下的需要CPU空闲并能调度。
- 进程在进入系统时,会被加入到作业队列(job queue),这个队列包括系统内所有进程。
- 驻留在内存中的、就绪的、等待运行的进程保存在就绪队列(ready queue)上,这个列表通常用链表实现。
- 系统还有其他队列,等待特定IO设备的进程列表,称为设备列表(device queue),每个设备都有自己的设备队列。
1.2.1 调度队列
就绪队列和各种I/O设备队列如下图所示。
进程调度通常用队列图表示:
1.2.2 调度程序
- 操作系统通过调度程序来选择进程,而后执行进程。
- 长期调度程序: 如批处理系统,提交的进程多于可以立即执行的,那么将进程保存到大容量存储设备(如磁盘)的缓冲池,以便后续执行。
- 短期调度程序: 从准备执行的进程中选择进程,并分配CPU执行。
- 中期调度程序: 将进程从内存(或从CPU竞争)中移出,从而降低多道程序调度。之后进程可以被重新调入内存,并从中断处继续执行。这种方案称为交换(swap),进程可以换出(swap out),并在后来可以换入(swap in)。
添加中期进程调度到队列图如下图所示。
- IO密集型进程: 执行进程时,IO占用更多的时间,可能需要等待IO,IO是一个耗时操作。
- CPU密集型进程: 执行进程时,CPU计算占用更多时间。
1.2.3 上下文切换
- 中断导致CPU从执行当前任务改变到执行内核程序,这种操作在系统中经常发生。当中断发生时,系统需要保存当前运行在CPU上的进程的上下文,以便在处理后能恢复上下文,即先挂起进程,再恢复进程。
- 进程的上下文采用进程PCB表示。
- 切换CPU到另一个进程需要保存当前进程状态和恢复另一个进程的状态,这个过程称为上下文切换(context switch)。
- 上下文切换的时间是纯粹的开销,因为在这个过程中,CPU没有做任何有用的工作。
1.3 进程运行
- 大多数系统的进程能够并发执行,它们可以动态创建和删除。
- 因此操作系统必须提供机制,以创建和终止进程。
1.3.1 进程创建
进程在执行的过程中可能创建多个新的进程。创建进程称为父进程,而新的进程称为子进程。每个新进程可以再创建其他进程,从而形成进程树(process tree)。
大多数操作系统对进程的识别采用唯一的进程标识符(process identifier,pid),通常是一个整数值。通过pid可以访问内核中的进程的各种属性。
典型Linux系统的一个进程树如下图所示。
当进程创建新进程时,可有两种执行可能:
- 父进程与子进程并发执行
- 父进程等待,直到某个或全部子进程执行完成
新进程的地址空间也有两种可能:
- 子进程是父进程的复制品(它具有与父进程同样的程序和数据)
- 子进程加载另一个新程序
如linux操作系统使用fork()
创建新进程,如下图所示。
1.3.2 进程终止
当进程完成执行最后语句并且通过系统调用exit()
请求操作系统删除自身时,进程终止。当进程终止时,操作系统会释放其资源。
父进程终止子进程的原因有很多:
- 子进程使用了超过它所分配的资源
- 分配给子进程的任务,不再需要
- 父进程正在退出,而操作系统不允许无父进程的子进程继续执行
有些系统不允许子进程在父进程已终止的情况下存在。对于这类系统,如果一个进程终止(正常或不正常终止),那么它的所有子进程也应终止,这种现象称为级联终止,通常由操作系统来启动。
1.4 进程间通信
- 操作系统内的并发执行进程可以是独立的也可以是协作的。
- 如果一个进程不能影响其他进程或受其他进程影响,那么该进程是独立的。
- 如果一个进程能影响其他进程或受到其他进程的影响,那么该进程是协作的。
- 允许进程协作,有许多理由:
- 信息共享
- 计算加速
- 模块化 - 协作进程需要一种进程间通信机制,以允许进程互相交换数据与信息。
- 进程间通信有两种基本模型:内存共享和消息传递。
1.4.1 共享内存系统
- 采用共享内存的进程间通信,需要通信进程建立共享内存区域。
- 通常操作系统试图阻止一个进程访问另一个进程的内存。共享内存的进程需要打破这个规则。
- 进程负责确保,它们不向同一个位置同时写入数据。
- 生产者进程 —> 缓冲区(存储共享数据) ---->消费者进程。
- 缓冲区: 分为无界缓冲区和有界缓冲区。
- 无界缓冲区没有限制缓冲区大小,当缓冲区为空,消费者需要等待。
- 有界缓冲区有固定大小。当缓冲区为空,消费者需要等待,如果缓冲区满,生产者必须等待。
1.4.2 消息传递系统
- 如互联网聊天程序。
- send(message) ----> receive(message)
- 直接通信: 需要通信的每个进程必须明确指定通信的接收者和发送者。
- 间接通信: 通过邮箱或端口来发送和接收消息。
- 阻塞和非阻塞(即同步和异步)
- 阻塞发送: 发送进程阻塞,直到消息由接收进程所接收
- 非阻塞发送: 发送进程发送消息,并且恢复操作
- 阻塞接收: 接收进程阻塞,直到消息可用
- 非阻塞接收: 接收进程收到一个有效消息或空消息 - 缓存
- 零容量队列: 队列长度为0,发送者阻塞,直到接收者收到消息
- 有限容量: 队列长度有限,队列空,消费者阻塞,队列满,生产者阻塞
- 无限容量: 队列长度无限,发送者从不阻塞
1.5 客户机/服务器通信
客户机-服务器系统通信的三种策略:
- socket(套接字)
- 远程过程调用RPC
- 管道
1.5.1 套接字
- 套接字(Socket)为通信的端点。通过网络通信的每对进程需要使用一对套接字,即每个进程各有一个。
- 每个socket由ip和端口组成。
采用套接字的通信如下图所示。
- 使用socket通信,虽然常用和高效,但是属于分布式进程之间的一种低级形式的通信。一个原因是,socket只允许在通信线程之间交换无结构的字节流。客户机和服务器程序需要自己加上数据结构。
1.5.2 远程过程调用
1.5.3 管道
管道(pipe)允许两个进程进行通信。
在实现管道时,应该考虑以下四个问题:
- 管道允许单向通信还是双向通信
- 如果允许双向通信,它是半双工的(数据在同一时间只能按一个方向传输)还是全双工(数据在同一时间内可以在两个方向传输)
- 通信进程之间是否有一定的关系(如父子关系)
- 管道通信能否通过网络,还是只能在同一台机器上进行
- 普通管道
⋄ \\diamond ⋄ 普通管道允许两个进程按标准的生产者-消费者方式进行通信。
⋄ \\diamond ⋄ 生产者向管道的一端写(写入端),消费者从管道的另一端读(读出端)。因此管道是单向的,只允许单向通信。
⋄ \\diamond ⋄ 如果需要双向通信,就要使用两个管道,而每个管道向不同方向发送数据。
⋄ \\diamond ⋄ 只有当进程相互通信时,普通管道才存在。一旦进程通信结束,普通管道就不存在了。 - 命名管道
⋄ \\diamond ⋄ 通信是可以双向的,并且父子关系不是必需的。
⋄ \\diamond ⋄ 当建立了一个命名管道后,多个进程都可用它通信。
⋄ \\diamond ⋄ 当通信进程完成后,命名管道继续存在。
以上是关于操作系统—— 进程管理:进程的主要内容,如果未能解决你的问题,请参考以下文章
在 Python 多处理进程中运行较慢的 OpenCV 代码片段