mmap详解
想写一篇文章,详细的介绍一下mmap,主要是原理、用法、mmap泄露来进行介绍。说到mmap,首先得从堆空间说起。
目录
想写一篇文章,详细的介绍一下mmap,主要是原理、用法、mmap泄露来进行介绍。说到mmap,首先得从堆空间说起。
申请堆空间
其实,不管是 32 位系统还是 64 位系统,内核都会维护一个变量 brk,指向堆的顶部,所以,brk 的位置实际上就决定了堆的大小。Linux 系统为我们提供了两个重要的系统调用来修改堆的大小,分别是 sbrk 和 mmap。接下来,我们来学习这两个系统调用是如何使用的。我们先来看 sbrk。
sbrk
sbrk 函数的头文件和原型定义如下:
#include <unistd.h>
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 的头文件和原型如下所示:
#include <unistd.h>
#include <sys/mman.h>
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 是如何用于父子进程之间的通信的,它的用法示例代码如下:
共享匿名映射 / 匿名共享映射
#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<MemPoolBuffer*>(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<MemPoolBuffer*>(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<MemPoolBufferHandle>(pBuffer);
m_pLock->Unlock();
return getBufferPoolResult;
}
mmap用途小结
mmap最常见的四种用途分别是:
- 私有匿名映射,用于分配堆空间;
- 私有文件映射,用于加载动态链接库;
- 共享匿名映射,用于父子进程之间通讯;
- 共享文件映射,用于多进程之间通讯,在camera hal中很常见。
mmap原理
私有匿名映射
私有文件映射
共享文件映射
共享匿名映射
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)