Nuttx相关的历史文章:
介绍
Nuttx的内存管理模块代码,位于nuttx/mm
目录下,分别有以下几个子目录:
- mm_heap:通用堆分配器相关代码
- umm_heap:用户模式下堆分配器相关代码
- kmm_heap:内核模式下堆分配器相关代码
- mm_gran:颗粒分配器相关代码
- shm:共享内存相关代码
介绍如下:
nuttx/mm
目录包含了Nuttx内存管理单元的逻辑,包括:
- 标准内存管理函数
标准函数
标准的函数接口就如stdlib.h
中描述一样,按IEEE Std 1003.1-2003
中来规定的。包括以下文件:
标准的接口:mm_malloc.c, mm_calloc.c, mm_realloc.c, mm_memalign.c, mm_free.c
不那么标准的接口:mm_zalloc.c, mm_mallinfo.c
内部实现接口:mm_initialize.c, mm_sem.c, mm_addfreechunk.c, mm_size2ndx.c, mm_shrinkchunk.c
编译和配置文件:Kconfig, Makefile
内存模型
小内存模型:如果MCU
只支持16-bit数据寻址的话,则自动使用小内存模型,堆的最大size为64K。通过配置CONFIG_SMALL_MEMORY
可以在更宽寻址的MCU上强制使用小内存模型。
大内存模型:分配器支持堆的大小最大到4G的范围。
这是通过使用一个可变长分配器来实现的,包括以下属性:
开销:大内存模型每次分配8个bytes,小内存模型每次分配4个bytes;
对齐:大内存模型8字节对齐,小内存模型4字节对齐。
堆分配的多种实现
这个分配器可以用于管理不同的堆。用数据结构struct_mm_heap_s
结构来描述堆,这个结构定义在include/nuttx/mm/mm.h
文件中。如果要创建一个堆的实例,你需要分配一个堆的结构,一般都是静态分配,比如:
include <nuttx/mm/mm.h>
static struct mm_heap_s g_myheap;
初始化则使用接口:
mm_initialize(&g_myheap, myheap_start, myheap_size);
当堆的实例被初始化后,就能被大多数的接口使用,比如:mm_malloc(), mm_realloc(), mm_free()
等,这些接口看起来很熟悉,因为跟malloc(), realloc(), free()
接口类似,不同的地方是,前边的接口需要把堆的实例当做参数传递进去。事实上,malloc(), realloc(), free()
底层都是调用mm_malloc(), mm_realloc(), mm_free()
接口来实现的。用户/内核堆
可以通过内核的配置选项,来支持用户模式堆和内核模式堆,子目录包括:
mm/mm_heap
:该目录存放所有堆分配器的基础逻辑;
mm/umm_heap
:该目录存放用户模式堆分配接口;
mm/kmm_heap
:该目录存放内核模式堆分配接口;
- 颗粒分配器
mm_gran
目录提供了一个颗粒分配器,颗粒分配器以固定大小块分配内存,分配可以与用户提供的地址边界对齐。颗粒分配器接口在nuttx/include/nuttx/mm/gran.h
头文件中定义,在这个目录中包含了实现的逻辑代码文件:mm_gran.h, mm_granalloc.c, mm_grancritical.c, mm_granfree.c, mm_graninit.c
。
在Nuttx中,颗粒分配器用的并不广泛,颗粒分配器的目的是提供一个工具来支持平台特定的DMA内存对齐。
注意:由于每个颗粒可能是对齐的,并且每个分配都是以颗粒大小为单位的,因此颗粒的大小选择是很重要的:较大的颗粒将提供更好的性能和更少的开销,但是由于量化浪费而造成更多的内存损失。对齐可能造成额外的内存浪费,堆对齐不应该被使用,除非:1)使用颗粒分配器来管理DMA内存;2)硬件有特定的内存对齐需求。
当前的实现将最大分配限制大小限制为32个颗粒。这种限制也可以通过额外的编码工作来消除,但是目前需要更大的粒度来进行更大的分配。
使用例子:
通过使用GCC section
属性来在内存中定位一个DMA的堆(在链接脚本中将.dmaheap分配给DMA内存)
FAR uint32_t g_dmaheap[DMAHEAP_SIZE] __attribute__((section(.dmaheap)));
通过调用gran_initialize()
接口来创建堆,假设颗粒设置的大小为64Byte,按16Byte对齐:
GRAN_HANDLE handle = gran_initialize(g_dmaheap, DMAHEAP_SIZE, 6, 4);
此时,GRAN_HANDLE
能被用于分配内存了(如果CONFIG_GRAN_SINGLE=y
的话,GRAN_HANDLE
不会被定义)
FAR uint8_t *dma_memory = (FAR uint8_t *)gran_alloc(handle, 47);
实际分配的内存为64Byte(浪费17Bytes),并且会对齐到至少(1 << log2align)
- 页分配器
页分配器是基于颗粒分配器的一个应用,它是一种特殊用途的内存分配器,用于为具有内存管理单元(MMU)的系统分配物理内存页。
它的逻辑代码也位于mm_gran
目录下。
- 共享内存管理
当Nuttx编译成内核模式时,地址空间都是分开的。具有特权的内核地址空间与不具备特权的用户模式地址之间的内存共享需要被管理起来。共享内存区域是用户可访问的区域,可以附加到用户进程地址空间中,以便在用户进程之间共享。
共享内存的逻辑代码位于mm/shm
目录下。
数据结构
内存管理相关的数据结构不多,关键的有三个,如下所示:
/* This describes an allocated chunk. An allocated chunk is
* distinguished from a free chunk by bit 15/31 of the 'preceding' chunk
* size. If set, then this is an allocated chunk.
*/
struct mm_allocnode_s
{
mmsize_t size; /* Size of this chunk */
mmsize_t preceding; /* Size of the preceding chunk */
};
/* This describes a free chunk */
struct mm_freenode_s
{
mmsize_t size; /* Size of this chunk */
mmsize_t preceding; /* Size of the preceding chunk */
FAR struct mm_freenode_s *flink; /* Supports a doubly linked list */
FAR struct mm_freenode_s *blink;
};
/* This describes one heap (possibly with multiple regions) */
struct mm_heap_s
{
/* Mutually exclusive access to this data set is enforced with
* the following un-named semaphore.
*/
sem_t mm_semaphore;
pid_t mm_holder;
int mm_counts_held;
/* This is the size of the heap provided to mm */
size_t mm_heapsize;
/* This is the first and last nodes of the heap */
FAR struct mm_allocnode_s *mm_heapstart[CONFIG_MM_REGIONS];
FAR struct mm_allocnode_s *mm_heapend[CONFIG_MM_REGIONS];
#if CONFIG_MM_REGIONS > 1
int mm_nregions;
#endif
/* All free nodes are maintained in a doubly linked list. This
* array provides some hooks into the list at various points to
* speed searches for free nodes.
*/
struct mm_freenode_s mm_nodelist[MM_NNODES];
};
-
struct mm_allocnode_s
用于描述已经分配的内存块,这个数据结构中preceding
成员的15bit/31bit的值用于标记该内存块是否已经释放掉了,具体是哪一位来标记,跟使用的内存模型有关系。 -
struct mm_freenode_s
描述一个空闲的内存块,其中空闲的内存块都会连接成一个双向链表。 -
struct mm_heap_s
描述堆的结构,有两处需要注意的:1)mm_heapstart/mm_heapend
用于描述堆的起始和结束,这个相当于是两个哨兵,用于确保分配是在这两个哨兵的中间,然后会在这两个哨兵中间创建一个内存节点;2)mm_nodelist
存放所有空闲的内存块,这个结构是一个数组,数组里的元素又是双向链表,数组的大小为MM_NNODES
,它的值为(MM_MAX_SHIFT - MM_MIN_SHIFT + 1)
,MM_MIN_SHIFT = 4
对应16bytes,MM_MAX_SHIFT = 22
对应4Mb,这么设置是类似于linux buddy system
的机制,内存块都以2的次幂来划分,这个数组每一项就对应2的某次幂的双向链表。
此外,有一点需要注意的是内存分配在低层按chunk块去组织,实际上一个块需要包含两部分的内容:header + payload
,也就是头部信息+实际可用的内存。
如图所示:
原理分析
mm_heap/kmm_heap/umm_heap/
以mm_malloc()
和mm_free()
为例来分析:
从上图可知,mm_nodelist[]
存放的是不同大小的内存块双向链表,按2的次幂,比如16,32,64,128,256,512等来划分,如果内存块的大小为16-32之间,那就存放在16对应的双向链表中,并按大小进行排序。以此类推。
mm_malloc()
- 当进行内存分配的时候,申请
size
大小的内存空间,先对size
进行对齐调整,然后再根据调整后的size对2求幂运算,从而找到mm_nodelist[]
的索引值,进而找到最匹配的双向链表; - 遍历双向链表(链表已经按大小排序),找到第一个大于申请
size
的chunk
; -
chunk
的大小大于申请的size
,所以会将chunk
分成两个chunk
,一个是申请部分node
用于返回给申请者,需要从链表中移除,另一个是剩余部分remainder
重新添加回堆结构中,根据remainder
部分的大小,对2求幂,找到合适的空闲链表,将该结构插入到对应的链表中。 - 在申请过程中,会去将
preceding
成员中设置MM_ALLOC_BIT
位,用于标记内存块已经被分配了。
在双向链表的操作过程中,数据结构是离散组织的,但是物理内存都是连续的一片区域。
/****************************************************************************
* Name: mm_malloc
*
* Description:
* Find the smallest chunk that satisfies the request. Take the memory from
* that chunk, save the remaining, smaller chunk (if any).
*
* 8-byte alignment of the allocated data is assured.
*
****************************************************************************/
FAR void *mm_malloc(FAR struct mm_heap_s *heap, size_t size)
{
FAR struct mm_freenode_s *node;
void *ret = NULL;
int ndx;
/* Handle bad sizes */
if (size < 1)
{
return NULL;
}
/* Adjust the size to account for (1) the size of the allocated node and
* (2) to make sure that it is an even multiple of our granule size.
*/
size = MM_ALIGN_UP(size + SIZEOF_MM_ALLOCNODE);
/* We need to hold the MM semaphore while we muck with the nodelist. */
mm_takesemaphore(heap);
/* Get the location in the node list to start the search. Special case
* really big allocations
*/
if (size >= MM_MAX_CHUNK)
{
ndx = MM_NNODES-1;
}
else
{
/* Convert the request size into a nodelist index */
ndx = mm_size2ndx(size);
}
/* Search for a large enough chunk in the list of nodes. This list is
* ordered by size, but will have occasional zero sized nodes as we visit
* other mm_nodelist[] entries.
*/
for (node = heap->mm_nodelist[ndx].flink;
node && node->size < size;
node = node->flink);
/* If we found a node with non-zero size, then this is one to use. Since
* the list is ordered, we know that is must be best fitting chunk
* available.
*/
if (node)
{
FAR struct mm_freenode_s *remainder;
FAR struct mm_freenode_s *next;
size_t remaining;
/* Remove the node. There must be a predecessor, but there may not be
* a successor node.
*/
DEBUGASSERT(node->blink);
node->blink->flink = node->flink;
if (node->flink)
{
node->flink->blink = node->blink;
}
/* Check if we have to split the free node into one of the allocated
* size and another smaller freenode. In some cases, the remaining
* bytes can be smaller (they may be SIZEOF_MM_ALLOCNODE). In that
* case, we will just carry the few wasted bytes at the end of the
* allocation.
*/
remaining = node->size - size;
if (remaining >= SIZEOF_MM_FREENODE)
{
/* Get a pointer to the next node in physical memory */
next = (FAR struct mm_freenode_s *)(((FAR char *)node) + node->size);
/* Create the remainder node */
remainder = (FAR struct mm_freenode_s *)(((FAR char *)node) + size);
remainder->size = remaining;
remainder->preceding = size;
/* Adjust the size of the node under consideration */
node->size = size;
/* Adjust the 'preceding' size of the (old) next node, preserving
* the allocated flag.
*/
next->preceding = remaining | (next->preceding & MM_ALLOC_BIT);
/* Add the remainder back into the nodelist */
mm_addfreechunk(heap, remainder);
}
/* Handle the case of an exact size match */
node->preceding |= MM_ALLOC_BIT;
ret = (void *)((FAR char *)node + SIZEOF_MM_ALLOCNODE);
}
mm_givesemaphore(heap);
/* If CONFIG_DEBUG_MM is defined, then output the result of the allocation
* to the SYSLOG.
*/
#ifdef CONFIG_DEBUG_MM
if (!ret)
{
mwarn("WARNING: Allocation failed, size %d\n", size);
}
else
{
minfo("Allocated %p, size %d\n", ret, size);
}
#endif
return ret;
}
mm_free
- 当内存进行释放的时候,先将内存地址(
payload
)减去SIZEOF_MM_ALLOCNODE
偏移,这个偏移是chunk
的头部大小,从而得到整个chunk
的描述符,将该chunk
标记成空闲的状态; - 检查
chunk
的下一个节点状态,如果也是空闲的状态,则进行内存合并; - 检查
chunk
的上一个节点状态,如果也是空闲的状态,则进行内存合并;
注意,此处检查节点,是物理上的连接的块,这些块可能由于大小不一致,导致描述这些块的数据结构,会位于不同的链表中。如下图:
/****************************************************************************
* Name: mm_free
*
* Description:
* Returns a chunk of memory to the list of free nodes, merging with
* adjacent free chunks if possible.
*
****************************************************************************/
void mm_free(FAR struct mm_heap_s *heap, FAR void *mem)
{
FAR struct mm_freenode_s *node;
FAR struct mm_freenode_s *prev;
FAR struct mm_freenode_s *next;
minfo("Freeing %p\n", mem);
/* Protect against attempts to free a NULL reference */
if (!mem)
{
return;
}
/* We need to hold the MM semaphore while we muck with the
* nodelist.
*/
mm_takesemaphore(heap);
/* Map the memory chunk into a free node */
node = (FAR struct mm_freenode_s *)((FAR char *)mem - SIZEOF_MM_ALLOCNODE);
node->preceding &= ~MM_ALLOC_BIT;
/* Check if the following node is free and, if so, merge it */
next = (FAR struct mm_freenode_s *)((FAR char *)node + node->size);
if ((next->preceding & MM_ALLOC_BIT) == 0)
{
FAR struct mm_allocnode_s *andbeyond;
/* Get the node following the next node (which will
* become the new next node). We know that we can never
* index past the tail chunk because it is always allocated.
*/
andbeyond = (FAR struct mm_allocnode_s *)((FAR char *)next + next->size);
/* Remove the next node. There must be a predecessor,
* but there may not be a successor node.
*/
DEBUGASSERT(next->blink);
next->blink->flink = next->flink;
if (next->flink)
{
next->flink->blink = next->blink;
}
/* Then merge the two chunks */
node->size += next->size;
andbeyond->preceding = node->size | (andbeyond->preceding & MM_ALLOC_BIT);
next = (FAR struct mm_freenode_s *)andbeyond;
}
/* Check if the preceding node is also free and, if so, merge
* it with this node
*/
prev = (FAR struct mm_freenode_s *)((FAR char *)node - node->preceding);
if ((prev->preceding & MM_ALLOC_BIT) == 0)
{
/* Remove the node. There must be a predecessor, but there may
* not be a successor node.
*/
DEBUGASSERT(prev->blink);
prev->blink->flink = prev->flink;
if (prev->flink)
{
prev->flink->blink = prev->blink;
}
/* Then merge the two chunks */
prev->size += node->size;
next->preceding = prev->size | (next->preceding & MM_ALLOC_BIT);
node = prev;
}
/* Add the merged node to the nodelist */
mm_addfreechunk(heap, node);
mm_givesemaphore(heap);
}
标准库中的malloc()/free()
函数就是调用mm_malloc()/mm_free()
接口来实现的,在malloc()
中,还调用了sbrk()
函数,sbrk()
低层是调用mm_sbrk()
接口,它的作用就是用来扩展堆的区域,上文中提到过堆结构中有一个成员mm_heapend
用于存放的是堆的尾部,mm_sbrk()
接口会去扩展尾部区域,最终扩大堆的空间。
umm_heap/, kmm_heap/
两个路径下的代码都是调用mm_heap/
目录中的接口来实现,因此逻辑都是一致的。
mm_gran/
mm_gran
目录下存放的是颗粒分配器的逻辑代码,关键的数据结构为struct gran_s
:
/* This structure represents the state of one granule allocation */
struct gran_s
{
uint8_t log2gran; /* Log base 2 of the size of one granule */
uint16_t ngranules; /* The total number of (aligned) granules in the heap */
#ifdef CONFIG_GRAN_INTR
irqstate_t irqstate; /* For exclusive access to the GAT */
#else
sem_t exclsem; /* For exclusive access to the GAT */
#endif
uintptr_t heapstart; /* The aligned start of the granule heap */
uint32_t gat[1]; /* Start of the granule allocation table */
};
-
log2gran
,描述的是颗粒的大小对2取对数的值,比如,log2gran = 4
,则表明颗粒的大小为16byte; -
ngranules
,描述的是整个堆中,颗粒的个数; -
irqstate/exclsem
,用于颗粒分配表的互斥访问; -
heapstart
,堆的起始地址; -
gat[]
,颗粒分配表的起始地址,这个数组元素只有一个,只是用于标记它是一个地址,并且该地址存放的是32位的数值,从该地址可以继续往后扩展;颗粒分配表数组中,每个元素为32位的值,每一位用于标记对应的颗粒是否已经被分配,这也就对应到颗粒分配时,每次最大只能分配32个颗粒。
原理如下图:
gran_alloc()
gran_alloc()
是调用gran_common_alloc()
接口来完成的
- 根据申请分配的
size
,得出需要分配颗粒granule
的数量ngranules
; - 查询颗粒分配表,找到
ngranules
个连续的颗粒区域,查找的过程中,颗粒的索引号以32为步长进行增加,也就是32个颗粒为一个跨度来查询;同时颗粒的索引号可以对应到颗粒分配表中表项的索引号,比如如果颗粒索引号为1-31
之间,对应的就是gat[0]
,如果是32-63
之间,对应的就是gat[1]
; - 在堆颗粒分配表中的表项进行位处理的时候,采用的是类似于二分法的策略,每次折半来判断比特位的状态,对应到颗粒是否被分配的状态,并对表项的值进行移位处理,此外,需要注意的是跨两个区域的处理,也就是申请的内存区域,可能是两部分组成:前32个颗粒的结束部分,后32个颗粒的开始部分。
static inline FAR void *gran_common_alloc(FAR struct gran_s *priv, size_t size)
{
unsigned int ngranules;
size_t tmpmask;
uintptr_t alloc;
uint32_t curr;
uint32_t next;
uint32_t mask;
int granidx;
int gatidx;
int bitidx;
int shift;
DEBUGASSERT(priv && size <= 32 * (1 << priv->log2gran));
if (priv && size > 0)
{
/* Get exclusive access to the GAT */
gran_enter_critical(priv);
/* How many contiguous granules we we need to find? */
tmpmask = (1 << priv->log2gran) - 1;
ngranules = (size + tmpmask) >> priv->log2gran;
/* Then create mask for that number of granules */
DEBUGASSERT(ngranules <= 32);
mask = 0xffffffff >> (32 - ngranules);
/* Now search the granule allocation table for that number of contiguous */
alloc = priv->heapstart;
for (granidx = 0; granidx < priv->ngranules; granidx += 32)
{
/* Get the GAT index associated with the granule table entry */
gatidx = granidx >> 5;
curr = priv->gat[gatidx];
/* Handle the case where there are no free granules in the entry */
if (curr == 0xffffffff)
{
alloc += (32 << priv->log2gran);
continue;
}
/* Get the next entry from the GAT to support a 64 bit shift */
if (granidx < priv->ngranules)
{
next = priv->gat[gatidx + 1];
}
/* Use all ones when are at the last entry in the GAT (meaning
* nothing can be allocated.
*/
else
{
next = 0xffffffff;
}
/* Search through the allocations in the 'curr' GAT entry
* to see if we can satisfy the allocation starting in that
* entry.
*
* This loop continues until either all of the bits have been
* examined (bitidx >= 32), or until there are insufficient
* granules left to satisfy the allocation.
*/
for (bitidx = 0;
bitidx < 32 && (granidx + bitidx + ngranules) <= priv->ngranules;
)
{
/* Break out if there are no further free bits in 'curr'.
* All of the zero bits might have gotten shifted out.
*/
if (curr == 0xffffffff)
{
break;
}
/* Check for the first zero bit in the lower or upper 16-bits.
* From the test above, we know that at least one of the 32-
* bits in 'curr' is zero.
*/
else if ((curr & 0x0000ffff) == 0x0000ffff)
{
/* Not in the lower 16 bits. The first free bit must be
* in the upper 16 bits.
*/
shift = 16;
}
/* We know that the first free bit is now within the lower 16
* bits of 'curr'. Is it in the upper or lower byte?
*/
else if ((curr & 0x0000ff) == 0x000000ff)
{
/* Not in the lower 8 bits. The first free bit must be in
* the upper 8 bits.
*/
shift = 8;
}
/* We know that the first free bit is now within the lower 4
* bits of 'curr'. Is it in the upper or lower nibble?
*/
else if ((curr & 0x00000f) == 0x0000000f)
{
/* Not in the lower 4 bits. The first free bit must be in
* the upper 4 bits.
*/
shift = 4;
}
/* We know that the first free bit is now within the lower 4 bits
* of 'curr'. Is it in the upper or lower pair?
*/
else if ((curr & 0x000003) == 0x00000003)
{
/* Not in the lower 2 bits. The first free bit must be in
* the upper 2 bits.
*/
shift = 2;
}
/* We know that the first free bit is now within the lower 4 bits
* of 'curr'. Check if we have the allocation at this bit position.
*/
else if ((curr & mask) == 0)
{
/* Yes.. mark these granules allocated */
gran_mark_allocated(priv, alloc, ngranules);
/* And return the allocation address */
gran_leave_critical(priv);
return (FAR void *)alloc;
}
/* The free allocation does not start at this position */
else
{
shift = 1;
}
/* Set up for the next time through the loop. Perform a 64
* bit shift to move to the next gran position andi ncrement
* to the next candidate allocation address.
*/
alloc += (shift << priv->log2gran);
curr = (curr >> shift) | (next << (32 - shift));
next >>= shift;
bitidx += shift;
}
}
gran_leave_critical(priv);
}
return NULL;
}
gran_free()
同样的,gran_free()
调用gran_common_free()
接口来完成内存释放的。
- 根据释放的内存地址,得出第一个颗粒的索引号;
- 根据释放的
size
得出要释放颗粒的总数ngranules
; - 判断
ngranules
是否超出了颗粒分配表中表项对应的可用颗粒数,超出了表明这个是跨区域分配的,释放的时候,需要修改两个颗粒分配表表项,否则只需要修改一个。
看代码吧:
static inline void gran_common_free(FAR struct gran_s *priv,
FAR void *memory, size_t size)
{
unsigned int granno;
unsigned int gatidx;
unsigned int gatbit;
unsigned int granmask;
unsigned int ngranules;
unsigned int avail;
uint32_t gatmask;
DEBUGASSERT(priv && memory && size <= 32 * (1 << priv->log2gran));
/* Get exclusive access to the GAT */
gran_enter_critical(priv);
/* Determine the granule number of the first granule in the allocation */
granno = ((uintptr_t)memory - priv->heapstart) >> priv->log2gran;
/* Determine the GAT table index and bit number associated with the
* allocation.
*/
gatidx = granno >> 5;
gatbit = granno & 31;
/* Determine the number of granules in the allocation */
granmask = (1 << priv->log2gran) - 1;
ngranules = (size + granmask) >> priv->log2gran;
/* Clear bits in the GAT entry or entries */
avail = 32 - gatbit;
if (ngranules > avail)
{
/* Clear bits in the first GAT entry */
gatmask = (0xffffffff << gatbit);
DEBUGASSERT((priv->gat[gatidx] & gatmask) == gatmask);
priv->gat[gatidx] &= ~gatmask;
ngranules -= avail;
/* Clear bits in the second GAT entry */
gatmask = 0xffffffff >> (32 - ngranules);
DEBUGASSERT((priv->gat[gatidx+1] & gatmask) == gatmask);
priv->gat[gatidx+1] &= ~gatmask;
}
/* Handle the case where where all of the granules came from one entry */
else
{
/* Clear bits in a single GAT entry */
gatmask = 0xffffffff >> (32 - ngranules);
gatmask <<= gatbit;
DEBUGASSERT((priv->gat[gatidx] & gatmask) == gatmask);
priv->gat[gatidx] &= ~gatmask;
}
gran_leave_critical(priv);
}
在nuttx中,页分配机制就是基于颗粒分配器来实现的。
shm/
调用接口
共享内存只有Nuttx在内核编译模式下(CONFIG_BUILD_KERNEL=y
)时才可用,包括了以下几个接口:
-
int shmget(key_t key, size_t size, int shmflg)
:获取key
对应的共享内存描述符; -
void *shmat(int shmid, FAR const void *shmaddr, int shmflg)
:将shmid
对应的共享内存描述符指定的内存段关联到调用进程的地址空间; -
int shmctl(int shmid, int cmd, FAR struct shmid_ds *buf)
:提供cmd
指定的各种共享内存控制操作; -
int shmdt(FAR const void *shmaddr)
:将shmaddr
指定的共享内存段从调用进程的地址空间中分离出来;
数据结构
核心数据结构如下:
/* Unsigned integer used for the number of current attaches that must be
* able to store values at least as large as a type unsigned short.
*/
typedef unsigned short shmatt_t;
struct shmid_ds
{
struct ipc_perm shm_perm; /* Operation permission structure */
size_t shm_segsz; /* Size of segment in bytes */
pid_t shm_lpid; /* Process ID of last shared memory operation */
pid_t shm_cpid; /* Process ID of creator */
shmatt_t shm_nattch; /* Number of current attaches */
time_t shm_atime; /* Time of last shmat() */
time_t shm_dtime; /* Time of last shmdt() */
time_t shm_ctime; /* Time of last change by shmctl() */
};
/* This structure represents the state of one shared memory region
* allocation. Cast compatible with struct shmid_ds.
*/
/* Bit definitions for the struct shm_region_s sr_flags field */
#define SRFLAG_AVAILABLE 0 /* Available if no flag bits set */
#define SRFLAG_INUSE (1 << 0) /* Bit 0: Region is in use */
#define SRFLAG_UNLINKED (1 << 1) /* Bit 1: Region perists while references */
struct shm_region_s
{
struct shmid_ds sr_ds; /* Region info */
bool sr_flags; /* See SRFLAGS_* definitions */
key_t sr_key; /* Lookup key */
sem_t sr_sem; /* Manages exclusive access to this region */
/* List of physical pages allocated for this memory region */
uintptr_t sr_pages[CONFIG_ARCH_SHM_NPAGES];
};
/* This structure represents the set of all shared memory regions.
* Access to the region
*/
struct shm_info_s
{
sem_t si_sem; /* Manages exclusive access to the region list */
struct shm_region_s si_region[CONFIG_ARCH_SHM_MAXREGIONS];
};
-
struct shm_info_s
:描述的是所有共享内存区域的集合,并且需要控制互斥访问,在实现中使用了该结构来定义了一个全局变量g_shminfo
,表示所有的共享内存区域; -
struct shm_region_s
:描述的是一个共享内存区域的信息,共享内存区域的使用标记,对应的key值,共享内存区域大小等; -
struct shmid_ds
:描述的是一个内存区域的信息,主要包括权限值、process ID值,以及不同操作的时间值;
int shmget(key_t key, size_t size, int shmflg)
- 通过
key
值去查找共享内存区域集合中的每个区域,看看是否能找到匹配的结构; - 如果没有找到,则需要调用
shm_create()
接口去创建一个,实际上这些都是静态 预留好的,只需要去struct shm_region_s si_region[]
数组中找寻一个可用的,并且做一些初始化设置即可; - 如果找到了,则判断这个共享区域的大小,是否符合申请的大小,不够的话,还需要调用
shm_extend()
接口去把共享物理内存区域进行扩大。
int shmget(key_t key, size_t size, int shmflg)
{
FAR struct shm_region_s *region;
int shmid = -1;
int ret;
/* Check for the special case where the caller doesn't really want shared
* memory (they why do they bother to call us?)
*/
if (key == IPC_PRIVATE)
{
/* Not yet implemented */
ret = -ENOSYS;
goto errout;
}
/* Get exclusive access to the global list of shared memory regions */
ret = sem_wait(&g_shminfo.si_sem);
if (ret >= 0)
{
/* Find the requested memory region */
ret = shm_find(key);
if (ret < 0)
{
/* The memory region does not exist.. create it if IPC_CREAT is
* included in the shmflags.
*/
if ((shmflg & IPC_CREAT) != 0)
{
/* Create the memory region */
ret = shm_create(key, size, shmflg);
if (ret < 0)
{
shmerr("ERROR: shm_create failed: %d\n", ret);
goto errout_with_semaphore;
}
/* Return the shared memory ID */
shmid = ret;
}
else
{
/* Fail with ENOENT */
goto errout_with_semaphore;
}
}
/* The region exists */
else
{
/* Remember the shared memory ID */
shmid = ret;
/* Is the region big enough for the request? */
region = &g_shminfo.si_region[shmid];
if (region->sr_ds.shm_segsz < size)
{
/* We we asked to create the region? If so we can just
* extend it.
*
* REVISIT: We should check the mode bits of the regions
* first
*/
if ((shmflg & IPC_CREAT) != 0)
{
/* Extend the region */
ret = shm_extend(shmid, size);
if (ret < 0)
{
shmerr("ERROR: shm_create failed: %d\n", ret);
goto errout_with_semaphore;
}
}
else
{
/* Fail with EINVAL */
ret = -EINVAL;
goto errout_with_semaphore;
}
}
/* The region is already big enough or else we successfully
* extended the size of the region. If the region was previously
* deleted, but waiting for processes to detach from the region,
* then it is no longer deleted.
*/
region->sr_flags = SRFLAG_INUSE;
}
/* Release our lock on the shared memory region list */
sem_post(&g_shminfo.si_sem);
}
return shmid;
errout_with_semaphore:
sem_post(&g_shminfo.si_sem);
errout:
set_errno(-ret);
return ERROR;
}
shmat()/shmdt()
这两个函数用于将指定的地址与进程的地址空间建立关联或者解除关联。
以shmat()
为例,在一个用户进程调用该接口时,会去调用gran_alloc
颗粒分配器接口分配一段虚拟地址空间,而共享内存对应的是一段物理空间内存,因此需要去调用架构相关的函数,完成这段虚拟地址空间,到物理共享内存之间的映射,简而言之,就是去修改页表项内容。一般体系结构代码会提供类似up_shmat()
的接口。
shmdt()
的原理也是一样的,最终通过清除页表项内容,解除关联。
总结
nuttx中内存管理,核心为两部分:1)mm_heap/
下,对物理内存的分配采用类似于Buddy System的机制,适用于在plat mode
编译模式下;2)mm_gran/
下,颗粒分配器,这个是分页机制的基础,同时也是共享内存的使用基础,用于内核编译模式下。