linux高端内存
‘壹’ linux怎么管理空闲内存
内存组织层次:页式管理—>(numa)—>node的zonelist—>32位DMA/NORMAL/HIGHMEM三个区,64位没有高端内存—>伙伴分配系统—>slab/slub/slob
2.创建进程时内存分配:实际上只分配task_struct和thread_info的内存,而且很可能是从slab缓存中分配的,当进程运行时由于缺页中断,才由内核层具体分配物理内存并与vm挂接
3.malloc是c runtime中的实现,是上层库的内存分配层,至于内核层的,可以看看__alloc_pages/alloc_pages/kmalloc(小内存直接slab,大内存还是alloc_pages)/vmalloc(alloc_page分配不连续的物理页,映射到连续的vm_struct中的pages指针数组)/vmap/map_vm_area等几个函数
‘贰’ Linux的内核空间和用户空间是如何划分的(以32位系统为例)
通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。地址分配如下图所示
直接映射区:线性空间中从3G开始最大896M的区间,为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。
动态内存映射区:该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理空间不一定连续。vmalloc分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存。
永久内存映射区:该区域可访问高端内存。访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。
固定映射区:该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。
‘叁’ Linux进程内存如何管理
Linux系统提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。在Linux系统中,进程的4GB内存空间被分为两个部分——用户空间与内核空间。用户空间的地址一般分布为0~3GB(即PAGE_OFFSET,在Ox86中它等于OxC0000000),这样,剩下的3~4GB为内核空间,用户进程通常只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。用户进程只有通过系统调用(代表用户进程在内核态执行)等方式才可以访问到内核空间。每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间的虚拟地址到物理地址映射是被所有进程共享的,内核的虚拟空间独立于其他程序。Linux中1GB的内核地址空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域。对于x86系统而言,一般情况下,物理内存映射区最大长度为896MB,系统的物理内存被顺序映射在内核空间的这个区域中。当系统物理内存大于896MB时,超过物理内存映射区的那部分内存称为高端内存(而未超过物理内存映射区的内存通常被称为常规内存),内核在存取高端内存时必须将它们映射到高端页面映射区。Linux保留内核空间最顶部FIXADDR_TOP~4GB的区域作为保留区。当系统物理内存超过4GB时,必须使用CPU的扩展分页(PAE)模式所提供的64位页目录项才能存取到4GB以上的物理内存,这需要CPU的支持。加入了PAE功能的Intel Pentium Pro及以后的CPU允许内存最大可配置到64GB,它们具备36位物理地址空间寻址能力。由此可见,对于32位的x86而言,在3~4GB之间的内核空间中,从低地址到高地址依次为:物理内存映射区隔离带vmalloc虚拟内存分配器区隔离带高端内存映射区专用页面映射区保留区。
‘肆’ 典型嵌入式linux软件部分由哪些模块组成他们的功能及相互联系 Bootloader分为哪两阶段分
典型的嵌入式系统,软件部分从下到上,分别是boot,kernel,rootfs,fsimg和上层应用。
起到的作用分别是,引导内核,启动内核,挂载根文件系统,挂载实际文件系统,开启上层应用主循环。
你问的这些问题,每一点都可以单独拿出来,长篇大论的讲很久了。建议去网上先看相关的资料。贪多求快是不好的,一个知识点一个知识点的掌握。
‘伍’ 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会分配一些内存块,空闲且不会被用到,造成内存的大量浪费。所以一般情况不要用内存池。
‘陆’ Linux内核空间内存动态申请
在Linux内核空间中申请内存涉及的函数主要包括kmalloc () 、_get_free _pages ()和vmalloc(等。kmalloc()和_get_free pages ()(及其类似函数)申请的内存位于DMA和常规区域的映射区,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmalloc()在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而vmalloc ()申请的虚拟内存和物理内存之间也没有简单的换算关系。
1.kmalloc ( )
给kmalloc() 的第一个参数是要分配的块的大小;第二个参数为分配标志,用于控制kmalloc ()的行为。最常用的分配标志是GFP_KERNEL,其含义是在内核空间的进程中申请内存。kmalloc ()的底层依赖于_get_free pages ()来实现,分配标志的前缀GFP正好是这个底层函数的缩写。使用GFP_KERNEL标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE申请内存。由于在中断处理函数、tasklet和内核定时器等非进程上下文中不能阻塞,所以此时驱动应当使用GFP_ATOMIC标志来申请内存。当使用GFP_ATOMIC标志申请内存时,若不存在空闲页,则不等待,直接返回。
其他的申请标志还包括GFP_USER(用来为用户空间页分配内存,可能阻塞)、GFP_HIGHUSER(类似GFP_USER,但是它从高端内存分配)、GFP_DMA(从DMA区域分配内存)、GFP_NOIO(不允许任何IO初始化)、GFP_NOFS(不允许进行任何文件系统调用)、__GFP_ HIGHMEM(指示分配的内存可以位于高端内存)、__(GFP COLD(请求一个较长时间不访问的页)、_GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)、_GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)、GFP_REPEAT(分配失败,则尽力重复尝试)、_GFP_NOFAIL(标志只许申请成功,不推荐)和__GFPNORETRY(若申请不到,则立即放弃)等。
使用kmalloc()申请的内存应使用kfree()释放,这个函数的用法和用户空间的free()类似。
2._get_free_pages ()
_get_free pages ()系列函数/宏本质上是Linux内核最底层用于获取空闲内存的方法,因为底层的buddy算法以2n页为单位管理空闲内存,所以最底层的内存申请总是以2n页为单位的。
get_free _pages ()系列函数/宏包括get_zeroed _page () 、_get_free_page ()和get_free pages () 。
__get_free_pages(unsigned int flags, unsigned int order) 该函数可分配多个页并返回分配内存的首地址,分配的页数为2order,分配的页也不清零。order允许的最大值是10(即1024页)或者11(即2048页),这取决于具体的硬件平台。