原文:http://blog.csdn.NET/nvd11/article/details/8856278
假如我们在编写1个c程序时想调用1个shell脚本或者执行1段 bash shell命令, 应该如何实现呢?
其实在<stdlib.h> 这个头文件中包含了1个调用shell命令或者脚本的函数 system();直接把 shell命令作为参数传入 system函数就可以了, 的确很方便. 关于system 有一段这样的介绍: system 执行时内部会自动启用fork() 新建1个进程, 效率没有直接使用fork() 和 exec函数高.
那么这篇文章其实就是介绍一下fork() 和 exec函数的用法, 以及如何使用它们来替代system函数.
一般来讲, 我们编写1个普通的c程序, 运行这个程序直到程序结束, 系统只会分配1个pid给这个程序, 也就就说, 系统里只会有一条关于这个程序的进程.
但是执行了fork() 这个函数就不同了.
fork 这个英文单词在英文里是"分叉"意思, fork() 这个函数作用也很符合这个意思. 它的作用是复制当前进程(包括进程在内存里的堆栈数据)为1个新的镜像. 然后这个新的镜像和旧的进程同时执行下去. 相当于本来1个进程, 遇到fork() 函数后就分叉成两个进程同时执行了. 而且这两个进程是互不影响
fork函数的特点概括起来就是“调用一次,返回两次”
实际应用中, 单纯让程序分叉意义不大, 我们新增一个子程序, 很可能是为了让子进程单独执行一段代码. 实现与主进程不同的功能.
要实现上面所说的功能, 实际上就是让子进程和主进程执行不同的代码啊.
所以fork() 实际上有返回值, 而且在两条进程中的返回值是不同的, 在主进程里 fork()函数会返回子进程的pid, 而在子进程里会返回0! 所以我们可以根据fork() 的返回值来判断进程到底是哪个进程, 就可以利用if 语句来执行不同的代码了!
int fork_test(){ int childpid; int ret; if (ret =fork() == -1){ printf("fork failed\n"); } }else if(ret == 0){ //child process printf("This is child process\n"); } else{ printf("This is parent process\n"); } }
由上面例子得知, 主程序和子程序的执行次序是随机的, 但是实际情况下, 通常我们希望子进程执行后, 才继续执行主进程.
wait()函数就提供了这个功能, 在if 条件内的 主进程呢部分内 加上wait() 函数, 就可以让主进程执行fork()函数时先hold 住, 等子进程退出后再执行, 通常会配合子进程的exit()函数一同使用.
需要注意的是exec并不是1个函数, 其实它只是一组函数的统称, 它包括下面6个函数:
#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[]); int execve(const char *path, char *const argv[], char *const envp[]);
可以见到这6个函数名字不同, 而且他们用于接受的参数也不同.
实际上他们的功能都是差不多的, 因为要用于接受不同的参数所以要用不同的名字区分它们, 毕竟C语言没有函数重载的功能嘛..
但是实际上它们的命名是有规律的:
exec[l or v][p][e]
exec函数里的参数可以分成3个部分, 执行文件部分, 命令参数部分, 环境变量部分.
例如我要执行1个命令 ls -l /home/gateman
执行文件部分就是 "/usr/bin/ls"
命令参赛部分就是 "ls","-l","/home/gateman",NULL 见到是以ls开头 每1个空格都必须分开成2个部分, 而且以NULL结尾的啊.
环境变量部分, 这是1个数组,最后的元素必须是NULL 例如 char * env[] = {"PATH=/home/gateman", "USER=lei", "STATUS=testing", NULL};
好了说下命名规则:
e后续, 参数必须带环境变量部分, 环境变零部分参数会成为执行exec函数期间的环境变量, 比较少用
l 后续, 命令参数部分必须以"," 相隔, 最后1个命令参数必须是NULL
v 后续, 命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针. 例如char * pstr就是1个字符串的指针, char * pstr[] 就是数组了, 分别指向各个字符串.
p后续, 执行文件部分可以不带路径, exec函数会在$PATH中找
还有1个注意的是, exec函数会取代执行它的进程, 也就是说, 一旦exec函数执行成功, 它就不会返回了, 进程结束. 但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码!
通常exec会放在fork() 函数的子进程部分, 来替代子进程执行啦, 执行成功后子程序就会消失, 但是执行失败的话, 必须用exit()函数来让子进程退出!
下面是各个例子:
int childpid; int i; if (fork() == 0){ //child process char * execv_str[] = {"echo", "executed by execv",NULL}; if (execv("/usr/bin/echo",execv_str) <0 ){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execv done\n\n"); }
注意字符串指针数组的定义和赋值
2.2 execvp 函数
if (fork() == 0){ //child process char * execvp_str[] = {"echo", "executed by execvp",">>", "~/abc.txt",NULL}; if (execvp("echo",execvp_str) <0 ){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execvp done\n\n"); }
if (fork() == 0){ //child process char * execve_str[] = {"env",NULL}; char * env[] = {"PATH=/tmp", "USER=lei", "STATUS=testing", NULL}; if (execve("/usr/bin/env",execve_str,env) <0 ){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execve done\n\n"); }
if (fork() == 0){ //child process if (execl("/usr/bin/echo","echo","executed by execl" ,NULL) <0 ){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execv done\n\n"); }
if (fork() == 0){ //child process if (execlp("echo","echo","executed by execlp" ,NULL) <0 ){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execlp done\n\n"); }
if (fork() == 0){ //child process char * env[] = {"PATH=/home/gateman", "USER=lei", "STATUS=testing", NULL}; if (execle("/usr/bin/env","env",NULL,env) <0){ perror("error on exec"); exit(0); } }else{ //parent process wait(&childpid); printf("execle done\n\n"); }
原文:http://www.cnblogs.com/dk666/p/7411312.html