进程控制
进程控制
进程相关概念
程序和进程
- 程序,是指编译好的二进制文件,在磁盘上,占用磁盘空间,是一个静态概念
- 进程,一个启动的程序,进程占用的是系统资源,如:物理内存,CPU,终端等,是一个动态的概念
- 程序 -> 剧本(纸)
- 进程 -> 戏(舞台、演员、灯光、道具……)
同一个剧本可以在多个舞台同时上演。同样,一个程序也可以加载为不同的进程(彼此之间互不影响)
并行和并发
并发,在一个短时间内,是在同一个 CPU 上,同时运行多个程序
如:若将 CPU 的 1S 的时间分成 1000 个时间片,每个进程执行完一个时间片必须无条件让出 CPU 的使用权,这样 1S 中就可以执行 1000 个进程。




并行是指两个或两个以上的程序在同一时刻发生(需要有多颗CPU核心)


PCB - 进程控制块
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。以下为常用成员:
- 进程 id。系统中每个进程有唯一的 id,在 C 语言中用 pid_t 类型表示,其实就是一个非负整数。
- 进程的状态,有就绪、运行、挂起、停止等状态。
- 进程切换时需要保存和回复的一些 CPU 寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目睹 (Current Working Directory)
- getcwd –pwd
- umask 掩码
- 文件描述符,包含很多指向 file 结构体的指针
- 和信号相关的信息
- 用户 id 和组 id
- 会话 (Session) 和进程组
- 进程可以使用的资源上限 (Resource Limit)
- ulimit -a
进程状态 (面试考)
进程基本的状态有 5 种。分别为初始态、就绪态、运行态、挂起态与终止态。其中初始态为进程准备阶段,常与就绪太结合来看。

创建进程
fork 函数
函数作用:创建子进程
原型:
pit_t fort();函数参数:无
返回值:
- 调用成功:父进程返回子进程的 PID,子进程返回 0;
- 调用失败:返回 -1,设置 errno 值。
fork 函数代码片段实例

调用 fork 函数的内核实现原理:

fork 函数总结
fork 函数的返回值:
父进程返回子进程的 PID,是一个大于 0 的数;
子进程返回0;
特别需要注意的是:不是 fork 函数在一个进程种返回 2 个值,二十在父子进程各自返回一个值。子进程创建成功后,代码的执行位置:
父进程执行到什么位置,子进程就从哪里执行
如何区分父子进程:
通过 fork 函数的返回值
父子进程的执行顺序
不一定,哪个进程先抢到 CPU,哪个进程就先执行
父子进程不能共享全局变量;
但是如果父子进程只是对全局变量做读操作,则步子进程在内存种只有一份,属于共享,但是如果父子进程中的任何一个进程对该变量做修改操作;会在内存种拷贝一个副本没然后在这个副本上进行修改,修改完成后再映射回去。
ps 命令和kill 命令
ps aux | grep "xxx"ps ajx | grep "xxx"- -a:(all) 当前系统所有用户的进程
- -u:查看进程所有者以及其他信息
- -x:显示没有控制终端的进程 – 不能与用户进行交互的进程 [输入、输出]
- -j:列出与作业控制相关的信息
kill -l查看系统有哪些信号kill -9 pid杀死某个线程
getpid / getppid
getpid - 得到当前进程的 PID
pid_t getpid();getppid - 得到当前进程的父进程的 PID
pid_t getppid();
exec 函数族
函数作用和函数介绍
有时候需要再一个进程里面执行其他的命令或者是用户自定义的应用程序,此时就用到了 exec 函数族当中的函数。
使用方法一般都是再父进程里面调用 fork 创建处子进程,然后在子进程里面 调用 exec 函数。
execl 函数
- 函数原型:
int execl(const char *pathname, const char *arg, .../* (char *) NULL */); - 参数介绍:
- path:要执行的程序的绝对路径
- 变参 arg:要执行的程序的需要的参数
- arg:占位,通常写应用程序的名字
- arg 后面的:命令的额参数
- 参数写完之后:NULL
- 返回值:
- 若是成功,则不反悔,不会再执行 exec 函数后面的代码;
- 若是失败:会执行 execl 后面的代码,可以用 perror 打印错误原因。
execl 函数一般执行自己写的程序。
execlp 函数
- 函数原型:
int execlp(const char *file, const char *arg, .../* (char *) NULL */); - 参数介绍:
- file:执行命令的名字,根据 PATH 环境变量来搜索该命令
- arg:占位
- arg 后面的:命令参数
- 参数写完之后:NULL
- 返回值:
- 若是成功,则不反悔,不会再执行 exec 函数后面的代码;
- 若是失败:会执行 execl 后面的代码,可以用 perror 打印错误原因。
execlp 函数一般是执行系统自带的程序或者命令。
exec 函数族原理介绍
exec 族函数的实现原理图:
如:execlp("ls", "ls", "-l", NULL);

