标签:
一个进程的退出通常是调用 exit()
函数。而在 main
函数中调用 return()
与 exit()
其含义是一致的。进程的退出通常有以下的几种方式:
终止的方式:
三个 exit
函数之间存在差异,调用 exit
函数会首先进行一些清理动作然后再返回内核。清理动作包括先调用用户通过 atexit(void (*func)(void))
函数登记的清理函,然后执行标准IO库的清理关闭操作,这造成输出缓冲中的所有数据都被冲洗。而_exit
还有 _Exit
函数则直接返回内核。它们的共同之处是都可以带一个整形参数,称作退出状态。在 shell 下可以通过 $?
变量来查看。而进程异常终止时,内核会产生一个指示该进程异常终止原因的终止状态。这两个概念稍有差异,正常退出情况下的退出状态也会被内核转换成终止状态,父进程需要调用 wait
函数来获得子进程的 退出状态 以避免子进程成为僵死进程。
fork
函数可以在当前进程中再创建出一个子进程来处理一些事情。其通常的写法如下:
/* the usual usage of fork */ pid_t pid; if ((pid = fork()) < 0) { printf("fork error\n"); return; } else if (pid == 0) { /* the child process code */ // do something } else { /* the parent process code */ // do something } /* both parent and child process code */ // do something
看上去有些困惑,分不清哪里是父进程执行代码哪里是子进程执代码。要搞清楚的话,必须明白以下两点:
fork
函数有两个返回值,如果不出错的话,对于子进程而言,它返回0;对父进程而言返回子进程ID,出错返回值小于0;sleep()
函数来使得子进程先执行,但并不能确保如此,这取决于内核的进程调度;除了要清楚 fork
之后子进程、父进程各自执行什么样的代码段,在哪里退出等等这些。还必须清楚子进程与父进程之间的关系,首先来说,它们是两个进程,子进程与父进程拥有独立的地址空间;其次,子进程是父进程的副本,它的含义解释如下:
下面的这个例子能很好的加深对子进程与父进程关系的理解
int main() { pid_t pid; int val = 5; printf("test fork function\n"); if ((pid = fork()) < 0) { printf("fork error\n"); return(1); } else if (pid == 0) { printf("fork success\n"); val++; } else sleep(2); printf("hello world\n"); printf("this process id is %d, and it‘s val is %d\n", getpid(), val); return 0; }
而 fork
函数通常有下面的这两种用法:
在这篇文章的最后,会用 fork
函数来实现我们自己的 system
函数!
当父进程通过 fork
函数创建出子进程之后,这两个进程退出时时序只有两种情况:
init
进程收养;wait
函数取得该子进程终止状态。所以说 wait
函数将会更好的控制我们所编写的程序,更好的利用我们的系统。wait
函数的定义如下:
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
关于 wait
函数的调用的注意点,说明如下:
waitpid
与 wait
函数的区别在于,waitpid
函数可以:
两个函数的共同之处就是都传入了 int *statloc
参数,它用来保存子进程的终止状态。也可以传入 NULL
, 表示父进程并不关心子进程的终止状态。
通过上面的学习至少清楚了 wait
函数的作用还有用法。下面通过这个例子来简单的练习一下,这个例子通过使用一个技巧来避免我们所编写的程序产生 僵死进程:
/* the skill to avoid the dead process */ pid_t pid; if ((pid = fork()) < 0) return; else if (pid == 0) { if ((pid = fork()) < 0) return; else if (pid > 0) exit(0); sleep(2); printf("second child, parent pid = %ld\n", (long)getppid()); /* do something in second child process */ exit(); } if (waitpid(pid, NULL, 0) != pid) /* no need to handle the second child process */ printf("waitpid error\n"); exit(0);
fork
函数在创建一个新的进程之后一种常用的用法便是调用 exec
函数以执行新的程序。可是子进程是父进程的副本,子进程应该是执行的父进程代码才是啊! 原来当调用 exec
函数时,该进程执行的程序完全被替换成新的程序,而新的程序则从其 main
函数开始执行。因为调用 exec
函数并不创建新的进程,所以前后进程ID并未改变。exec
只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆还有栈。
一共有七种 exec
函数,记忆起来确实繁琐。如果需要用的话到时候再去翻书好了。无谓就是传入可执行程序的路径名,还有供程序使用的命令行参数,有的还需要传入环境表参数。下面就只列出其中的一种 exec
函数
#include <unistd.h>
int execv(const char *pathname, char *const argv[]);
关于返回值: 若出错,返回-1;若成功,不返回
通过 exec
函数我们可以在我们的程序中调用 脚本文件。 这简直是很大的福利,会使得我们的程序处理更加灵活。控制力更强。脚本程序作为一种解释器文件,最终的解释以及执行都依靠 bash 程序。只要在 exec
函数调用 /bin/sh
,再传入脚本文件参数即可。下面是一个例子:
int main() { pid_t pid; char *cmd[] = {"./date.sh", (char*)0}; if ((pid = fork()) < 0) { printf("fork error\n"); return -1; } else if (pid == 0) { if (execv("/bin/sh", cmd) < 0); { printf("execv error\n"); return -1; } } else sleep(2); return 0; }
上面的例子演示了在程序中如何调用脚本问价,有的时候我们需要在程序中执行一些简单的 shell 命令或者脚本文件,使得我们程序的控制力更强。但如果每次都需要 fork
以及 exec
的话,显然是太繁琐。unix提用了 system
函数,我们通常可以像这样调用: system("date > file1")
。下面我们通过上面的函数来自己实现 system
函数,也算是一种练习!
/* to release ourself system() funtciotn */ void system(const char *cmd) { pid_t pid; int status; if (cmd == NULL) return; if ((pid = fork()) < 0) { printf("fork error\n"); return -1; } else if (pid == 0) { if (execv("/bin/sh", &cmd) < 0) { printf("execv error\n"); return -1; } } else { while (waitpid(pid, &status, 0)) ; } return status; }
标签:
原文地址:http://www.cnblogs.com/Gru--/p/5022451.html