文章目录
进程标识符
进程标识符:每个进程都有一个非负整数表示的唯一进程ID,因为进程ID标识符总是唯一的,常将其用作其他标识符的一部分以保证其唯一性。
获取进程的标识符
getpid():
获取当前进程的ID;
getppid:
获取父进程的ID;
进程的创建
fork():
创建一个新的进程;
- c程序一开始,就会产生 一个进程,当这个进程执行到fork()的时候,会创建一个子进程。
- 此时父进程和子进程是共存的,它们俩会一起向下执行c程序的代码。
- 子进程创建成功后,fork是返回两个值,一个代表父进程,一个代表子进程:代表父进程返回的值是非负整数的数字串,这串数字是子进程的ID(地址);另一个代表子进程,值为0。
上述代码运行后:
vfork():
创建一个新进程,但是区别于fork();
- vfork直接使用父进程的存储空间,不拷贝;
- vfork保证子进程先运行,当子进程调用exit()退出后,父进程才开始执行;
上述代码运行后:会发现当子进程运行三次后父进程才开始运行;
exec族函数
fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。
将当前进程的.text、.data 替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程 ID 不变,换核不换壳。
exec 函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值-1。所以通常我们直接在 exec 函数调用后直接调用 perror()和 exit(),无需 if 判断。
其实有六种以 exec 开头的函数,统称 exec 函数:
- int execl(const char *path, const char *arg, …);
- int execlp(const char *file, const char *arg, …);
- int execle(const char *path, const char *arg, …, char *const envp[]);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execve(const char *path, char *const argv[], char *const envp[]);
参数 | 含义 |
---|---|
l (list) | 命令行参数列表 |
p (path) | 搜素 file 时使用 path 变量 |
v (vector) | 使用命令行参数数组 |
e (environment) | 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量 |
- 带l 的exec函数:execl,execlp,execle,表示后边的参数以可变参数的形式给出且都以一个空指针NULL结束。
- 带 p 的exec函数:execlp,execvp,表示第一个参数path不用输入完整路径,只有给出命令名即可,它会在环境变量PATH当中查找命令
execl():
加载一个进程, 通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, …);
- path参数:表示你要启动程序的名称包括路径名
- arg参数:表示启动程序所带的参数,一般第一个参数为要执行命令名,不是带路径且arg必须以NULL结束
成功:无返回;失败:-1
execl("/bin/ls", “ls”, “-l”, “-F”, NULL); 使用参数 1 给出的绝对路径搜索。
execlp():
加载一个进程,借助 PATH 环境变量来加载。
int execlp(const char *file, const char *arg, …);
- file参数:path环境变量下的文件名。
- 参数 1:要加载的程序的名字。该函数需要配合 PATH 环境变量来使用,当 PATH 中所有目录搜索后没有参数 1 则出错返回。
成功:无返回;失败:-1
对比 execl,如加载"ls"命令带有-l,-F 参数
execlp(“ls”, “ls”, “-l”, “-F”, NULL); 使用程序名在 PATH 中搜索。
该函数通常用来调用系统程序。如:ls、date、cp、cat 等命令。
execvp():
加载一个进程,使用自定义环境变量 env
int execvp(const char *file, const char *argv[]);
- 变参形式: ①… ② argv[] (main 函数也是变参函数,形式上等同于 int main(int argc, char *argv0, …))
- 变参终止条件:① NULL 结尾 ② 固参指定
execvp 与 execlp 参数形式不同,实际上只是将参数定义成字符指针数组的形式传参,原理一致。
回收子进程
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为 init 进程领养孤儿进程。
wait():
pid_t wait(int *status);
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。
父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
成功:清理掉的子进程 ID;失败:-1 (没有子进程)
- 当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
- 可使用 wait 函数传出参数 status 来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。
- 宏函数可分为如下三组:
- WIFEXITED(status) 为非 0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit 的参数) - WIFSIGNALED(status) 为非 0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。 - WIFSTOPPED(status) 为非 0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
waitpid():
作用同 wait,但可指定 pid 进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);
成功:返回清理掉的子进程 ID;失败:-1(无子进程)
特殊参数和返回情况:
-
参数 pid:
> 0 回收指定 ID 的子进程
-1 回收任意子进程(相当于 wait)
0 回收和当前调用 waitpid 一个组的所有子进程
< -1 回收指定进程组内的任意子进程 -
返回 0:参 3 为 WNOHANG,且子进程正在运行。
注意:一次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环。
进程的退出
exit():
退出当前进程;
属于C库函数;_exit()和_Exit()属于系统调用;作用相似;传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS);可读性比较好一点。