一、进程
1.进程的描述
进程可以理解为程序的一次执行,也可以理解为程序运行的一个实例。
进程是分配资源的基本单位
一个进程由三部分组成:进程控制块PCB(Process Control Block),有关程序段,该程序段对其操作的数据结构集。
进程到底是什么样子呢?不妨让我们打开任务管理器。
这样我们不仅仅看到了每个正在运行的进程的名称,在详细信息里还能看到了进程分配的内存空间,pid等属性。
2. 进程控制块PCB
进程的PCB是系统感知进程的唯一实体。
所以说到底,对进程进行操作,也可以理解为对PCB进行操作。
PCB是要常驻内存的。
所以在创建进程的时候,就是分配一个PCB,里面记录了进程的各种信息,这些信息都包括:
- 描述信息:进程的标识号、用户标识号、家族关系
- 控制信息:进程当前状态、进程优先级、程序开始地址、计时信息、通信信息
- 资源管理信息:管理内存数据结构的指针、文件系统的指针等包括存储器的信息,IO设备、文件系统的信息。
- CPU现场保护结构:各个寄存器的内容
2.1 PCB的组织方式
一般来说,系统把所有PCB组织在一起,并把他们放在内存的固定区域,构成PCB表。
PCB表的大小决定了系统中最多可同时存在的进程的个数。
① 链接方式:把具有同一状态的PCB,链接成一个队列,这样可以形成若干就绪队列、阻塞队列和空白队列等,优先级高的进程的PCB排在前面。
② 索引方式:系统根据所有进程的状态建立几张索引表,如就绪索引表,阻塞索引表等,并把各索引表在内存的首地址记录在内存的一些专用单元中,在每个索引表的表目中,记录具有相应状态的某个PCB在PCB表中的地址。
2.2 EOS中的PCB
以EOS操作系统为例,看一下EOS是如何定义PCB的数据结构
typedef struct _PROCESS {
BOOLEAN System; // 是否系统进程
UCHAR Priority; // 进程的优先级
PMMPAS Pas; // 进程地址空间
PHANDLE_TABLE ObjectTable; // 进程的内核对象句柄表
LIST_ENTRY ThreadListHead; // 线程链表头
PTHREAD PrimaryThread; // 主线程指针
LIST_ENTRY WaitListHead; // 等待队列,所有等待进程结束的线程都在此队列等待。
PSTR ImageName; // 二进制映像文件名称
PSTR CmdLine; // 命令行参数
PVOID ImageBase; // 可执行映像的加载基址
PPROCESS_START_ROUTINE ImageEntry; // 可执行映像的入口地址
HANDLE StdInput;
HANDLE StdOutput;
HANDLE StdError;
ULONG ExitCode; // 进程退出码
} PROCESS;
3. 进程的状态
一个进程至少具有5种基本状态:初始态、执行状态、等待(阻塞)状态、就绪状态、终止状态
初始状态:进程刚被创建,由于其他进程正占有CPU所以得不到执行,只能处于初始状态。
执行状态:任意时刻处于执行状态的进程只能有一个。
就绪状态:只有处于就绪状态的经过调度才能到执行状态
等待状态:进程等待某件事件完成
停止状态:进程结束
有的系统,为了暂时缓和内存的紧张状态,或为了调节系统负荷,又引入了挂起的功能:暂时挂起一部分进程,把他们从内存临时换出到外存。
阻塞挂起:进程在外存并等待某事件的出现
就绪挂起:进程在外存,但只要进入内存,就可以运行
4. 进程控制
创建、撤销进程以及完成进程各状态之间的转换,由具有特定功能的原语完成
- 进程创建原语
- 进程撤销原语
- 阻塞原语
- 唤醒原语
- 挂起原语
- 激活原语
- 改变进程优先级
进程创建
一个进程可以创建一个子进程,子进程会继承父进程所拥有的资源,如继承父进程打开的文件,分配到的缓冲区等。
当子进程被撤销时,应该讲其从父进程哪里获得的资源归还给父进程,此外,撤销父进程时,也必须同时撤销其所有的子进程。
引起进程创建的事件如下:
- 用户登录
- 作业调度
- 提供服务
- 应用请求
进程创建的步骤:
- 创建PCB
- 赋予一个唯一的进程标识符pid
- 为新进程分配资源,为新进程的程序和数据以及用户栈分配必要的内存空间
- 初始化PCB(初始化标识信息,将系统分配的标识符和父进程标识符填入新的PCB中;初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶;初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态。)
- 将新进程插入到就绪队列
进程撤销
引起进程的撤销的事件如下:
- 正常结束:进程运行完毕
- 异常结束:进程运行中由于出现错误而终止。如越界等
- 外界干预:进程应外界的请求而终止运行,如操作员或操作系统干预,父进程请求
进程撤销的步骤:
- 根据被终止的进程的标识符,从PCB集合汇总检索除该进程的PCB,从中读出该进程的状态。
- 若被终止的进程正处于执行状态,应立即终止该进程的执行,并置调度标志位真,用于指示该进程被终止后应重新进行调度。
- 若该进程还有子孙进程,还应将其子孙进程予以终止,以防他们成为不可控的进程。
- 将被终止的进程所拥有的全部资源,或者归还给其父进程,或者归还给操作系统。
- 将被终止的进程PCB从所在队列或链表中移出,等待其他程序来搜集信息。
进程阻塞
引起进程阻塞的事件:
- 请求系统服务:当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求,该进程只能转变为阻塞状态来等待。
- 启动某种操作:当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成。
- 新数据尚未到达:对于相互合作的进程,如果其中一个进程需要先获得另一合作进程提供的数据后才能对数据进行处理,则只要其所需数据尚未到达,该进程只有(等待)阻塞。
- 无新工作可做:系统往往设置一些具有某些特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来。
进程阻塞的步骤:
- 保护CPU现场到PCB
- PCB插入到阻塞队列
进程唤醒
- PCB插入到就绪队列
要注意,进程阻塞和唤醒的原语不能出现在一个进程内
二、线程
1. 线程的描述
引入线程是因为创建进程、进程间切换的开销比较大
同一个进程下的多个线程贡献这个进程的资源
线程是进程的一条执行路径,共享进程的地址空间
线程也是CPU调度的一个基本单位
2. 线程控制块TCB
EOS中TCB的数据结构如下
//
// 线程对象结构体 (TCB)。
//
typedef struct _THREAD {
PPROCESS Process; // 线程所属进程指针
LIST_ENTRY ThreadListEntry; // 进程的线程链表项
UCHAR Priority; // 线程优先级
UCHAR State; // 线程当前状态
ULONG RemainderTicks; // 剩余时间片,用于时间片轮转调度
STATUS WaitStatus; // 阻塞等待的结果状态
KTIMER WaitTimer; // 用于有限等待唤醒的计时器
LIST_ENTRY StateListEntry; // 所在状态队列的链表项
LIST_ENTRY WaitListHead; // 等待队列,所有等待线程结束的线程都在此队列等待。
//
// 为了结构简单,EOS没有对内核进行隔离保护,所有线程都运行在内核状态,所以目
// 前线程没有用户空间的栈。
//
PVOID KernelStack; // 线程位于内核空间的栈
CONTEXT KernelContext; // 线程执行在内核状态的上下文环境状态
//
// 线程必须在所属进程的地址空间中执行用户代码,但可在任何进程的地址空间中执行
// 内核代码,因为内核代码位于所有进程地址空间共享的系统地址空间中。
//
PMMPAS AttachedPas; // 线程在执行内核代码时绑定进程地址空间。
PTHREAD_START_ROUTINE StartAddr; // 线程的入口函数地址
PVOID Parameter; // 传递给入口函数的参数
ULONG LastError; // 线程最近一次的错误码
ULONG ExitCode; // 线程的退出码
} THREAD;