执行程序
执行一个新程序
Linux环境下使用 exec函数 执行一个新程序,该函数在文件系统中搜索指定路径的文件,并将该文件内容复制到调用exec函数的地址空间,取代原进程的内容。
- 因此 exec 函数并不创建一个新进程, 虽然进程的内容改变了,但是其进程ID没有改变,系统认为这仍是同一个进程,函数原型为:
#include<unistd.h>
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, char *const argv[]);
int execvp(const char *file, char *const argv[]);
- 函数说明: https://www.cnblogs.com/mickole/p/3187409.html
- 返回值: exec函数成功执行则不返回任何值,失败返回-1
- 案例: 下例演示如何使用exec函数,这里使用execvp函数作为例子,该演示程序使用exec函数运行hello程序。该程序首先调用fork函数创建一个子进程,子进程代码中调用execvp函数加载hello程序,并使用其代码覆盖子进程的代码。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t pid;
pid=fork();//创建子进程
if(pid < 0){
printf("fail to fork\n");
exit(1);
}else if(0 == pid){
if(execvp("hello",NULL) < 0){
printf("fail to exec\n");
exit(1);
}
printf("the child is not hello\n");//绝对不会执行这里!!!
exit(0);
}
printf("the parent\n");
return 0;
}
- 对于新程序的命令行参数和环境表有长度大小的限制,对于Linux来讲这个限制是4096个字节。执行了exec函数的进程不改变一下进程特征:
- 总结: exec函数几乎没有改变任何关于进程身份的属性,该函数只改变进程的内容。exec 会关闭打开的目录流,而且会改变有效用户ID和有效组ID.
执行解释器文件
- 可执行文件:Linux中的可执行文件分为两种,一种是 二进制可执行文件,这种文件是经过编译系统编译连接后生成的。另一种是解释器文件,这种文件不是二进制的,而是一-般的文本文件。
- 解释器文件:这种文本文件的起始形式为:
#! 解释器程序的路径 解释器程序所需要的参数
例如,Linux环境下使用最多的shell脚本,其脚本文件的起始路径是:/bin/sh
- shell脚本需要调用shell解释器来解释执行,而Linux shell程序的路径是/bin/sh,除去第一行之外的文件内容都被认为是解释器文件的内容,其处理交由制定的解释器程序处理。由此可知,用户也可以使用解释器文件的这个特性运行一个程序。这个程序可以是任意程序,而不一定是解释器程序,如果这个程序是一般程序,该解释器文件只有一行。
例如:运行hello程序: #! /home/admin/hello - exec函数运行解释器文件的特点和参数传递: exec 函数中传给新程序的命令行参数和解释器文件中的命令行参数以其作为参数传给解释器程序,顺序是解释器文件中的命令行参数在前,exec函数中传给新程序的命令行参数在后。
在程序中执行shell命令
- 使用情形: 在Linux环境编程中往往需要调用shell 命令进行一些操作,例如,需要取一个目录中的文件名的列表时,使用 shell 命令Is可以很轻松地完成需求。如果不使用shell命令则需要使用opendir函数打开目录,对目录中的每项调用stat函数得出其文件名。这样做加大了编程的难度,而且不利于代码移植。对于这种情况,调用shell已有的命令可以解决以上的问题。Linux环境使用system函数调用shell指令,函数原型为:
#include<stdlib.h>
int system(const char* cmdstring);
- 案例:使用system函数在程序中输出当前目录下的文件列表,并将其输出到 temp.txt 文件中,之后打开文件读入文件并输出到屏幕。
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#define MAX 1024
int main(){
int fd,n;
char buf[MAX];
if(-1 == system("ls > temp.txt")){
perror("fail to system\n");
exit(1);
}
fd=open("temp.txt",O_RDONLY);
if(-1 == fd){
perror("fail to open\n");
exit(1);
}
n=read(fd,buf,MAX-1);
if(-1 == n){
perror("fail to read\n");
exit(1);
}
buf[n]='\0';
printf("%s",buf);
return 0;
}
实现system函数
那么system函数执行流程是什么呢?
- system函数执行流程:执行流程分两步,首先由调用 system 函数的进程创建出一个子进程,并调用 wait 函数等待子进程执行完毕:然后由这个子进程调用exec 函数加载shell 运行cmdstring中指定的命令。
- 代码实现system原理:
#include<stdio.h>
#include<stdlib.h>
#include<unistd>
int system(const char* cmdstring){
int rc;
pid_t pid;
if(NULL == cmdstring)
return 1;
pid=fork();
if(pid < 0){
perror("fail to foek\n");
rc=-1;
}else if(0 == pid){
execl("/bin/sh","sh","-c",cmdstring,NULL);
__exit(127);
}
if(-1 == waitpid(pid,&status,0)){
status=-1;
}
return status;
}
- 特点: system函数的执行效率比较低。看过了它的实现,读者现在已经知道,在system函数中要2次调用fork和exec加载一个新的程序。 第1次是加载shell程序,第2次是加载需要执行的程序(这个程序由shell 负责加载)。但是对比直接使用fork + exec的方法,system函数虽然在效率上有些低,却有以下优点:
1、system函数添加了出错处理操作:
2、system函数添加了信号处理操作(具体内容参照本书):
3、system调用了wait 函数,保证不会出现僵尸进程。这一点往往被许多程序员所忽略。