Linux中内存相关概念与内存申请的几种方式

来源:互联网 发布:91.v6p.co index.php 编辑:程序博客网 时间:2024/06/03 13:52

1 物理地址,MMU相关概念

Intel X86存在IO空间,相对于内存空间,通过IN OUT指令访问。大多数ARM PowerPC仅有内存空间。内存空间通过地址,指针访问。程序,程序运行中使用的变量都在内存空间。

物理地址

unsigned char *p = (unsigned char*) 0xF000FF00;*p = 1

0xF000FF00这个地址对于x86是16bit段地址+16bit偏移地址,即,0xF000 * 16 + 0xFF00 = 0xF0000 + 0xFF00 = 0xFFF00
地址对于ARM等为采用段地址的处理器,就是空间0xF000FF00。

x86处理器用实际地址做第一步跳转(软重启):

typedef void (*lpFunction) ();//define a function pointer typelpFunction lpReset = (lpFunction)0xF000FFF0; //get a pointer that point to the addrlpRest(); //go to the function at addr

MMU
memory management unit,辅助内存管理,提供虚拟和物理地址映射、内存访问权限保护、Cache缓存控制。
Kernel借助MMU让用户感觉可以使用很大的内存空间,而让开发者在写程序的时候可以不考虑物理实际容量。

TLB:Translation Lookaside Buffer,转换旁路缓存。是MMU的核心部件,缓存少量的虚拟–物理关系,是转换表的Cache,也称为“快表”。
TTW:Translation Table walk,转换表漫游。当TLB没有需要的转换对,通过内存中的转换表(常常是多级页表,从页表记地址寄存器找到页表,一层一层直到代码页。)访问得到虚拟–物理关系,TTW成功就写入TLB。
写入之后如果权限正确将访问Cache或者内存找到相应的数据。如果不允许,MMU会向ARM发送一个存储器异常。

Linux三级页表

  • PGD,Page Global Directory (页目录);
  • PMD,Page Middle Directory (页目录);
    *前两者内部的成为PDE,页目录项,Page Directory Entry。
  • PTE,Page Table Entry (页表项,每一个表项对应一个物理页)。

相关宏可见下图:
Macro

一般由虚拟地址三级查询得到PTE的页表的过程(page table walk):

  • 有描述进程占有资源的struct mm_sturct mm和需要访问的虚拟地址unsigned long addr
  • 通过pgd_offset(mm, addr)得到一级页表入口
  • 通过pmd_offset(pgd, addr)得到二级页表入口
  • 通过pte_offset_map(pmd, addr)得到目标页表项

更多详细可查看reference [1]

注:Linux 2.6支持不带MMU的处理器。其为了兼容嵌入式系统,融合了uClinux,来支持MMU-Less系统。

2 Linux内存管理

包含MMU的处理器可以使进程的访问空间达到4G,0-3G是User Space,3-4G是Kernel Space。PAGE_OFFSET为3G末尾,即0x86的0xC000 0000
每个进程有自己的页表,相互独立。内核空间由内核负责映射,固定不随进程变化。而1G的Kernel Space划分为:

  • 物理内存映射区(0-896MB),线性映射,常规内存。当物理内存大于896MB,超出部分称为高端内存。
  • 896MB之后的区域:
    • vmalloc分配器区(前后有隔离带,地址VMALLOC_START ~ VMALLOC_END)
    • 高端内存映射区(高端内存只能以映射在这里)(PKMAP_BASE) 更多关于 高端内存
    • 专用页面映射区(实际中FIXADDR_START ~ FIXADDR_TOP)这部分需要配置。
    • 保留区域(实际中FIXADDR_TOP ~ 4G区域)

当内存超过4G,需要使用CPU扩展分页(PAE)模式提供的64bit也目录项才能访问到更高物理内存,需要CPU支持。

3 内存存取

内存申请

