Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5607160
  • 博文数量: 922
  • 博客积分: 19333
  • 博客等级: 上将
  • 技术积分: 11226
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-27 14:33
文章分类

全部博文(922)

文章存档

2023年(1)

2020年(2)

2019年(1)

2017年(1)

2016年(3)

2015年(10)

2014年(17)

2013年(49)

2012年(291)

2011年(266)

2010年(95)

2009年(54)

2008年(132)

分类: LINUX

2012-01-13 22:06:31

++++++APUE读书笔记-08进程控制(02)++++++


3、fork
================================================
 (1)创建进程使用fork来实现,fork函数如下:
 #include
 pid_t fork(void);
 返回:在子进程中返回为0;父进程中返回为子进程PID,如果出错返回1(实际值一般为-1)。
 通过fork创建的新进程叫做子进程。这个fork函数调用了一次(在父进程中),但是返回了两次(在父进程中,以及新出现的子进程中)。通过返回值可知是父还是子进程。因为没有获得一个进程子进程PID的函数,所以在父进程的fork返回中会返回子进程PID,以做记录和判断等用。因为所有子进程只可能有一个父进程,所以在子进程中fork返回0,子进程可以通过getppid来获得父进程的进程ID(进程PID为0的进程是内核中的swapper所以它不可能是某个进程的子进程)。
 在调用fork之后,产生的子进程和父进程会继续在代码中fork调用的后面继续执行。子进程是父进程的一个拷贝,它拷贝了父进程的数据空间,堆和栈空间,一定要注意这是一份拷贝,父子进程不会共享这部分内存的内容。父子进程共享的部分是代码段部分。
 由于fork一般会接着一个exec函数,当前的系统实现一般不会立即执行父进程数据,堆,栈的拷贝。一般会使用一种copy-on-write(COW)的技术,也就是说,这些区域起初是父子进程共享的,一旦有进程进行了修改这些区域的操作,内核才会创建相应区域内存的一份拷贝(一般是虚拟内存的页)。
