【Linux】环境变量、进程替换、wait/waitpid

2024-05-04 07:44

本文主要是介绍【Linux】环境变量、进程替换、wait/waitpid,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、环境变量
    • 1. 查看环境变量的方法
      • 1.1 env
      • 1.2 echo $环境变量名
    • 2. 在代码中使用环境变量的方法
      • 2.1 命令行参数传参
      • 2.2 environ变量
      • 2.3 getenv( )函数
    • 3. export
  • 二、进程替换
    • 1. execl
    • 2. execlp
    • 3. execle
    • 4. execv
    • 5. execvp
    • 6. execvpe
    • 7. 补充
      • 7.1 命名理解
      • 7.2 返回值
  • 三、wait/waitpid
    • 1. wait
    • 2. waitpid
    • 3. 获取子进程的退出状态
      • 3.1 位运算获取退出状态
      • 3.2 内置宏获取退出状态


一、环境变量

1. 查看环境变量的方法

1.1 env

可以查看当前进程的所有环境变量.
在这里插入图片描述

1.2 echo $环境变量名

可以查看指定的环境变量.
在这里插入图片描述

2. 在代码中使用环境变量的方法

2.1 命令行参数传参

通过设置 main 函数的第三个形参 char* envp[ ], 接收环境变量.

#include <iostream>
using namespace std;int main(int argc, char* argv[], char* envp[])
{for (int i = 0; envp[i]; ++i){	cout << envp[i] << endl;}   return 0;
}

程序运行起来会是 bash 的子进程, 子进程会继承父进程的环境变量, envp 的最后一个位置会存储一个 NULL, 所以可以用作循环条件的判断.

2.2 environ变量

通过 extern 关键字引用外部变量, extern char** environ.

#include <iostream>
using namespace std;extern char** environ;int main()
{for (int i = 0; environ[i]; ++i){	cout << environ[i] << endl;}   return 0;
}

environ 不包含在任何头文件中, 所以要使用 extern 引入.

2.3 getenv( )函数

通过 getenv( ) 可以获取指定的环境变量, 传递的参数是环境变量名.
头文件: #include <stdlib.h>
函数声明: char *getenv(const char *name);

#include <iostream>
#include <stdlib.h>
using namespace std;int main()
{cout << getenv("USER") << endl;cout << getenv("HOME") << endl;return 0;
}

运行结果:
在这里插入图片描述

3. export

在 Linux 中, 像 ls, pwd 这样的指令可以直接敲了就运行, 而我们编写的代码在生成了可执行文件后要通过 ./xxx 的方式才可以运行, 这是为什么呢? 本质都是一个程序, 跑起来变成进程, 原因是 ls, pwd 这种指令的路径添加到了环境变量 PATH 中, 所以在运行时系统在环境变量 PATH 中可以找到, 而我们自己编写的程序的路径不存在环境变量 PATH 中, 所以要指定路径.

自己编写的程序, 如果不加 ./ 直接跑起来是这样的:
在这里插入图片描述

那么试着把自己编写的程序的所在路径添加到环境变量 PATH 中, 看看可不可以不需要 ./ 直接跑起来, 通过 export 添加到环境变量:

export PATH=$PATH:/root/EnvTest/Test/

$PATH 表示之前添加到 PATH 中的所有路径, 将自己编写的程序的所在路径写在冒号右边就好, 不要直接写成 PATH=/root/EnvTest/Test/, 这样写会把之前的路径都覆盖掉, 属于覆盖式写入.

运行结果:
在这里插入图片描述
可以看到可以不带 ./ 直接运行了, 再看看 PATH 中也存在了刚才添加的路径:
在这里插入图片描述

二、进程替换

用 fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支), 子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种exec函数时, 该进程的代码和数据完全被新程序替换, 从新程序的启动例程开始执行. 调用exec并不创建新进程,所以调用exec 前后该进程的 pid 不会改变.

下面介绍各种 exec 函数, 头文件均为: #include <unistd.h>

1. execl

函数声明: int execl(const char *path, const char *arg, …);

  • path: 替换程序的路径
  • arg/…: 可变参数列表
#include <iostream>
#include <unistd.h>
using namespace std;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;execl("/usr/bin/ls", "ls", "-l", "-a", NULL);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

假设要替换的进程为 ls, 那么就把 ls 的路径写在 path 处, 在可变参数列表分别加上在终端如何执行 ls 的指令, 每个选项单独写, 最后必须以 NULL 结尾.

运行结果:
在这里插入图片描述
可以看到前面三行代码正常输出, 进程替换后的代码不会输出, 因为在函数调用处后的代码会被替换.

2. execlp

函数声明: int execlp(const char *file, const char *arg, …);

  • file: 替换程序名
  • arg/…: 可变参数列表
#include <iostream>
#include <unistd.h>
using namespace std;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;execl("ls", "ls", "-l", "-a", NULL);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

path 处不需要指明路径了, 直接写上替换程序的名称即可, 后面的可变参数列表处和 execl 一致.

运行结果:
在这里插入图片描述
需要注意的是, 如果替换的程序是你自己编写的, 那么需要把它所在的路径添加到 PATH 中, 否则不会替换.

