#include "SVPType.h" #include <sys/wait.h> #include "SVPLog.h" #ifdef SVP_LOG_TAG #undef SVP_LOG_TAG #endif #define SVP_LOG_TAG "procguard" int32_t main (int32_t argc, char* argv[]) { if (argc < 2) { exit(EXIT_FAILURE); } char strArg[256] = {0}; snprintf(strArg, 256, "%s", argv[1]); GUARD_ACTIVE: if ( 0 == fork()) { if (execvp(argv[1], &argv[1]) == -1) { SVP_ERROR("execvp(%s) failed, error no = %d.", strArg, errno); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { int32_t status = 0; pid_t pid = wait(&status); SVP_INFO("%s(%d) exit with status %d, restart after 50ms ...", strArg, pid, status); SVP_Sleep(50); goto GUARD_ACTIVE; } exit(EXIT_SUCCESS); }
在execute函数中最先调用fork()函数,那fork()函数做了些什么呢?其实fork()函数创建了和当前进程基本一模一样的一个子进程。当控制转到内核中的fork代码之后,内核先分配新的内存块和内核数据结构,然后将原来的进程复制到新的进程中去。最后向运行进程中添加新的进程并且控制重新返回到进程中。开始可能觉得挺奇怪的,搞个基本差不多的进程干什么?其实看了后面的内容你就会知道,只要调用个execvp函数,子进程就变得完全不一样啦。好,现在我们就有两个进程了,并且它们的代码相同,都运行到
pid=fork();//创建子进程
这一步,那我们怎么判断哪个是父进程,哪个是子进程呢?其实在父进程中fork函数的返回值是子进程的进程ID,而在子进程中,fork返回的是0,所以我们通过fork的返回值就能判断父子进程了。下面进入switch部分,若fork返回-1,说明创建子进程失败,若是在子进程中,则调用execvp函数(其实execvp不是系统调用,而是一个库函数,它通过调用execve来调用内核服务)来执行指明的程序。那我们就来看看execvp这个函数干了些什么?
result=execvp(])
其中第一个参数指明了要执行的进程,如:“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这个内核操作,来进行释放内存,关闭相关文件这些善后工作。