目录
想写一篇文章,详细的介绍一下mmap,主要是原理、用法、mmap泄露来进行介绍。说到mmap,首先得从堆空间说起。
其实,不管是 32 位系统还是 64 位系统,内核都会维护一个变量 brk,指向堆的顶部,所以,brk 的位置实际上就决定了堆的大小。Linux 系统为我们提供了两个重要的系统调用来修改堆的大小,分别是 sbrk 和 mmap。接下来,我们来学习这两个系统调用是如何使用的。我们先来看 sbrk。
sbrk 函数的头文件和原型定义如下:
- #include
-
- 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 的头文件和原型如下所示:
- #include
- #include
-
- 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 是如何用于父子进程之间的通信的,它的用法示例代码如下:
- #include <stdio.h>
- #include <sys/mman.h>
- #include <stdlib.h>
- #include <unistd.h>
-
-
- int main(int argc,char* argv[])
- {
- pid_t pid;
-
- char* shm = (char*)mmap(0, 4096, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, -1, 0);
- if(!(pid = fork()))
- {
- sleep(1);
- printf("child got a message: %s\n",shm);
- sprintf(shm, "%s", "hello, father.");
- exit(0);
- }
-
- sprintf(shm, "%s", "hello, my child");
- sleep(2);
- printf("parent got a message: %s\n", shm);
-
- return 0;
- }
- wj@wj:~/WORK/Learning/learning/cpp_learning$ gcc test.c -o test.out
- wj@wj:~/WORK/Learning/learning/cpp_learning$ ./test.out
- child got a message: hello, my child
- parent got a message: hello, father.
在这个过程中,我们先是用 mmap 方法创建了一块共享内存区域,命名为 shm,接着,又通过 fork 这个系统调用创建了子进程:具体来讲,子进程休眠一秒后,从 shm 中取出一行字符并打印出来,然后又向共享内存中写入了一行消息。
在子进程的执行逻辑之后,是父进程的执行逻辑:父进程先写入一行消息,然后休眠两秒,等待子进程完成读取消息和发消息的过程并退出后,父进程再从共享内存中取出子进程发过来的消息。
我想请你结合我刚才的讲解,来分析一下这个程序运行的结果,这样你就理解的更透彻
了。
关于共享匿名映射,我们就讲到这里,至于 mmap 的另一个组合共享文件映射。它的作用其实和共享匿名映射相似,也可以用于进程间通讯。不同的是,共享文件映射是通过文件名来创建共享内存区域的,这就让没有父子关系的进程,也可以通过相同的文件创建共享内存区域,从而可以使用共享内存进行进程间通讯。
将上面的例子,映射方式修改为:MAP_PRIVATE。分析一下会输出什么结果?以及为什么会有这样的结果输出?
-
- #include <stdio.h>
- #include <sys/mman.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- int main(int argc,char* argv[])
- {
- pid_t pid;
-
- char* shm = (char*)mmap(0, 4096, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if(!(pid = fork()))
- {
- sleep(1);
- printf("child got a message: %s\n",shm);
- sprintf(shm, "%s", "hello, father.");
- exit(0);
- }
-
- sprintf(shm, "%s", "hello, my child");
- sleep(2);
- printf("parent got a message: %s\n", shm);
-
- return 0;
- }
编译输出为:
- wj@ubuntu:~/learning$ gcc test.c -o test.out
- wj@ubuntu:~/learning$ ./test.out
- child got a message:
- parent got a message: hello, my child
映射方式修改为:MAP_PRIVATE,为私有匿名映射,用于分配堆内存。所以,shm这块映射区域只能用于分配堆内存。所以,父进程修改 shm 这快内存区域,对于子进程来说是看不见的。所以,就会输出以上的结果。
私有文件映射,常用于加载动态库。
如果只是读和执行,那么物理内存中只有一份副本。
如果动态库中存在全局变量,则数据段就会被复制一份,变成每个进程独有的,而这正是我们所期望的。
//后续补充对应的例子
在实际的工程中,也经常用共享文件映射,用于多个进程之间的内存共享。
-
- // OsUtils::MemMap
-
- VOID* OsUtils::MemMap(
- INT bufferFD,
- SIZE_T bufferLength,
- SIZE_T offset)
- {
- VOID* pMem = NULL;
-
- CAMX_ASSERT(bufferFD >= 0);
- if ((bufferLength > 0) && (bufferFD >= 0))
- {
- pMem = mmap(NULL, bufferLength, (PROT_READ | PROT_WRITE), MAP_SHARED, bufferFD, offset);
- if (MAP_FAILED == pMem)
- {
- CAMX_LOG_ERROR(CamxLogGroupCSL, "mmap() failed! errno=%d, bufferFD=%zd, bufferLength=%d offset=%zd",
- errno, bufferFD, bufferLength, offset);
- pMem = NULL;
- }
- }
-
- return pMem;
- }
-
-
- // OsUtils::MemUnmap
-
- INT OsUtils::MemUnmap(
- VOID* pAddr,
- SIZE_T size)
- {
- INT result = -1;
-
- CAMX_ASSERT(NULL != pAddr);
- if (NULL != pAddr)
- {
- result = munmap(pAddr, size);
- if (0 != result)
- {
- CAMX_LOG_ERROR(CamxLogGroupCSL, "munmap() failed! errno %d", errno);
- }
- }
-
- return result;
- }
-
- // MemPoolGroup::GetBufferFromPool
-
- MemPoolGetBufferResult MemPoolGroup::GetBufferFromPool(
- MemPoolBufferManager* pMemPoolBufMgr,
- CSLBufferInfo* pCSLBufferInfo,
- BufferHandle* phGrallocBuffer,
- ImageFormat* pAltImgFormat)
- {
- CamxResult result = CamxResultSuccess;
- MemPoolBuffer* pBuffer = NULL;
- MemPoolGetBufferResult getBufferPoolResult = {};
-
- if (FALSE == pMemPoolBufMgr->bActivated)
- {
- CAMX_LOG_VERBOSE(CamxLogGroupMemMgr, "MemPoolGroup[%s][%p]-->MemPoolBuffer[%s][%p] calling Activate internally",
- m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr);
-
- // Make sure this Buffer Manager is activated
- result = ActivateBufferManager(pMemPoolBufMgr);
- }
-
- m_pLock->Lock();
-
- if ((CamxResultSuccess == result) &&
- (pMemPoolBufMgr->memPoolBufferList.NumNodes() == pMemPoolBufMgr->createData.maxBufferCount))
- {
- CAMX_LOG_ERROR(CamxLogGroupMemMgr, "MemPoolGroup[%s][%p] : MemPoolBuffer[%s][%p] exceeding max Buffers %d",
- m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr, pMemPoolBufMgr->createData.maxBufferCount);
-
- result = CamxResultENoMore;
- }
-
- if (CamxResultSuccess == result)
- {
- /// @todo (CAMX-4157) Wait for the offline thread to finish, if in progress
- // Make sure to wait if the offline thread is in the process of allocating a buffer after wait is done, do below
-
- pMemPoolBufMgr->inStartDeactivateState = FALSE;
-
- if (0 == m_freeBufferList.NumNodes())
- {
- // No free buffers, grow Pool.
-
- UINT32 numBuffersAllocated = AllocateBuffers(1);
-
- if (1 != numBuffersAllocated)
- {
- CAMX_LOG_ERROR(CamxLogGroupMemMgr,
- "MemPoolGroup[%s] Failed in Allocating buffers heap=%d, Required=1, Allocated=%d",
- m_pGroupName, m_bufferHeap, numBuffersAllocated);
-
- result = CamxResultENoMemory;
- }
- }
-
- if (CamxResultSuccess == result)
- {
- LDLLNode* pNode = NULL;
-
- UBWCPLib_buf_attrs buffAttr = {};
-
- if (Utils::IsBitMaskSet(pMemPoolBufMgr->createData.bufferProperties.memFlags, CSLMemFlagUBWCPHeap))
- {
- GetUBWCPBufferAttributes(*pAltImgFormat, buffAttr);
- }
-
- if ((Utils::IsBitSet(HwInterface::GetInstance()->GetStaticSettings()->enableUBWCP, DMAUBWCPOpt)) &&
- (Utils::IsBitMaskSet(pMemPoolBufMgr->createData.bufferProperties.memFlags, CSLMemFlagUBWCPHeap)) &&
- (NULL != pCSLBufferInfo))
- {
- // Try and get a mapped buffer with same properties
- // TO DO: Improve Buffer pick run time from O(n) - right now there should only be a handful of buffers
- // in each MPG for this to make any difference
- pNode = m_freeBufferList.Head();
- while (NULL != pNode)
- {
- pBuffer = static_cast
(pNode->pData); -
- if (*(pBuffer->pBuffAttr) == buffAttr)
- {
- // Sw Link can get a matching attribute with mismatching access type
- // Hw Link can get a match for Sw Mapped buffer
- m_freeBufferList.RemoveNode(pNode);
- break;
- }
-
- pBuffer = NULL;
- pNode = m_freeBufferList.NextNode(pNode);
- }
- }
-
- if (NULL == pNode)
- {
- // Default op: Take a free buffer or in case of UBWCP we failed to find a mapped buffer with matching attr.
- // Hw Link can get a mapped buffer - leading to unnecessary mapping
- pNode = m_freeBufferList.RemoveFromHead();
- }
-
- if (NULL != pNode)
- {
- pBuffer = static_cast
(pNode->pData); -
- CAMX_ASSERT(NULL != pBuffer);
-
- BOOL ubwcpOpsEnabled = Utils::IsBitSet(HwInterface::GetInstance()->GetStaticSettings()->enableUBWCP,
- EnableUBWCPOps);
- const UINT32 CSLFlags = m_bufferAllocProperties.flags;
-
- // Previously set Buffer Attributes, reset if they are not a match with current required
- if ((TRUE == ubwcpOpsEnabled) &&
- (NULL != pAltImgFormat) &&
- (TRUE == pBuffer->isUBWCPAttrSet) &&
- (UBWCPLib_NUM_FORMATS != buffAttr.image_format) &&
- (buffAttr != *(pBuffer->pBuffAttr)))
- {
- // The fact that it is in the free pool means there is no access on this buffer, End previous CPU
- // access if one is there
- UpdateAccess(pBuffer, CPUAccessEnd);
-
- auto& rBufferInfo = pBuffer->bufferInfo;
-
- if (NULL != rBufferInfo.pVirtualAddr)
- {
- if (0 > CamX::OsUtils::MemUnmap(rBufferInfo.pVirtualAddr, pBuffer->mappedBufferSize))
- {
- CAMX_LOG_ERROR(CamxLogGroupMemMgr,
- "MemUnmap()failed for buffer with hdl = %u, VAddr= %p with size = %zu",
- rBufferInfo.hHandle, rBufferInfo.pVirtualAddr, pBuffer->mappedBufferSize);
- result = CDKResultEFailed;
- }
- else
- {
- rBufferInfo.pVirtualAddr = NULL;
- }
- }
-
- pBuffer->isUBWCPAttrSet = FALSE;
- }
-
- if ((TRUE == ubwcpOpsEnabled) &&
- (CamxResultSuccess == result) &&
- (TRUE == Utils::IsBitMaskSet(CSLFlags, CSLMemFlagUBWCPHeap)) &&
- (NULL != pAltImgFormat) &&
- (UBWCPLib_NUM_FORMATS != buffAttr.image_format) &&
- (FALSE == pBuffer->isUBWCPAttrSet))
- {
- *(pBuffer->pBuffAttr) = buffAttr;
- pBuffer->isUBWCPAttrSet = TRUE;
-
- UBWCPExtLibOpType opData = {};
-
- opData.UBWCPOpParam.setBufferAttributeParam = {pBuffer->bufferInfo.fd, pBuffer->pBuffAttr};
-
- if (UBWCPLib_LINEAR != pBuffer->pBuffAttr->image_format)
- {
- opData.opType = UBWCPOpValidateStride;
- result = CSLUBWCPLibOp(opData);
- }
-
- // Set UBWCP Buffer Attributes and mmap buffer
- if (CamxResultSuccess == result)
- {
- opData.opType = UBWCPOpSetBuffAttr;
- result = CSLUBWCPLibOp(opData);
- }
-
- if ((CamxResultSuccess == result) &&
- (NULL == pBuffer->bufferInfo.pVirtualAddr))
- {
- VOID* pLocalVirtualAddr = NULL;
- auto& rBufferInfo = pBuffer->bufferInfo;
- if ((Utils::IsBitMaskReset(CSLFlags, CSLMemFlagProtected)) &&
- (Utils::IsBitMaskReset(CSLFlags, CSLMemFlagProtectedUMDAccess)) &&
- (Utils::IsBitMaskReset(CSLFlags, CSLMemFlagNoPixelSecureAccess)))
- {
- pBuffer->mappedBufferSize = ImageFormatUtils::GetTotalSize(pAltImgFormat);
- pLocalVirtualAddr = CamX::OsUtils::MemMap(pBuffer->bufferInfo.fd,
- pBuffer->mappedBufferSize, 0);
- if (NULL == pLocalVirtualAddr)
- {
- CAMX_LOG_ERROR(CamxLogGroupMemMgr, "mmap() failed!");
- result = CamxResultEFailed;
- }
- else
- {
- pBuffer->bufferInfo.pVirtualAddr = pLocalVirtualAddr;
- }
- }
- else
- {
- rBufferInfo.pVirtualAddr = pLocalVirtualAddr;
- CAMX_LOG_VERBOSE(CamxLogGroupMemMgr, "Skipping mmap() for secure buffer");
- }
- }
- }
-
- // We got a buffer to give back.. but make sure this is mapped to the devices that this Buffer manager requires.
- // This can happen if - this MemPool Group allocated some buffers before this Buffer manager attached to this
- // group (based on size). In that case, buffer may not have mapped to the devices that listed in this
- // buffer manager.
- // So, check if array of devices listed in this Buffer Manager create data are present in the array of devices
- // listed in MemPoolBuffer device list.
- result = CheckAndMapBufferToDevices(pBuffer,
- pMemPoolBufMgr->createData.deviceIndices,
- pMemPoolBufMgr->createData.deviceCount,
- pMemPoolBufMgr->createData.bufferProperties.memFlags);
-
- if (CamxResultSuccess == result)
- {
- pBuffer->pMemPoolBufMgr = pMemPoolBufMgr;
-
- pMemPoolBufMgr->memPoolBufferList.InsertToTail(pNode);
-
- pMemPoolBufMgr->peakBuffersUsed = Utils::MaxUINT(pMemPoolBufMgr->peakBuffersUsed,
- pMemPoolBufMgr->memPoolBufferList.NumNodes());
-
- pMemPoolBufMgr->bActiveInLastPeriod = TRUE;
-
- CAMX_LOG_VERBOSE(CamxLogGroupMemMgr,
- "MemPoolGroup[%s][%p] : MemPoolBuffer[%s][%p] Moved from Free to MemPoolBufMgr[%s][%p]",
- m_pGroupName, this, pBuffer->name, pBuffer, pMemPoolBufMgr->name, pMemPoolBufMgr);
-
- CAMX_LOG_VERBOSE(CamxLogGroupMemMgr,
- "MemPoolGroup[%s][%p]-->MemPoolBufMgr[%s][%p] now has %d in use buffers, %d peak buffers",
- m_pGroupName, this, pMemPoolBufMgr->name, pMemPoolBufMgr,
- pMemPoolBufMgr->memPoolBufferList.NumNodes(), pMemPoolBufMgr->peakBuffersUsed);
-
- m_peakNumBuffersUsed = Utils::MaxUINT(m_peakNumBuffersUsed,
- m_numBuffersAllocated - m_freeBufferList.NumNodes());
- m_numBuffersIdle = Utils::MinUINT(m_freeBufferList.NumNodes(), m_numBuffersIdle);
-
- if (NULL != pCSLBufferInfo)
- {
- *pCSLBufferInfo = pBuffer->bufferInfo;
- }
-
- if (NULL != phGrallocBuffer)
- {
- *phGrallocBuffer = pBuffer->hGrallocBuffer;
- }
- }
- else
- {
- // Even though we have a free buffer, we failed in mapping the buffer to all required devices.
- // so this buffer doesn't meet requirements, its a fatal now. Add this buffer back to free list
- m_freeBufferList.InsertToTail(pNode);
- }
- }
- }
-
- if (m_freeBufferList.NumNodes() < GetStaticSettings()->MPMKeepMinNumFreeBuffers)
- {
- /// @todo (CAMX-4157) Trigger offline thread to allocate buffers in parallel
-
- // No more buffers available, keep minimum number of buffers extra always
- // Allocate : GetStaticSettings()->MPMKeepMinNumFreeBuffers - m_freeBufferList.NumNodes()
- // Trigger thread to allocate the buffer offline
- }
- }
-
- getBufferPoolResult.result = result;
- getBufferPoolResult.hBufferHandle = static_cast
(pBuffer); -
- m_pLock->Unlock();
-
- return getBufferPoolResult;
- }
mmap最常见的四种用途分别是: