Linux:Linux:进程终止和进程替换
一、进程终止
1.1 进程退出场景和创建退出方式
进程退出有3种场景:代码执行完毕,结果正确;代码执行完毕,结果错误;代码异常终止!
而进程退出的常见方式主要分为以下两大类:
正常终止 | |
从main返回 | |
调用exit退出 | |
调用_exit退出 | |
异常终止 | ctrl c, 信号终止 |
1.2 exit 和 _exit区别
exit 和 _exit都可以直接终止进程。但_exit是系统调用接口,而exit为函数调用,底层封装了exit。
不同的是,exit终止进程时,会刷新缓冲区,执行用户的清理函数,关闭流等操作;而_exit则是直接“粗暴”的退出进程,不做任何其他工作!!
二、进程程序替换
fork()创建子进程时,子进程执行的代码和数据都是父进程的一部分。如果我们想让子进程执行全新的代码,访问全新的数据,我们可以采用一种技术 —— 程序替换!而进程替换可以将命令行参数和环境变量传递给被替换程序的main()函数参数!!
2.1 进程替换函数
进程替换函数有6种以exec开头的函数,统称为exec函数。
#include <unistd.h>
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 execvpe(const char *file, char *const argv[], char *const envp[]);
而exec函数底层都封装了系统调用接口execve的疯转,以实现不同的需求!
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
2.2 函数解释及命名解释
函数解释
- 如果这些函数调用成功,也就意味着进程替换成功。此时重新加载新的程序,从启动代码开始执行,并且不再返回。即进程替换后,执行新程序,执行完后直接退出!!
- 如果进程替换函数执行失败,此时返回值设为-1。
- exec函数只有出错的返回值,没有成功的返回值。
命名解释
- l(list):参数采用列表形式。
- v(vector):参数采用数组。
- p(path):带p表示执行程序时,OS会自带去环境变量PATH中查找路径。
- e(env):表示自己维护环境变量。
2.3 单进程程序替换(无子进程)
2.3.1 带l
函数进程替换(execl为例)
下面我们在一段代码的开头和结尾分别输出打印相关信息,然后两段信息输出代码直接调用execl
替换ls -a-l
。
【源代码】:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d, ecec command begin!\n", getpid());
//进程替换,执行ls指令相关程序
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
printf("pid: %d, ecec command end!\n", getpid());
return 0;
}
【运行结果】:
【函数参数原型解释】:
2.3.2 带`p‘函数进程替换(execlp为例)
【源代码】:(头文件省略)
int main()
{
printf("pid: %d, ecec command begin!\n", getpid());
execlp("pwd", "pwd", NULL);
printf("pid: %d, ecec command end!\n", getpid());
return 0;
}
【运行结果】:
【函数参数原型解释】:
2.3.3 execv、execvp替换函数应用实例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char *const argv[] = {
"ls",
"-l",
"-a",
NULL
};
printf("pid: %d, ecec command begin!\n", getpid());、
// 和execl、execlv类似,只不过下面函数时通过指针数组的方式,指明替换程序的执行方式!!
execv("/usr/bin/ls", argv);
execvp("ls", argv);
printf("pid: %d, ecec command end!\n", getpid());
return 0;
}
2.4 进程替换其他程序,调用运行其他语言程序
上述所有的程序替换都是替换系统指令程序,那如何替换自己写的程序。
下面我们在c程序中创建子进程,让子进程发送进程替换一段c++可执行程序,并且父进程等待子进程!!
【待替换C++程序】:
#include <iostream>
int main()
{
std::cout << "hello c++!" << std::endl;
std::cout << "hello c++!" << std::endl;
std::cout << "hello c++!" << std::endl;
return 0;
}
【主代码C程序】:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("pid: %d, ecec command begin!\n", getpid());
execl("./mytest", "mytest");//替换C++程序
//替换失败,执行下面代码
printf("pid: %d, ecec command end!\n", getpid());
exit(1);
}
pid_t rid = waitpid(-1, NULL, 0);
if(rid == id)
{
printf("wait pid: %d success!!!\n", rid);
}
return 0;
}
【运行结果】:
- 为啥在c程序中,可以直接替换c++程序?根本原因在于exec函数发生的是进程替换,任何语言程序一旦运行起来就变成了进程,便可发生进程替换。系统大于一切!!
三、进程替换时环境变量的继承
3.1 进程替换时,子进程环境变量由来
环境变量是数据,所有可以通过地址空间实现父子间通过写时拷贝的方式共享数据 —— 环境变量。所以当通过exec函数进行进程替换时,子进程的环境变量是直接从父进程来的。
3.2 为何父子进程间环境变量的继承不受进程替换的影响
父进程和子进程间通过写时拷贝的方式,让环境变量别子进程继承,从而实现环境变量的全局性!但为何调用exec函数进行进程替换后,环境变量没有发生修改,变为被替换程序的环境变量?
原因很简单,程序替换,只替换新程序的代码和数据,环境变量不会被替换!!
3.3 子进程获取环境变量的3种方式
- 操作系统直接将环境变量传递给子进程,子进程直接用。或者直接通过
execle、execvpe
的最后一个参数直接传递给子进程!
【实例】:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
int main()
{
pid_t id = fork();
if(id == 0)
{
execle("./mytest", "mytest", NULL, environ);//进程替换,直接将environ传递给子进程
exit(1);
}
pid_t rid = waitpid(-1, NULL, 0);
if(rid == id)
{
printf("wait pid: %d success!!!\n", rid);
}
return 0;
}
- 直接构造自己的环境变量表传递给子进程。
【实例】:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
int main()
{
pid_t id = fork();
if(id == 0)
{
execle("./mytest", "mytest", NULL, environ);//进程替换,直接将environ传递给子进程
exit(1);
}
pid_t rid = waitpid(-1, NULL, 0);
if(rid == id)
{
char *const envp[] = {
"MYENV1 = 111111111111111111",
"MYENV2 = 111111111111111111",
"MYENV3 = 111111111111111111",
NULL
};
execle("./mytest", "mytest", NULL, envp);//直接将自己构造的函数变量表envp传递给子进程
}
return 0;
}
- 借助putenv(),新增环境变量给父进程然后传递给子进程
【实例】:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
int main()
{
pid_t id = fork();
if(id == 0)
{
char *myenv = "MYENV = 111111111111111111";
putenv(myenv);//将环境变量MYENV添加到父进程环境变量表中
execl("./mytest", "mytest");//直接传递给子进程
exit(1);
}
pid_t rid = waitpid(-1, NULL, 0);
if(rid == id)
{
printf("wait pid: %d success!!!\n", rid);
}
return 0;
}