linux内存池能分配连续物理内存吗

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内存池能分配连续物理内存吗相关的知识,希望对你有一定的参考价值。

处理器通过地址访问内存单元,程序中用到的基址加偏移地址是线性地址,需要通过MMU将虚拟地址映射成物理地址。这给分配和释放内存带来方便:1)物理地址不连续的空间可以映射为逻辑上连续的虚拟地址。2)进程可以获得比实际内存大的"空间",虚拟内存使得进程在这种情况下仍可正常运行。
linux内核为驱动程序提供了一致的内存管理接口,因此不用考虑不同体系结构如何管理内存的。
在linux内核中分配内存用kmalloc和kfree。
kmalloc分配时可以被阻塞,且不对所获得的区域清零。它分配的区域在物理内存中也是连续的。
原型:
#include<linux/slab.h>
void *kmalloc(size_t size,int flags); //参数为分配大小及分配标志
flags参数:
GFP_KERNEL:内核内存通用分配方法,表示内存分配是由运行在内核空间的进程执行的。可休眠,所以使用GFP_KERNEL分配内存的函数必须是可重入的。
GFP_ATOMIC:用于在中断处理例程或者运行在进程上下文之外的代码中分配内存,不可休眠。内核通常会为原子性的分配预留一些空闲页面。
所有标志定义在 <linux/gfp.h>中。
size参数:
内核是基于页技术分配内存,以最佳的利用系统的RAM。
linux处理内存分配的方法是:创建一系列的内存对象池,每个池的内存大小事固定的,处理分配请求时,就直接在包含足够大的内存块中传递一个整款给请求者。内核只能分配一些预定义的固定大小的字节数组。kmalloc能处理的的最小内存块是32或者64,不大于128KB。
内存区段:
linux内核把内存分为3个区段:可用于DMA的内存,常规内存以及高端内存。kmalloc不能分配高端内存。内存区段在mm/page_alloc.c中实现。区段的初始化在对应的arch树下的mm/init.c中。

后备高速缓存 (lookaside cache)
内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间,因此不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。
linux2.6中USB和SCSI驱动程序使用了这种高速缓存,是为一些反复使用的块增加某些特殊的内存池。后背高速缓存管理也叫slab分配器,相关函数和类型在<linux/slab.h>中申明。
slab分配器实现高速缓存具有kmem_cache_t类型。
kmem_cache_t * kmem_cache_create( const char *name, size_t size, size_t align,
unsigned long flags;
void (*constructor)(void*,kmem_cache_t *, unsigned long),
void (*destructor)(void*, kmem_cache_t *, unsigned long));
用于创建一个新的高速缓存对象。
constructor用于初始化新分配的对象,destructor用于清除对象。
一旦某个对象的高速缓存被创建以后,就可以调用kmem_cache_alloc从中分配内存对象。
void * kmem_cache_alloc(kmem_cache_t *cache,int flags);
释放内存对象使用kmem_cache_free
void kmem_cache_free(kmem_cache_t *cache,const void *obj);
在内存空间都被释放后,模块被卸载前,驱动程序应当释放他的高速缓存。
int kmem_cache_destory(kmem_cache_t *cache);
要检查其返回状态,如果失败,表明莫块中发生了内存泄露。
基于slab的高速缓存scullc
kmem_cache_t *scullc_cache;
scullc_cache=kmem_cache_creat("scullc",scullc_quantum,0,SLAB_HWCACHE_ALIGN,NULL,NULL);
if(!scullc_cache)

scullc_cleanup();
return -ENOMEM;

if(!dpte->data[s_pos])

dptr->data[s_pos]=kmem_cache_alloc(scullc_cache,GFP_KERNEL);
if(!dptr->data[s_pos])
goto nomem;
memset(dptr->data[s_pos],0,scullc_quantum);

for(i=0;i<qset;i++)

if(dptr->data[i])
kmem_cache_free(scullc_cache,dptr->data[i]);

if(scullc_cache)
kmem_cache_destory(scullc_cache);
内存池:
内核中有些地方的内存分配是不允许失败的,为确保能分配成功,内核建立一种称为内存池的抽象,他试图始终保持空闲状态,以便紧急情况使用。
mempool_t * mempool_creat(int min_nr,
mempool_alloc_t *alloc_fn, //对象分分配 mempool_alloc_slab
mempool_free_t *free_fn, //释放 mempool_free_slab
void *pool_data);
可以用如下代码来构造内存池
cache=kmem_cache_creat(...); //创建一个高速缓存
pool=mempool_creat(MY_POOL_MINIMUM,mempool_alloc_slab,mempool_free_slab,cache);//建立内存池对象
void *mempool_alloc(mempool_t *poll,int gfp_mask);//分配对象
void *mempool_free(void *element,mempool_t *poll);//释放对象
void mempool_destroy(mempool_t *poll);//销毁内存池
注意:mempool会分配一些内存块,空闲且不会被用到,造成内存的大量浪费。所以一般情况不要用内存池。
参考技术A 不会,内存管理与内存回收机制决定它不可能连续分配物理内存。

