引言
参考博文
fork函数创建新的子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec的函数时,该进程执行的程序完全替换为新的程序(就是通过子进程取执行其他程序,此时子进程ID不变,exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段),新程序执行完毕之后exec不会回到原程序继续执行。
exec有7个函数供我们使用,他们能实现的功能相同,只是方式不太一样,称为exec族函数。
int execl(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//返回值:若成功,没有返回值;若失败,返回-1。
以上一个函数大致有3个区别:
第一个: 前3个函数取路径名作为参数,后3个函数取文件名作为参数。当以文件名作为参数时:
- 如果file中包含/,则将其视为路径名;
- 否则就按PATH环境变量,在它所指定的各目录中搜寻可执行文件。
PATH环境变量包含了一张目录表(称为路径前缀),目录之间用冒号(:)分隔。例如,下列name=value 环境字符串指定在4个目录中进行搜索。
PATH=/bin:/usr/bin:/usr/local/bin:.
第二个: 与传递的参数表有关(l表示列表list,v表示矢量vector)。函数execl、execlp和execle要求把新程序的每个命令行参数作为一个单独的参数。这种参数表以空指针结尾。对于execv、execvp和execvpe则应先构造一个指针数组,将新程序的命令行参数作为数组的元素,然后将数组的地址作为exec函数的参数。
第三个: 与向新程序传递的环境表有关。以e结尾的三个函数可以传递一个指向环境字符串指针数组的指针。其他4个函数则使用调用进程中的environ变量为新程序复制现有的环境。
这7几个exec函数的参数很难记忆,函数名中的字符会给我们一些帮助。字母p表示该函数取filename作为第一个参数,并且用PATH环境变量寻找可执行文件。字母l表示该函数取一个参数表,它与字母v互斥。v表示该函数取一个argv[]矢量。最后字母e表示该函数取envp数组,而不使用当前环境。
可以使用echo
P
A
T
H
查
看
当
前
环
境
变
量
,
e
x
p
o
r
t
P
A
T
H
=
PATH 查看当前环境变量,export PATH=
PATH查看当前环境变量,exportPATH=PATH:地址 修改环境变量。
在很多Unix实现中,这7个函数中只有execve是内核的系统调用。另外6个只是库函数,它们最终要调用该系统调用。
#include<stdio.h>
#include <unistd.h>
int main()
{
if(execl("file","file","zhou","wang",NULL)==-1)//这是execl的用法,将所需的每一个命令行参数作为单独的参数传递,并且以NULL结尾。 file是新程序的可执行文件,包括路径,因为此时file文件就在当前路径下,所以可不写。
{
perror("execl");
}
printf("after exec");
return 0;
}
~
~
~
#include<stdio.h>
#include <unistd.h>
int main()
{
char* argv[]={"file","zhou","wang"};
if(execv("file",argv)==-1)//这是execv 的用法将新程序所需的命令行参数收集到数组里,再传递数组的地址。file也是文件路径。
{
perror("execl");
}
printf("after exec");
return 0;
}
#include<stdio.h>
#include <unistd.h>
int main()
{
if(execlp("ps","ps","-aux",NULL)==-1)//这是execlp 的用法,带p的情况下它会尝试每个PATH中的前缀,最终找到可执行文件,所以直接用文件名即可。
{
perror("execl");
}
printf("after exec");
return 0;
}
system函数参考博文
system函数类似于exec函数,都是调用一个新程序,但是它比exec更方便、好用。
int system(const char *command);
//参数command是一个字符串,这里调用了exec函数并将该字符串作为参数,实际上是执行一个shell指令。
如果command是一个空指针则,仅当命令处理程序时可用,system返回0值,这一特征可以确定在一个给定的操作系统上是否支持system函数。
其实system函数实际上是对了fork、exec和waitpid3个函数的封装,因此有3种返回值。
- fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,并且设置errno以指示错误类型。
- 如果exec失败(表示不能执行shell),则返回值如同shell执行了exit(127)一样。
- 否则所有3个函数(fork、waitpid和exec)都成功,那么system返回值时shell的终止状态。
值得注意的是system函数使用fork创建了子进程,并且调用子进程执行exec,所以这里的环境也是子进程的环境,在FTP项目中使用system进入目录时,是子进程的子进程进入目录,所以没有办法进入别的目录,必须调用别的函数。
#include<stdio.h>
#include<stdlib.h>
int main()
{
system("./file zhou wang");
return 0;
}
system源码
#include<sys/wait.h>
#include<errno.h>
#include<unistd.h>
int system(const char* cmdstring)
{
pid_t pid;
int status;
if(cmdstring==NULL) //判断指令非空
{
return(1);
}
if(pid=fork()<0) //创建子进程
{
status=-1; //创建失败退出状态-1
}
else if(pid==0) //创建成功,子进程调用exec函数
{
execl("/bin/sh","sh","-c",cmdstring,NULL);
exit(127); //exec函数一旦执行成功不返回,该语句不执行。
}
else{
while(waitpid(pid,&status,0)<0)
{
if(errno!=EINTR)
{
status=-1;
break;
}
}
}
return status;
}
~
popen函数和pclose函数参考博文1 参考博文2
popen函数与system其它功能相同,不同的是它可以将执行的结果读出。
原因是它在创建子进程后会在父子进程之间建立一个管道,执行一个shell运行命令,等待命令终止。
FILE *popen(const char *command, const char *type);
//返回值:若成功,返回文件指针;若出错,返回NULL。
int pclose(FILE *stream);
//返回值:若成功,返回cmdstring终止状态;若出错,返回-1。
函数说明:
(1)popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。
(2)参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。
(3)此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。pclose则是最后用来关闭文件的。
(4)如果 type 为 r,那么调用进程读进 command 的标准输出。如果 type 为 w,那么调用进程写到 command 的标准输入。
返回值: 若成功则返回文件指针,否则返回NULL,错误原因存于errno中。
注意: popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。
以下是popen参数type为“r”时的代码。
#include<stdio.h>
int main()
{
FILE* fp;
char buf[1024]={0};
fp=popen("ls -l","r");
if(fp==NULL)
{
perror("popen");
}
if(fread(buf,1,1024,fp)==0)
{
perror("read");
}
pclose(fp);
printf("%s\n",buf);
return 0;
}
~
popen源码
/*
* popen.c Written by W. Richard Stevens
*/
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include "ourhdr.h"
static pid_t *childpid = NULL;
/* ptr to array allocated at run-time */
static int maxfd; /* from our open_max(), {Prog openmax} */
#define SHELL "/bin/sh"
FILE *
popen(const char *cmdstring, const char *type)
{
int i, pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" or "w" */
if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
errno = EINVAL; /* required by POSIX.2 */
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child pids */
maxfd = open_max();
if ( (childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if (pipe(pfd) < 0)
return(NULL); /* errno set by pipe() */
if ( (pid = fork()) < 0)
return(NULL); /* errno set by fork() */
else if (pid == 0) { /* child */
if (*type == 'r') {
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
} else {
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (i = 0; i < maxfd; i++)
if (childpid[ i ] > 0)
close(i);
execl(SHELL, "sh", "-c", cmdstring, (char *) 0);
_exit(127);
}
/* parent */
if (*type == 'r') {
close(pfd[1]);
if ( (fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ( (fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember child pid for this fd */
return(fp);
}
int
pclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if (childpid == NULL)
return(-1); /* popen() has never been called */
fd = fileno(fp);
if ( (pid = childpid[fd]) == 0)
return(-1); /* fp wasn't opened by popen() */
childpid[fd] = 0;
if (fclose(fp) == EOF)
return(-1);
while (waitpid(pid, &stat, 0) < 0)
if (errno != EINTR)
return(-1); /* error other than EINTR from waitpid() */
return(stat); /* return child's termination status */
}