资讯专栏INFORMATION COLUMN

研究一下 fork 的原理,并且还有 vfork 的一些使用场景

Joyven / 2335人阅读

摘要:也就是说,子进程对大多数变量的操作,都是不会影响父进程的。不过事实上,这也是父子进程使用互相通信的原理基础。

笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 I/O 也有不少学习。从几个方面学习了 vfork() 的用法。

本文纯粹记录一下。不过不同于其他资料的大段代码,本文更多地用文字和排版来尽可能清晰地说明。

本文地址:https://segmentfault.com/a/1190000010411198

Reference:

linux网络编程之socket(四):使用fork并发处理多个client的请求和对等通信p2p
fork和vfork的区别

fork 和 vfork

fork() 应该说是 UNIX 和类 UNIX 系统中最古老和最原始的创建子进程的系统调用了。甚至可以说,fork 不仅仅是创建子进程,而更多的是创建进程的主要手段。除了 init 进程之外,所以进程都是从 init 派生出来的。很多进程都可以是由 init 或者是 bash 的子进程,但这种情况下,我们已经不把它们作为子进程来看待了。


fork( )

简单一句话,fork()的做法就是拷贝父进程的上下文,然后再从父进程中分离出来。其实可能大部分程序员对 fork() 的用法是:创建子进程,然后父子进程使用 pipe 通信。

fork 复制的上下文与父进程的关系

在实际使用中,确实就按照上文而言,fork() 之后的子进程对父进程是拷贝关系。也就是说,子进程对大多数变量的操作,都是不会影响父进程的。

但是文件描述符等涉及操作系统层面的全局资源需要注意:这些仅仅是上下文在内存中的位置不同,但是它们底层所指向的资源是共享的,操作时需要注意。不过事实上,这也是父子进程使用 pipe 互相通信的原理基础。

fork 复制上下文的原理

我被问过一个问题:“fork() 调用时要拷贝上下文,这么做在大进程中调用的时候是不是效率很低?”

Linux 作为开源运动的集大成之作,显然不会那么蠢。事实上,调用 fork 之后,Linux 不会立即复制上下文,而是需要时才复制。所以我们可以放心效率和内存占用。不过有一个例外,下文会很快说到。

至于 Linux 实现这一过程的原理,下面是我的推测,如果不对,请读者指出——

现代操作系统依赖于一个很重要的技术,就是内存映射,这需要硬件 CPU 支持 MMU。一个进程看到的内存地址,实际并不是 RAM 的实际内存偏移值。操作系统会将进程实际使用的内存地址值映射到实际的硬件 RAM 中。如果系统强行或者意外访问了映射表中未注册的地址值区间,那么硬件 MMU 模块会发出一个底层硬件中断。操作系统监听这个中断,就可以知道发生了非法内存访问,也就是segmentation fault

有了 MMU 这么强大的东西,怎么不用呢?fork 之后,对于那些子进程还没有使用到的父进程内存内容,操作系统可以先放一放,不急着在内存中创建副本。如果子进程处理中出现了越界访问,那么操作系统完全可以判断一下该内存是否父进程的内存内容。如果不是,则抛出段错误;如果是,就复制内存内容并且创建内存映射——这就是子进程真正复制父进程内容的时刻。

至于程序段?那是只读内容,压根不用拷贝,共享同一段实际内存就行了。

所以就像前文所说,我们不用担心子进程的内存浪费问题。但是例外的情况就是:比如进程 A fork 了子进程 B,子进程 B 的实际内存使用很少,因而增加的实际内存不多。但是一旦进程 A 退出了,那么进程 A 所占有的那些内存不能回收呀,因为操作系统咋知道进程 B 要不要使用 A 的内存内容?

换句话说,调用 fork() 的进程还是尽量节省内存,或者说尽量即用即还。


vfork( )

