关于进程的简单理解
1 引言
进程(The process)是操作系统提供给用户最为基础的抽象模型,我们可以简单的定义其为:一个正在运行的程序(A running program)。而程序本身就是一堆指令的集合(还有一些静态数据),呆在硬盘里等待着操作系统调用这些指令信息,让程序跑起来,来完成我们想要的任务。
我们知道,单进程是难以满足用户需求的。举个简单例子,平时用户在使用电脑或者笔记本时,浏览网页的同时,会听歌,会下载,会收发邮件等。正是这些‘同时’运行的进程令用户感到操作的流畅和方便。
然而CPU其实只有一个,这么多进程同时运行显然是个假象,那么是谁给用户制造出这种假象的?毫无疑问就是操作系统(OS)。
操作系统通过虚拟化CPU:运行一个进程,过一会将其停止,然后运行其他进程。操作系统通过这种方式制造出有很多逻辑上的CPU,而事实上真正的物理硬件只有一个CPU而已。这种基础技术允许用户同时运行很多进程,我们将其称之为CPU时间共享(Time sharing of the CPU)。当然,这种机制会导致每个进程的运行速度变慢。
实现虚拟化CPU,操作系统需要低层次机构(low-level machinery)和高层次策略(high-level intelligence)。低层次机构我们称之为机制(mechanisms),为操作系统提供基本的底层基础功能,例如上下文转换(context switch)。高层次策略主要是一些算法让操作系统更好的去决策。比如操作系统该运行什么程序,该运行多长时间,这些都是由操作系统的调度策略(scheduling policy)来决定的。
2 进程
操作系统正在运行的程序,我们称之为进程。为了能够更好的理解进程,我们需要了解它的机器状态(machine state):一个进程能够读什么?更新什么?主机的哪一个部分对进程的运行起着关键作用?
内存是构成进程机器状态的一个关键内容,无论是指令还是进程运行时的读写数据都存储在内存当中。另外,寄存器也是构成进程机器状态的一部分,许多指令的读取更新都是需要寄存器的参与。
3 进程API
这里我们来简单了解一下关于进程的API函数,任何一个现代操作系统都包含以下功能函数。
- Create:操作系统必然可以创建新的进程。当你在shell中输入命令或者用鼠标双击应用图标时,操作系统会创建相应的进程,运行相关的程序。
- Destroy:操作系统可以创建线程同样也可以销毁进程。当然很多时候,进程在运行完成后会自动销毁,如果进程并没有按预期销毁或者用户希望Kill掉进程,该接口是非常有用的。
- Wait:在有些情况下,一个进程需要等待其他进程停止运行。因此提供了该接口。
- Miscellaneous Control:除了杀死和等待进程等操作,这里还有其他对进程的操作控制。比如说,很多操作系统提供类似于延缓进程的接口(终止进程的执行过一会再恢复进程执行)
- Status:还有一些API函数用于获取进程的状态信息,比如进程运行了多长时间,进程目前的状态等。
4 关于创建进程:操作系统干了什么
我们来简要描述一个程序如何转变成一个进程的过程。当然这整个过程的操纵者便是操作系统。
操作系统首先得将程序的源码(code)和所有的静态数据(static data)从磁盘(disk)载入到内存(memory)和进程地址空间当中。早期的操作系统是在程序运行之前将所有的代码和数据载入到内存当中,而现代操作系统在程序运行时,在程序需要相关代码和数据时,才将部分载入到内存当中。
当代码和数据加载完毕后,在运行进程之前,操作系统需要为程序的运行栈(run-time stack)申请开辟(allocate)内存。我们知道,C程序中的局部变量,函数参数,返回地址等都得用到栈。这时候操作系统还会初始化相关参数,比如main()函数的argc变量和argv数组。
操作系统还会为程序堆开辟内存。在C程序中,堆被用于动态申请内存的数据。程序中常用函数malloc()来申请空间,函数free()来用来释放空间。一些数据结构诸如链表、哈希表、树等都需要用到程序堆。程序在调用malloc()函数申请内存时,操作系统会参与到该过程中,开辟内存给进程满足其需求。
操作系统还会做些与输入输出相关初始化任务。比如,在UNIX系统中,每个进程有默认的三个文件描述符(file descriptor):标准输入(standard input)、标准输出(standard output)和标准错误(standard error)。这些描述符可以让程序非常方便的从终端读入和将输出打印到终端。
在上述的准备工作完成后,操作系统已经为程序的运行做好了准备,现在最后的任务是找到main()的地址,然后开始运行程序。这时候,操作系统将CPU的控制权交给新创建的进程,程序开始执行。
5 进程状态(Process States)
简单来说一个进程有以下三种状态:-
- 运行态(running):运行态指一个进程正在处理器上运行。意思是正在执行指令。
- 就绪态(Ready):就绪态指一个进程准备好运行了,但操作系统在此刻选择不让该进程运行,CPU没有分配过去。
- 阻塞态(Blocked):阻塞态指一个进程等待某些事件,这段期间无法运行。比如说当一个进程等待I/O操作完成的期间。这时候处理器可用于其他进程。
三个状态的关系如上图所示,可以看到:
- 就绪态到运行态:进程被操作系统调度(schedule),分配CPU执行进程。
- 运行态到就绪态:进程没有被操作系统调度(deschedule)(占用CPU时间过长或有更高优先级进程需要被执行,操作系统强迫其让出CPU)
- 阻塞态到就绪态: I/O操作完成等
- 运行态到阻塞态:发生了I/O请求等
最后我们来看一个例子,看看两个进程是如何从这三种状态变换的。如下图所示:
Process0进程的I/O事件发生时,操作系统发现Process0让出了CPU,于是从就绪队列中调度Process1,分配CPU,开始执行Process1。当Process0的I/O事件完成时,Process0将转变成就绪态。直到Process1完成时,则Process0开始转变运行态继续执行,然后完成。