文章目录
- 一、环境变量
- 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;
}
因为没有提供查看终止信号的宏, 所以查看终止信号仍需要用位运算的方法.
运行结果: