linux 中的多进程中,父进程与子进程共享的代码段和数据段、堆栈段,是整个程序还是出现在fork()函数后?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 中的多进程中,父进程与子进程共享的代码段和数据段、堆栈段,是整个程序还是出现在fork()函数后?相关的知识,希望对你有一定的参考价值。

首先来说 不是共享 共享是同一个区域 但是fork后相当于复制了一份 也就相当于一个副本 所以 在以后的程序执行 父进程改变父进程的数据 子进程改变子进程的数据 所以不能说共享 他们相同的是整个程序 差不多就相当于是两个相同的程序在执行

共享数据是线程 创建一个线程后他们的数据是共享的 他们是同一个数据
参考技术A 共享是同一个区域 但是fork后相当于复制了一份 也就相当于一个副本 所以 在以后的程序执行 父进程改变父进程的数据 子进程改变子进程的数据 所以不能说共享 他们相同的是整个程序

父进程与子进程的内存关系

父进程与子进程的内存关系

(1)首先我们可以确定父子进程的代码段是相同的,所以代码段是没必要复制的,因此内核将代码段标记为只读,这样父子进程就可以安全的共享此代码段了。fork之后在进程创建代码段时,新子进程的进程级页表项都指向和父进程相同的物理页帧

(2)而对于父进程的数据段,堆段,栈段中的各页,由于父子进程要相互独立,所以我们采用写实复制的技术,来最大化的提高内存以及内核的利用率。刚开始,内核做了一些设置,令这些段的页表项指向父进程相同的物理内存页。调用fork之后,内核会捕获所有父进程或子进程针对这些页面的修改企图(说明此时还没修改)并为将要修改的页面创建拷贝。系统将新的页面拷贝分配给被内核捕获的进程,还会对子进程的相应页表项做适当的调整,现在父子进程就可以分别修改各自的上述段,不再互相影响了
写实复制前:

写实赋值后:


表面看起来fork()创建子进程子进程拷贝了父进程的地址空间其实不然
刚调用完fork()之后,子进程只是拥有一份和父进程相同的页表,其中页表中指向RAM代码段的部分是不会改变的,而指向数据段,堆段,栈段的会在我们将要改变父子进程各自的这部分内容时,才会将要操作的部分进行部分复制


fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。于是起初我就感到奇怪,子进程的物理空间没有代码,怎么去取指令执行exec系统调用呢?!原来在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。
在网上看到还有个细节问题就是,fork之后内核会通过将子进程放在队列的前面,以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。


ok,现在有一个父进程P1,这是一个主体,那么它是有灵魂也就身体的哦。现在在其虚拟地址空间(有相应的数据结构表示)上有:正文段,数据段,堆,栈这四个部分,相应的,内核要为这四个部分分配各自的物理块。即:正文段块,数据段块,堆块,栈块。至于如何分配,这是内核去做的事,在此不详述。

\\1. 现在P1用fork()函数为进程创建一个子进程P2,内核:(1)复制P1的正文段,数据段,堆,栈这四个部分,注意是其内容相同。(2)为这四个部分分配物理块,P2的:正文段->PI的正文段的物理块,其实就是不为P2分配正文段块,让P2的正文段指向P1的正文段块,数据段->P2自己的数据段块(为其分配对应的块),堆->P2自己的堆块,栈->P2自己的栈块。如下图所示:同左到右大的方向箭头表示复制内容。

\\2. 写时复制技术:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。


由于fork()的子进程经常调用exec()函数,且exec()会用新的程序替换当前的正文段、数据段、堆栈(也就是分配新的物理空间),此时子进程不需要父进程内存的副本。因此采用COW技术提高了效率。

其他
fork之后内核会通过将子进程放在队列的前面(取决于调度算法),以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。
vfork():
子进程目的是exec一个新程序
直接共享了父进程的虚拟空间
保证子进程先运行,直到调用exec或exit,父进程才可能被调度。
stl::string:有些版本的stl采用浅拷贝,只拷贝指针,只有在内容被修改的时候, 才真正分配了新的内存并copy。也就是COW技术。
父子进程共享文件偏移量


写时拷贝
前面说到,代码是共享的,数据是私有的,这表明子进程被创建出来之后,子进程就将父进程的数据拷贝了一份给自己,但是如果父进程数据非常多,但是实际上这些数据只需要读,不需要修改,那么全部拷贝的话属实有点费时间和费空间。

修改哪个拷贝哪个,不修改的不拷贝,我们称这种技术为写时拷贝,这种技术很好的解决了上述的问题。

如果数据只是只读的话,那么数据代码通过页表是共享的,一但有任一一方的进程后续修改了代码,那么操作系统就会修改相应进程的虚拟空间与物理空间的映射关系,将修改过的数据重新存放在新的物理内存中,对于那些未修改的物理空间仍不需要改动。


以上是关于linux 中的多进程中,父进程与子进程共享的代码段和数据段、堆栈段,是整个程序还是出现在fork()函数后?的主要内容,如果未能解决你的问题,请参考以下文章

父进程与子进程的内存关系

父进程与子进程的内存关系

我可以以某种方式与子进程共享一个异步队列吗?

看表情包学Linux进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct

看表情包学Linux进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct

Linux学习-进程管理