shell是一个管理进程和运行进程的程序,下面我们就通过模拟一个shell程序这个实例来更好地认识认识在Linux/Unix系统中,进程的创建和结束,以及父子进程之间的一些关系。接下来先贴上源代码的中命令的读取部分:
numargs=0; while(numargs<MAXARGS){ printf("Arg[%d]?",numargs); if(fgets(argbuf,ARGLEN,stdin)&&*argbuf!='\n') arglist[numargs++]=makestring(argbuf); else{ if(numargs>0){ arglist[numargs]=NULL; execute(arglist); numargs=0; } } }
execute(char *arglist[]){ int pid,exitstatus; pid=fork();//创建子进程 switch(pid){ case -1: perror("fork failed"); exit(1); case 0: execvp(arglist[0],arglist);//替换子进程 perror("execvp failed"); exit(1); default: while(wait(&exitstatus)!=pid);//父进程等待 printf("child exited with status %d,%d\n",exitstatus>>8,exitstatus&0377); } }
pid=fork();//创建子进程这一步,那我们怎么判断哪个是父进程,哪个是子进程呢?其实在父进程中fork函数的返回值是子进程的进程ID,而在子进程中,fork返回的是0,所以我们通过fork的返回值就能判断父子进程了。下面进入switch部分,若fork返回-1,说明创建子进程失败,若是在子进程中,则调用execvp函数(其实execvp不是系统调用,而是一个库函数,它通过调用execve来调用内核服务)来执行指明的程序。那我们就来看看execvp这个函数干了些什么?
result=execvp(const char*file,const char*argv[])其中第一个参数指明了要执行的进程,如:“ls”,"ps"等等命令,而第二个参数则为指向要执行的命令及相关参数的字符串指针。通过调用execvp我们就能在一个进程中,执行像"ls"这样另外一个进程了。但是有一个问题需要注意,那就是execvp会清除当前进程,并加载由file指定的进程。也就是说,比如当"ls"执行完之后,execvp下面的那句perror是不会执行的,因为它早就被“ls”的代码替换掉了。这其实也就是我们为什么要创建子进程的原因。如果在父进程中调用execvp的话,我们做的这个shell程序就只能调用一条命令了。
那我们就要想了,父进程这个时候在干嘛呢?其实在fork之后,父子进程是并行执行的,而我们想要的效果是父进程先等等,等子进程结束之后再继续执行。接下来的wait函数就满足了我们的愿望啦!
pid=wait(&status)wait函数主要做两件事,首先wait暂停调用它的进程直到子进程结束,然后wait通过status取得子进程结束时传给exit的值。wait返回结束进程的PID,如果进程没有子进程或没有得到终止状态值,则返回-1。
这样通过不断地创建子进程,用想要执行的程序代替子进程并且让父进程等待,最后执行完毕,回到父进程,我们也就模拟了一个shell程序啦。最后来说说,结束进程的函数exit。exit的话,它会先刷新所有的流,调用一些函数,执行当前系统定义的其他和exit相关的操作。最后,调用_exit这个内核操作,来进行释放内存,关闭相关文件这些善后工作。
就这样,通过几个函数的调用,我们就帮一个进程走过了它短暂的一生。其实仔细想想也不是那么复杂嘛,咔咔咔~
参考文献:《Understanding Unix/Linux Programming ----A Guide to Theory and Practice》
模拟shell ( 进程函数:fork(),execvp(),wait() )
原文地址:http://blog.csdn.net/u011915301/article/details/39211053