1.进程的概念
进程是对正在运行的程序的一个抽象,是操作系统的核心概念。进程的概念使得一个单独的CPU变换成多个虚拟的CPU,来支持进程间的并发操作,每个进程有专属的CPU。日常使用电脑的时候,我们会一边听音乐一边工作,我们运行的网易云音乐是一个进程,修饰图片打开的Photoshop是一个进程,需要搜索素材时运行的火狐浏览器也是一个进程。即进程是正在运行的进程的一个实例,它包含程序计数器、寄存器和变量的当前值等。
使用电脑时,我们会感觉3个进程同时在运行,但实际上物理程序计数器只有一个(假设此电脑为单核),同一时间只有1个进程处于执行状态。所以进程概念就是操作系统提供给应用程序的抽象:
- 一个进程独占地使用处理器
- 一个进程独占地使用存储器
我们要澄清一下进程和程序的关系,两者经常被混淆。程序是一堆代码和数据以文件形式存储在介质中,而进场是依据程序而执行的过程总和。打个比方,厨师安装菜谱烘焙出美味的菜肴,菜谱就是程序,而做菜的过程总和是进场。
2.创建进场
进程可以在如下情况下被创建:
- 系统初始化
- 某个处于运行状态的进程调用了系统调用(System Call)来创建一个新的进程
- 用户请求创建一个新的进程
新的进程都是在已存在的进程执行了一个用于创建进程的系统调用而创建的。在Linux中,第一个进程是进程0,是在系统初始化时加载的,然后进程0会创建进程init进程(进程1)和页面守护进程(进程2),init进程会创建更多的进程为系统和用户服务。Linux中创建新进程的系统调用为fork,会产生一个与父进程几乎一样的子进程(当然,无性繁殖),通常子进程会接着执行一个系统调用execve或对execve封装的类似系统调用,指定参数如程序文件位置和环境变量等,得以运行一个新的进程。考虑我们在Linux shell中输入firefox,shell进程便会创建一个新进程来启动火狐浏览器,火狐浏览器作为一个新的进程运行。
正是由于新进程只能由已存在的进程创建,在Linux操作系统中,所有的进程会构成以进程0为根进程的具有层次的树。父进程会持有子进程的pid(每个进程都有一个唯一的pid),可以监听子进程的状态。
3.进程的终结
进程终结的条件有:
- 正常退出(自愿)
- 出错退出(自愿)
- 严重错误(非自愿)
- 被其他进程杀死(非自愿)
正常退出的情况占大多数,进程完成工作之后会通知操作系统调用exit系统调用结束进程。C语言中主函数执行最后一行 return 0; 之后紧跟着会执行exit(0); 相当于
exit(main(int argc,char* argv[]));
出错退出可能 是进程执行过程中出现了参数错误等情况引发的,如请求读写文件时没有权限。
严重错误导致进程终止的原因可为执行非法指令、访问权限之外的地址、除零错误等。Linux中,出现此类错误时会产生一个信号,进程可以注册对应信号的处理函数来自行处理严重错误。
被其他进程杀死。一些拥有权限的进程可调用kill系统调用来给其他进程发送信号,终止其他进程的执行。
4.进程的状态
一般而言进程的数量远多于CPU的数量,所有的进程不能同时执行,进程就有了3中状态:
- Running运行态(使用CPU中)
- Ready就绪态(可运行,等待调度)
-
Blocked阻塞态(等待外部时间发生)
一个处于运行态的进程在需要用户输入数据时会转换为阻塞态(转换1),若用户输入数据按回车键后进程便会进入就绪态等待被执行(转换2),转换3和转换4会在操作系统的调度下发生,调度操作是由进程调度程序执行的。进程调度程序的主要任务是决定何时运行哪个程序以及运行时长,它依据的是进程表(process table)。
在Linux内核中维护这一张进程表,每个进程占据一条记录,包含了程序计数器、堆栈指针、内存分配情况、所打开文件的状态和运行的状态信息等。Linux用一个机器复杂的C结构体来描述进程。下面是进程表中的一些关键字段:
进程管理 | 存储管理 | 文件管理 |
---|---|---|
寄存器 | 正文段指针 | 根目录 |
程序计数器 | 数据段指针 | 工作目录 |
程序状态字 | 堆栈段指针 | 文件描述符 |
堆栈指针 | 用户ID | |
进程状态 | 组ID | |
优先级 | ||
调度参数 | ||
进程ID | ||
父进程 | ||
进程组 | ||
信号 | ||
使用CPU时间 |
参考书籍:《现代操作系统第三版》《Linux内核设计与实现》《深入理解计算机系统》