《UNIX环境高级编程》学习笔记——进程控制(一)

来源:互联网 发布:09外设淘宝店地址 编辑:程序博客网 时间:2024/05/01 10:58

fork函数

一个现存的进程可以通过fork函数创建一个新的进程:

#include <unistd.h>pid_t fork(void);                                     Returns:0 in child,process ID of child in parent, -1 on error
fork函数创建的新进程叫子进程。这个函数被调用一次,但返回两次。在子进程中该函数返回的值为0,而在父进程中该函数返回的是子进程的进程ID。

还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括数据空间,堆,程序调用栈,环境变量,缓冲区,等等(不过父子进程共享正文段)。

由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write, COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”
为了更好的理解fork函数,这里给出一个有关fork函数的面试题:

请问下面的程序一共输出多少个“-”?

#include <stdio.h>#include <sys/types.h>#include <unistd.h> int main(void){   int i;   for(i=0; i<2; i++){      fork();      printf("-");   }    return 0;}

答案是输出8个“-”。首先说肯定至少输出6个“-”,因为父进程会调用两次fork,创建两个子进程,之后又会创建一个进程,所以会输出6个“-”。给出下图,会让结果更明显:


另外两个“-”是因为:printf(“-”);语句有buffer,所以,对于上述程序,printf(“-”);把“-”放到了缓存中,并没有真正的输出,在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,就成了8个,而不是6个。

文件共享

内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

  1. 文件描述符标志(close_on_exec)。
  2. 指向一个文件表项的指针
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:

  1. 文件状态标志(读、写、添写、同步和非阻塞等)。
  2. 当前文件偏移量。
  3. 指向该文件v节点表项的指针。
(3)每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件,v节点喊包含了该文件的i节点(i-node)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。

如下是打开文件的内核数据结构:

fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,父、子进程的每个相同的打开描述符共享一个文件表项

考虑下述情况,一个进程具有三个不同的打开文件,它们是标准输入、标准输出和标准出错。在从fork返回时,打开文件的内核数据结构如下:


这种共享文件的方式使父、子进程对同一个文件使用了一个文件偏移量。考虑下述情况:一个进程fork了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父、子进程都向标准输出进行写操作。如果父进程的标准输出已重定向(很可能是由shell实现的),那么子进程写到该标准输出时,他将更新与父进程共享的该文件的偏移量。在我们所考虑的例子中,当父进程等待子进程时,它将更新与父进程共享的该文件的偏移量。当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出上,并且知道其输出会添加在子进程所写数据之后。如果父子进程不同想同一个文件偏移量,这种形式的交互就很难实现。

用fork函数产生的子进程与父进程的区别:

  • fork的返回值。
  • 进程ID不同
  • 两个进程具有不同的父进程ID。
  • 子进程的tms_utime、tms_stime、tms_cutime和tms_cstime均被设置为0.
  • 父进程设置的文件锁不会被子进程继承。
  • 子进程的未处理的闹钟被清除。
  • 子进程的未处理信号集设置为空集。
使fork失败的两个主要原因是:系统中已经有了太多的进程(通常意味着某方面出现了问题),或者该实际用户ID的进程总数超过了系统限制。

fork有下面两种用法:

  1. 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理该请求。父进程则继续等待下一个服务请求到达。
  2. 一个进程要执行一个不同的程序。这对于shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
vfork函数

vfork函数的调用序列和返回值与fork相同,但两者的语义不同。

vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork和fork一样都是创建一个子进程,但是它们的不同之处:

  • vfork并不将父进程的地址空间复制到子进程中,因为子进程会立马调用exec(或exit),于是也就不会存访该地址空间。所以在子进程调用exec或exit之前,它都在父进程的地址空间中运行。
  • vfork保证子进程先运行,在子进程调用exec或exit后吗,父进程才可能被调度运行。


0 0
原创粉丝点击