什么是进程替换
进程的替换简单来讲就是将进程的代码替换为另一个程序的代码。但是进程还是原先的进程,只不过内容换掉而已。而exec 函数工作是将当前进程替换为一个新进程。可以根据指定的文件名或者路径找到可执行文件。
请注意:进程的替换并不是创建新的进程,只是替换而已。创建新的进程请选择fork或者vfork。
exec函数族原型及代码示例
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execle(const char *path, const char *arg0, ... /*,
(char *)0, char *const envp[]*/);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *file, char *const argv[]);
exec系列函数应该用新的进程映像替换当前进程映像。新映像应该由一个称为新进程映像文件的常规可执行文件构建。执行成功不会返回,因为调用的进程映像被新的进程映像覆盖。
execl
#include <stdio.h>
#include <unistd.h> //execl
int main(int argc, char const *argv[])
{
execl("/bin/ls","ls","-l",NULL);//执行/bin目录下的ls
printf("DONE\n");
return 0;
}
输出结果:
当进程调用一种execl函数时,该进程完全由新程序代换,exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。想想printf为什么没有输出字符串"DONE"呢? 原因是当前进程的正文都被替换了,那么execl后的语句,即便execl退出了,都不会被执行。所以没有打印出来。
execlp
execlp不需要传可执行文件的路径,只需传可执行名字就好了。前提是可执行文件必须在$PATH环境变量路径下:
#include <stdio.h>
#include <unistd.h> //execl
int main(int argc, char const *argv[])
{
execlp("ls","ls","-l",NULL);//
printf("DONE\n");
return 0;
}
输出结果:
execle
execle的最后一个字母e,表示存有环境变量字符串地址的指针数组的地址。而环境变量的作用域是进程。
execle有两个功能:
1.在进程中启动程序
2.把当前的环境变量改掉
test.c
#include <unistd.h>
#include <stdio.h>
extern char** environ;
int main(int argc, char **argv)
{
//printf("test pid=%d\n", getpid());
int i;
for (i=0; environ[i]!=NULL; ++i)
{
printf("%s\n", environ[i]); //打印环境变量信息
}
return 0;
}
execle.c
#include <stdio.h>
#include <unistd.h> //execle
int main(int argc, char const *argv[])
{
char *env[] = { "NAME=minger", "AGE=18", NULL};
execle ("./test", "test", NULL, env);
return 0;
}
输出结果:
可以看出确实将给定的环境变量传递过来了
execv
int execv(const char *path, char *const argv[]);
把可执行文件放到指针数组里char *const argv[]。
execv.c
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *arg[]={"ls", "-l", NULL};
execv("/bin/ls", arg);
perror("execv");
return 0;
}
输出结果
execvp
int execvp(const char *file, char *const argv[]);
带 p 的exec函数:execlp,execvp,表示第一个参数path不用输入完整路径,只有给出命令名即可,它会在环境变量PATH当中查找命令。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *arg[]={"ls", "-l", NULL};
execvp("ls", arg);
perror("execv");
return 0;
}
输出结果:
execvp表示命令所需的参数以char *arg[]形式给出且arg最后一个元素必须
是NULL。
制作shell命令解释器(外部命令)
外部命令:
在/bin/ 目录下能找到命令的可执行程序的令被称为外部命令。
如:ls、pwd等,如:ls、pwd等,通过exec来执行可执行程序实现命令功能。
内部命令:
在/bin/ 目录下找不到可执行程序的命令被称为内部命令。
如:cd、exit、export等,shell程序内部通过调用函数来实现命令功能(如shell通过调用chdir函数来实现cd命令)。
shell外部命令实现
#include<errno.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
void Sehll_command_parser(char brkuf[]);
void Show_env(char env[]);
#define MAXLINE 100
int main(int argc, char **argv)
{
char brkuf[MAXLINE];//用来存放命令行输入
Sehll_command_parser(brkuf);
return 0;
}
void Sehll_command_parser(char brkuf[])
{
pid_t pid;//子进程id
int status = 0;//waitpid中的参数
char Envbuf[MAXLINE];//
char *brk=" ";//命令中命令以及参数是以空格为区分的,brk就代表了区分的空格,来区分命令和参数
char *com = " ";//要将接收到的命令行字符串拆分,拆分的每一个子字符串放到c中,然后传递给strtemp
char * str[MAXLINE];//作为给exec函数族传递参数的字符串
int flags=0;//用来表征命令行命令中有“<<”,"<","<"这些符号
int pos,ii;//pos用来表征<<,<,>这个几个符号的位置
char *strtemp[15] = {0};//对接收到的命令行输入拆分之后,放到这个缓冲区中,进行处理
if(setenv("SHOW","->",1)!=0)//设置环境变量,作为命令输入的提示符
printf("设置环境变量失败");
char* env=getenv("SHOW");
printf("%s",env);
while (fgets(brkuf, MAXLINE, stdin) != NULL) //获取字符串
{
memset(str,'\0',MAXLINE);
memset(strtemp,'\0',MAXLINE);//每次循环的开始都要把str,strtemp清空,来接受新的命令,flag作为标志位,也要复位
flags=0;
if (brkuf[strlen(brkuf) - 1] == '\n')//当命令行中输入是空格然后回车的时候,要提示重新输入,不能报错
brkuf[strlen(brkuf) - 1] = 0; //把最后一位'\n'变成字符串结束标志'\0'
if(strcmp(brkuf,"qt")==0)//判断是不是结束shell进程的标志
break;
//判断是否是修改环境变量SHOW,修改命令提示符的操作
if(strcmp(brkuf,";/")==0)
{
printf("修改环境变量SHOW=");
fgets(Envbuf,MAXLINE,stdin);
setenv("SHOW",Envbuf,1);
env=getenv("SHOW");
env[strlen(env)-1]='\0';
printf("%s",env);
continue;
}
if(strcmp(brkuf,"\n")==0)//当直接在命令行中回车,要提示重新输入命令,不能报错
{
Show_env(env);
continue;
}
//int ppgid =getpgid(getpid()); //父进程id,也就是本进程id
com=strtok(brkuf,brk);//对字符串使用strtok函数拆分,将子字符串保存到strtemp中
int i=0;
while(com) //如果cmo 不等于NULL
{
strtemp[i]=com;
if(strcmp(strtemp[i],">>")==0||strcmp(strtemp[i],">")==0||strcmp(strtemp[i],"<")==0)//判断有没有<<,<,>这些字符,有则把标志位置为1
{
flags=1;
pos=i;
}
i++;
com=strtok(NULL,brk);
}
if ((pid = fork()) < 0)
{
printf("fork error");
}
else if (pid == 0) //子进程
{
if((strcmp(strtemp[i-1],"&"))==0)//如果有&,则对进程进行后台处理的过程
{
setpgid(getpid(),getpid());//子进程建立新的组,与父进程控制终端脱离,成为后台进程
ii=0;
for(;ii<i-1;ii++)
str[ii]=strtemp[ii];
}
else//没有&,则依然为父进程组中的进程
{
ii=0;
for(;ii<i;ii++)
str[ii]=strtemp[ii];
}
if(flags==1)//有<<,<,>表示,要重定向处理。
{
memset(str,'\0',100);
int fd;
if(strcmp(strtemp[pos],">>")==0)
{
fd=open(strtemp[pos+1],O_CREAT|O_RDWR|O_APPEND,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);/*>>是以追加的方式输入*/
dup2(fd,1);
}
if(strcmp(strtemp[pos],">")==0)
{
fd=open(strtemp[pos+1],O_CREAT|O_RDWR,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);/*>是以取代的方式输入*/
ftruncate(fd,0);
dup2(fd,1);
}
if(strcmp(strtemp[pos],"<")==0)//<表示将stdin进行重定向,重定向到strtemp[pos+1]这个文件上
{
fd=open(strtemp[pos+1],O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
dup2(fd,0);
}
ii=0;
for(;ii<pos;ii++)
{
str[ii]=strtemp[ii];//将处理万的strtemp传递给str,str作为execvp的实参。
printf("str %d is %s",ii,str[ii]);
}
close(fd);
}
execvp(str[0],str);//执行命令行命令
printf("couldn't execute: %s\n", brkuf);
exit(127);
}
wait(NULL); //父进程等待子进程结束。
Show_env(env);
}
}
void Show_env(char env[])
{
env=getenv("SHOW");
printf("%s",env);
}
输出结果:
扫二维码关注微信公众号,获取技术干货