EXEC函数族
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支,通过if语句对i进行判断),子进程往往要调用一种exec函数以执行另一个程序(不再回来了,即剩下没有执行完的程序等数据都被清除了)。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程(其实启动例程才是真正的程序入口地址,启动例程是调用main函数的哪个函数,先于main函数执行,启动例程由C和汇编混合编写)开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
Linux使用exec函数族来执行新的程序,以新的子进程来代替原有的进程。exec函数族执行成功后不会返回(没有返回值),因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容所替换(因此返回值也没有意义),只是进程ID等一些表面上的信息仍然保持原样,看上去还是旧的躯壳,却已经注入了新的灵魂。只有当exec函数族执行失败了,才会返回-1,此时从exec函数族的调用点继续向下执行。所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
综上,一个进程想要再不放弃原程序的情况下,同时去执行另一个程序,可以fork一个子进程,让子进程调用exec函数族去执行另一个程序,而自己仍保持源程序的执行。
其实有六种以exec开头的函数,统称exec函数族:
#include <unistd.h>
extern char **environ; //要用到这个变量,需要申明 当然也可以自己重新定义
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...); //const char * 指针所指的内容为常量
int execv(const char *path, char *const argv[]); //char *const 指针为常量,其内容可变化
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
l (list) :命令行参数列表 p (path):搜索file时使用path环境变量
v (vector):使用命令行参数数组,即argv e (environment):使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量,即environ,如果没有e(即不指定e这个参数),则不意味着不适用环境变量,而是把默认的环境变量(当前进程的环境变量environ)隐式传给被执行的应用程序;带上e,则用指定的环境变量去替代默认的那些环境变量,因此需要自己进行定义。
综上,l与v其实相同的参数,只是方式不一样。以上6个函数,由于有了l或v和e参数,使得函数的形参个数不定,因此属于变参函数(形参个数或类型不确定的函数)。main函数也是一个变参函数。
使用excel函数族,既可以重新加载一个用户自定义的程序,也可以加载一个系统的可执行程序,如各种命令文件。
int execlp(const char *file, const char *arg, ...);
加载一个进程(新的程序),借助PATH环境变量(不用提供完整路径,只需要提供程序文件名即可,就会自动在PATH中去查找)。
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回-1。该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。后面的参数均为命令行参数,argv[0] argv[1] 注意,最后一个参数必须为NULL。没有e,则默认将进程的环境变量传入到第一个参数所指定的程序中。如果有e,则先定义好environ变量,然后直接将erviron作为最后一个参数传入即可。从而,加载出一个新的进程。其它函数功能都一样,只是使用方法有些不同。注意,excelp函数是使用的进程本身的环境变量。
int execl(const char *path, const char *arg, ...);
加载一个进程(新的程序),通过“路径+程序名”来加载。
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
上述两个函数被调用后,相当于进程重新被加载,等效执行了:ls -l -F 命令。ls为argv[0],以此类推。在所有函数中,argv[0]的信息出错(只要形式正确,为一个字符串)不会影响程序的正常执行,依然可以获得正确结果,因为实际要传入到程序内部的参数是argv[1]及其以后的所有参数,至于argv[0]其实相当于是执行程序本身,即函数第一个参数。
int execle(const char *path, const char *arg, ..., char *const envp[]);
加载一个进程,使用自定义环境变量env。
//代码举例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == -1 ) {
perror("fork");
exit(1);
} else if (pid > 0) {
printf("I'm parent pid = %d, parentID = %d\n", getpid(), getppid());
} else if (pid == 0) {
sleep(3); //子进程睡了3秒,因此会导致父进程先结束
printf("i am child\n");
execl("/bin/ls", "ls", "-l", NULL); //此时,子进程重新加载ls程序,后面代码不再执行(不再回来了)
perror("exec");
exit(1);
}
printf("-------finish...%d\n", getpid());
return 0;
}
[[email protected] exec]# ./fork
I'm parent pid = 24375, parentID = 17448
-------finish...24375
[[email protected] exec]# i am child //由于父进程先结束
total 89
-rwxrwxrwx. 1 root root 9901 Oct 13 09:37 execl
-rwxrwxrwx. 1 root root 285 Oct 13 09:37 execl.c
-rwxrwxrwx. 1 root root 10185 Oct 13 09:37 execlp
-rwxrwxrwx. 1 root root 474 Oct 13 09:37 execlp.c
-rwxrwxrwx. 1 root root 9805 Oct 13 09:37 exec_ps
//代码举例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
extern char **envrion;
int main(void)
{
char *argvv[] = {"ls", "-l", NULL};
//execl("/bin/ls", "ls", "-l", NULL);
//execlp("ls", "ls", "-l", NULL);
execv("/bin/ls", argvv); //这三行的效果是一样的,后面程序不再执行
perror("exec");
exit(1);
return 0;
}
//练习:将当前系统中的进程信息,打印到文件中。
方法一: ps aux > out //输出重定向到out文件中,且清空写入。out文件不存在,则其会自动创建。
方法二:利用exec函数族、dup2、open函数,代码如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
fd = open("ps.out", O_WRONLY|O_CREAT|O_TRUNC, 0664); //创建并打开一个ps.out文件,并将其截断为0。
if(fd == -1){
perror("open ps.out error");
exit(1);
}
dup2(fd, STDOUT_FILENO); //文件描述符重定向,将标准输出定向到ps.out文件中
execlp("ps", "ps", "-aux", NULL); //直接执行命令 ps -aux ,此时会输出到文件中
perror(“execlp”);
exit(1);
close(fd); //若exec成功,后面的程序不再执行了,因此关闭该文件是没有意义的。
return 0;
}
[[email protected] exec]# ./exec_ps
[[email protected] exec]# ls
execl execlp exec_ps fork makefile output.c test upper wrapper
execl.c execlp.c exec_ps.c fork.c output ps.out test.c upper.c wrapper.c
[[email protected] exec]# ls -l ps.out
-rwxrwxrwx. 1 root root 38914 Mar 26 00:28 ps.out
分析:即使exec成功,没有通过close关闭打开的文件,但是当该进程结束后,会隐式关闭打开的文件,隐式回收各种资源。
上一篇: ucontext族函数详解
下一篇: exec函数族