昨天刚把fork的大致使用情况说清楚对吧?关键就在于“调用一次,返回两次”,其它的都是浮云啦。在最后我们也注意到,当父进程结束并退出返回到shell时,child process被init process“收养”了,那我们就先看看几个special processes吧:
- swapper: ID = 0;scheduler process, no program on disk correspond to this process, which is part of the kernel and is known as a system process.
- init process: ID = 1;init process, be invoked by the kernel at the end of the bootstrap procedure. This process is responsible for bringing up a UNIX system after the kernel has been bootstrapped. 总之,这个进程就是在引导程序结束之后将系统初始化到某个状态嘛(比如说多用户)。但是,请注意,他是一个normal user process,可以以superuser权限运行,但真的不是一个system process啊!
- pagedaemon: ID = 2;页守护进程,负责virtual memory system的分页。
那么,special process看完了,我们来看图8-1的程序。这里代码我就不贴出来啦,因为8-1的代码也没有太多要说的,主要注意的就是下面两条:
(1) write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1; 其中如果 char buf[] = "abc\n"; 这里为什么要sizeof(buf) - 1呢???听清楚了,sizeof和strlen的区别就在这里:
sizeof是key word,那么它当然是在编译时就计算好缓冲区的长度啦!strlen是function,那么它当然要执行一次函数调用。我们看buf的存储格式,每个string最后都要跟一个 '\0',也就是terminating null byte。sizeof计算出的长度包含null而strlen不包含,所以sizeof(buf) = 5, strlen(buf) = 4.
(2) 还记得从前那句话吗?“如果标准输出被连接到终端设备,则它是line buffer;否则是all buffer”。我们来看下面简化版代码:
{
...
printf("before fork.\n");
fork();
...
}
那么结果很令人诧异:$./a.out则输出一遍“before fork.”,$./a.out > temp.out结果输出两遍"before fork." "before fork."很神奇吧?!我们来看看为什么:在前一种情况下,由于是line bufer,所以遇到'\n'之后,buffer被flush清空,输出;而后一种情况重定向到文件,是all buffer,printf之后并不立即输出,而是放在了buffer中,当fork时,复制了相同的一份数据数据空间给子进程,所以父子buffer中同时拥有这句话,当最后buffer被flush的时候,当然父子进程各输出一次"before fork."啦!
到现在为止,进程的创建我们了解的差不多了,那它的退出呢?
先看一个概念:Zombie~~~僵尸!!!no,it is 僵死进程!kernel为每个死了的进程保留一些信息,如果这些进程不被善后那当然会一直占着资源,这些就是zombie啦。以后会介绍避免zombie的方法。
前面我们说了进程的5种normal termination和3种abnormal termination,但不管以什么状态退出,最后kernel都会执行相同的一段code,即关闭所有打开的file descriptors,free used memory。那么,如果想得到进程的 exit 或 termination status 可以吗?
(在最后调用_exit函数时,kernel将退出状态转换成终止状态:正常退出时,产生exit status并最终转化;异常退出时,产生termination status)
当然可以!用wait或者waitpid function。
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
/*Both return: process ID if OK, 0 or −1 on error*/
当一个进程正常终止时,kernel向父进程发一个SIGCHLD信号(可以在父进程运行的任何时候发,所以是个异步信号)。父进程可以处理它也可以忽略它。等等,等等。这当然不是现在说的重点,我们现在只关注,调用wait或waitpid会发生什么。(还是简单的说吧,用到再查嘛!)
(1)wait:不管一个进程有几个子进程,父进程执行wait的时候就一直在那里等,不管哪个子进程终止,wait就返回其ID;
(2)waitpid:通过设置pid参数,可以等待任一子进程(-1),或者等待指定子进程(>0),或者等待group ID的相关进程(不多说了)等。
下面看进程的竞争Race conditions。
到底什么情况下叫发生了race condition呢?竞争,肯定是多个进程都要争用同一个东西,这同一个东西当然就是共享数据啦;而他们到底哪一个先使用它呢?使用它的顺序当然就是进程运行的顺序,这个就是进程之间的race condition嘛!很简单吧~为了避免竞争,UNIX用IPC机制啦,这个我们后边会讲到,这里只是了解一下竞争的概念。
最后一个大头放在这里,file sharing!!!
如果一个进程有若干个打开的文件,当它fork之后,会发生什么事情呢?来,看图吧!
看~它们共享file table(因为子进程实现了父进程的copy嘛)。当然,我们下意识的会问,如果同时读写文件会怎么办?由于它们共享同一current file offset,很有可能会写乱啊!当然,这种情况是可能发生的。但一般我们会把父子进程分开处理不同的任务,那么它们各自关闭自己不需要的file descriptor,互不干扰。
fork会不会失败啊?当然啦!一般来说,使fork失败的原因有两个:
(1)系统中的进程数太多了,某方面出了问题;(2)real user ID进程拥有的子进程数量超过了限制(CHILD_MAX)。
还记得close_on_exec标志吗?是的,FD_CLOEXEC,我们简单说明一下(看这里:http://blog.csdn.net/chrisniu1984/article/details/7050663)。
通常使用的COW(copy on write)前面我们已经提到过,上面那张图说了fork之后父子进程指向同样的file tables。我们知道通常情况下,子进程会执行一段崭新的程序而替换掉原来父进程的数据空间,此时父进程打开的那些file descriptors便已经不需要了。如果我们想在子进程运行的时候关闭它们,手动一个个关闭当然很麻烦。所以,系统提供了一个colse_on_exec:即,当子进程在运行exec时,被设置了此位的fd统统关闭~nice!
晚上打球,明天再写~