系列文章
第二章 进程的描述与控制
2.1 什么是进程
程序代码+相关数据+程序控制块PCB
当处理器开始执行一个程序的代码时,称这个执行的实体为进程
2.1.1 进程和进程控制块PCB
PCB(Process Control Block)
- PCB是进程中断的基础,恢复执行后好像从未中断一样
- PCB由OS创建和管理
- 支持多进程的工具
PCB是用于进程管理的数据结构
PCB元素 | 作用 |
---|---|
id | 标识符 |
state | 状态 |
PC | 程序计数器 |
memory pointer | 内存指针 |
context | 上下文 |
I/O status | I/O状态 |
…… | …… |
2.1.2 进程VS程序
程序是剧本,进程是演出
进程基本特征:
- 动态性(本质特征):进程存在生命周期
- 并发性(重要特征):可同其他进程一同推进
- 独立性:各进程地址空间相互独立(逻辑地址)
- 异步性:进程按不可预知速度推进
与程序相比:
1. 进程是正在计算机执行的实例
2. 进程=PCB+程序代码+数据
3. 程序是静态的,没有生命周期
4. 进程与程序并无一一对应关系,如一个dad()程序中涉及多个PV进程
5. 引入进程的概念是为了容易正确解释多道程序并发
2.2 进程状态
进程轨迹
进程的待执行指令序列,描述单个指令的行为(指南)
分派器Dispatcher
将cpu从一个进程切换到另一进程
2.2.1 两状态模型
2.2.2进程创建与终止
进程创建
OS创建用于管理进程的PCB
OS为进程分配内存
创建原因
- 新作业<-旧的已完成
- 用户登录<-字面意思
- 用户请求服务<-比如请求打印
- 进程派生<-如fork()
相关
进程派生:进程显式请求创建一个进程(子进程)
父进程:原来的进程
子进程:派生的新进程
终止原因
- 作业完成
- timeout
- 无可用内存
- 无权限访问
- 超出可用内存范围
- 访问受保护的文件
- 不可运算
- 超时(等不及)
- I/O失效 如读写失败
- ……
- 父进程终止 父死子亡
五状态模型
排队模型
- 单阻塞队列:所有阻塞队列在同一队列,事件后满足需遍历整个队列
- 多阻塞队列:不同阻塞条件对应不同队列
同样的,可以根据不同优先级维护多个就绪队列
2.2.4 被挂起的进程
是为了解决内存有限的问题,把暂时阻塞的进程部分或全部换出到外存的挂起队列中
挂起后
- OS调度新进程
- 从挂起队列取新进程
挂起
内存中处于阻塞、就绪甚至执行的进程释放到外存,不再参与cpu竞争
七状态模型
挂起的进程
不能立刻运行、在外存中、不会自行换入、可能在等待或不等待条件
挂起原因
- 交换(腾出空间)
- 用户请求
- 父进程请求
- OS请求
- 定时:定时执行,在等待时挂起
2.3 进程描述
2.3.1 OS控制结构
OS用表格来维护管理资源
-
内存表:用于跟踪内存、外存
-
分配给进程的内存和外存
-
内存外存受保护信息
-
管理外存信息
-
I/O表
-
I/O状态(可用/已分配给XX进程)
-
I/O数据源和目的内存
-
文件表
-
文件是否存在
-
文件在外存位置
-
当前状态
-
其他属性
-
进程表特征
-
用于管理进程
-
有对内存、I/O\文件的间接引用
-
本身可被OS访问(废话-_-)
2.3.2进程控制结构
进程映像:程序、数据、栈(保存调用地址)和PCB的集合
PCB中的信息
-
进程标识信息:存储在进程中的数字标识符
- 进程id
- 父进程id
- 用户id
-
处理器状态信息:cpu寄存器内容
- 用户可见寄存器
- 控制和状态寄存器(PC、PSW、中断允许/禁用标志、执行模式)
- 栈指针(指向该进程的栈顶)
-
进程控制信息:操作系统调度进程所需其他信息
- 进程状态state:“running”……
- 进程优先级priority
- 事件标识:进程等待事件
- 调度相关信息:不同调度算法可能需要空间存放数据
- 数据结构:用于支持队列、环、指针
- 进程通信:关联无关进程之间的通信
- 进程特权
- 存储管理:指向虚存段表或页表的指针
- 资源所有权及使用情况
2.4 进程控制
2.4.1 执行模式
内核模式功能
- 进程管理
- PCB
- create、delete、schedule
- shift
- 同步
- 存储管理
- 分配
- 交换
- 分页分段
- I/O设备管理
- I/O缓冲区
- 分配I/O
内核的支撑功能
- 中断处理
- 时钟中断
- 记账
OS支持的两种模式及其特点
用户模式
- 优先权少
- 用户程序执行
系统模式/内核模式
- 优先权多
- 运行在内核
- 特权指令
- 特权内存空间访问
注:当前模式由PSW中的特殊位标识,两种模式也是为了保护OS数据结构不被篡改
2.4.2进程创建
OS行为:
分配id–>分配空间–>init pcb(id、栈、state、priority……)–>插入就绪/挂起队列–>初始化其他数据结构
2.4.3进程切换
中断
- 普通中断
- timeout:进程时间片耗尽
- I/O中断:I/O就绪,OS决定该唤醒哪个进程
- 内存失效(缺页):阻塞当前进程
- 陷阱(自陷)
- 进程错误或异常
进程切换原因
- 普通中断:对外部事件的反应
- 陷阱:处理异常
- 系统调用:调用内核程序
中断过程
save context(P1)–>renew pcb(P1)–>move pcb(P1) in queue–>change process(P1->P2)–>renew pcb(P2)–>renew storage、data structure–>reload context(P1)
注意:中断不一定导致进程切换
- 一个例子:I/O中断后,系统决定仍由先前进程继续执行(比如因为该进程priority更高)
模式切换
用户模式<==>内核模式
原因
- 系统调用:调用内核服务
- 中断:调用中断处理程序
模式切换与进程切换
模式切换不一定导致进程切换,要进程切换一定要模式切换
2.4.4 OS的执行
- 无进程的内核:内核独立于进程(separate kernel)
- 用户进程内的操作系统:进程与内核函数同时执行(OS function execute with process)
- 基于进程的操作系统:内核函数与进程同级执行(OS function execute with separate process)
2.5 UNIX SVR4进程管理
fork()用于创建进程:pid=fork();
注意三点:
- fork()创建子进程之后,父子进程均各自继续运行
- 子进程的fork()返回pid=0,父进程的fork()返回所创建的子进程id
- 父子进程的推进顺序不可估计
举三个例子帮助理解:
例1:下面代码共输出几个hello world?
#include <stdio.h>
#include <unistd.h>
int main()
{
int i;
for (i=0;i<3;i++)
fork();
printf("hello, world\n");
return 0;
}
一次fork()理解为:旧进程不变,继续运行,产生了一个新进程
3次fork(),会有2^3个进程,所以会打印8个hello world
例2:下面代码的输出是什么?
void main(void) {
int i;
static char buffer[10];
if (fork()==0)
strcpy(buffer, “Child\n”);
else
strcpy(buffer, “Parent\n”);
for (i=0; i < 5; ++i) {
sleep(1);
write(1, buffer, sizeof(buffer));
}
}
5个Child,5个Parent,但顺序不一定
例3:下面代码的输出是什么?
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int global = 4;
void main(void) {
int pid;
int vari = 5;
printf(“before fork\n”);
if ((pid = fork()) < 0) {
printf(“fork error\n”);
exit(0);
} else if (pid == 0) {
global++;
vari--;
}
printf(“global=%d,vari=%d\n”, global, vari);
}
父进程:global=4,vari=5
子进程:global=5,vari=4
2.6 线程
进程分为两个组成部分
- 调度的基本单位
- 资源的独立单元
线程作为基本调度单位独立出来,由OS调度
多线程
单个进程内支持多个并发路径的能力
单线程
一个进程只有一个线程
线程是进程中的一个实体,是独立调度和分派的单元
2.6.1 进程vs线程
一个进程可以有一个或多个线程
挂起或终止一个进程=挂起或终止其内部所有线程
线程
- 执行状态state:派生、阻塞、解除阻塞、结束
- 上下文context
- 栈
- 局部变量
- 共享的资源和内存
多线程比单线程多一个tcb(线程的“pcb”)
线程优点
- 创建耗时短
- 终止耗时短
- 切换耗时短(同进程内)
- 提高了不同执行程序间的通信效率
线程同步
- 一个进程中的所有线程共享该进程数据和空间
- 一个线程对数据修改会影响其他线程
2.6.2线程分类
用户级线程 ULT(User-Level Thread)
- 线程内核不可见,由应用程序管理
- 进程阻塞,线程不一定阻塞;进程运行,线程也可能阻塞
- 优点
- 线程切换不需要切换内核模式
- 调度策略可以应用程序定制
- 可运行在任何种类的OS上
- 缺点
- 线程系统调用时会阻塞该进程所有线程
- 不适用于多核系统
- 缺点的改进办法
- jacketing技术:由应用级调用替代系统调用
内核级线程 KLT(Kernel level Thread)
- 内核管理线程,应用程序不管理线程
- 优点
- 可以将多个线程分配到多个cpu上
- 一个线程阻塞时,同进程其他线程不会阻塞
- 内核本身也可以使用多线程
- 缺点
- 线程切换要进行模式切换
混合方法(ULT+KLT)
- 线程在用户级创建
- 线程调度、同步由应用程序完成
- 用户线程一个或多个映射到内核线程上(内核线程要更少)