• mmap详解


    目录

    申请堆空间

    sbrk

    mmap

    mmap 的其他应用场景

    共享匿名映射 / 匿名共享映射

    私有匿名映射

    私有文件映射

    共享文件映射

    mmap用途小结

    mmap原理

    私有匿名映射

    私有文件映射

    共享文件映射

    共享匿名映射


    想写一篇文章,详细的介绍一下mmap,主要是原理、用法、mmap泄露来进行介绍。说到mmap,首先得从堆空间说起。

    申请堆空间

    其实,不管是 32 位系统还是 64 位系统,内核都会维护一个变量 brk,指向堆的顶部,所以,brk 的位置实际上就决定了堆的大小。Linux 系统为我们提供了两个重要的系统调用来修改堆的大小,分别是 sbrk 和 mmap。接下来,我们来学习这两个系统调用是如何使用的。我们先来看 sbrk。

    sbrk

    sbrk 函数的头文件和原型定义如下:

    1. #include
    2. void* sbrk(intptr_t incr);

    sbrk 通过给内核的 brk 变量增加 incr,来改变堆的大小,incr 可以为负数。当 incr 为正数时,堆增大,当 incr 为负数时,堆减小。如果 sbrk 函数执行成功,那返回值就是 brk的旧值;如果失败,就会返回 -1,同时会把 errno 设置为 ENOMEM。

    在实际应用中,我们很少直接使用 sbrk 来申请堆内存,而是使用 C 语言提供的 malloc 函数进行堆内存的分配,然后用 free 进行内存释放。这里你要注意的是,malloc 和 free 函数不是系统调用,而是 C 语言的运行时库。Linux 上的主流运行时库是 glibc,其他影响力比较大的运行时库还有 musl 等。C 语言的运行时库多是以动态链接库的方式实现的。

    在 C 语言的运行时库里,malloc 向程序提供分配一小块内存的功能,当运行时库的内存分配完之后,它会使用 sbrk 方法向操作系统再申请一块大的内存。我们可以将 C 语言的运行时库类比为零售商,它从操作系统那里批发一块比较大的内存,然后再通过零售的方式一点点地提供给程序员使用。

    mmap

    另一个可以申请堆内存的系统调用是 mmap,它是最重要的内存管理接口。mmap 的头文件和原型如下所示:

    1. #include
    2. #include
    3. void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

    我来解释一下上述代码中的各个变量的意义:

    start 代表该区域的起始地址;

    length 代表该区域长度;

    prot 描述了这块新的内存区域的访问权限;

    flags 描述了该区域的类型;

    fd 代表文件描述符;

    offset 代表文件内的偏移值。

    mmap 的功能非常强大,根据参数的不同,它可以用于创建共享内存,也可以创建文件映射区域用于提升 IO 效率,还可以用来申请堆内存。决定它的功能的,主要是 prot, flags和 fd 这三个参数,我们分别来看看。

    prot 的值可以是以下四个常量的组合:

    PROT_EXEC,表示这块内存区域有可执行权限,意味着这部分内存可以看成是代码段,它里面存储的往往是 CPU 可以执行的机器码。

    PROT_READ,表示这块内存区域可读。

    PROT_WRITE,表示这块内存区域可写。

    PROT_NONE,表示这块内存区域的页面不能被访问。

    而 flags 的值可取的常量比较多,你可以通过 man mmap 查看,这里我只列举一下最重要的四种可取值常量:

    MAP_SHARED:创建一个共享映射的区域,多个进程可以通过共享映射的方式,来共享同一个文件。这样一来,一个进程对该文件的修改,其他进程也可以观察到,这就实现了数据的通讯。

    MAP_PRIVATE:创建一个私有的映射区域,多个进程可以使用私有映射的方式,来映射同一个文件。但是,当一个进程对文件进行修改时,操作系统就会为它创建一个独立的副本,这样它对文件的修改,其他进程就看不到了,从而达到映射区域私有的目的。

    MAP_ANONYMOUS:创建一个匿名映射,也就是没有关联文件。使用这个选项时,fd 参数必须为空。

    MAP_FIXED:一般来说,addr 参数只是建议操作系统尽量以 addr 为起始地址进行内存映射,但如果操作系统判断 addr 作为起始地址不能满足长度或者权限要求时,就会另外再找其他适合的区域进行映射。如果 flags 的值取是 MAP_FIXED 的话,就不再把addr 看成是建议了,而是将其视为强制要求。如果不能成功映射,就会返回空指针。

    通常,我们使用私有匿名映射来进行堆内存的分配。

    我们再来看参数 fd。当参数 fd 不为 0 时,mmap 映射的内存区域将会和文件关联,如果fd 为 0,就没有对应的相关文件,此时就是匿名映射,flags 的取值必须为MAP_ANONYMOUS。

    明白了 mmap 及其各参数的含义后,你肯定想知道什么场景下才会使用 mmap,我们又该怎么使用它。

    mmap 的其他应用场景

    mmap 这个系统调用的能力非常强大,我们在后面还会经常遇到它。在这节课里,我们先来了解一下它最常见的用法。
    根据映射的类型,mmap 有四种最常用的组合:

    其中,私有匿名映射常用于分配内存,也就是我们上文讲的申请堆内存,而私有文件映射常用于加载动态库。

    这里我们重点看看共享匿名映射。我们通过一个例子,来了解一下 mmap 是如何用于父子进程之间的通信的,它的用法示例代码如下:

    共享匿名映射 / 匿名共享映射

    1. #include <stdio.h>
    2. #include <sys/mman.h>
    3. #include <stdlib.h>
    4. #include <unistd.h>
    5. int main(int argc,char* argv[])
    6. {
    7. pid_t pid;
    8. char* shm = (char*)mmap(0, 4096, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    9. if(!(pid = fork()))
    10. {
    11. sleep(1);
    12. printf("child got a message: %s\n",shm);
    13. sprintf(shm, "%s", "hello, father.");
    14. exit(0);
    15. }
    16. sprintf(shm, "%s", "hello, my child");
    17. sleep(2);
    18. printf("parent got a message: %s\n", shm);
    19. return 0;
    20. }

    1. wj@wj:~/WORK/Learning/learning/cpp_learning$ gcc test.c -o test.out
    2. wj@wj:~/WORK/Learning/learning/cpp_learning$ ./test.out
    3. child got a message: hello, my child
    4. parent got a message: hello, father.

    在这个过程中,我们先是用 mmap 方法创建了一块共享内存区域,命名为 shm,接着,又通过 fork 这个系统调用创建了子进程:具体来讲,子进程休眠一秒后,从 shm 中取出一行字符并打印出来,然后又向共享内存中写入了一行消息。

    在子进程的执行逻辑之后,是父进程的执行逻辑:父进程先写入一行消息,然后休眠两秒,等待子进程完成读取消息和发消息的过程并退出后,父进程再从共享内存中取出子进程发过来的消息。

    我想请你结合我刚才的讲解,来分析一下这个程序运行的结果,这样你就理解的更透彻
    了。

    关于共享匿名映射,我们就讲到这里,至于 mmap 的另一个组合共享文件映射。它的作用其实和共享匿名映射相似,也可以用于进程间通讯。不同的是,共享文件映射是通过文件名来创建共享内存区域的,这就让没有父子关系的进程,也可以通过相同的文件创建共享内存区域,从而可以使用共享内存进行进程间通讯。

    私有匿名映射

    将上面的例子,映射方式修改为:MAP_PRIVATE。分析一下会输出什么结果?以及为什么会有这样的结果输出?

    1. #include <stdio.h>
    2. #include <sys/mman.h>
    3. #include <stdlib.h>
    4. #include <unistd.h>
    5. int main(int argc,char* argv[])
    6. {
    7. pid_t pid;
    8. char* shm = (char*)mmap(0, 4096, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    9. if(!(pid = fork()))
    10. {
    11. sleep(1);
    12. printf("child got a message: %s\n",shm);
    13. sprintf(shm, "%s", "hello, father.");
    14. exit(0);
    15. }
    16. sprintf(shm, "%s", "hello, my child");
    17. sleep(2);
    18. printf("parent got a message: %s\n", shm);
    19. return 0;
    20. }

    编译输出为:

    1. wj@ubuntu:~/learning$ gcc test.c -o test.out
    2. wj@ubuntu:~/learning$ ./test.out
    3. child got a message:
    4. parent got a message: hello, my child

    映射方式修改为:MAP_PRIVATE,为私有匿名映射,用于分配堆内存。所以,shm这块映射区域只能用于分配堆内存。所以,父进程修改 shm 这快内存区域,对于子进程来说是看不见的。所以,就会输出以上的结果。

    私有文件映射

    私有文件映射,常用于加载动态库。

    如果只是读和执行,那么物理内存中只有一份副本。

    如果动态库中存在全局变量,则数据段就会被复制一份,变成每个进程独有的,而这正是我们所期望的。

    //后续补充对应的例子

    共享文件映射

    例子:内存映射(memory map) - 知乎

    在实际的工程中,也经常用共享文件映射,用于多个进程之间的内存共享。

    1. // OsUtils::MemMap
    2. VOID* OsUtils::MemMap(
    3. INT bufferFD,
    4. SIZE_T bufferLength,
    5. SIZE_T offset)
    6. {
    7. VOID* pMem = NULL;
    8. CAMX_ASSERT(bufferFD >= 0);
    9. if ((bufferLength > 0) && (bufferFD >= 0))
    10. {
    11. pMem = mmap(NULL, bufferLength, (PROT_READ | PROT_WRITE), MAP_SHARED, bufferFD, offset);
    12. if (MAP_FAILED == pMem)
    13. {
    14. CAMX_LOG_ERROR(CamxLogGroupCSL, "mmap() failed! errno=%d, bufferFD=%zd, bufferLength=%d offset=%zd",
    15. errno, bufferFD, bufferLength, offset);
    16. pMem = NULL;
    17. }
    18. }
    19. return pMem;
    20. }
    21. // OsUtils::MemUnmap
    22. INT OsUtils::MemUnmap(
    23. VOID* pAddr,
    24. SIZE_T size)
    25. {
    26. INT result = -1;
    27. CAMX_ASSERT(NULL != pAddr);
    28. if (NULL != pAddr)
    29. {
    30. result = munmap(pAddr, size);
    31. if (0 != result)
    32. {
    33. CAMX_LOG_ERROR(CamxLogGroupCSL, "munmap() failed! errno %d", errno);
    34. }
    35. }
    36. return result;
    37. }

    1. // MemPoolGroup::GetBufferFromPool
    2. MemPoolGetBufferResult MemPoolGroup::GetBufferFromPool(
    3. MemPoolBufferManager* pMemPoolBufMgr,
    4. CSLBufferInfo* pCSLBufferInfo,
    5. BufferHandle* phGrallocBuffer,
    6. ImageFormat* pAltImgFormat)
    7. {
    8. CamxResult result = CamxResultSuccess;
    9. MemPoolBuffer* pBuffer = NULL;
    10. MemPoolGetBufferResult getBufferPoolResult = {};
    11. if (FALSE == pMemPoolBufMgr->bActivated)
    12. {
    13. CAMX_LOG_VERBOSE(CamxLogGroupMemMgr, "MemPoolGroup[%s][%p]-->MemPoolBuffer[%s][%p] calling Activate internally",
    14. m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr);
    15. // Make sure this Buffer Manager is activated
    16. result = ActivateBufferManager(pMemPoolBufMgr);
    17. }
    18. m_pLock->Lock();
    19. if ((CamxResultSuccess == result) &&
    20. (pMemPoolBufMgr->memPoolBufferList.NumNodes() == pMemPoolBufMgr->createData.maxBufferCount))
    21. {
    22. CAMX_LOG_ERROR(CamxLogGroupMemMgr, "MemPoolGroup[%s][%p] : MemPoolBuffer[%s][%p] exceeding max Buffers %d",
    23. m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr, pMemPoolBufMgr->createData.maxBufferCount);
    24. result = CamxResultENoMore;
    25. }
    26. if (CamxResultSuccess == result)
    27. {
    28. /// @todo (CAMX-4157) Wait for the offline thread to finish, if in progress
    29. // Make sure to wait if the offline thread is in the process of allocating a buffer after wait is done, do below
    30. pMemPoolBufMgr->inStartDeactivateState = FALSE;
    31. if (0 == m_freeBufferList.NumNodes())
    32. {
    33. // No free buffers, grow Pool.
    34. UINT32 numBuffersAllocated = AllocateBuffers(1);
    35. if (1 != numBuffersAllocated)
    36. {
    37. CAMX_LOG_ERROR(CamxLogGroupMemMgr,
    38. "MemPoolGroup[%s] Failed in Allocating buffers heap=%d, Required=1, Allocated=%d",
    39. m_pGroupName, m_bufferHeap, numBuffersAllocated);
    40. result = CamxResultENoMemory;
    41. }
    42. }
    43. if (CamxResultSuccess == result)
    44. {
    45. LDLLNode* pNode = NULL;
    46. UBWCPLib_buf_attrs buffAttr = {};
    47. if (Utils::IsBitMaskSet(pMemPoolBufMgr->createData.bufferProperties.memFlags, CSLMemFlagUBWCPHeap))
    48. {
    49. GetUBWCPBufferAttributes(*pAltImgFormat, buffAttr);
    50. }
    51. if ((Utils::IsBitSet(HwInterface::GetInstance()->GetStaticSettings()->enableUBWCP, DMAUBWCPOpt)) &&
    52. (Utils::IsBitMaskSet(pMemPoolBufMgr->createData.bufferProperties.memFlags, CSLMemFlagUBWCPHeap)) &&
    53. (NULL != pCSLBufferInfo))
    54. {
    55. // Try and get a mapped buffer with same properties
    56. // TO DO: Improve Buffer pick run time from O(n) - right now there should only be a handful of buffers
    57. // in each MPG for this to make any difference
    58. pNode = m_freeBufferList.Head();
    59. while (NULL != pNode)
    60. {
    61. pBuffer = static_cast(pNode->pData);
    62. if (*(pBuffer->pBuffAttr) == buffAttr)
    63. {
    64. // Sw Link can get a matching attribute with mismatching access type
    65. // Hw Link can get a match for Sw Mapped buffer
    66. m_freeBufferList.RemoveNode(pNode);
    67. break;
    68. }
    69. pBuffer = NULL;
    70. pNode = m_freeBufferList.NextNode(pNode);
    71. }
    72. }
    73. if (NULL == pNode)
    74. {
    75. // Default op: Take a free buffer or in case of UBWCP we failed to find a mapped buffer with matching attr.
    76. // Hw Link can get a mapped buffer - leading to unnecessary mapping
    77. pNode = m_freeBufferList.RemoveFromHead();
    78. }
    79. if (NULL != pNode)
    80. {
    81. pBuffer = static_cast(pNode->pData);
    82. CAMX_ASSERT(NULL != pBuffer);
    83. BOOL ubwcpOpsEnabled = Utils::IsBitSet(HwInterface::GetInstance()->GetStaticSettings()->enableUBWCP,
    84. EnableUBWCPOps);
    85. const UINT32 CSLFlags = m_bufferAllocProperties.flags;
    86. // Previously set Buffer Attributes, reset if they are not a match with current required
    87. if ((TRUE == ubwcpOpsEnabled) &&
    88. (NULL != pAltImgFormat) &&
    89. (TRUE == pBuffer->isUBWCPAttrSet) &&
    90. (UBWCPLib_NUM_FORMATS != buffAttr.image_format) &&
    91. (buffAttr != *(pBuffer->pBuffAttr)))
    92. {
    93. // The fact that it is in the free pool means there is no access on this buffer, End previous CPU
    94. // access if one is there
    95. UpdateAccess(pBuffer, CPUAccessEnd);
    96. auto& rBufferInfo = pBuffer->bufferInfo;
    97. if (NULL != rBufferInfo.pVirtualAddr)
    98. {
    99. if (0 > CamX::OsUtils::MemUnmap(rBufferInfo.pVirtualAddr, pBuffer->mappedBufferSize))
    100. {
    101. CAMX_LOG_ERROR(CamxLogGroupMemMgr,
    102. "MemUnmap()failed for buffer with hdl = %u, VAddr= %p with size = %zu",
    103. rBufferInfo.hHandle, rBufferInfo.pVirtualAddr, pBuffer->mappedBufferSize);
    104. result = CDKResultEFailed;
    105. }
    106. else
    107. {
    108. rBufferInfo.pVirtualAddr = NULL;
    109. }
    110. }
    111. pBuffer->isUBWCPAttrSet = FALSE;
    112. }
    113. if ((TRUE == ubwcpOpsEnabled) &&
    114. (CamxResultSuccess == result) &&
    115. (TRUE == Utils::IsBitMaskSet(CSLFlags, CSLMemFlagUBWCPHeap)) &&
    116. (NULL != pAltImgFormat) &&
    117. (UBWCPLib_NUM_FORMATS != buffAttr.image_format) &&
    118. (FALSE == pBuffer->isUBWCPAttrSet))
    119. {
    120. *(pBuffer->pBuffAttr) = buffAttr;
    121. pBuffer->isUBWCPAttrSet = TRUE;
    122. UBWCPExtLibOpType opData = {};
    123. opData.UBWCPOpParam.setBufferAttributeParam = {pBuffer->bufferInfo.fd, pBuffer->pBuffAttr};
    124. if (UBWCPLib_LINEAR != pBuffer->pBuffAttr->image_format)
    125. {
    126. opData.opType = UBWCPOpValidateStride;
    127. result = CSLUBWCPLibOp(opData);
    128. }
    129. // Set UBWCP Buffer Attributes and mmap buffer
    130. if (CamxResultSuccess == result)
    131. {
    132. opData.opType = UBWCPOpSetBuffAttr;
    133. result = CSLUBWCPLibOp(opData);
    134. }
    135. if ((CamxResultSuccess == result) &&
    136. (NULL == pBuffer->bufferInfo.pVirtualAddr))
    137. {
    138. VOID* pLocalVirtualAddr = NULL;
    139. auto& rBufferInfo = pBuffer->bufferInfo;
    140. if ((Utils::IsBitMaskReset(CSLFlags, CSLMemFlagProtected)) &&
    141. (Utils::IsBitMaskReset(CSLFlags, CSLMemFlagProtectedUMDAccess)) &&
    142. (Utils::IsBitMaskReset(CSLFlags, CSLMemFlagNoPixelSecureAccess)))
    143. {
    144. pBuffer->mappedBufferSize = ImageFormatUtils::GetTotalSize(pAltImgFormat);
    145. pLocalVirtualAddr = CamX::OsUtils::MemMap(pBuffer->bufferInfo.fd,
    146. pBuffer->mappedBufferSize, 0);
    147. if (NULL == pLocalVirtualAddr)
    148. {
    149. CAMX_LOG_ERROR(CamxLogGroupMemMgr, "mmap() failed!");
    150. result = CamxResultEFailed;
    151. }
    152. else
    153. {
    154. pBuffer->bufferInfo.pVirtualAddr = pLocalVirtualAddr;
    155. }
    156. }
    157. else
    158. {
    159. rBufferInfo.pVirtualAddr = pLocalVirtualAddr;
    160. CAMX_LOG_VERBOSE(CamxLogGroupMemMgr, "Skipping mmap() for secure buffer");
    161. }
    162. }
    163. }
    164. // We got a buffer to give back.. but make sure this is mapped to the devices that this Buffer manager requires.
    165. // This can happen if - this MemPool Group allocated some buffers before this Buffer manager attached to this
    166. // group (based on size). In that case, buffer may not have mapped to the devices that listed in this
    167. // buffer manager.
    168. // So, check if array of devices listed in this Buffer Manager create data are present in the array of devices
    169. // listed in MemPoolBuffer device list.
    170. result = CheckAndMapBufferToDevices(pBuffer,
    171. pMemPoolBufMgr->createData.deviceIndices,
    172. pMemPoolBufMgr->createData.deviceCount,
    173. pMemPoolBufMgr->createData.bufferProperties.memFlags);
    174. if (CamxResultSuccess == result)
    175. {
    176. pBuffer->pMemPoolBufMgr = pMemPoolBufMgr;
    177. pMemPoolBufMgr->memPoolBufferList.InsertToTail(pNode);
    178. pMemPoolBufMgr->peakBuffersUsed = Utils::MaxUINT(pMemPoolBufMgr->peakBuffersUsed,
    179. pMemPoolBufMgr->memPoolBufferList.NumNodes());
    180. pMemPoolBufMgr->bActiveInLastPeriod = TRUE;
    181. CAMX_LOG_VERBOSE(CamxLogGroupMemMgr,
    182. "MemPoolGroup[%s][%p] : MemPoolBuffer[%s][%p] Moved from Free to MemPoolBufMgr[%s][%p]",
    183. m_pGroupName, this, pBuffer->name, pBuffer, pMemPoolBufMgr->name, pMemPoolBufMgr);
    184. CAMX_LOG_VERBOSE(CamxLogGroupMemMgr,
    185. "MemPoolGroup[%s][%p]-->MemPoolBufMgr[%s][%p] now has %d in use buffers, %d peak buffers",
    186. m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr,
    187. pMemPoolBufMgr->memPoolBufferList.NumNodes(), pMemPoolBufMgr->peakBuffersUsed);
    188. m_peakNumBuffersUsed = Utils::MaxUINT(m_peakNumBuffersUsed,
    189. m_numBuffersAllocated - m_freeBufferList.NumNodes());
    190. m_numBuffersIdle = Utils::MinUINT(m_freeBufferList.NumNodes(), m_numBuffersIdle);
    191. if (NULL != pCSLBufferInfo)
    192. {
    193. *pCSLBufferInfo = pBuffer->bufferInfo;
    194. }
    195. if (NULL != phGrallocBuffer)
    196. {
    197. *phGrallocBuffer = pBuffer->hGrallocBuffer;
    198. }
    199. }
    200. else
    201. {
    202. // Even though we have a free buffer, we failed in mapping the buffer to all required devices.
    203. // so this buffer doesn't meet requirements, its a fatal now. Add this buffer back to free list
    204. m_freeBufferList.InsertToTail(pNode);
    205. }
    206. }
    207. }
    208. if (m_freeBufferList.NumNodes() < GetStaticSettings()->MPMKeepMinNumFreeBuffers)
    209. {
    210. /// @todo (CAMX-4157) Trigger offline thread to allocate buffers in parallel
    211. // No more buffers available, keep minimum number of buffers extra always
    212. // Allocate : GetStaticSettings()->MPMKeepMinNumFreeBuffers - m_freeBufferList.NumNodes()
    213. // Trigger thread to allocate the buffer offline
    214. }
    215. }
    216. getBufferPoolResult.result = result;
    217. getBufferPoolResult.hBufferHandle = static_cast(pBuffer);
    218. m_pLock->Unlock();
    219. return getBufferPoolResult;
    220. }

    mmap用途小结

    mmap最常见的四种用途分别是:

    1. 私有匿名映射,用于分配堆空间;
    2. 私有文件映射,用于加载动态链接库;
    3. 共享匿名映射,用于父子进程之间通讯;
    4. 共享文件映射,用于多进程之间通讯,在camera hal中很常见。

    mmap原理

    私有匿名映射

    私有匿名映射是最简单的情况, 在调用 mmap 时,只需要在文件映射区域分配一块内存, 然后创建这块内存所对应的 vma 结构,这次调用就结束了
    当访问到这块虚拟内存时,由于这块虚拟内存都没有映射到物理内存上,就会发生缺页中 断,但这一次的缺页中断与 execve 时的缺页中断不一样,这次是匿名映射,所以关联文件属性为空。此时,内核就会调用 do_anonymous_page 来分配一个物理内存,并将整个物理页全部初始化为 0,然后在页表里建立起虚拟地址到物理地址的映射关系。

    私有文件映射

    在内核中,如果有一个进程打开了一个文件,PCB 中就会有一个 struct file 结构与这个文件对应。struct file 结构是与进程相关,假如进程 A 与进程 B 都打开了文件 f,那么进程 A 中就会有一个 struct file 结构,进程 B 中也会有一个。
    Linux 的文件系统中有一个叫做 inode 的结构,这个结构与具体的磁盘上的文件是一一对应的,也就是说对于同一个文件,整个内核中只会有一个 inode 结构。所以进程 A 与进程 B 的 file struct 结构都有一个指针指向 inode 结构,这就将 file struct 与 inode 结构联系起来了。
    在 inode 结构中,有一个哈希表,以文件的页号为 key,以物理内存页为 value。当进程 A 打开了文件 f,然后读取了它的第 4 页,这时,内核就会把 4 和这个物理页,放入这个 哈希表中。当进程 B 再打开文件 f,要读取它的第 4 页时,因为 f 的第 4 页的内容已经被 加载到物理页中了,所以就不用再加载一次了。只需要将 B 的虚拟地址与这个物理页建立 映射就可以了,如下图所示:

    我要提醒你的是, 哈希表在现代的 Linux 内核中,已经被优化成了 Radix tree 和最小堆 的一种优化的数据结构,它们比哈希表有更好的时间效率,所以你在阅读不同版本的 Linux 内核代码时要注意这个变化
    如果文件是只读的话,那这个文件在物理页的层面上其实是共享的。也就是进程 A 和进程 B 都有一页虚拟内存被映射到了相同的物理页上。但如果要写文件的时候,因为这一段内 存区域的属性是私有的,所以内核就会做一次写时复制,为写文件的进程单独地创建一份 副本。这样,一个进程在写文件时,并不会影响到其他进程的读。
    对于共享库文件,代码段的私有属性其实并不影响它在所有进程间共享;但如果数据段在 执行的过程发生变化,内核就可以通过写时复制机制为每个进程创建一个副本。这就是对 于共享库文件要选择私有文件映射的根本原因。
    这里我们就有这样一个结论: 私有文件映射的只读页是多进程间共享的,可写页是每个进 程都有一个独立的副本,创建副本的时机仍然是写时复制

    共享文件映射

    在私有文件映射的基础上,共享文件映射就很简单了: 对于可写的页面,在写的时候不进 行复制就可以了 。这样的话,无论何时,也无论是读还是写,多个进程在访问同一个文件 的同一个页时,访问的都是相同的物理页面。

    共享匿名映射

    在这节课之前,你可能会觉得共享匿名映射在父子进程间通讯是最简单的,因为父子进程 共享了相同的 mmap 的返回值,看上去最直观。但实际上,从内核的角度说,它却是最复杂的。
    原因是 mmap 并不真正分配物理内存,它只是分配了一段虚拟内存,也就是说只在 PCB 中创建了一个 vma 结构而已。这就导致 fork 在复制页表的时候,页表中共享匿名映射区 域都是未映射状态
    请你设想一下,如果内核不做特殊处理的话,在父进程因为访问共享内存区域而遇到缺页 中断时,内核为它分配了物理页面,等子进程再访问共享内存区域时,内核也没有办法知 道子进程的虚拟内存,应该映射到哪个物理页面上,因为缺页中断只能知道当前进程是 谁,以及发生问题的虚拟地址是什么,这些信息不足够计算出,是否有其他进程已经把共享内存准备好了。
    在内核中使用虚拟文件系统来解决这个问题之前,早期的 Linux 内核中并不支持共享匿名 映射。虚拟文件并不是真实地在磁盘上存在的。它只是由内核模拟出来的,但是它也有自 己的 inode 结构。这样一来,内核就能在创建共享匿名映射区域时,创建一个虚拟文件, 并将这个文件与映射区域的 vma 关联起来。
    当 fork 创建子进程时,子进程会复制父进程的全部 vma 信息。接下来发生的事情就和共 享文件映射完全一样了,我们就不再重复了。
    至此,我们才终于把 mmap 的核心原理分析清楚。 mmap 的功能之所以十分强大,主是因为操作系统综合使用写保护中断、缺页中断和文件 机制来实现 mmap 的各种功能

  • 相关阅读:
    ES6 入门教程 19 Generator 函数的语法 19.1 简介
    Docker容器的数据卷
    【Linux系统化学习】进程的状态 | 僵尸进程 | 孤儿进程
    SPEOS—光学产品设计及仿真工具
    人声分离网站,帮你快速提取视频中的人声和背景音乐
    学生dreamweaver网页设计作业成品___辅导网站( 4页 登录注册 轮播图)
    springboot基于web的在线问答社区系统设计与实现毕业设计源码061628
    多线程【锁策略与CAS的ABA问题】
    B树与B+树的区别
    phy调试2
  • 原文地址:https://blog.csdn.net/weixin_42136255/article/details/132856700