Linux进程控制

进程标识符

进程标识符:每个进程都有一个非负整数表示的唯一进程ID,因为进程ID标识符总是唯一的,常将其用作其他标识符的一部分以保证其唯一性。

获取进程的标识符

getpid():

获取当前进程的ID;

getppid:

获取父进程的ID;
在这里插入图片描述

进程的创建

fork():

创建一个新的进程;

  • c程序一开始,就会产生 一个进程,当这个进程执行到fork()的时候,会创建一个子进程。
  • 此时父进程和子进程是共存的,它们俩会一起向下执行c程序的代码。
  • 子进程创建成功后,fork是返回两个值,一个代表父进程,一个代表子进程:代表父进程返回的值是非负整数的数字串,这串数字是子进程的ID(地址);另一个代表子进程,值为0。
    在这里插入图片描述
    在这里插入图片描述
    上述代码运行后:
    在这里插入图片描述

vfork():

创建一个新进程,但是区别于fork();

  1. vfork直接使用父进程的存储空间,不拷贝;
  2. 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)使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
  1. 带l 的exec函数:execl,execlp,execle,表示后边的参数以可变参数的形式给出且都以一个空指针NULL结束。
  2. 带 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 来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。
  • 宏函数可分为如下三组:
  1. WIFEXITED(status) 为非 0 → 进程正常结束
    WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit 的参数)
  2. WIFSIGNALED(status) 为非 0 → 进程异常终止
    WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
  3. 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);可读性比较好一点。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值