首先:
vforkfork 最大的区别是:子进程与父进程共享相同的内存空间。换句话说,子进程对所有变量的操作,都会直接影响父进程——而这也就是很多人忌惮 vfork 的原因。为了避免这样的操作,vfork 有一个额外的与 fork 的不同:

其次:
vfork 之后得到的子进程,可以保证在调用 exit 或者 exec 系列调用之前,父进程都不会被执行。这是一个非常重要的特性,上述的两个特性,也就引出了 vfork 的应用场景。

Shell 调用系统命令

这里主要是应用了上文提到的第二个特性。详情请见我以前的文章。

跨进程计数

这源于我最近看的某个代码中的一个功能,那就是对各个进程中某个操作进行计数(也就是++)。

这个时候 vfork 就派上用场了。但要注意,因为上面提到的 vfork 的隐患在,因此这非常考验程序设计艺术……呃,本来想要多写一些的,但是实际上,这样的一个场景,要怎么设计、怎么实现,还没好好思考好好考虑……所以先把思路放在这儿,继续好好学习吧。

(啥?进程互斥?放心,++ i 是一个原子操作,只需要一条机器指令就可以完成,不用考虑互斥)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/10168.html

相关文章

  • 浅析 Linux 进程与线程

    摘要:它的功能与相同,子进程在父进程的地址空间运行。不过,父进程会阻塞,直到子进程退出或者执行。子进程会负责调用释放子进程的遗留信息。自以来,中使用的就是新的线程库,。所以说内核完全不区分进程和线程,甚至不知道线程的存在这种说法现在是不准确的。 简介 进程与线程是所有的程序员都熟知的概念,简单来说进程是一个执行中的程序,而线程是进程中的一条执行路径。进程是操作系统中基本的抽象概念,本文介绍 ...

    thekingisalwaysluc 评论0 收藏0
  • fork 和 vfork 使用注意事项和 system() 函数替代

    摘要:然而不少情况下,是有危险的。但是又不能简单使用替换就成了。这个笔记说明了两者使用的一些注意点。父子进程执行顺序不确定。这个危险的函数,现在我们已经禁用了。可不用使用时用直接替代即可,还支持动态参数列表呢 在Linux编程中,我们经常使用 Fork()。然而不少情况下,fork是有危险的。但是又不能简单使用vfork替换就成了。这个笔记说明了两者使用的一些注意点。本文地址:https:/...

    boredream 评论0 收藏0
  • linux 进程学习笔记

    摘要:进程学习笔记参考进程控制块在中结构体即是。子进程中返回值为,父进程中返回值为子进程的。一般来说,之后父子进程执行顺序是不确定的,这取决于内核调度算法。孤儿进程将被进程进程号为所收养,并由进程对它们完成状态收集工作。 linux进程学习笔记 参考 https://www.cnblogs.com/jackl... 进程控制块(PCB) 在Linux中task_struct结构体即是PCB。...

    GitChat 评论0 收藏0
  • 关于fork简略版源码剖析

    摘要:还有一点需要注意,子进程得到的只是父进程的拷贝,而不是父进程资源的本身。 首先声明一下所剖析的源码版本是Linux2.6.11.12 fork()函数和vfork()等都调用的是do_fork()函数,我们所用的fork工作都是由do_fork()来进行的。 do_fork()函数: 进入后首先去通过查找pidmap_array位图,寻找一个子进程所需要的新的pid alloc_...

    苏丹 评论0 收藏0
  • UNIX高级环境编程 第8章 进程控制

    摘要:函数函数声明特性函数被调用依次,返回两次父进程的所有文件描述符被复制到子进程,即父子进程每个打开的文件描述符共享一个文件表项。一个重点是父子进程共享一个文件偏移量。保证子进程先运行,在它调用或之后父进程才可能被调用运行。 8.2 进程标示 ID为0的进程通常是调度进程(交换进程),属于内核的一部分。ID为1的进程是init进程,在自举过程结束时由内核调用,该进程读写系统初始化文件,将系...

    novo 评论0 收藏0

发表评论

0条评论

Joyven

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<