总结:
exec 函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程的 PID 没有发生变化。
注意:当 execl 和 execlp 函数执行成功后,不返回,并且不会执行 execl 后面的代码逻辑,原因是调用 execl 函数成功以后,exec 函数指定的代码段已经将原有的代码段替换了。
进程回收
为什么要进行进程资源回收
当一个进程推出之后,进程能够回收自己的用户区资源,但是不能回收内核空间的 PCB 资源,必须由它的父进程调用 wait 或者 waitpid 函数完成对子进程的回收,避免造成系统资源的浪费。
孤儿进程
- 孤儿进程的额概念:
若子进程的父进程已经死掉,而子进程还存活者,这个进程就成了孤儿进程。 - 为了保证每个进程都有一个父进程,孤儿进程会被 init 进程领养,init 进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由 init 进程完成对孤儿进程的回收。
僵尸进程
- 僵尸进程的概念:
若子进程死了,父进程还活着,但是父进程没有调用 wait 或 waitpid函数完成对子进程的回收,则该子进程就成了僵尸进程。 - 如何解决僵尸进程:
- 由于僵尸进程是一个已经死亡的进程,所以不能使用 kill 命令将其杀死
- 通过杀死其父进程的方法可以消除僵尸进程。
杀死其父进程后,这个僵尸进程会被 init 进程领养,由 init 进程完成对僵尸进程的回收
进程回收函数
wait 函数
- 函数原型:
pid_t wait(int* status); - 函数作用:
- 阻塞并等待子进程退出
- 回收子进程残留资源
- 获取子进程结束状态 (退出原因)
- 返回值:
- 成功:清理掉的子进程 ID
- 失败:-1 (没有子进程)
- status 参数:子进程的退出状态 – 传出参数
- WIFEXITED(status):为非 0 -> 进程正常结束
WEXITSTATUS(status):获取进程退出状态 - WIFSIGNALED(status):为非 0 -> 进程异常终止
WTERMSIG(status):取得进程终止的信号编号
- WIFEXITED(status):为非 0 -> 进程正常结束
waitpid 函数
- 函数原型:
pid_t waitpid(pid_t pid, int *wstatus, int options); - 函数作用:同 wait 函数
- 函数参数:
- pid:
- pid = -1 等待任一子进程。与 wait 等效。
- pid > 0 等到其进程 ID 与 pid 相等的子进程
- pid = 0 等待进程组 ID 与目前进程相同的任何子进程,也就是说任何和调用 waitpid 函数的进程在同一个进程组的进程。
- pid < -1 等待其组 ID 等于 pid 的绝对值的任一子进程。(适用于子进程在其他组的情况)
- status:子进程的退出装,用法同 wait 函数
- options:设置为 WNOHANG,函数非阻塞,设置为 0,函数阻塞。
- pid:
- 函数返回值:
- >0:返回回收掉的子进程 ID;
- -1:无子进程
- =0:参 3 为 WNOHANG,且子进程正在运行。