首页 > 其他分享 >fork函数写时拷贝

fork函数写时拷贝

时间:2025-01-18 12:29:14浏览次数:3  
标签:fork 文件 描述符 进程 拷贝 页面 写时

fork函数写时拷贝

        当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并且把两个进程中的每个区域结构都标记为私有的写时复制。
        当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

fork系列函数的调用过程

        通常情况下,进程都会有独立的地址空间,为了提高系统的资源利用率,我们所使用的地址都是虚拟地址,控制台打印出来的地址是虚拟地址(我们用户能看到的),真正的物理地址是给CPU看的,虚拟地址与真实物理地址之间是有一个对应关系的。
        每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。
示例:
        假设我们定义了一个str字符串,在执行fork之后,子进程对str字符串进行了修改操作。
因为str的数据改变了,str所在的页面操作系统会给子进程重新分配,为什么打印出来的地址是一模一样的?
        因为fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。

        fork子进程完全复制了父进程的栈空间,也复制了它的内存分配页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,直到其中任意一个进程要对共享的页面进行“写操作”,这时内核会 分配+复制 一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。操作性同在内存分配这块非常懒,仅仅只分配"新的页面" 并在页表里边改变相关属性。

Linux的fork()使用写时拷贝(copy-on-write)页实现。


        写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只有在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下—例如,fork()后立即执行exec(),地址空间就无需被复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建一个进程描述符。在一般情况下,进程创建后都为马上运行一个可执行的文件,这种优化,可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。

        在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

        fork之后内核会通过将子进程放在队列的前面,以让子进程先执行,以免父进程执行导致写时复制,而后子进程执行exec系统调用,因无意义的复制而造成效率的下降。


fork的文件操作

        fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
        但是需要注意的是文件操作,由于文件的操作是通过文件描述符表、文件表、v-node表三个联系起来控制的,其中文件表、v-node表是所有的进程共享,而每个进程都存在一个独立的文件描述符表。父子进程虚拟存储空间的内容是大致相同的,父子进程是通过同一个物理区域存储文件描述符表,但如果修改文件描述符表,也会发生写时拷贝操作,只有这样才能保证子进程中对文件描述符的修改,不会影响到父进程的文件描述符表。例如close操作,因为close会导致文件的描述符的值发生变化,相当于发生了写操作,这是产生了写时拷贝过程,实现新的物理空间,然后再次发生close操作,这样就不会产生子进程中文件描述符的关闭而导致父进程不能访问文件。
        原因分析:由于父子进程的文件描述符表是相同的,但是如果在子进程中对fd(文件描述符表中的项)进行了修改,这时会发生写时拷贝过程,内核在物理内存中分配一个新的页面存储子进程原文件描述符fd存在页面的内容,然后再进行写操作,实现将fd修改掉。但是父进程的fd并没有发生改变,还是与其他的子进程共享文件描述符表,因此仍然是对原文件进行操作。

        因此需要注意 fork()函数实质上是按着写时拷贝的方式实现文件的映射 ,并不是共享,写时拷贝操作使得内存的需求量大大的减少了!

 

标签:fork,文件,描述符,进程,拷贝,页面,写时
From: https://blog.csdn.net/2301_78353179/article/details/145225298

相关文章

  • 内存管理优化技术:写时复制(Copy-On-Write, COW)
    文章目录说明写时复制(Copy-On-Write,COW)概念一写时复制的工作原理二为什么需要写时复制三COW在fork()中的应用四COW的优势五COW的应用场景六COW的局限性和挑战七总结说明本文是针对个人专业知识查缺补漏,结合大模型对话内容整理而来,请辩证看待文章!写时......
  • 深拷贝和浅拷贝
    在Python中,深拷贝和浅拷贝是两种常见的对象复制方法,它们之间的区别在于如何处理对象中的子对象。具体来说:1.浅拷贝(ShallowCopy)定义:浅拷贝创建一个新的对象,但不会复制对象中的嵌套对象(如列表、字典中的元素等)。而是将原对象中嵌套的子对象的引用复制到新对象中。结果:新对象......
  • .NET 数据拷贝方案选择
     应用中我们经常使用到数据的复制,在.NET中有多种方式可以实现复制数据或对象。选择哪种方式、是浅拷贝还是深拷贝,具体需求场景可以取决于对象的复杂性、数据量等,本文我们介绍主要的拷贝方式以及相对高性能的方案。 1.MemberwiseClone拷贝浅拷贝 Object.MemberwiseClone方法......
  • 拷贝构造函数
    文章目录一、4.拷贝构造函数今天我们来学习拷贝构造函数。一、4.拷贝构造函数如果⼀个构造函数的第⼀个参数是自身类型的引用,且任何额外的参数都有默认值,则此叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。它的形式是这样的:#include<iostream>usin......
  • Effective C++读书笔记——item12(自定义拷贝构造函数和拷贝赋值运算符可能出现的问题
    1.拷贝函数相关背景及编译器行为在面向对象系统中,拷贝构造函数和拷贝赋值运算符统称为拷贝函数,若不自行声明,编译器会按需生成默认的拷贝函数,其会拷贝被拷贝对象的全部数据。但当自行声明拷贝函数后,编译器若发现实现存在错误,往往不会主动提示,比如在新增数据成员却未更新拷贝函......
  • ROBOCOPY Windows文件拷贝神器
    ROBOCOPY,即RobustFileCopy,是Windows操作系统中一个命令行实用程序,用于文件和目录的复制。它最初是作为XCOPY的替代品开发的,提供了更多的功能和更好的可靠性。ROBOCOPY能够处理大规模的数据复制任务,并且在遇到错误时具备重试机制,可以跳过不可用的文件,继续复制其余的文件,这使得它......
  • 【Java并发编程线程池】 ForkJoinPool 线程池是什么 怎么工作的 和传统的ThreadPoolEx
    Java中的ForkJoinPool线程池是什么怎么工作的Java中的ForkJoinPool线程池是什么怎么工作的相比较于传统的线程池,ForkJoinPool线程池更适合处理大量的计算密集型任务,它的核心思想是将一个大任务拆分成多个小任务,然后将这些小任务分配给多个线程去执行,最后将这些小任务的......
  • python 赋值、深拷贝浅拷贝及切片使用
    赋值、深浅拷贝先复习一下赋值与深浅拷贝i=[1,2,1,3,[1,2]]j=i#赋值k=i.copy()#浅拷贝m=copy.deepcopy(i)#深拷贝#赋值,二者物理地址相同,一方变化另一方同步变化j.pop(0)print(i,j)[2,1,3,[1,2]][2,1,3,[1,2]]#取浅拷贝,二者物理......
  • Object.assign()是浅拷贝还是深拷贝?
    Object.assign()在JavaScript中是执行浅拷贝(shallowcopy)的。这意味着,它只复制对象的顶层属性和值。如果对象的属性值是一个引用类型(例如,数组或另一个对象),Object.assign()不会复制这个引用类型的实际内容,而是复制这个引用本身。因此,原对象和新对象会共享这个引用,对一个对象的......
  • memmove函数:内存重叠时拷贝
    最近测试遇到一个memcpy在x86和arm平台上拷贝字节时不一致的问题。出现拷贝是memcpy函数少量字节拷贝错误。分析参考:https://blog.csdn.net/shuidaoqingyi520/article/details/131669163在内存有重合的时候memcpy是不稳定的,要使用memmove函数。我于是自己写了个简单代码对比#i......