进程替换
用fork创建子进程后,子进程执行的是和父进程相同的一段程序(但有可能执行不同的代码分支),如果想让子进程执行另一个程序该怎么办呢?这时候就有了exec函数。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的进程ID并未发生改变。
来看看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 execve(const char *path, char *const argv[], char *const envp[]);
大家会发现这些函数的原型有些类似,来看看它们的规律:
- 函数名不带p:第一个参数必须是程序的相对路径或绝对路径,
- 函数名带e:可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序
- 函数名带l:要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,而且的最后一个可变参数应该是NULL
- 函数名带v:应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL
看一张表格理解一下:
再来看看exec函数的例子吧
我们先在hello.c文件中编写一段简单的代码如下:
再来编写我们测试exec函数的代码(在这里只演示一个函数,其他的函数读者可以自行测试)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("before execvp\n");
char* argv[]=
{
"hello",
NULL
};
execvp("./hello",argv);//带路径,且参数形式是数组,用的是当前的环境变量
printf("afert execvp\n");
return 0;
}
来看看运行结果:
我们会发现,execvp函数下面的语句并没有被执行,这是因为程序已经被替换了
事实上,只有execve是真正的系统调用,而其它五个函数最终都调用了execve
我们还可以根据exec函数做一个简易的shell
直接上代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>
void do_action(char *argv[])
{
pid_t pid=fork();
if(pid==-1)
{
perror("fork");
return;
}
if(pid==0)
{
execvp(argv[0],argv);
}
else
{
wait(NULL);
}
}
void do_parse(char* buf)
{//解析字符串
char *argv[8];
int argc=0;
int status=0;
int i=0;
for(i=0;buf[i]!='\0';i++)
{
if(!isspace(buf[i])&&status==0)
{
argv[argc++]=buf+i;
status=1;
}
else if(isspace(buf[i]))
{
buf[i]=0;
status=0;
}
}
argv[argc]=NULL;
do_action(argv);
}
int main()
{
char buf[1024]={};
while(1)
{
printf("^_^: ");
memset(buf,0,sizeof(buf));
scanf("%[^\n]%*c",buf);
if(strcmp(buf,"exit")==0)
{
break;
}
do_parse(buf);
}
printf("exit\n");
}
来看看运行结果: