一、内存管理单元MMU
该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持。
(1) TLB:它缓存少量的虚拟地址与物理地址的转换关系。“快表”
(2) TTW:包含所有虚拟地址与物理地址的转换关系。TTW成功后,结果写入TLB
二、arm平台linux内存映射(来自《解析基于ARM9 的Linux 内存映射》)
arm中的地址转换表不是一下子就建立的,分为三个阶段
(1) 第一阶段是发生在内核解压缩,自引导时,也就内核镜像zimage 的文件头部分。在/arch/arm/boot/compressed/head.S中实现,这个映射表是临时的,是为了提高内核解压缩时的速度而实现的。在解压缩结束之后,进入内核代码之前,MMU 功能就被关闭了,随之的映射表也被废弃不用。
(2) 第二阶段是的页表创建是非常关键的。同样也是使用汇编语言来实现,具体文件路径为:/arch/arm/kernel/head.S。在代码,有个函数__create_page_tables,这就是创建MMU 映射表,开启MMU 做准备的重要动作
(3) 第三阶段是创建更高级别的映射关系和映射方法,主要和架构相关。不同的架构有不同的映射方法,比如ARM 就是二级映射的方法实现地址映射,但是必须按照LINUX 的三级映射模型PGD、PMD、PTE 来实现。从内核代码上来看,内核启动的过程中主要有两个函数比较重要:(1)start_kernel->setup_arch->paging_init->bootmem_init->bootmem_init_node->create_mapping(2)start_kernel->setup_arch->paging_init->devicemaps_init(3)start_kernel->trap_init函数一:重新建立了一级映射表,添加了对内存空间的二级映射,同时建立了bootmem 这个内存管理器。函数二:在再bootmem 的帮助下实现了平台部分设备IO 地址的映射。函数三:则是完成了中断矢量的映射。
三、用户空间内动态申请
#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr_to memory);
四、内核空间内存动态申请
void * kmalloc(size_t size,int flags);
kmalloc()的底层依赖__get_free_pages()实现。最常用的分配标志GFP_KERNEL,其含义为在内核空间的进程中申请内存。该函数标志可引起进程阻塞。
GFP_ATOMIC,不会引起阻塞。
GFP_USER,为用户空间分配内存。
GFP_HIGHUSER,类似GFP_USER,但是从高端分配内存。
GFP_NOIO,不允许I/O初始化
GFP_NOFS,不允许进行任何文件系统调用。
__GFP_DMA,要求分配在能够DMA的内存区。
__GFP_HIGHMEM,指示分配的内存可以位于高端内存。
__GFP_COLD,请求一个较长时间不访问的页。
__GFP_NOWARN,分配无法满足时,阻止内核发出警告。
__GFP_HIGH,高优先级请求,允许获得被内核保留给紧急状态使用的最后的内存页。
__GFP_REPEAT,分配失败则尽力重复尝试。
__GFP_NOFAIL,标志只允许申请成功,不推荐。
__GFP_NORETRY,若申请不成功,则放弃。
__get_free_pages()系列函数
get_zeroed_page(unsigned int flags);
返回一个指向新页的指针,并将该页清零。
__get_free_page(unsigned int flags);
返回一个指向新页的指针,不清零。
__get_free_pages(unsigned int flags,unsigned int order);
返回可分配多个页并返回分配内存的首地址,分配的页数为2的order次方。
void free_page(unsigned long addr);
void free_pages(unsigned long addr,unsigned long order);
flags的参数说明与kmalloc()的flags一致。
void *vmalloc(unsigned long size);
void vfree(void *addr);
此函数一般用于分配较大的缓冲区,分配少量的内存是不合理的
slab与内存池
slab在底层每次申请1页或多页,之后再分隔这些页为更小的单元,从而节省了内存。内存池和slab设计目的相仿。
创建slab
struct kmem_cache *kmem_cache_create(const char *name,
size_t size,size_t align,unsigned long flags,
void (*ctor)(void *,struct kmem_cache *,unsigned long),
void (*dtor)(void *,struct kmem_cache *,unsigned long));
flags,SLAB_NO_REAP,即使内存紧缺时也不自动收缩这块缓存。
SLAB_HWCACHE_ALIGN,每个数据对象被对齐到一人缓存行。
SLAB_CACHE_DMA,要求数据对象在DMA内存区分配。
分配slab缓存
void *kmem_cache_alloc(struct kmem_cache *cachep,gfp_t flags);
释放
void kmem_cache_free(struct kmem_cache *cachep,void *objp);
回收
int kmem_cache_destroy(struct kmem_cache *cachep);
slab使用模板
static kmem_cache_t *xxx_cachep;
xxx_cachep=kmem_cache_create(“xxx”,sizeof(struct xxx),0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,NULL,NULL);
struct xxx *ctx;
ctx=kmem_cache_alloc(xxx_cachep,GFP_KERNEL);
kmem_cache_free(xxx_cachep,ctx);
kmem_cache_destroy(xxx_cachep);
在/proc/slabinfo节点中可以获知当前slab的分配和使用情况。
创建内存池
mempoo_t *mempool_create(int min_nr,mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn,void *pool_data);
min_nr,预分配对象的数目
alloc_fn的free_fn,内存池对象分配和回收函数的指针。
pool_data,是分配和回收函数要用到的指针。
分配和回收对象
void *mempool_alloc(mempool_t *pool,int gfp_mask);
void *mempool_free(void *element,mempool_t *pool);
gfp_mask,分配标记,只有当__GFP_WAIT标记被指定时,分配函数才会休眠。
回收内存池
void mempool_destroy(mempool_t *pool);
五、I/O内存
动态映射
void *ioremap(unsigned long offset,unsigned long size);
void inunmap(void *addr);
访问
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value,void *addr);
void iowrite16(u16 value,void *addr);
void iowrite32(u32 value,void *addr);
void ioread8_rep(void *addr,void *buf,unsigned long count);
void ioread16_rep(void *addr,void *buf,unsigned long count);
void ioread32_rep(void *addr,void *buf,unsigned long count);
void iowrite8_rep(void *addr,void *buf,unsigned long count);
void iowrite16_rep(void *addr,void *buf,unsigned long count);
void iowrite32_rep(void *addr,void *buf,unsigned long count);
void memcpy_fromio(void *dest,void *source,unsigned int count);
void memcpy_toio(void *dest,void *source,unsigned int count);
I/O内存申请
struct resource *request_mem_region(unsigned long start,unsigned long len,char *name);
void release_mem_region(unsigned long start,unsigned long len);
上面两个函数是检查申请的资源是否可用,如果可用再进行ioremap()申请,这样会访问会相对安全一些。