摘要:也就是说,子进程对大多数变量的操作,都是不会影响父进程的。不过事实上,这也是父子进程使用互相通信的原理基础。
笔者一直试图从最基本的原理上去理解(甚至尝试原理性设计)一个服务器的架构,为此提出了一些问题。此外,笔者对 异步 I/O 也有不少学习。从几个方面学习了 vfork() 的用法。
本文纯粹记录一下。不过不同于其他资料的大段代码,本文更多地用文字和排版来尽可能清晰地说明。
本文地址:https://segmentfault.com/a/1190000010411198
Reference:linux网络编程之socket(四):使用fork并发处理多个client的请求和对等通信p2p
fork和vfork的区别
fork() 应该说是 UNIX 和类 UNIX 系统中最古老和最原始的创建子进程的系统调用了。甚至可以说,fork 不仅仅是创建子进程,而更多的是创建进程的主要手段。除了 init 进程之外,所以进程都是从 init 派生出来的。很多进程都可以是由 init 或者是 bash 的子进程,但这种情况下,我们已经不把它们作为子进程来看待了。
简单一句话,fork()的做法就是拷贝父进程的上下文,然后再从父进程中分离出来。其实可能大部分程序员对 fork() 的用法是:创建子进程,然后父子进程使用 pipe 通信。
在实际使用中,确实就按照上文而言,fork() 之后的子进程对父进程是拷贝关系。也就是说,子进程对大多数变量的操作,都是不会影响父进程的。
但是文件描述符等涉及操作系统层面的全局资源需要注意:这些仅仅是上下文在内存中的位置不同,但是它们底层所指向的资源是共享的,操作时需要注意。不过事实上,这也是父子进程使用 pipe 互相通信的原理基础。
我被问过一个问题:“fork() 调用时要拷贝上下文,这么做在大进程中调用的时候是不是效率很低?”
Linux 作为开源运动的集大成之作,显然不会那么蠢。事实上,调用 fork 之后,Linux 不会立即复制上下文,而是需要时才复制。所以我们可以放心效率和内存占用。不过有一个例外,下文会很快说到。
至于 Linux 实现这一过程的原理,下面是我的推测,如果不对,请读者指出——
现代操作系统依赖于一个很重要的技术,就是内存映射,这需要硬件 CPU 支持 MMU。一个进程看到的内存地址,实际并不是 RAM 的实际内存偏移值。操作系统会将进程实际使用的内存地址值映射到实际的硬件 RAM 中。如果系统强行或者意外访问了映射表中未注册的地址值区间,那么硬件 MMU 模块会发出一个底层硬件中断。操作系统监听这个中断,就可以知道发生了非法内存访问,也就是segmentation fault。
有了 MMU 这么强大的东西,怎么不用呢?fork 之后,对于那些子进程还没有使用到的父进程内存内容,操作系统可以先放一放,不急着在内存中创建副本。如果子进程处理中出现了越界访问,那么操作系统完全可以判断一下该内存是否父进程的内存内容。如果不是,则抛出段错误;如果是,就复制内存内容并且创建内存映射——这就是子进程真正复制父进程内容的时刻。
至于程序段?那是只读内容,压根不用拷贝,共享同一段实际内存就行了。
所以就像前文所说,我们不用担心子进程的内存浪费问题。但是例外的情况就是:比如进程 A fork 了子进程 B,子进程 B 的实际内存使用很少,因而增加的实际内存不多。但是一旦进程 A 退出了,那么进程 A 所占有的那些内存不能回收呀,因为操作系统咋知道进程 B 要不要使用 A 的内存内容?
换句话说,调用 fork() 的进程还是尽量节省内存,或者说尽量即用即还。
首先:
vfork 与 fork 最大的区别是:子进程与父进程共享相同的内存空间。换句话说,子进程对所有变量的操作,都会直接影响父进程——而这也就是很多人忌惮 vfork 的原因。为了避免这样的操作,vfork 有一个额外的与 fork 的不同:
其次:
vfork 之后得到的子进程,可以保证在调用 exit 或者 exec 系列调用之前,父进程都不会被执行。这是一个非常重要的特性,上述的两个特性,也就引出了 vfork 的应用场景。
这里主要是应用了上文提到的第二个特性。详情请见我以前的文章。
跨进程计数这源于我最近看的某个代码中的一个功能,那就是对各个进程中某个操作进行计数(也就是++)。
这个时候 vfork 就派上用场了。但要注意,因为上面提到的 vfork 的隐患在,因此这非常考验程序设计艺术……呃,本来想要多写一些的,但是实际上,这样的一个场景,要怎么设计、怎么实现,还没好好思考好好考虑……所以先把思路放在这儿,继续好好学习吧。
(啥?进程互斥?放心,++ i 是一个原子操作,只需要一条机器指令就可以完成,不用考虑互斥)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/10168.html
摘要:它的功能与相同,子进程在父进程的地址空间运行。不过,父进程会阻塞,直到子进程退出或者执行。子进程会负责调用释放子进程的遗留信息。自以来,中使用的就是新的线程库,。所以说内核完全不区分进程和线程,甚至不知道线程的存在这种说法现在是不准确的。 简介 进程与线程是所有的程序员都熟知的概念,简单来说进程是一个执行中的程序,而线程是进程中的一条执行路径。进程是操作系统中基本的抽象概念,本文介绍 ...
摘要:然而不少情况下,是有危险的。但是又不能简单使用替换就成了。这个笔记说明了两者使用的一些注意点。父子进程执行顺序不确定。这个危险的函数,现在我们已经禁用了。可不用使用时用直接替代即可,还支持动态参数列表呢 在Linux编程中,我们经常使用 Fork()。然而不少情况下,fork是有危险的。但是又不能简单使用vfork替换就成了。这个笔记说明了两者使用的一些注意点。本文地址:https:/...
摘要:进程学习笔记参考进程控制块在中结构体即是。子进程中返回值为,父进程中返回值为子进程的。一般来说,之后父子进程执行顺序是不确定的,这取决于内核调度算法。孤儿进程将被进程进程号为所收养,并由进程对它们完成状态收集工作。 linux进程学习笔记 参考 https://www.cnblogs.com/jackl... 进程控制块(PCB) 在Linux中task_struct结构体即是PCB。...
摘要:还有一点需要注意,子进程得到的只是父进程的拷贝,而不是父进程资源的本身。 首先声明一下所剖析的源码版本是Linux2.6.11.12 fork()函数和vfork()等都调用的是do_fork()函数,我们所用的fork工作都是由do_fork()来进行的。 do_fork()函数: 进入后首先去通过查找pidmap_array位图,寻找一个子进程所需要的新的pid alloc_...
摘要:函数函数声明特性函数被调用依次,返回两次父进程的所有文件描述符被复制到子进程,即父子进程每个打开的文件描述符共享一个文件表项。一个重点是父子进程共享一个文件偏移量。保证子进程先运行,在它调用或之后父进程才可能被调用运行。 8.2 进程标示 ID为0的进程通常是调度进程(交换进程),属于内核的一部分。ID为1的进程是init进程,在自举过程结束时由内核调用,该进程读写系统初始化文件,将系...
阅读 527·2021-11-24 09:39
阅读 2046·2021-11-22 13:54
阅读 2014·2021-11-16 11:44
阅读 2050·2021-09-23 11:46
阅读 3059·2019-08-30 15:55
阅读 2509·2019-08-30 15:54
阅读 2302·2019-08-30 14:18
阅读 1419·2019-08-29 14:15