linux下,system函数和execl函数都是用于执行一条系统命令。今天仔细看了system函数的实现,想找出和execl函数的差别。
这里先进行一些背景知识补充:
fork(创建一个新的进程):
定义函数 pid_t fork(void);
函数说明 fork()会产生一个新的子进程,其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。
返回值 如果fork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。如果fork 失败则直接返回-1,失败原因存于errno中。
waitpid(等待子进程中断或结束):
定义函数 pid_t waitpid(pid_t pid,int * status,int options);
函数调用 waitpid(pid, NULL, 0);
函数说明 waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用waitpid()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码,其他数值意义如下:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
返回值 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno。
waitpid把子进程的结束状态返回后存于status,有几个宏可判别结束情况:
WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的(通过exit()或者_exit()),如果是,它会返回一个非零值。
WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。
接下来进入正题看看execl和system函数:
execl:
定义函数 int execl(const char * path,const char * arg,....);
函数说明 execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
返回值 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
调用ls命令范例: execl("/bin/ls", "/bin/ls", "-l" , "/etc", NULL);
exec系列函数的特点很明显,就是执行成功的时候是不会返回的,一旦返回一定是有错误产生了。但是请注意,执行成功的意思是这条命令本身没错,但是这条命令成不成功exec管不了,他只负责执行,比如说"rm 1.txt",这条指令是正确的,即使没有1.txt这个文件报了无法删除1.txt文件,exec函数还是算执行成功,不会有返回。我们看下例子:
#include <stdio.h> int main(int argc, char* argv[]){ int a = execl("/bin/rm", "rm", "1.txt", NULL); printf("%d ", a); printf("exiting... "); return 0; }
上面这段代码的执行结果是:
明显没有返回值,我们再看看执行出错的返回值:
#include <stdio.h> int main(int argc, char* argv[]){ int a = execl("aaa", "bbb", NULL); printf("%d ", a); printf("exiting... "); return 0; }
上面这段代码的执行结果:
很明显,返回值是-1。这个时候execl函数就执行失败了。
那么如果想要调用一个系统命令,又想要返回呢?这时候system函数就可以帮上忙了。实际上,system也是调用了exec函数去执行一个系统命令,可以把system函数理解成对exec函数的一个包装。可是,光光包装起来,加个返回值,有可能吗?比如说把return语句在exec后面,根据exec系列的函数的特性,return语句肯定不会执行,那system函数到底是怎么实现的呢?实际上,system函数的具体执行步骤是这样的:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。
创建出一个子进程,然后在子进程中用exec来执行命令,即是子进程成功执行了没返回也没关系,还有父进程可以返回嘛!
接下来我们仔细看看system的详细介绍。
#include <stdlib.h> int system(const char *command);
system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.
int system(const char * cmdstring) { pid_t pid; int status; if(cmdstring == NULL) { return (1); //如果cmdstring为空,返回非零值,一般为1 } if((pid = fork())<0) { status = -1; //fork失败,返回-1 }else if(pid == 0) { execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~ }else //父进程 { while(waitpid(pid, &status, 0) < 0) { if(errno != EINTR) { status = -1; //如果waitpid被信号中断,则返回-1 break; } } } return status; //如果waitpid成功,则返回子进程的返回状态 }
int status; if(NULL == cmdstring) //如果cmdstring为空趁早闪退吧,尽管system()函数也能处理空指针 { return XXX; } status = system(cmdstring); if(status < 0) { printf("cmd: %s error: %s", cmdstring, strerror(errno)); // 这里务必要把errno信息输出或记入Log return XXX; } if(WIFEXITED(status)) { printf("normal termination, exit status = %d ", WEXITSTATUS(status)); //取得cmdstring执行结果 } else if(WIFSIGNALED(status)) { printf("abnormal termination,signal number =%d ", WTERMSIG(status)); //如果cmdstring被信号中断,取得信号值 } else if(WIFSTOPPED(status)) { printf("process stopped, signal number =%d ", WSTOPSIG(status)); //如果cmdstring被信号暂停执行,取得信号值 }
到于取得子进程返回值的相关介绍可以参考另一篇文章:http://my.oschina.net/renhc/blog/35116
文章转自: