承接上一节所写内容,首先通过fork()和vfork()函数创建子进程,子进程与父进程执行的代码是相同的。通常创建了一个进程,目的是为了执行与父进程不同的操作,实现不用的功能,有错引进了exec()函数族。
一、exec()函数族:
1、有六个exec开头的函数组成
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,const char *argv[]);
int execve(const char *path,const char *argv[],char *const envp[]);
int execvp(const char *file,const char *argv[]);
这些函数都定义在函数库中,在使用前需包含头文件<sys/types.h>和<unistd.h>,并且在预定义时定义一个外部的全局变量 extern char **environ;
其作用就是,定义完此命令,可在当前目录执行系统程序,如同执行vim一样。
exec()函数族中的函数,都实现对子进程的数据段、代码段和堆栈段进行替换的功能,如果调用成功,则加载新程序,没有返回值。如果出错返回-1.
2、如何记住exec函数的区别:
(1) 函数名中带有p:代表文件的绝对路径(或称相对路径),当函数中带有p时可以不用书写文件的相对路径,只写出文件名即可。
(2) 函数名中带有l:表示将新程序的每个命令行参数都当作一个参数传给它,参数个数可变,并且最后要输入一个NULL参数表示参数输入结束。
(3) 函数名中带有v:表示该类函数支持使用参数数组,数组中的最后一个指针也要输入NULL参数作为结束标志,类似于main()函数的形参argv[].
3、其他exec函数
一、exec()函数族:
1、有六个exec开头的函数组成
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,const char *argv[]);
int execve(const char *path,const char *argv[],char *const envp[]);
int execvp(const char *file,const char *argv[]);
这些函数都定义在函数库中,在使用前需包含头文件<sys/types.h>和<unistd.h>,并且在预定义时定义一个外部的全局变量 extern char **environ;
其作用就是,定义完此命令,可在当前目录执行系统程序,如同执行vim一样。
exec()函数族中的函数,都实现对子进程的数据段、代码段和堆栈段进行替换的功能,如果调用成功,则加载新程序,没有返回值。如果出错返回-1.
2、如何记住exec函数的区别:
(1) 函数名中带有p:代表文件的绝对路径(或称相对路径),当函数中带有p时可以不用书写文件的相对路径,只写出文件名即可。
(2) 函数名中带有l:表示将新程序的每个命令行参数都当作一个参数传给它,参数个数可变,并且最后要输入一个NULL参数表示参数输入结束。
(3) 函数名中带有v:表示该类函数支持使用参数数组,数组中的最后一个指针也要输入NULL参数作为结束标志,类似于main()函数的形参argv[].
(4) 函数名以e结尾:该类函数表示可以将一份新的函数变量表传给它。
示例:execve函数
//new2.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
extern char **environ;
int main(void)
{
puts("welcome to mrsoft");
return 0;
}
//execve.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
extern char **environ;
int main(int argc,char* argv[])
{
execve("new",argv,environ);//此处调用可执行文件new,也就是new2.c编译后的执行文件
//此时进程中的代码段、数据段和进程段都进行了修改,使得新创建的进程只执行新加载的这个程序的代码
puts("正常情况下无法输出此信息");
}
执行结果:welcome to mrsoft!
//修改后的execve.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
extern char **environ;
int main(int argc,char* argv[])
{
pid_t pid;
if((pid=fork())<0)
printf("create child process failed!\n");
else if(pid==0)
execve("new",argv,environ);
else
puts("正常情况下输出此信息!\n");
}
//运行结果:正常情况下输出此信息!
// welcome to mrsoft!
//再次修改execve.c
//在那个进程中调用,PID还是那个进程的
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int gvar=2;//全局变量
int main(void)
{
pid_t pid;
int var=5;//局部变量
printf("process id:%ld\n",(long)getpid());//输出父进程的PID
printf("gvar=%d var=%d\n",gvar,var);
if((pid=fork())<0)//创建子进程失败
{
perror("error!");
return 1;
}
else if(pid==0)//如果在子进程中执行以下程序
{
gvar--;
var++;
//输出子进程的PID和修改后的变量值
printf("the child process id:%ld,gvar=%d var=%d\n",(long)getpid(),gvar,var);
return 0;
}
else
{ //输出父进程的PID以及变量值
printf("the parent process id:%ld,gvar=%d var=%d\n",(long)getpid(),gvar,var);
execve("new",argv,environ);
return 0;
}
}
//修改后的new.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
extern char **environ;
int main(void)
{
puts("welcome to mrsoft");
printf("newPID=%ld\n",(long)getpid());
return 0;
}
/*运行结果:process id:4043
gvar=2 var=5
the parent process id:4043,gvar=2,var=5
the child process id:4044,gvar=1,var=6
welcome to mrsoft
newPID=4043*/
3、其他exec函数
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
//以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
char *arg[] = {"ls", "-a", NULL};
/**
* 创建子进程并调用函数execl
* execl 中希望接收以逗号分隔的参数列表,并以NULL指针为结束标志
* 包含有 l ① 说明需要在另加上可执行程序作为第二个参数 当然可以加第三、四个等 待参数
* ② 注意不能够外加参数,因为它不能添加参数argv,因此不能再加参数
* 不包含l时参数就不用以NULL结尾
*/
if( fork() == 0 )
{
// in clild
printf( "1------------execl------------\n" );
if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
{
perror( "execl error " );
exit(1);
}
}
/**
*创建子进程并调用函数execv,包含v的特点
*execv中希望接收一个以NULL结尾的字符串数组的指针,
*也可以说可以通过外加参数比如:./a -l 等价于 ls -l
*/
if( fork() == 0 )
{
// in child
printf("2------------execv------------\n");
//注意:execv( "/bin/ls","-l",arg) 是错误的写法,其他参数应另外添加
if( execv( "/bin/ls",arg) < 0)
{
perror("execv error ");
exit(1);
}
}
/**
*创建子进程并调用 execlp
*execlp中与execl类似,多了个p
*l希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
printf("3------------execlp------------\n");
if( execlp( "ls", "ls", "-a", NULL ) < 0 )
{
perror( "execlp error " );
exit(1);
}
}
/**
*创建子里程并调用execvp
*v 望接收到一个以NULL结尾的字符串数组的指针
*p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
*/
if( fork() == 0 )
{
printf("4------------execvp------------\n");
if( execvp( "ls", arg ) < 0 )
{
perror( "execvp error " );
exit( 1 );
}
}
/**
*创建子进程并调用execle
*l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
*e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*/
if( fork() == 0 )
{
printf("5------------execle------------\n");
if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
{
perror("execle error ");
exit(1);
}
}
/**
*创建子进程并调用execve
* v 希望接收到一个以NULL结尾的字符串数组的指针
* e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境
*在运行时另加参数,如:./a -l 如同执行:ls -l
*/
if( fork() == 0 )
{
printf("6------------execve-----------\n");
if( execve( "/bin/ls", arg, NULL ) == 0)
//必须加上路径,绝对路径或相对路径否则找不到要执行的程序
{
perror("execve error ");
exit(1);
}
}
return EXIT_SUCCESS;
}