Linux中的堆栈内存在物理上是连续的吗?

【中文标题】Linux中的堆栈内存在物理上是连续的吗?【英文标题】:Is stack memory contiguous physically in Linux? 【发布时间】:2018-09-10 17:02:18 【问题描述】:

据我所知,堆栈内存在虚拟内存地址上是连续的,但堆栈内存在物理上也是连续的?这与堆栈大小限制有关吗?

编辑:

我曾经认为堆栈内存在物理上不一定是连续的,但为什么我们认为堆栈内存总是比堆内存快?如果它在物理上不是连续的,堆栈如何更好地利用缓存?还有一件事总是让我感到困惑,cpu在数据段中执行指令,它不在虚拟内存中的堆栈段附近,我认为操作系统不会使堆栈段和数据段在物理上彼此靠近,所以这可能会损害缓存效果,您怎么看?

再次编辑: 也许我应该举个例子来更好地表达自己,如果我们要对大量数字进行排序,使用数组来存储数字比使用列表更好,因为每个列表节点都可能由malloc构造,所以它可能没有好好利用缓存,所以我说栈内存比堆内存快。

【问题讨论】:

我们有虚拟内存,不用关心物理内存布局。如果您不编写内核或驱动程序(或设计硬件,或设计对缓存的攻击),请忘记物理内存。 Nah - 进程/线程堆栈可以并且经常像任何其他虚拟内存一样被分页。用于中断处理的内核堆栈必须是非分页的。 Is stack memory contiguous?、Virtually contiguous vs. physically contiguous memory、Contiguous physical memory from userspace 等可能重复 @jww 我已经编辑了我的问题以显示我的真正困惑,如果您得到答案,请在下面发布您的答案或添加评论,非常感谢。 在大多数架构上,堆栈都可以通过简单的算术指令来增长或缩小。 malloc 是对一个函数的调用,该函数需要保留一个内存区域,最终分配新页面。那要花很多钱。简单来说,栈是一种限制性更强的数据结构。缓存与它关系不大,栈顶在缓存中几乎总是热的,因为它使用非常频繁,但很容易构造一个将其踢出缓存的函数。 【参考方案1】:

据我所知,堆栈内存在虚拟内存中是连续的 地址,但堆栈内存在物理上也是连续的?并这样做 与堆栈大小限制有关吗?

不,堆栈内存在物理地址空间中不一定是连续的。它与堆栈大小限制无关。这与操作系统如何管理内存有关。操作系统仅在第一次访问相应的虚拟页面(或自从它被分页到磁盘后第一次访问)时才分配一个物理页面。这称为demand-paging,它有助于节省内存使用量。

为什么我们认为堆栈内存总是更快 比堆内存?如果它不是物理上连续的,如何堆叠 充分利用缓存?

它与缓存无关。从堆栈分配和释放内存比堆更快。这是因为从堆栈分配和释放只需要一条指令(递增或递减堆栈指针)。另一方面,从堆中分配和/或解除分配内存涉及更多工作。有关详细信息,请参阅this 文章。

现在,一旦分配了内存(从堆或堆栈),访问分配的内存区域所需的时间取决于它是堆栈内存还是堆内存。这取决于内存访问行为以及是否是friendly 到缓存和内存架构。

如果我们要对大量的数字进行排序,使用数组来存储 数字比使用列表更好,因为每个列表节点都可能是 由 malloc 构造,因此它可能无法很好地利用缓存, 这就是为什么我说堆栈内存比堆内存快。

使用数组更快不是因为数组是从堆栈中分配的。可以从任何内存(堆栈、堆或任何地方)分配数组。它更快,因为数组通常一次连续访问一个元素。当第一个元素被访问时,包含该元素和其他元素的整个高速缓存行从内存中提取到 L1 高速缓存。因此访问该缓存行中的其他元素可以非常有效地完成,但访问缓存行中的第一个元素仍然很慢(除非缓存行是prefetched)。这是关键部分:因为缓存行是 64 字节对齐的,并且虚拟页面和物理页面也是 64 字节对齐的,所以可以保证任何缓存行完全驻留在单个虚拟页面和单个物理页面中。这使得获取缓存行变得高效。同样,所有这些都与数组是从堆栈还是堆中分配的无关。无论哪种方式都是正确的。

