进程属性
进程的属性保存在一个被称为进程控制块(Process Contral Block,简称PCB)的结构体中,内核为每个进程维护了一个进程控制块,用于管理相应进程的属性信息。PCB的本质是一个task_struct结构体,其中包括进程控制符(Process Identifier,PID)、进程组、进程环境、进程的运行状态等。Linux内核通过管理PCB,来调度进程。
task_struct结构体可以在Linux源码的/include/linux/sched.h中找到,搜索“/struct task_struct”即可查看文件中结构体的定义(需先安装源码包)。掌握PCB中的一些属性信息,对我们理解、学习和操作进程都有帮助,下面我们来学习PCB中一些常用的重要属性。
1、 标识符
Linux系统中进程的标识符有三个,分别为:进程标识符、用户标识符和组标识符。
① 进程标识符
进程标识符(Process Identifier)即进程ID,简称pid,它是进程的唯一标识。内存中同时可以存在多个进程,每个进程都有不同的pid,内核通过这个标识来识别不同的进程;用户也可以根据内核提供的pid,通过系统调用去操作用户进程。
pid是一个32位的非负无符号整型数据,通常进程的pid会被顺序编号,即新创建的进程的pid通常为前一个进程的pid加1。但是Linux系统上pid的取值范围是有限的,因此若当前进程的pid已为最大值,系统创建的下一个进程的pid就必须使用闲置的数值。
② 父进程标识符
父进程标识符(Parent Process Identifier),简称ppid,是进程的父进程,即创建该进程的进程,所对应的PID。在Linux系统中,除编号为1的进程(init进程)外,其它进程都应有对应的父进程。
③ 用户标识符
用户标识符(User Identifier)标识创建这个进程的用户,简称UID。除此之外,PCB中还有“EUID”的概念,即有效用户标识符,表示以有效权限发起进程的用户。比如发起一个进程的用户是itheima,但实际有权限的是root,也就是itheima以root的权限发起了进程,那么这个进程的uid对应的用户为itheima,euid对应的用户为root。
④ 组标识符
组标识符(Group Identifier)标识创建进程用户的所属组,简称GID。euid对应的组标识符记为egid。
Linux中提供了获取进程标识符的接口,不同标识符对应的函数如表1所示。
表1 进程标识符与函数接口
标识符 | 函数接口 |
---|---|
pid/ppid | pid_t getpid(void);/pid_t getppid(void); |
uid/euid | uid_t getuid(void);/uid_t geteuid(void); |
gid/egid | gid_t getgid(void);gid_t getegid(void); |
这些函数在头文件unistd.h与sys/types.h中声明,其返回值类型pid_t、uid_t、gid_t都是宏定义,其实质是unsigned int。在使用函数接口前需要先在文件中包含对应头文件。
2、 进程状态
系统中的资源是有限的,进程若要运行,就必须能先获取到足够的资源;多个进程分时复用cpu,当分配给进程的时间片结束后,内核会收回进程对cpu的使用权,因此,进程在内存中可能会出现不同的状态。
通常进程的状态被划分为五种:初始态、就绪态、运行态、睡眠态和终止态。初始态一般不进行讨论,因为当初始化完成后,进程会立刻转化为就绪态。
① 就绪态
处于就绪态(Ready)的进程,所需的其它资源已分配到位,此时只等待cpu,当可以使用cpu时,进程会立刻变为运行态。内核中的进程通常不是唯一的,因此内核会维护一个运行队列,用来装载所有就绪态的进程,当cpu空闲时,内核会从队列中选择一个进程,为其分配cpu。
② 运行态
进程处于运行态(Execting)时会占用cpu,处于此状态的进程的数目必定小于等于处理器的数目,即每个cpu上至多能运行一个进程。
③ 睡眠态
处于睡眠态(Sleeping)的进程会因某种原因而暂时不能占有cpu。睡眠态分为不可中断的睡眠和可中断的睡眠。不可中断的睡眠,是由外部I/O调用等造成的睡眠,此时该进程正在等待所需I/O资源,即便强制中断睡眠状态,进程仍无法运行,这种睡眠态亦可称为阻塞;当进程处于可中断的睡眠态时,往往是因为进程对应的当前用户请求已处理完毕,因此暂时退出cpu,当用户再次发出请求时,该进程可随时被唤醒,这种睡眠态也被称为挂起。
④ 终止态
处于终止态的进程已运行完毕,此时进程不会被调度,也不会再占用CPU。
进程通常会在这四种状态中转换,这四种状态间可能发生的转换,如图1所示。
图1 进程状态转换
3、 寄存器信息
cpu中寄存器的数量是有限的,若进程p1的时间片结束,进程p2将获得cpu, cpu中的寄存器应给进程p2使用。但进程p1可能尚未执行结束,在之后的某个时间片,进程p1需重新获得cpu的使用权。因此在进程切换时,应先保存寄存器中存储的进程p1的数据,以便进程p1再次使用cpu时,能从中断的位置继续向下执行。
4、 页表指针
当程序运行时,系统会为程序开辟一段4G大小的虚拟内存,其中0~3G的虚拟地址英语存放程序的代码段、数据段等信息,当虚拟内存与物理内存相映射时,各个虚拟内存中地址相同的数据,会被MMU(Memory Management Unit,内存管理单元)映射到内存中不同的物理地址,为保证内核能根据进程中的虚拟地址在物理磁盘中找到进程中所需的数据,PCB应存储虚拟地址与物理地址的对应关系。
Linux系统采用分页存储的方式管理内存:在进程装载入内存之前,系统将用户进程的逻辑地址空间分成若干个大小相等的片,这些片称为页面或页,并为各个页编号;相应地,内存空间也使用相同的方式,划分为与逻辑地址页面大小相同的块并进行编号。之后当为进程分配内存时,以块为单位,将进程中的若干个页装入多个可以不相邻的物理块中。此时逻辑地址与物理地址间应有个对应关系。Linux操作系统中使用页表来存储这个对应关系,这个页表的实质是一个结构体。每个进程的pcb中都有一个指向页表的指针,进程、页表与内存之间的映射关系如图2所示。
图2 映射关系示意图
5、 进程组与会话
功能相近的进程被放在一起,就组成了一个进程组。例如在听歌时,音乐播放、歌词显示、时间控制等多个进程往往会被同时启动,因此可以将这些进程放在同一个进程组中。会话是进程组的一个集合。
除以上介绍的几项属性外,PCB中还包含很多其它属性,如控制终端的描述信息、文件描述符表、当前工作目录、进程可用的资源上限等,这些属性比较简单,此处便不再讲解。