博客园  :: 首页  :: 联系 :: 管理

读书笔记-Linux内核设计与实现-part 3

Posted on 2012-11-30 21:18  Apprentice89  阅读(331)  评论(0编辑  收藏  举报

Chapter 12 内存管理》

 

MMU:内存管理单元,管理内存,地址转换,通常以页为单位管理。

从虚存角度看,页是最小单位。

 

struct page {
  unsigned long     flag;             //页是否脏,是否被锁定在内存
  atomic_t          _count;           //页引用计数
  atomic_t          _mapcount;       //
  unsigned long     private;
  struct address_space *mapping;
  pdoff_t           index;
  struct list_head     lru;
  void              *virtual;
}

 

 

一个页的用途:由页缓存使用(mapping域指向与这个页关联的address_space)、作为私有数据(由private指向)、作为进程页表中的映射。

virtual域是页的虚拟地址(页在虚存中的地址)。

struct page是与物理页相关。

页的拥有者:用户进程空间、动态分配的内核数据、静态代码段、页高速缓存 等。

 

内存分区(ZONE):

某些硬件特性导致内存页面不能被一视同仁。

Linux使用了四种区:ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM。

区的使用与体系结构相关,例如,x86下,ISA设备不能使用32位的地址空间,只能使用0~16M的空间,所以ZONE_DMA包含在0~16M这个范围内。

struct zone;

 

获得页:

//获取2^order个连续的物理页。

struct page* alloc_page(gfp_t gfp_mask, unsigned int order);

//将struct page转为对应的逻辑地址

void* page_address(struct page* page);

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

struct page* alloc_page(gfp_t gfp_mask);

unsigned long __get_free_page(gfp_t gfp_mask);

unsigned long get_zeroed_page(gfp_t gfp_mask);

分配内存可能失败,失败时可能需要回到以前的状态,所以一开始就申请内存是个不错的想法。

 

释放页:

void __free_pages(struct page* page, unsigned int order);

void free_pages(unsigned long addr, unsigned int order);

void free_page(unsigned long addr);

释放页时只能释放属于自己的页,这个需要函数调用者保证。

 

kmalloc:

按字节分配,物理上连续。

void* kmalloc(size_t sizem gfp_t flag);

分配器标志:行为修饰符、区修饰符、类型。

void kfree(const void *ptr)

 

vmalloc:类似于kmalloc,但是vmalloc分配的内存虚拟地址是连续的,物理地址可能不连续。它分配非连续的物理页,然后通过‘修正’页表,将内存映射到逻辑地址空间的连续区域中。大多数情况下,只有硬件设备需要得到物理地址连续的内存。

vmalloc性能差于kmalloc(页表转换),且可能导致TLB抖动,所以vmalloc在不得已时才使用(例如,大块内存)。

 

slab:

一种对象对应于一种高速缓存,每一个高速缓存又被划分成slab,一个slab是由一个或连续几个物理页组成的。slab中存放数据结构。slab可能是满的,部分满的,空的。

例如struct inode由inode_cachep分配。

 

 

 

高速缓存永kmem_cache结构体来表示。这个结构体有三个链表 slab_full, slab_partial, slab_empty。

 

struct slab {
  struct list_head list;    //满(/部分/空)链表
  unsigned long coloroff;   //着色偏移?
  void *s_mem;                //第一个对象
  unsigned int inuse;        //已分配的对象个数
  kmem_bufctl_t free;        //第一个空闲对象
}

 

相关接口:

 

构造高速缓存:

struct kmem_cache *kmem_cache_create(const char* name,

                                       size_t size,   //每个元素的大小

                                       size_t align,

                                       unsigned long  flags,

                                       void (*ctor)(void*));

ctor是构造器,在高速缓存分配新页的时候调用。事实上,Linux不使用,设为NULL即可。

 

销毁高速缓存:

int kmem_cache_destroy(struct kmem_cache *cachep);

 

从高速缓存中分配/释放一个对象:

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

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

 

 

内核栈可以是一页或者两页,历史上,中断处理程序和进程共享一个内核栈,当内核栈只有一页时,中断使用自己的栈(中断栈)。

 

高端内存的映射:

将一个struct page(对应一个物理页)映射到虚存(返回虚拟地址):

void* kmap(struct page *pg) //可能睡眠

解除映射:

void* kunmap(struct page *pg)

临时映射(不睡眠)和解除映射

void* kmap_atomic(struct page* pg, enum km_type type)

void* kunmap_atomic(struct page* pg, enum km_type type)

 

 

CPU数据:

使用每CPU数据的原因:

  1. 减少了数据锁定
  2. 减少缓存失效

 

接口:

编译时定义每个CPU变量 

DEFINE_PER_CPU(type, name); //为每个处理器都定义了一个类型为type变量名为name的变量。

DECLARE_PER_CPU(type, name);

get_cpu_var(name)++; //加1,同时禁止抢占

put_cpu_var(name);   //重新允许抢占

 

运行时每CPU数据:

void *alloc_percpu(tpye); //一个宏

viud *__alloc_percpu(size_t size, size_t align);

void free_percpu(const void*);

 

get_cpu_var(ptr); //返回一个void类型的指针,指向ptr

put_cpu_var(ptr); //重新激活内核抢占