实际上有不同种类的fork函数。本文中的四种系统都支持vfork(2)函数,这个函数在后面会提到。
 Linux 2.4.22 提供使用clone系统调用来创建新进程。这个系统调用是一个fork的通用形式,允许调用者控制在父子进程中共享哪部分数据。
 FreeBSD 5.2.1 提供rfork系统调用,和Linux中的clone系统调用类似,rfork是从Plan 9操作系统中继承过来的。
 Solaris 9 提供两个线程库,一个是POSIX的,一个是Solaris threads.两种fork的动作有所不同。对于POSIX,fork创建一个只包含调用线程的进程,但是Solaris的fork创建的进程包含调用线程的进程中的所有线程的拷贝。为了提供和posix类似的fork,solaris提供了一个fork1函数,它可以创建一个只拷贝调用线程的进程。

 (2)fork的使用
 下面给出一个使用fork函数的例子:
 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
 
 在上面的这个例子中,给出了一个使用fork的例子,并且通过这个例子我们应该知道:
 a)如果fork之后,是parent先运行还是children这是不确定的,取决于内核的调度算法。
 b)子进程是父进程的拷贝,包括文件缓存等都是一个副本,所以子进程修改不会影响父进程。
 c)由于子进程拷贝了父进程的数据空间,如果使用库函数打印,重定向时可能同一个打印语句在文件中出现两次,因为缓存也拷贝了。

 (3)文件共享
 父子进程之间共享文件偏移量,这样在父子进程同时写一个文件的时候容易控制它们之间的配合;如果不共享的话,有些定位之类的操作很难配合进行或者很麻烦。当然如果父子不同步的话,两者对文件的输出可能会相互干扰。
 一般fork之后,有两种操作文件描述符号的情况:
 a)父进程等待子进程结束。这时候父进程不需要对文件描述符号做任何事情,在子进程结束之后子进程读写的文件描述符号的偏移会自动更新。
 b)父子进程进行它们各自的操作。这时候,父子进程需要关闭它们不需要的文件描述符号,防止两者之间互相干扰。这在网络服务的环境下面经常会用到。
 下面的图给出了调用fork之后父子进程打开共享文件的图示情况:

          parent process table entry                  file table                v-node table
          +------------------------+ ----------->+-------------------+     ---->+-----------+
          |      fd       file     |/       +--->| file status flags |    /     |   v-node  |
          |     flags    pointer   /        |    +-------------------+   /      |information|
          |     +-----+---------+ /|        |    |current file offset|  /       +-----------+
          | fd0:|     |         |/ |        |    +-------------------+ /        |   i-node  |
          |     +-----+---------+  |        |    |   v-node pointer  |/         |information|
          | fd1:|     |         |\ |        |    +-------------------+          |...........|
          |     +-----+---------+ \|        |                                   |  current  |
          | fd2:|     |         |\ \        |                                   | file size |
          |     +-----+---------+ \|\       |                                   +-----------+
          |     |               |  \ -------+--->+-------------------+
          |     |   ......      |  |\       |  ->| file status flags |    ----->+-----------+
          |     |               |  | \      | /  +-------------------+   /      |   v-node  |
          |     +---------------+  |  \    / /   |current file offset|  /       |information|
          +------------------------+   \  / /    +-------------------+ /        +-----------+
                                        \/ /     |   v-node pointer  |/         |   i-node  |
                                        /\/      +-------------------+          |information|
                                       / /\                                     |...........|
          parent process table entry  / /  \                                    |  current  |
          +------------------------+ / /    ---->+-------------------+          | file size |
          |      fd       file     |/ / -------->| file status flags |          +-----------+
          |     flags    pointer   / / /         +-------------------+
          |     +-----+---------+ /|/ /          |current file offset|  ------->+-----------+
          | fd0:|     |         |/ / /           +-------------------+ /        |   v-node  |
          |     +-----+---------+ /|/            |   v-node pointer  |/         |information|
          | fd1:|     |         |/ /             +-------------------+          +-----------+
          |     +-----+---------+ /|                                            |   i-node  |
          | fd2:|     |         |/ |                                            |information|
          |     +-----+---------+  |                                            |...........|
          |     |               |  |                                            |  current  |
          |     |   ......      |  |                                            | file size |
          |     |               |  |                                            +-----------+
          |     +---------------+  |
          +------------------------+

 除了打开的文件之外,还有许多父子进程共享的内容,具体参见参考资料给出列表。
 父子进程不同的地方是:
 a)fork返回值不同。
 b)进程PID不同。
 c)进程的父进程PID不同,子进程的父进程PID设置为父进程,父进程的父进程PID不变。
 d)子进程的tms_utime, tms_stime, tms_cutime, 和 tms_cstime取值设置为0.
 e)父进程设置的文件锁不会被子进程继承。
 f)在子进程中会把申请的警钟清空。
 g)子进程会把申请的信号集合清空。
 fork失败的原因有两个:
 a)系统中进程数目太多(一般这是因为出现了什么问题而导致的)
 b)当前用户 UID的进程数目超过了系统的限制。可以通过CHILD_MAX指定每个用户UID同时可以运行的进程的数目。
 fork 一般有两种使用的方法:
 a)进程想要复制自己,并且在同时执行另外的代码。这在网络中很常见:服务器端监听,接收到一个客户请求,之后它fork一个子进程来处理客户请求,然后父进程继续监听其他的请求。
 b)进程想要执行一个不同的程序:一般都是fork之后调用exec来做的。
 有些系统把b)的fork紧接这exec合并成了一个操作spawn.unix 把操作分成了两个部分,因为许多时候fork不需紧接着exec或者fork和exec之间需要做一些修改进程属性的操作等等。
 Single UNIX Specification 在高级的real-time选项组包含了spawn接口。这些接口不是fork和exec的替代。他们用来支持很难高效地执行fork的系统,尤其是那些没有内存管理硬件支持的系统。

参考:

 

 

阅读(431) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~