操作系统系列之——进程
相关前文:操作系统系列之——程序
1. 什么是进程
进程本质上是正在执行的一个程序,是对正在运行程序的一个抽象。——《现代操作系统》
在操作系统系列之——程序中,我们通过C语言和gcc编译器得到了a.out
,它是存放在硬盘中的程序,当它被计算机执行时,就会产生一个进程。
2. 执行一个程序
直接在a.out
所在目录通过相对路径执行这个程序,可以看到程序运行并将字符输出到了控制台中:
其中
➜ c
是命令提示符,请忽略该部分,关注后面的内容。
➜ c ./a.out
HelloWorld!
➜ c
由于我们的程序非常简单,在非常短的时间内这个程序被执行时产生的进程就运行完毕了,命令提示符因此也再次出现,提醒我们可以进行新的操作。
如果你还是懵懵懂懂,感觉程序和进程没有区别,让我们继续做一个实验:
3. 进程的小实验
再次强调,以下代码适用于类Unix系统,不同操作系统之间需要使用的库和编译工具会有所不同。
首先改造一下我们的经典,引入了unistd
来实现睡眠功能,我们的程序现在会每隔4s
输出一条带有编号的HelloWorld,一共输出三条。
#include <stdio.h>
#include <unistd.h>
#define SLEEP_SECONDS 4
int main(){
for(int i=0; i < 3; i++){
sleep(SLEEP_SECONDS);
printf("No.%d: HelloWorld!\n",i);
}
return 0;
}
编译并运行一次查看效果,是符合我们编写代码的预期的:
➜ c ./a.out
No.0: HelloWorld!
No.1: HelloWorld!
No.2: HelloWorld!
➜ c
接下来就要进入重点了,我们需要用到&
这个符号,这在命令行中意味着后台运行。
注意上文中当没有在末尾添加&
执行程序时,我们需要等待a.out运行完毕才可以继续下一个命令操作,有了&
系统就会把我们要运行的程序挂到后台运行,不影响我们的下一步操作。尝试一下加上&
会有什么变化:
➜ c ./a.out &
[1] 63965
➜ c No.0: HelloWorld!
No.1: HelloWorld!
No.2: HelloWorld!
[1] + 63965 done ./a.out
➜ c
可以看到加上&后没有等待a.out的执行而直接输出了[1] 63965
(暂时不用理会这个信息),并且命令提示符马上出现,这意味着我们可以直接进行下一步的操作而不用等待a.out的运行,同时a.out的运行结果也照旧输出到了当前的控制台上。
现在我们可以知道&
的作用:
- 让程序在后台运行
- 程序的输出依然不受影响
我们做最后一个实验,在执行完第一次的./a.out &
后,马上通过键盘的方向上键↑
呼出./a.out &
再次执行一遍,一共执行两遍。
➜ c ./a.out &
[1] 63691
➜ c ./a.out &
[2] 63694
➜ c No.0: HelloWorld!
No.0: HelloWorld!
No.1: HelloWorld!
No.1: HelloWorld!
No.2: HelloWorld!
[1] - 63691 done ./a.out
➜ c No.2: HelloWorld!
[2] + 63694 done ./a.out
➜ c
我们执行了两遍同一个程序,但输出的结果并不是0-1-2-0-1-2
,而是0-0-1-1-2-2
。
这会给你一种有两个同样的程序在同时执行的感觉,但显然我们有且仅有一个a.out
程序。事实上在同时执行的不是程序,而是进程,因为我们两次执行了同一个程序,这会产生两个不同的进程,尽管他们有着一样的运行逻辑,这两个进程在较短的时间间隔内产生,因此他们会同时输出相同的内容在控制台上。
3. 程序和进程的关系、区别
a.out
是我们的程序,但程序只是一段指令,仅仅存放在硬盘上的话它就是死的(静态的),只要你的硬盘不坏,这个程序放一万年都是原来的那个程序。但程序执行产生的进程是活的(动态的),会随着时间的推移发生状态的改变,例如当所有指令运行完毕进程就会因为无事可做而终止了。
进程是由程序的执行产生的,而程序又可以被多次执行,自然的一个程序可以产生多个进程,但一个进程只能源于某一个程序。
4. 为什么需要引入进程的概念
通过2
和3
的例子,现在你应该知道对程序和进程有比较明确的认知了。在深入一些,这个看起来和程序没有太大区别的进程到底是出于什么原因被引入的?
在今天的操作系统中,并发已经是一件非常司空见惯的事情了,一边听歌一边看博客,一边看电影一边聊天,这些在电脑上运行着的程序都是在 操作系统 的管理下达到同时运行互不影响的效果的。
我们的电脑中能够执行指令的 CPU 只有一个(暂时忽略多核CPU),多个进程之间之所以能够呈现出同时运行的效果,是因为每个程序都只能被CPU执行很小一段时间,在时间结束后操作系统就换上别的程序让CPU执行(想象在 100ms 的时间内,每个程序只运行 5ms)。CPU实际上是在 很快地切换执行 不同的程序,而不是真正在 同时执行 那么多程序。
而不断切换不同程序运行会导致有些程序还没运行完就失去CPU了,这要求操作系统能够知道每个程序当前的执行状态,以便于重新得到CPU时 还原现场,执行信息则是在每次程序失去CPU时被记录在特定的结构中(保存现场)。
上面的信息可以总结为:
- 我们有同时运行多个不同的程序的需求。
- 操作系统需要帮助我们管理这些不同的运行着的程序,让他们能够同时运行。
- 操作系统管理这些程序实现并发的关键在于,分 时间片 运行这些程序。
- 操作系统需要引入额外的结构来为运行着的程序 保存现场 。
出于对运行着的程序的管理需求,很多操作系统需要的信息被附加在程序上,这种被附加了许多信息后的结构就是 PCB(进程控制块),这是实实在在存在于我们的电脑中的数据结构,而 进程 则它的一种抽象概念。
以书中一个很棒的例子作为结束:进程和程序间的区别是很微妙的,但非常重要。用一个比喻可以更容易理解这一点。想象一位有一手好厨艺的计算机科学家正在为她的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中。做蛋糕的食谱就是程序,计算机科学家就是处理器(CPU)。而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。——《现代操作系统》