什么是进程
进程(Process) ,官方解释为 计算机中已运行程序的实体 ,是系统进行资源分配与调度的基本单位。
在计算机中,程序是指令、数据及其组织形式的描述,而进程则是程序的实体,一个进程是由 PCB + 数据 构成的。。
(PCB 是一个结构体,里面是该进程的属性,可以理解为进程的 "身份证" )
进程是具体的,它具有动态性,也被认为是一个 "过程" ,因为它随着指令的执行不断改变,它会被创建,运行,销毁。
Linux 下的进程
进程在不同操作系统下的实现不同,但进程的底层逻辑在任何操作系统下都是一样的,本文只是在 Linux 下进行讲解,所述的进程知识并非 Linux 特有。
PID:
使用指令
可以查看当前所有进程:

可以看到进程有很多的信息,我标记出了一个名为 :PID的标记,这是进程号,就是进程的 "身份证号" 。
进程像树一样分父和子(结构不是树),一个进程可以用 fork 进行 "复制" ,原来的进程变成了父进程 ,复制出来的进程就是它的子进程,一个父进程可以有多个子进程 。可以编写程序通过 getpid 和 getppid 来分别查看当前进程的 PID 和 其父进程的 PID :

也可以用 ps -ef | grep (执行的文件名)来查看 PID ,但是这种方法要开两个终端窗口。

奇怪的 fork :
前面提到了 fork ,这家伙其实有点古怪的地方,我们通过一个程序来看看:

从该程序可以得到一个惊人的信息:fork 竟然能有不同的返回值,id 竟然能接收不同的返回值。这个结论好像颠覆了以前学习的 C语言 逻辑,应该怎么解释这种情况呢?
前面有说到,fork 的作用是把当前进程复制一份。但这个复制的行为其实在 fork 里面就已经完成了,并且子进程 已经开始运行,于是就有了两个独立的进程,它们执行各自的 return ,父进程 返回的是子进程 的 PID ,而子进程返回 0。
那 id 变量如果做到接收两个不同的值的呢?答案是: "一个变量同时有两个值" 只是表面,底层其实有两个 id ,它们只是名字一样。这涉及到 虚拟内存空间 的知识,这里不细讲,但可以理解为:fork 在复制子进程 出来时,把 id 也复制了一份 ,这样父进程 和子进程就有不同的变量了。
写时拷贝:
fork 在进行 复制 时,按理说应该完全复制一份父进程 的副本来作为子进程 ,但一个进程包含的数据量可能会很大,如果对这对父子进程 只进行读操作,那么这个复制就很没有性价比了!为了 "偷懒" ,Linux 在复制时并不会直接把所有数据都复制一份,而是只复制 页表 (页表,是操作系统给每个进程配的「地址翻译 + 内存账本」,把虚假的虚拟地址,翻译成真实的物理内存地址),让父子进程 指向同一块物理地址,只有当父进程 或子进程 任意一方要对数据进行 "写" 操作时,才真正进行数据拷贝,这就是 写时拷贝 。它大大节省了内存,也提高了程序运行效率。
进程状态
进程的标准状态:
进程有三种标准状态:就绪,运行,阻塞 ,后面扩展出了 挂起 状态。在 Linux 中,用不同字母来表示不同的状态:

(R 合并了 就绪 和 运行 ,S/D 就是 阻塞 状态,T 是 挂起)
就绪 和 运行 从字面上理解就可以了,重点讲一下 阻塞 和 挂起 。
阻塞 ,可以理解为进程正在等待,它不占用 CPU 资源 ,但赖在 内存 中不走。像 C语言 中,使用 scanf 获取输入就会让进程进入阻塞状态,此时进程就是在等待输入。S 状态 和 D 状态 都是阻塞,但 S 是可中断的,也可以使用 kill 指令杀死,而 D 状态则不行,它必须等到所需要的资源,不可中断,不可杀死。
挂起 ,相当于暂停,分两种。第一种是用 T 状态表示的挂起,这种挂起是手动 的,是被外力强制按下的暂停,比如 Ctrl + Z ,还有调试时的断点(用 t 表示),手动的挂起不会自己启动,必须手动恢复。第二种是内存管理挂起 ,在 内存紧张 时,OS(操作系统)将暂时用不上的进程挂到磁盘中,属于内存调度的手段之一。
Linux 的特殊进程状态:
I,Z,X 都是 Linux 特有进程状态,其中值得一讲的是 Z ,僵尸进程。
前面提到过父子进程的概念,当子进程先于父进程退出(此时是 X,死亡状态),但父进程没有调用 wait / waitpid 回收其退出状态,这个子进程就变成了一个僵尸进程。
僵尸进程无法被 kill 杀死,它的 PCB 无法被释放,留着内存中,会导致内存泄漏 ;僵尸进程不占用 CPU 资源,但是会占用 PID 资源 ,若存在大量僵尸进程,会导致新的进程无法被创建,系统崩溃。
解决方法是:重启父进程 ,这样父进程死了,僵尸进程会被 init 回收,或者调用 wait / waitpid回收子进程的退出信息。
我们可以模拟出一个僵尸进程并查看其状态:

运行并查看:

STAT 这一列表示的就是进程状态,后面会讲到。
查看进程状态:
可以用 ps aux 所有进程的状态,如果要查看特定进程可以用管道符结合 grep 进行指定。

前面提到,STAT 这一列的符号表示的就是进程的状态,可是我们发现,在有些进程的字母后面还跟着一些符号,这些其实是 状态修饰符,下面列举一些常见的状态修饰符(不做细致讲解):
