前言
本文为笔者学习笔记,若有不妥之处,欢迎斧正。
一、exec族函数简介
exec族函数的作用:用在fork函数创建新的进程后,经常会在新进程中调用exec函数去执行另外的一个程序。
注意:当进程调用exec函数时,该进程被完全替换为新程序。在调用exec函数时并不创建新的进程,所以前后的进程pid没有改变。
exec族函数有:execl、execv execle execve execlp execvp fexecve等。
二、exec族函数详解
1.函数原型
#include <unistd.h>
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, 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[]);
作用:在fork函数后,让子进程去执行另一个程序。
参数说明:
- path:可执行文件路径名
- arg:可执行程序所带的参数,第一个参数为可执行文件的名字,没有带有路径且arg必须以NULL结束。
- file:如果参数file中包含/,则将其视为路径名,否则就按path环境变量,在黄静变量所指的目录中搜寻可执行文件。
- envp:指向环境字符串的各字符指针构成的数组的指针
返回值:
函数执行成功时不会返回。
函数执行失败时,会设置errno并返回-1,然后原程序的调用点继续往下执行。
exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
2.详解上述API
- int execl(const char *path, const char arg, … / (char *) NULL */);
注意事项:函数的参数path一定得有,arg表示参数,跟在path后的为执行文件的文件名,后面是可执行文件的参数,可以有很多个,但是最后必须有NULL结尾。
代码演示:
/*
* 调用 execl 函数的进程
*/
#include <stdio.h>
int main(int argc,char **argv)
{
int i;
for(i=0;i<argc;i++)
{
printf("argv[%d]:%s\n",i,argv[i]);
}
return 0;
}
/*
*被调用execl函数进程调用的进程
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
printf("before execl\n");
if(execl("./aaa","aaa","bbb","ccc","ddd","eee",NULL)==-1)
{
printf("execl fail\n");
}
printf("after execl\n");
return 0;
}
运行结果如下:
before execl
argv[0]:aaa
argv[1]:bbb
argv[2]:ccc
argv[3]:ddd
argv[4]:eee
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
printf("before execl\n");
if(execl("/bin/ls","ls","-l",NULL)==-1)
{
printf("execl fail\n");
}
printf("after execl\n");
return 0;
}
运行结果如下:
before execl
total 48
-rwxr-xr-x 1 cjh cjh 8304 May 15 15:14 aaa
-rw-r--r-- 1 cjh cjh 191 May 15 15:13 aaa.c
-rwxr-xr-x 1 cjh cjh 8344 May 15 15:14 demo1
-rw-r--r-- 1 cjh cjh 291 May 15 15:14 demo1.c
-rwxr-xr-x 1 cjh cjh 8344 May 15 15:21 demo2
-rw-r--r-- 1 cjh cjh 273 May 15 15:21 demo2.c
- int execlp(const char *file, const char arg, … / (char *) NULL */);
注意事项:带p的一类exac函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
printf("before execl\n");
if(execlp("ls","ls","-l",NULL)==-1)
{
printf("execl fail\n");
}
printf("after execl\n");
return 0;
}
运行结果:
before execl
total 64
-rwxr-xr-x 1 cjh cjh 8304 May 15 15:14 aaa
-rw-r--r-- 1 cjh cjh 191 May 15 15:13 aaa.c
-rwxr-xr-x 1 cjh cjh 8344 May 15 15:14 demo1
-rw-r--r-- 1 cjh cjh 291 May 15 15:14 demo1.c
-rwxr-xr-x 1 cjh cjh 8344 May 15 15:21 demo2
-rw-r--r-- 1 cjh cjh 273 May 15 15:21 demo2.c
-rwxr-xr-x 1 cjh cjh 8344 May 15 15:24 demo3
-rw-r--r-- 1 cjh cjh 270 May 15 15:24 demo3.c
- int execle(const char *path, const char *arg, …, char * const envp[]);
注意事项:函数的参数path一定得有,arg表示参数,跟在path后的为执行文件的文件名,后面是可执行文件的参数,可以有很多个,但是最后必须有NULL结尾
- 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[]);
目的是为了参数可视化,将所有的参数放在数组中。
注意事项:带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
注意事项:带e的一类exac函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。
注意事项:带p的一类exac函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
代码演示:
//文件execvp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execvp(const char *file, char *const argv[]);
int main(void)
{
printf("before execlp****\n");
char *argv[] = {
"ps","-l",NULL};
if(execvp("ps",argv) == -1)
{
printf("execvp failed!\n");
}
printf("after execlp*****\n");
return 0;
}
运行结果:
before execlp****
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 1917 1908 0 80 0 - 5646 wait pts/0 00:00:00 bash
0 R 1000 5564 1917 0 80 0 - 7229 - pts/0 00:00:00 ps
三、Linux system函数
函数原型:
#include <stdlib.h>
int system(const char *command);
system函数实质上的过程如下:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。 对于fork失败,system()函数返回-1。 如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。
(注意,command顺利执行不代表执行成功比如command:“rmdebuglog.txt”,不管文件存不存在该command都顺利执行了) 如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回非0值,一般为1。
注意事项:system()函数用起来很容易出错,返回值太多,而且返回值很容易跟command的返回值混淆。
笔者不推荐使用system函数,推荐使用下面要介绍的popen函数
四、Linux popen函数
函数原型:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
函数作用:允许一个程序将另外一个程序作为新进程来启动,并可以传递数据或者通过它接受数据。
参数说明:
1.执行一个以NULL结束的shell命令字符串的指针。
2.打开文件流的权限,只读或只写
返回值:
如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
popen函数原理:popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。
注意事项:这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
popen缺点:使用popen的不好影响是,针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将多启动两个进程。
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char ret[1024]={
0};
FILE *fp;
int size;
fp=popen("ps","r");
size = fread(ret,1,1024,fp);
printf("read %d byte,ret:%s\n",size,ret);
pclose(fp);
return 0;
}
运行结果如下:
PID TTY TIME CMD
1917 pts/0 00:00:00 bash
5685 pts/0 00:00:00 ps