3. execle

函数声明: int execle(const char *path, const char *arg, …, char * const envp[ ]);

  • path: 替换程序的路径
  • arg/…: 可变参数列表
  • envp: 环境变量列表

程序 myenv, 执行起来就是查看该进程的环境变量:
在这里插入图片描述
execle 示例代码:

#include <iostream>
#include <unistd.h>
using namespace std;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;char*const envp[] = {"MYENV=100", NULL};execle("/root/ExecTest/myenv", "myenv", NULL, envp);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

传递环境变量需要自定义一个数组进行传递, 在数组中依然要以 NULL 结尾, 但是此时来看一看运行结果:
在这里插入图片描述
确实是把传递的环境变量给输出出来了, 但是之前的环境变量被覆盖了, 没错 execle 传递环境变量就是覆盖式传入, 那么如何做到不覆盖进程原本的环境变量, 又把新的环境变量传入呢? 先看一个函数 putenv( ):

头文件: #include <stdlib.h>
函数声明: int putenv(char *string);

该函数的功能很简单, 就是哪个进程调用该函数, 就把传递的环境变量形参设置到哪个进程的环境变量中.

结合该函数就可以实现不覆盖进程原本的环境变量, 又把新的环境变量传入, 如下:

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;extern char** environ;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;putenv("MYENV=100");execle("/root/ExecTest/myenv", "myenv", NULL, environ);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

其实也很简单, 就是先把新的环境变量导入当前进程, 再把 environ 当作形参传递就好了.

运行结果:
在这里插入图片描述
可以看到, 不仅输出了原本的环境变量, 而且新添加的也输出了, 那么就成功做到了不覆盖进程原本的环境变量, 又把新的环境变量传入.

4. execv

函数声明: int execv(const char *path, char *const argv[]);

  • path: 替换程序的路径
  • argv: 执行该程序的指令数组
#include <iostream>
#include <unistd.h>
using namespace std;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;char*const argv[] = {"ls", "-l", "-a", NULL};execv("/usr/bin/ls", argv);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

其实很简单, 和 execl 没什么区别, 无非是把替换程序的执行指令写在一个数组中, 再把数组作为参数传给 execv, 注意数组最后必须以 NULL 结尾即可.

运行结果:
在这里插入图片描述

5. execvp

函数声明: int execvp(const char *file, char *const argv[]);

  • file: 替换程序名
  • argv: 执行该程序的指令数组
#include <iostream>
#include <unistd.h>
using namespace std;extern char** environ;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;char*const argv[] = {"ls", "-l", "-a", NULL};execv("ls", argv);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

和 execv 差不多, 无非是函数的第一个参数直接传可执行程序名即可.

运行结果:
在这里插入图片描述

6. execvpe

函数声明: int execvpe(const char *file, char *const argv[], char *const envp[]);

  • file: 替换程序名
  • argv: 执行该程序的指令数组
  • envp: 环境变量列表
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;extern char** environ;int main()
{cout << "111" << endl;cout << "222" << endl;cout << "333" << endl;putenv("MYENV=100");char*const argv[] = {"myenv", NULL}; execvpe("myenv", argv, environ);cout << "444" << endl;cout << "555" << endl;cout << "666" << endl;return 0;
}

无非就是第一个参数直接传递替换程序名, 第二个参数传递执行该程序的指令数组, 第三个参数传递环境变量数组, 和之前的大同小异
运行结果:
在这里插入图片描述

7. 补充

7.1 命名理解

exec 函数名中的规律:

  • 带 l 的: l = list, 表示传参直接传在函数参数列表中.
  • 带 v 的: v = vector, 表示一部分参数可以先写在数组中, 再把数组当作参数传递.
  • 带 p 的: p = path, 表示不需要传路径, 直接传递替换程序名即可.
  • 带 e 的: e = env, 表示自己维护环境变量.

7.2 返回值

exec( ) 函数仅在发生错误时返回, 返回值为 -1,并设置 errno 以指示错误, 其实不需要关心返回值, 因为如果成功程序会被替换, 失败不会被替换, 剩于的代码也会执行, 无论成功与否都可以很直观的判断出函数执行的结果.

三、wait/waitpid

子进程在退出后会变成僵尸进程, 通过 wait/waitpid 可以再获取了子进程的退出状态后将身为僵尸进程的子进程释放, 如果长期不释放会导致内存泄漏.

头文件均为:
#include <sys/types.h>
#include <sys/wait.h>

1. wait

函数原型: pid_t wait(int *status);
参数: 输出型参数, 可以获取进程的退出状态, 不需要获取退出状态设置为NULL.
返回值:

  • 成功: 返回被等待的进程的 pid.
  • 失败: 返回 -1, 这时errno会被设置成相应的值以指示错误所在.

示例代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}}else{pid_t ret2 = wait(NULL);cout << "wait success, ret2:" << ret2 << endl;}return 0;
}

子进程调用 5 次 cout << “I am child, my pid:” << getpid() << endl; 输出信息, 最后由父进程成功 wait 并且输出 wait 的返回值.