另一方面,由于链表的元素通常不连续(甚至在虚拟地址空间中也不连续),因此包含一个元素的缓存行可能不包含任何其他元素。所以获取每一个元素可能会更昂贵。

【讨论】:

还有一个问题:为什么Linux中的堆栈内存限制为8M? @cong 这样堆栈溢出只会使有问题的应用程序崩溃,而不会消耗系统的所有内存,这可能会使整个系统本身崩溃。通常,不希望应用程序同时从堆栈分配大量内存。大对象应该从堆中分配。【参考方案2】:

记忆就是记忆。堆栈内存不比堆内存快,也不慢。都是一样的。使内存成为堆栈或堆的唯一因素是应用程序如何分配它。完全可以在堆上分配一块内存,把它变成程序栈。

速度差异在于分配。通过从堆栈指针中减去一条指令来分配堆栈内存。

分配堆的过程取决于堆管理器,但它要复杂得多,可能需要将页面映射到地址空间。

【讨论】:

1.也许我应该举个例子更好地表达自己,如果我们要对大量数字进行排序,使用数组存储数字比使用列表更好,因为每个列表节点都可能由malloc构造,所以可能不需要缓存的优势,这就是为什么我说堆栈内存比堆内存快。 2.it is much more complex and may requiring mapping pages to the address space,至于栈,我认为栈内存在页表中肯定有条目,那么栈内存什么时候映射到地址空间。 当您想猜测缓存时,您的编程就会遇到麻烦。内存就是内存,因此 CPU 中的缓存管理器甚至无法区分堆和缓存。【参考方案3】:

不,不保证物理地址的连续性。不过没关系,因为用户空间程序不使用物理地址,所以不知道是不是这样。

【讨论】:

我已经编辑了我的问题以显示我的真正困惑,如果你有答案,请更新你的答案,非常感谢。 @cong:L1 缓存线通常为 64 字节,但单个 VM 页面通常为 4096 字节。因此,堆栈性能会因物理内存连续性而提高的想法站不住脚,因为在一个高速缓存行的级别上,内存始终是物理上连续的。我认为您的大多数新问题都可以回答为“不,这不是它的工作原理”,您需要开始阅读有关现代处理器中缓存如何工作的内容。 那么,为什么我们总是说栈内存比堆内存快呢?我想这一定和缓存有关,是不是因为堆内存在物理上不连续? 我知道堆和栈的内存访问本身是一样的,我可以举个例子更好地表达自己,如果我们要对大量数据进行排序,使用数组存储数据比使用数组更好使用列表,因为每个列表节点都可能由malloc构造,所以它可能没有很好地利用缓存,所以我说堆栈内存比堆内存快。 @cong:堆栈内存在使用方面并不比堆内存快。它只是在分配方面更快。在堆栈上分配只是增加堆栈指针。在最坏的情况下,在堆上分配需要一个系统调用(sbrkmmap)。【参考方案4】:

这是一个复杂的话题。

堆和堆栈(通常)具有相同的内存和内存类型(MTRR、每页缓存设置等)。 [mmap、文件、驱动程序可能有不同的策略,或者当用户显式更改时]。

堆栈可能会更快,因为它经常被使用。当你调用一个函数时,参数和局部变量被放入堆栈,所以缓存是新鲜的。另外,由于函数调用和返回的频率很高,可能在其他缓存级别中还有一些堆栈,并且很少对堆栈顶部进行分页(因为它是最近使用的)。

所以缓存可能会更快,但前提是您的变量很少。如果您允许堆栈上的大型数组,例如用alloca,优势就消失了。

总的来说,这是一个非常复杂的话题,最好不要优化太多,因为这会导致代码复杂,因此更难重构和代码的高级优化。 (例如,在多维数组上,索引(以及内存)和循环的顺序可以提高速度,但很快代码将无法维护)。

【讨论】:

以上是关于linux内存池能分配连续物理内存吗的主要内容,如果未能解决你的问题,请参考以下文章

Linux内存从0到1学习笔记(六,物理内存初始化之四 --- 内存分配器)---持续更新

Linux下内存空间分配物理地址与虚拟地址映射

Linux下内存空间分配物理地址与虚拟地址映射

Linux下内存空间分配物理地址与虚拟地址映射

Linux下内存空间分配物理地址与虚拟地址映射

Linux内存从0到1学习笔记(6.7,物理内存初始化之CMA初始化)