下面会提到的内存申请:

  • malloc - free: 用户空间
  • kmalloc - kfree: 内核空间,物理连续
  • __get_free_pages - free_pages: 内核空间,物理连续
  • vmalloc - vfree: 内核空间,物理不连续,虚拟连续
  • slab: kmem_cache_create - kmem_cache_destory

1 用户空间内存动态申请
malloc()申请的空间在heap上,需要申请者用free()释放。注意尽量成对出现,避免内存泄漏。注:C Linux的malloc常用brk()mmap()系统调用实现。

2 内核空间内存动态申请

kmalloc()
申请内存位于物理内存映射区,物理上也连续。和真实物理地址只有一个固定的offset。
void *kmalloc(size_t size, int flags);size是大小,flag是标志,GFP_KERNEL表示在内核空间进程中申请内存。其底层依赖__get_free_pages()实现。使用这个flag后,如果不能满足,进程会睡眠等待页,可能会引起阻塞。因此不能在++中断上下文,spin lock++中使用GFP_KERNEL申请内存。
在++中断处理函数,tasklet,内核定时器++非进程上下文不能阻塞,应该用GFP_ATOMIC申请内存,不存在空闲会直接返回。
相应其他标志位定义于:include/linux/gfp.h。
kfree()释放空间。

__get_free_pages()
Linux Kernel最底层使用的获取空间的方法。底层以page的2^n为单位管理空闲内存,所以内存页的申请是以page为单位。
get_zeroed_page(unsigned int flags);指向一个清零的新page。
__get_free_page(unsigned int flags);指向新页但不清零,实际上是用了order为0的下一个函数。
__get_free_pages(unsigned int flags, unsigned int order);获取多个pages数量是2^order,不清零。order最大是10或11,硬件相关。
前面三个函数的实现其实是调用了alloc_pages(),该函数可以在用户空间,也可以在内核空间使用。返回struct page *
释放:
void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order); 特别注意order前后要一致。

vmalloc()
void *vmalloc(unsigned long size);
在虚拟空间得到一块连续区域,在vmalloc专用区,物理内存不一定连续。用于较大的顺序缓冲区分配内存,开销远大于GFP,新的页表要被建立。如果是用它申请少量内存,是不妥的。
释放void vfree(void *addr)

slab
以page为单位容易产生内部碎片(internal fragmentation)。同时设想如果能让前后两次相同对象的分配在同一块内存,而且已经保留的数据结构,就能提高效率。得到slab概念,驻留任意数目,同样大小的后背缓存。
创建:

struct kmem_cache *kmem_cache_create(    const char *name, size_t size,  //size为每个等大结构的大小byte    size_t align, unsigned long flags,    viod (*ctor)(void*, struct kmem_cache *, unsigned long),    void (*dtor)(void*, struct kmem_cache *, unsigned long));`

分配slab缓存:

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);//在之前分配的slab中分出一块并返回首指针

释放slab缓存:

void kmem_cache_free(struct kmem_cache *cachep, void *objp);

回收整个slab:

int kmem_cache_destroy(struct kmem_cache *cachep);

cat /proc/slabinfo可以获知slab的分配使用情况。

注意slab底层也依赖于__get_free_pges(),只是分割了小单元减少内部碎片方便管理。

内存池
也是用与分配大量小对象的后背缓存技术。
相关函数有mempool_createmempool_allocmempool_freemempool_destory

虚拟地址和物理地址

使用virt_to_phys()实现内核虚拟向物理地址的转化,函数实现和体系结构相关。phys_to_virt()物理向虚拟。仅仅适用于常规内存区域。


reference
[1] 三级页表,http://blog.csdn.net/myarrow/article/details/8624687
[2] 高端内存,http://ilinuxkernel.com/?p=1013
notification

source: 《Linux设备驱动开发详解》(第二版),内容为读书笔记和网络资料,有些资料原始来源不详,分享为了方便自己和他人查阅。如有侵权请及时告知,对于带来的不便非常抱歉。转载请注明来源。Terrence Zhou.

0 0