运行结果:
在这里插入图片描述
通过图片无法看出, 但是以上代码是两个进程再跑, 但是情况是这样的, 父进程一直 wait 子进程退出, 而这种等待是阻塞式等待, 所以其结论就是父进程在使用 wait 时是处于阻塞式等待的, 需要子进程退出后并且父进程 wait 结束后才会往下执行父进程剩于的代码.

2. waitpid

函数原型: pid_t waitpid(pid_t pid, int *status, int options);
参数:

  • pid: 设置为 -1 表示等待任一子进程, 设置为 >0 表示等待指定 pid 的子进程.
  • status: 输出型参数, 可以获取进程的退出状态, 不需要获取退出状态设置为NULL.
  • options: 设置为 0 表示阻塞式等待, 设置为 WNOHANG 表示非阻塞式等待.

返回值:

  • 正常返回时, 返回等待的子进程的 pid.
  • 如果 options 设置了选项WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可等待, 则返回0.
  • 失败返回 -1, 这时errno会被设置成相应的值以指示错误所在.

示例代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}}else{int status = 0;pid_t ret2 = 0;do{ret2 = waitpid(ret1, &status, WNOHANG);cout << "father is running." << endl;sleep(1);}while(ret2 == 0)cout << "waitpid success, ret2:" << ret2 << endl;}return 0;
}

运行结果:
在这里插入图片描述
可以看到在子进程没有退出时, 父进程还是有在往后执行的, 而且在子进程退出时, 父进程也能成功的等待到子进程.

3. 获取子进程的退出状态

在 wait/waitpid 中都有 status, status 是 int 类型, 那么共有 32 位, status 是当作一个位图使用, 在其中存储着退出状态信息, 如下图:
在这里插入图片描述
其中 status 的高 16 位不用, 只使用低 16 位, 而次低 8 位用于存储退出码, 就是 return, exit 携带的数字, 低 7 位存储终止信号, 如果终止信号为 0 表示正常退出, 接着可以获取其退出码, 判断结果是否正确, 如果终止信号不为 0, 表示进程是被信号所终止退出的, 此时为异常退出, 异常退出退出码没有意义, 所以未被设置, 直接获取终止信号判断分析异常退出原因即可.

3.1 位运算获取退出状态

获取退出码: ((status >> 8) & (0xff))
获取终止信号: (status & (0x7f))

退出码示例代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}}else{int status = 0;pid_t ret2 = 0;do{ret2 = waitpid(ret1, &status, WNOHANG);cout << "father is running." << endl;sleep(1);}while(ret2 == 0)cout << "waitpid success, ret2:" << ret2 << ", exit code:" << ((status >> 8) & (0xff)) << ", signal:" << (status & (0x7f)) << endl;}return 0;
}

运行结果:
在这里插入图片描述
为了直观的看到退出码, 改变一下代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}return 111; //为了更直观的看到退出码}else{int status = 0;pid_t ret2 = 0;do{ret2 = waitpid(ret1, &status, WNOHANG);cout << "father is running." << endl;sleep(1);}while(ret2 == 0)cout << "waitpid success, ret2:" << ret2 << ", exit code:" << ((status >> 8) & (0xff)) << ", signal:" << (status & (0x7f)) << endl;}return 0;
}

运行结果:
在这里插入图片描述

终止信号示例代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}return 111; //为了更直观的看到退出码}else{int status = 0;pid_t ret2 = 0;do{ret2 = waitpid(ret1, &status, WNOHANG);cout << "father is running." << endl;sleep(1);}while(ret2 == 0)if((status & (0x7f))){cout << "waitpid success, ret2:" << ret2 << ", signal:" << (status & (0x7f)) << endl;                                                                       } }return 0;
}

如果终止信号不为 0, 表示异常退出, 此时就没必要获取退出码了, 因为此时的退出码不具有参考性, 没有获取的必要.

发送 9 号信号 kill 掉进程:

kill -9 20266

运行结果:
在这里插入图片描述

3.2 内置宏获取退出状态

WIFEXITED(status): 若为正常终止子进程返回的状态, 则为真.(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零, 提取子进程退出码.(查看进程的退出码)

示例代码:

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;int main()
{pid_t ret1 = fork(); //以此往后分化为两个进程执行//子进程if (ret1 == 0){for (int i = 0; i < 5; ++i){cout << "I am child, my pid:" << getpid() << endl;sleep(1);}return 111; //为了更直观的看到退出码}else{int status = 0;pid_t ret2 = 0;do{ret2 = waitpid(ret1, &status, WNOHANG);cout << "father is running." << endl;sleep(1);}while(ret2 == 0)//正常退出,查看退出码if(WIFEXITED(status)){cout << "waitpid success, ret2:" << ret2 << ", exit code:" << WEXITSTATUS(status) << endl;}else{cout << "waitpid success, ret2:" << ret2 << ", signal:" << (status & (0x7f)) << endl;}}return 0;
}

因为没有提供查看终止信号的宏, 所以查看终止信号仍需要用位运算的方法.

运行结果:
在这里插入图片描述

这篇关于【Linux】环境变量、进程替换、wait/waitpid的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/958681

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo