赞
踩
可以查看当前进程的所有环境变量.
可以查看指定的环境变量.
通过设置 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, 所以可以用作循环条件的判断.
通过 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 引入.
通过 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;
}
运行结果:
在 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>
函数声明: int execl(const char *path, const char *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 结尾.
运行结果:
可以看到前面三行代码正常输出, 进程替换后的代码不会输出, 因为在函数调用处后的代码会被替换.
函数声明: int execlp(const char *file, const char *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 中, 否则不会替换.
函数声明: int execle(const char *path, const char *arg, …, char * const 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 当作形参传递就好了.
运行结果:
可以看到, 不仅输出了原本的环境变量, 而且新添加的也输出了, 那么就成功做到了不覆盖进程原本的环境变量, 又把新的环境变量传入.
函数声明: int execv(const char *path, char *const 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 结尾即可.
运行结果:
函数声明: int execvp(const char *file, char *const 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 差不多, 无非是函数的第一个参数直接传可执行程序名即可.
运行结果:
函数声明: int execvpe(const char *file, char *const argv[], char *const 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; }
无非就是第一个参数直接传递替换程序名, 第二个参数传递执行该程序的指令数组, 第三个参数传递环境变量数组, 和之前的大同小异
运行结果:
exec 函数名中的规律:
exec( ) 函数仅在发生错误时返回, 返回值为 -1,并设置 errno 以指示错误, 其实不需要关心返回值, 因为如果成功程序会被替换, 失败不会被替换, 剩于的代码也会执行, 无论成功与否都可以很直观的判断出函数执行的结果.
子进程在退出后会变成僵尸进程, 通过 wait/waitpid 可以再获取了子进程的退出状态后将身为僵尸进程的子进程释放, 如果长期不释放会导致内存泄漏.
头文件均为:
#include <sys/types.h>
#include <sys/wait.h>
函数原型: pid_t wait(int *status);
参数: 输出型参数, 可以获取进程的退出状态, 不需要获取退出状态设置为NULL.
返回值:
示例代码:
#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 结束后才会往下执行父进程剩于的代码.
函数原型: pid_t waitpid(pid_t pid, int *status, int options);
参数:
返回值:
示例代码:
#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; }
运行结果:
可以看到在子进程没有退出时, 父进程还是有在往后执行的, 而且在子进程退出时, 父进程也能成功的等待到子进程.
在 wait/waitpid 中都有 status, status 是 int 类型, 那么共有 32 位, status 是当作一个位图使用, 在其中存储着退出状态信息, 如下图:
其中 status 的高 16 位不用, 只使用低 16 位, 而次低 8 位用于存储退出码, 就是 return, exit 携带的数字, 低 7 位存储终止信号, 如果终止信号为 0 表示正常退出, 接着可以获取其退出码, 判断结果是否正确, 如果终止信号不为 0, 表示进程是被信号所终止退出的, 此时为异常退出, 异常退出退出码没有意义, 所以未被设置, 直接获取终止信号判断分析异常退出原因即可.
获取退出码: ((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
运行结果:
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; }
因为没有提供查看终止信号的宏, 所以查看终止信号仍需要用位运算的方法.
运行结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。