2022年创建新进程:fork函数 2.pdf
创建新进程:fork 函数1.fork函数干什么?#include#include pid_t fork(void);fork()函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。你可以通过检查 fork()函数的返回值知道哪个是父进程,哪个是子进程。父进程得到的返回值是子进程的进程号,而子进程则返回0。以下这个范例程序说明它的基本功能:pid_t pid;switch(pid=fork()case-1:/*这里 pid 为-1,fork 函数失败 */*一些可能的原因是 */*进程数或虚拟内存用尽 */perror(The fork failed!);break;case 0:/*pid为 0,子进程 */*这里,我们是孩子,要做什么?*/名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 5 页 -/*.*/*但是做完后,我们需要做类似下面:*/_exit(0);default:/*pid大于 0,为父进程得到的子进程号*/printf(Childs pid is%dn,pid);当然,有人可以用 if().else.语句取代 switch()语句,但是上面的形式是一个有用的惯用方法。知道子进程自父进程继承什么或未继承什么将有助于我们。下面这个名单会因为不同 Unix 的实现而发生变化,所以或许准确性有了水份。请注意子进程得到的是这些东西的 *拷贝*,不是它们本身。由子进程自父进程继承到:*进程的资格(真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和组号(GIDs)*环境(environment)*堆栈*内存*打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)*执行时关闭(close-on-exec)标志(译者注:close-on-exec标志可通过fnctl()对文件描述符设置,POSIX.1 要求所有目录流都必须在exec 函数调用时关闭。更详细说明,参见 W.R.Stevens,1993,尤晋元等译(以下简称),3.13节和 8.9 节)名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 5 页 -*信号(signal)控制设定*nice值(译者注:nice 值由 nice 函数设定,该值表示进程的优先级,数值越小,优先级越高)*进程调度类别(scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice 值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)*进程组号*对话期 ID(Session ID)(译者注:译文取自,指:进程所属的对话期(session)ID,一个对话期包括一个或多个进程组,更详细说明参见 9.5 节)*当前工作目录*根目录 (译者注:根目录不一定是“/”,它可由 chroot 函数改变)*文件方式创建屏蔽字(file mode creation mask(umask)(译者注:译文取自,指:创建新文件的缺省屏蔽字)*资源限制*控制终端子进程所独有:*进程号*不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同,父进程号可由 getppid 函数得到)*自己的文件描述符和目录流的拷贝(译者注:目录流由opendir 函数创建,因其为顺序读取,顾称“目录流”)*子进程不继承父进程的进程,正文(text),数据和其它锁定内存(memory locks)(译者注:锁定内存指被锁定的虚拟内存页,锁定后,不允许内核将其在必要时换出(page out),详细说明参见 2.2版,1999,3.4.2节)名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 5 页 -*在 tms 结构中的系统时间(译者注:tms 结构可由 times 函数获得,它保存四个数据用于记录进程使用中央处理器(CPU:Central Processing Unit)的时间,包括:用户时间,系统时间,用户各子进程合计时间,系统各子进程合计时间)*资源使用(resource utilizations)设定为 0*阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork 函数手册页稍做修改)*不继承由 timer_create函数创建的计时器*不继承异步输入和输出2.fork函数 与 vfork函数的区别在哪里里?有些系统有一个系统调用vfork(),它最初被设计成 fork()的较少额外支出(lower-overhead)版本。因为 fork()包括拷贝整个进程的地址空间,所以非常“昂贵”,这个 vfork()函数因此被引入。(在 3.0BSD中)(译者注:BSD:Berkeley Software Distribution)但是,自从 vfork()被引入,fork()的实现方法得到了很大改善,最值得注意的是“写操作时拷贝”(copy-on-write)的引入,它是通过允许父子进程可访问相同物理内存从而伪装(fake)了对进程地址空间的真实拷贝,直到有进程改变内存中数据时才拷贝。这个提高很大程度上抹杀了需要vfork()的理由;事实上,一大部份系统完全丧失了vfork()的原始功能。但为了兼容,它们仍然提供 vfork()函数调用,但它只是简单地调用fork(),而不试图模拟所有vfork()的语义(semantics,译文取自,指定义的内容和做法)。结论是,试图使用任何 fork()和 vfork()的不同点是*很*不明智的。事实上,可能使用 vfork()根本就是不明智的,除非你确切知道你想干什么。两者的基本区别在于当使用 vfork()创建新进程时,父进程将被暂时阻塞,名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 5 页 -而子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程要么退出,要么调用 execve(),至此父进程才继续执行。这意味着一个由vfork()创建的子进程必须小心以免出乎意料地改变父进程的变量。特别的,子进程必须不从包含vfork()调用的函数返回,而且必须不调用exit()(如果它需要退出,它需要使用_exit();事实上,对于使用正常 fork()创建的子进程这也是正确的)3.为何在一个 fork 的子进程分支中使用 _exit函数而不使用 exit函数exit()与 _exit()有不少区别在使用 fork(),特别是 vfork()时变得很突出。exit()与_exit()的基本区别在于前一个调用实施与调用库里用户状态结构(user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序(译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对应,后一个函数只为进程实施内核清除工作。在由 fork()创建的子进程分支里,正常情况下使用exit()是不正确的,这是因为使用它会导致标准输入输出(译者注:stdio:Standard Input Output)的缓冲区被清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建在系统临时目录下,文件名由系统随机生成)。在 C+程序中情况会更糟,因为静态目标(static objects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情况,比如守护程序,它们的*父进程*需要调用 _exit()而不是子进程;适用于绝大多数情况的基本规则是,exit()在每一次进入main函数后只调用一次。)在由 vfork()创建的子进程分支里,exit()的使用将更加危险,因为它将影响父进程的状态。名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 5 页 -