分类:
2008-11-26 17:55:36
8.3 fork function
Fork创建子进程。
Fork后,两个进程的data, stack, heap互相独立,而且采用了copy-on-write的方法,即,fork后,起初,2者共享data, stack, heap区域,所以,如果子进程调用exec去执行别的程序了,那么就加载别的程序就行了,也就是说没有必要将父进程的data, stack, heap拷贝一份给子进程。如果2者一旦有人写某个page,那么该page的内容就首先由父进程copy给子进程,然后再去写。注意,父子进程的代码段是共享的。
父子进程之间处理共享文件的方式:
1. 父子进程之间共享的是打开的文件,即他们有相同的文件descriptors。共享相同的file table entries。由于file table entries里面带有file offset,因此位移也是共享的。
2. Socket也是共享的,所以一般父进程作为server当接受了一个从client发来的连接请求后,即得到了一个新的socket,然后就fork出一个子进程,让子进程只负责管理那个新的socket,关闭最开始的socket,而父进程关闭新的socket,依旧只关心老的socket。
3. 还有一种方式就是父子之间采用同步机制,轮流读写操作。
Fork操作后,父子执行相同的代码段。如下有个有趣的例子,讲述的是实用line-buffered标准输出和采用full buffered标准输出,并使用了fork,同一个程序产生的不同的结果:
#include "apue.h"
int glob = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
err_sys("write error");
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
glob++; /* modify variables */
var++;
} else {
sleep(2); /* parent */
}
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
运行结果:
$ ./a.out
a write to stdout
before fork
pid = 430, glob = 7, var = 89 child's variables were changed
pid = 429, glob = 6, var = 88 parent's copy was not changed
$ ./a.out > temp.out
$ cat temp.out
a write to stdout
before fork
pid = 432, glob = 7, var = 89
before fork
pid = 431, glob = 6, var = 88
解释:
1. 标准输出是有缓冲的,即printf是buffered i/o,那么他会在一定的时候才会真正的写到STDOUT上。在写给STDOUT之前,standard library有自己的buffer缓冲着,而是line buffer还是full buffer模式就产生了不同的影响
2. System call 是无缓冲的,即write是无缓冲的,即write调用后就会立即体现到STDOUT上。
3.由于write是无缓冲得,所以就直接输出了。
4.当程序没有重定向时,标准输出流对应的是terminal, 那么标准输出流默认采用line buffered i/o,当遇到写\n换行符时才会将数据真正写到STDOUT_FILENO。在fork之前,我们写了printf(“before fork \n”),遇到了换行,所以“before fork”也写了出去。此时标准输出的缓冲已经空了。然后才进行的fork。然后呢父子进程各自输出各自的数据。
5.当我们的程序被重定向到普通文件后,标准输出就成了默认的full buffered模式,那么,write调用直接输出是没有疑问的,它不经过标准输出的缓冲。而printf操作虽然收到了一行数据,但是它并没有将标准输出的缓冲flush出去,因为输出的缓冲还没有满。然后fork出两个进程,此时2个进程拥有相同的内容的标准库的缓冲,等各自携入各自的数据后,当程序退出时各自flush并关闭各自的输出流,那么“before fork”相当于执行了2遍。所以输出2遍。
多数情况下,进程新建一个子进程然后调用exec去执行一个程序,所以为了简化两个操作,产生了一个spawn调用,它将fork和exec合并为一个操作了。
子进程除了继承父进程的文件,还继承了如下内容:
Besides the open files, there are numerous other properties of the parent that are inherited by the child:
· Real user ID, real group ID, effective user ID, effective group ID
· Supplementary group IDs
· Process group ID
· Session ID
· Controlling terminal
· The set-user-ID and set-group-ID flags
· Current working directory
· Root directory
· File mode creation mask
· Signal mask and dispositions
· The close-on-exec flag for any open file descriptors
· Environment
· Attached shared memory segments
· Memory mappings
· Resource limits
The differences between the parent and child are
· The return value from fork
· The process IDs are different
· The two processes have different parent process IDs: the parent process ID of the child is the parent; the parent process ID of the parent doesn't change
· The child's tms_utime, tms_stime, tms_cutime, and tms_cstime values are set to 0
· File locks set by the parent are not inherited by the child
· Pending alarms are cleared for the child
· The set of pending signals for the child is set to the empty set