Processes
约 2168 个字 25 行代码 5 张图片 预计阅读时间 15 分钟
进程的概念
进程是正在运行中的程序,进程的执行必须是有序的。
一个进程中包含如下部分
- text section (code):存储代码
- data section (global vars):存储全局变量和静态变量
- stack (function parameters, local vars, return addresses):栈,存储函数参数、局部变量以及返回值等
- heap (dynamically allocated memory):堆,由程序员动态分配的内存
- program counter:指向当前正在执行指令的下一条指令
思考:为什么堆和栈是反向的,而不是都是往同一个方向增长?
反向增长可以增加分配的灵活性,中间那部分空间既可以被堆使用,也可以被栈使用。如果二者是同向增长的,因为不知道进程执行中会使用多大的空间来分配给堆和栈,导致空间分配的不变。
中间那空的部分在实际的使用中并不是在进程启动的时候就分配的,而是一块虚拟的映射,只有在实际使用的时候才会分配具体的物理空间给它
进程状态(Process State)
- 进程在运行的过程中会有不同的状态
- new:进程被创建
- running:进程中的指令正在被执行
- ready:进程准备完成,等待 CPU 资源进行执行(已经加载到内存中)
- waiting/blocked:进程处于等待或者阻塞的状态(比如等待 I/O 操作)
- terminated:进程已经完成执行
一个例子
运动会比赛模型
- 运动员:Process
- 号码簿:Process ID
- 跑道:CPU core
- 每条跑道只能有一个运动员
- 运动场可以有一条/多条跑道
- 跑步:Running
- 允许打水喝
- 接受调度,让出跑道
- 离场地点必须记录以便继续
- 口渴下场喝水:Wait I/O
- 打水过程运动员处于wait状态,直到喝完才能返回运动场
- 打水很慢,其他人也打水
- 等待上跑道: Ready 状态
进程控制块(PCB)
PCB 中存储着每个进程的相关信息
- Process state:进程状态
- Program counter:进程执行的位置,也就是下一条需要执行指令的位置
- Contents of CPU registers:寄存器相关信息
- CPU scheduling information:CPU 调度的参考信息(优先级、调度队列的指针、调度参数等)
- Memory-management information:内存管理信息(页表、段表等信息,与具体的内存系统有关)
- Accounting information:进程的动态数据的统计和记录(总计执行时间、运行时间限制、进程号等)
- I/O status information:I/O 状态信息(分配给进程的 I/O 设备列表、打开的文件列表等)
进程切换
图中显示的是从进程 P0 切换到进程 P1 的过程,首先将 PCB0 的信息存储到内存中,接着从内存中将 PCB1 的内容加载到 CPU 中,最后执行进程 P1 期望执行的内容。
进程调度
进程调度的队列
- Job queue:系统中所有进程的集合
- Ready queue:内存中所有处于 ready 态的进程的集合
- Device queue:正在等待 I/O 设备资源的进程的集合
操作系统中的进程就不断地在各个队列之间切换
注意:具体一个时刻,某个进程只能处于一个队列中,不能同时在两个队列中。
Schedulers(调度器)
- Long-term scheduler(or job scheduler):选择应将哪些进程放入内存中(就绪队列)
- Short-term scheduler(or CPU scheduler):选择下一个执行的进程并且分配 CPU 资源
- 有些时候还需要进程的换入换出
UNIX 和 Windows 不使用 long-term scheduling。
- Short-term scheduler 的调用是非常频繁的,所以必须要非常快
- Long-term scheduler 的调用则相对较少,所以可以稍微慢些
- Long-term scheduler 决定多进程的数量
根据进程在运行时对不同硬件的需求情况,可以将硬件分为两类
- I/O bound:花费更多的时间在 I/O 上的进程(下载)
- CPU bound:花费更多时间在计算上的进程(科学计算、打游戏)
上下文切换(Context Switch)
CPU 切换进程的时候需要保存上一个进程的信息,并且加载下一个进程的相关信息。上下文切换的时间是多余的,系统在上下文切换的时候没有做任何实际有效的工作,而上下文切换的时间决定于硬件的支持情况。
进程操作
进程创建
父进程创建子进程,子进程进一步创建其他进程,最终形成一棵进程树。
- 资源共享
- 父进程和子进程共享资源
- 子进程共享父进程的部分资源
- 父进程和子进程不共享资源
- 执行
- 父进程和子进程同时执行
- 父进程等待子进程执行完成后执行
- 地址空间
- 子进程是父进程的复制品(一段程序)
进程中断
- 进程执行完最后一条语句,请求操作系统删除
- 从子进程到父进程输出数据(通过 wait)
- 进程的资源会被操作系统回收
- 父进程可以终止子进程的执行(abort)
- 子进程使用的资源已经超过了分配给它的资源
- 分配给子进程的任务不再被需要了
- 如果父进程被取消了
- 一些操作系统当父进程取消后,就不允许子进程的继续运行,也就是说需要删除这个父进程的全部子进程(cascade)
- 另外一些操作系统中允许子进程继续存在,并且会设置父进程位初始化进程
合作进程
- 独立的进程不能被被的进程影响,也不能影响别的进程
- 合作进程可以被被的进程影响,也可以影响别的进程
- 进程合作的优势
- 信息共享
- 多 CPU 情况下可以加快计算速度
- 模块化
- 方便
生产者消费者模型
生产者进程生产信息,消费者进程消费信息。
- unbounded-buffer:对缓冲区的大小没有限制。如果缓冲区中没有资源,消费者进程就必须等待。
- bounded-buffer:缓冲区的大小是固定的。如果缓冲区中的资源已经满了,生产者进程必须等待。
#define BUFFER_SIZE 10
typedef struct {
...
}item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
while (true) {
Produce an item;
while (((in + 1) % BUFFER_SIZE) == out)
; // do nothing -- no free buffers
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
}
while (true) {
while (in == out)
; //do nothing -- nothing to consume
Remove an item from the buffer;
item = buffer[out];
out = (out + 1) % BUFFER SIZE;
return item;
}
进程间通信(IPC)
IPC 的两种模型:消息传递和共享内存
- 消息传递(message passing)
- 进程间通过两个操作函数来进行消息传递
- send(message):消息的长度可以是固定的,也可以是可变的
- receive(message)
- 如果两个进程之间想要交流,就需要
- 首先建立通信连接:这个连接的实现可以是物理层面上的(共享内存、硬件总线),也可以是逻辑层面上的(逻辑属性)
- 通过两个操作函数进程信息交换
- 进程间通过两个操作函数来进行消息传递
- 直接通信
- 进程之间必须要显示命名
- send(P, message):向进程 P 发送消息
- receive(Q, message):从进程 Q 接收到消息
- 通信连接的属性
- 连接时自动建立的
- 一个连接仅支持一对进程间的通信
- 连接既可以是单向的,也可以是双向的
- 进程之间必须要显示命名
- 间接通信
- 信息是通过mailboxes(或者是 ports)来传递的
- 每一个邮箱都有唯一的 id
- 共享同一个邮箱的进程之间可以通信
- 通信连接的属性
- 仅当进程共享同一个邮箱时才建立连接
- 一个连接中可能会有多个进程
- 每一对进程可能处于多个通信连接中
- 连接既可以是单向的,也可以是双向的
- 信息是通过mailboxes(或者是 ports)来传递的
间接通信存在的问题
三个进程共享同一个邮箱时,如果 P1 发送了消息,P2 和 P3 同时接收了消息,那么这个消息是谁收到的?
解决方案
- 一个连接最多允许两个进程
- 同一时刻只允许一个进程调用 receive 函数
- 允许系统任意选择接收端,发送方被系统通知接收方是谁
同步和异步
信息的传输有两种形式:blocking 和 non-blocking
- Blocking 被认为是同步的
- 发送发只有接收方接收信息后才能继续发送信息
- 接收方只有有消息能接收时才能调用接收函数
- Non-blocking 被认为是异步的
- 发送发可以连续发送信息
- 接收方无论是否有信息,都可以调用接收函数