linux虚拟内存管理功能

? 大地址空间;
? 进程保护;
? 内存映射;
? 公平的物理内存分配;
? 共享虚拟内存。
实现结构剖析
 
(1)内存映射模块(mmap):负责把磁盘文件的逻辑地址映射到虚拟地址,以及把虚拟地址映射到物理地址
(2)交换模块(swap)负责控制内存内容的换入与换出,淘汰最近没访问的页,保留最近访问的页.
(3)core(核心内存管理模块):负责内存管理功能.
(4)结构特定模块:实现虚拟内存的物理基础
 
内核空间和用户空间
Linux简化了分段机制,使得虚拟地址跟线性地址一样.Linux将虚拟地址空间(4G)分为最高1G部分内核使用(所有进程共享).最低3G给进程使用

 内核占据了虚拟空间的最高1G直接,但映射到地址为0开始,是很简单的线性映射.PAGE_OFFSET为0xc0000000(物理地址与线性地址之间的位移量)
include/asm/i386/page.h 中对内核空间中地址映射的说明及定义
  1. /*
  2. * qkxue.net This handles the memory map.. We could make this a config
  3. * option, but too many people screw it up, and too few need
  4. * it.
  5. *
  6. * A __PAGE_OFFSET of 0xC0000000 means that the kernel has
  7. * a virtual address space of one gigabyte, which limits the
  8. * amount of physical memory you can use to about 950MB.
  9. *
  10. * If you want more physical memory than this then see the CONFIG_HIGHMEM4G
  11. * and CONFIG_HIGHMEM64G options in the kernel configuration.
  12. */
  13. #define __PAGE_OFFSET (0xC0000000)
  14. ……
  15. #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
  16. #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
  17. #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
代码注释说明:如果你的物理内存大于950MB,那么在编译内核时就需要加

CONFIG_HIGHMEM4G 和 CONFIG_HIGHMEM64G 选项,这种情况我们暂不考虑。如果物理内存小于
950MB,则对于内核空间而言,给定一个虚地址 x,其物理地址为“x- PAGE_OFFSET”,给定
一个物理地址 x,其虚地址为“x+ PAGE_OFFSET”。
这里再次说明,宏__pa()仅仅把一个内核空间的虚地址映射到物理地址,而决不适用于
用户空间,用户空间的地址映射要复杂得多。
2.内核映像

内核的代码和数据就叫内核映像.系统启动时,Linux内核被加载到物理地址为0x001000000开始的地方.1M开始区间.然而
正常运行时,整个内核映像应该在虚拟空间,因此,所有的符号地址都该加一个PAGE_OFFSET.这样内核映像为0xc01000000处
程的页目录 PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要
将寄存器 CR3 设置成指向新进程的页目录 PGD,而该目录的起始地址在内核空间中是虚地址,
但 CR3 所需要的是物理地址,这时候就要用__pa()进行地址转换。在 mm_context.h 中就有这么一行语句:
asm volatile(“movl %0,%%cr3”: :”r” (__pa(next->pgd));
这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址 next_pgd,通过
__pa()转换成物理地址,存放在某个寄存器中,然后用 mov 指令将其写入 CR3 寄存器中。经
过这行语句的处理,CR3 就指向新进程 next 的页目录表 PGD 了。

虚拟内存地址实现机制的关系
? 内存分配和回收机制;
? 地址映射机制;
? 缓存和刷新机制;
? 请页机制;
? 交换机制;
? 内存共享机制

 内存管理程序通过映射把用户的逻辑地址映射到物理地址,用户程序运行时发现虚地址没对应物理地址就发出(1)请求,如果有空闲的内存
可供分配,就请求分配内存(2),并且把正在使用的物理页记录在页缓存中(3).如果没有足够的内存分配,那就调用交换机制,腾出一部分内存(4)(5)
另外在地址映射时要通过TLB来选物理页(8);交换机制也要用到交换缓存(6).并且把物理页内容交换到交换文件后也要修改页表来映射文件地址(7)
--------------Linux内存管理初始化-------------------
Linux启动时,运行在实模式.随后要转到保护模式下运行.
,
  1. 1.页表的初步初始化
  2. /*
  3. * The page tables are initialized to only 8MB here - the final page
  4. * tables are set up later depending on memory size.
  5. */
  6. .org 0x2000
  7. ENTRY(pg0)//存放的是虚拟地址
  8. .org 0x3000
  9. ENTRY(pg1)
  10. /*
  11. * empty_zero_page must immediately follow the page tables ! ( The
  12. * initialization loop counts until empty_zero_page)
  13. */
  14. .org 0x4000
  15. ENTRY(empty_zero_page)
  16. /*
  17. * www.qixoo.qixoo.com Initialize page tables
  18. */
  19. movl $pg0-__PAGE_OFFSET,%edi /* initialize page tables 将物理地址存放在edi中,位置为0x1002000处*/
  20. movl $007,%eax /* "007" doesn‘t mean with right to kill, but
  21. PRESENT+RW+USER */
  22. 2: stosl
  23. add $0x1000,%eax
  24. cmp $empty_zero_page-__PAGE_OFFSET,%edi
  25. jne 2b
内核执行此代码时,页机制还未启动,指令还是纯物理地址.因为pg0存放的是虚拟地址,因此$pg0-__PAGE_OFFSET获得其物理地址
pg0存放在相对于内核代码起点为 0x2000 的地方,即物理地址为 0x00102000,而 pg1 的物理地址
则为 0x00103000。Pg0 和 pg1 这个两个页表中的表项则依次被设置为 0x007、0x1007、0x2007
等。其中最低的 3 位均为 1,表示这两个页为用户页,可写,且页的内容在内存中(参见图
2.24)。所映射的物理页的基地址则为 0x0、0x1000、0x2000 等,也就是物理内存中的页面 0、
1、2、3 等等,共映射 2K 个页面,即 8MB 的存储空间。由此可以看出,Linux 内核对物理内
存的最低要求为 8MB。紧接着存放的是 empty_zero_page 页(即零页),零页存放的是系统启
动参数和命令行参数,
启动分页机制--------------------
代码功能:将页目录swapper_pg_dir的物理地址加入cr3.并且把cr0最高位置为1
  1. /*
  2. * This is initialized to create an identity-mapping at 0-8M ( for bootup
  3. * purposes) and another mapping of the 0-8M area at virtual address
  4. * PAGE_OFFSET.
  5. */
  6. .org 0x1000
  7. ENTRY(swapper_pg_dir)
  8. .long 0x00102007 //两个页表是用户页表、可写且页表的内容在内存。
  9. .long 0x00103007
  10. .fill BOOT_USER_PGD_PTRS-2,4,0
  11. /* default: 766 entries */
  12. .long 0x00102007
  13. .long 0x00103007
  14. /* default: 254 entries */
  15. .fill BOOT_KERNEL_PGD_PTRS-2,4,0
  16. /*
  17. * Enable paging
  18. */
  19. 3:
  20. movl $swapper_pg_dir-__PAGE_OFFSET,%eax
  21. movl %eax,%cr3 /* set the page table pointer.. */
  22. movl %cr0,%eax
  23. orl $0x80000000,%eax
  24. movl %eax,%cr0 /* ..and set paging (PG) bit */
  25. jmp 1f /* flush the prefetch-queue */
  26. 1:
  27. movl $1f,%eax
  28. jmp *%eax /* make sure eip is relocated */

 
 物理内存的描述
Linux把物理内存划分3个层次管理;存储节点,管理区,和页表,并且用3个相应的数据结构来描述
页面数据结构
对一个物理页面的描述
  1. struct page{
  2. struct list_head list; //通过使用它进入下面的数据结构free_area_struct结构中的双向链队列
  3. struct address_space * mapping; //用于内存交换的数据结构
  4. unsigned long index;//当页面进入交换文件后,指向其去向
  5. struct page *next_hash; //自身的指针,这样就可以链接成一个链表
  6. atomic t count; //用于页面交换的计数,若页面为空闲则为0,分配就赋值1,没建立或恢复一次映射就加1,断开映射就减一
  7. unsigned long flags;//反应页面各种状态,例如活跃,不活跃脏,不活跃干净,空闲
  8. struct list_head lru;
  9. unsigned long age; //表示页面寿命
  10. wait_queue_head_t wait;
  11. struct page ** pprev_hash;
  12. struct buffer_head * buffers;
  13. void * virtual
  14. struct zone_struct * zone; //指向所属的管理区
  15. }
系统每个物理页面都有一个Page结构,系统初始化内存的大小建立一个Page的数组mem_map
 
为了对物理页面进行有效管理,Linux把物理页面分为3个区:
? 专供 DMA 使用的 ZONE_DMA 区(小于 16MB);
? 常规的 ZONE_NORMAL 区(大于 16MB 小于 896MB);
? 内核不能直接映射的区 ZONE_HIGME 区(大于 896MB)。
DMA 控制器不能依靠 CPU 内部的 MMU 将连续的虚存页面映射到物理上也连续的页面上,因此用于DMA的物理页面必须加以单独管理。

存储节点的数据结构
  1. typedef struct pglist_data {
  2. zone_t node_zones[MAX_NR_ZONES];//节点的最多3个页面管理区
  3. zonelist_t node_zonelists[GFP_ZONEMASK+1];//一个管理区指针数组,指向上面的管理区
  4. int nr_zones;
  5. struct page *node_mem_map;//指向具体节点的page结构数组
  6. unsigned long *valid_addr_bitmap;
  7. struct bootmem_data *bdata;
  8. unsigned long node_start_paddr;
  9. unsigned long node_start_mapnr;
  10. unsigned long node_size;
  11. int node_id;
  12. struct pglist_data *node_next;//形成一个单链表节点队列
  13. } pg_data_t;
  1. typedef struct zonelist_struct {
  2. zone_t *zone[MAX_NR_ZONE+1]; //NULL delimited 管理区
  3. Int gfp_mast;
  4. } zonelist_t
zone[]是个指针数组,各个元素按特定的次序指向具体的页面管理区,表示分配
页面时先试 zone[0]所指向的管理区, 如果不能满足要求就试 zone[1]所指向的管理区, 
内存管理区

  1. typedef struct zone_struct {
  2. /*
  3. * Commonly accessed fields:
  4. */
  5. spinlock_t lock; 用于暴走对该结构中其他域的串行访问
  6. unsigned long free_pages;//这个区中现有空闲页的个数
  7. unsigned long pages_min, pages_low, pages_high;//对这个区最少,次少及最多页面个数的描述
  8. int need_balance;//与kswapd合在一起
  9. /*
  10. * free areas of different sizes
  11. */
  12. free_area_t free_area[MAX_ORDER];/在伙伴分配系统中的位图数组和页面链表
  13. /*
  14. * Discontig memory support fields.
  15. */
  16. struct pglist_data *zone_pgdat;//本管理区所在的存储节点
  17. struct page *zone_mem_map;//本管理区的内存映射表
  18. unsigned long zone_start_paddr;//本管理区的物理地址
  19. unsigned long zone_start_mapnr;//mem_map索引
  20. /*
  21. * rarely used fields:
  22. */
  23. char *name;
  24. unsigned long size;
  25. } zone_t;
free_area[MAX_ORDER]是一组队列的链表每一个队列保持1 2 4 ...各一个用来分配物理大小快
  1. type struct free_area_struct {
  2. struct list_head free_list
  3. unsigned int *map
  4. } free_area_t
物理页面的分配和释放
Linux 把不连续的存储
空间也归类为非一致存储结构(NUMA)。这是因为,不连续的存储空间本质上是一种广义的
NUMA,因为那说明在最低物理地址和最高物理地址之间存在着空洞,而有空洞的空间当然是
“不一致”的。所以,在地址不连续的物理空间也要像结构不一样的物理空间那样划分出若干
连续且均匀的“节点”。因此,在存储结构不连续的系统中,每个模块都有若干个节点,因而
都有个 pg_data_t 数据结构队列。我们先来看 mm/numa.c 中的 alloc_page()函数:

  1. //表示哪种分配策略,order表示所需物理块的大小,1,2,4.....
  2. struct page * _alloc_pages(unsigned int gfp_mask, unsigned int order)
  3. {
  4. struct page *ret = 0;
  5. pg_data_t *start, *temp;
  6. #ifndef CONFIG_NUMA
  7. unsigned long flags;
  8. static pg_data_t *next = 0;
  9. #endif
  10. if (order >= MAX_ORDER)
  11. return NULL;
  12. #ifdef CONFIG_NUMA
  13. temp = NODE_DATA(numa_node_id());//通过NUMA_DATA()找到cpu所在节点的数据结构队列,存放在temp中
  14. #else
  15. spin_lock_irqsave(&node_lock, flags);
  16. if (!next) next = pgdat_list;
  17. temp = next;
  18. next = next->node_next;
  19. spin_unlock_irqrestore(&node_lock, flags);
  20. #endif
  21. start = temp;
  22. while (temp) {
  23. if ((ret = alloc_pages_pgdat(temp, gfp_mask, order)))//从当前节点扫到最后节点,能否满足分配内存
  24. return(ret);
  25. temp = temp->node_next;
  26. }
  27. temp = pgdat_list;
  28. while (temp != start) {//从头节点扫到当前节点,视图分配内存
  29. if ((ret = alloc_pages_pgdat(temp, gfp_mask, order)))
  30. return(ret);
  31. temp = temp->node_next;
  32. }
  33. return(0);
  34. }
一致存储结构(UMA)中页面的分配
连续空间 UMA 结构的 alloc_page()是在 include/linux/mm.h 中定义的:

  1. #ifndef CONFIG_DISCONTIGMEM
  2. static inline struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)
  3. {
  4. /*
  5. * Gets optimized away by the compiler.
  6. */
  7. if (order >= MAX_ORDER)
  8. return NULL;
  9. return __alloc_pages(gfp_mask, order,
  10. contig_page_data.node_zonelists+(gfp_mask & GFP_ZONEMASK));
  11. }
  12. #endi
_alloc_pages()在管理区链表 zonelist 中依次查找每个区,从中找到满足要求的区,
然后用伙伴算法从这个区中分配给定大小(2 order 个)的页面块。如果所有的区都没有足够的
空闲页面,则调用 swapper 或 bdflush 内核线程,把脏页写到磁盘以释放一些页面.而且被映射的页面可能只是做了标记
只有在正在需要分配时才分配
  1. struct page * __alloc_pages(unsigned int gfp_mask, unsigned int order, zonelist_t *zonelist)
  2. {
  3. unsigned long min;
  4. zone_t **zone, * classzone;
  5. struct page * page;
  6. int freed;
  7. zone = zonelist->zones;
  8. classzone = *zone;
  9. min = 1UL << order;
  10. for (;;) {//遍历各种区空闲页面的总量
  11. zone_t *z = *(zone++);
  12. if (!z)
  13. break;
  14. min += z->pages_low;
  15. if (z->free_pages > min) {//如果总理安大于最低水平线与所请求页面数之和,就调用rmqueue()试图分配
  16. page = rmqueue(z, order);
  17. if (page)
  18. return page;//分配成功,返回第一page指针
  19. }
  20.  
  21. }
如果发现管理区中的空闲页面总量已经降到最低点,则把 zone_t 结构中需要重新平衡

的标志(need_balance)置 1,而且如果内核线程 kswapd 在一个等待队列中睡眠,就唤醒它,
让它收回一些页面以备使用(可以看出, need_balance 是和 kswapd 配合使用的)。

  1. classzone->need_balance = 1;
  2. mb();
  3. if (waitqueue_active(&kswapd_wait))
  4. wake_up_interruptible(&kswapd_wait);
如果给定分配策略中所有页面管理区都分配失败,那就把原来的最低水位除以4下调.看能否满足要求,可以满足调用rmqueue分配并返回
  1. zone = zonelist->zones;
  2. min = 1UL << order;
  3. for (;;) {
  4. unsigned long local_min;
  5. zone_t *z = *(zone++);
  6. if (!z)
  7. break;
  8. local_min = z->pages_min;
  9. if (!(gfp_mask & __GFP_WAIT))
  10. local_min >>= 2;
  11. min += local_min;
  12. if (z->free_pages > min) {
  13. page = rmqueue(z, order);
  14. if (page)
  15. return page;
  16. }
  17. }
分配还不成功,那就需要看那类进程在请求分配内存页面,PF_MEMALLOC表示正在分配页面的进程.
PF_MEMDIE为1表示使用内存溢出而被杀死的进程,这2种必须给进程分配页面
  1. if (current->flags & (PF_MEMALLOC | PF_MEMDIE)) {
  2. zone = zonelist->zones;
  3. for (;;) {
  4. zone_t *z = *(zone++);
  5. if (!z)
  6. break;
  7. page = rmqueue(z, order);
  8. if (page)
  9. return page;
  10. }
  11. return NULL;
  12. }
分配页面不可以等待,也不能调度,直接返回,没有分配到页面
  1. /* Atomic allocations - we can‘t balance anything */
  2. if (!(gfp_mask & __GFP_WAIT))
  3. return NULL;
如果必须要得到页面的进程还还是没分配到页面,就要调用

balance_classzone()函数把当前进程所占有的局部页面释放出来。如果释放成功,则返回
一个 page 结构指针,指向页面块中第一个页面的起始地址。

  1. page = balance_classzone(classzone, gfp_mask, order, &freed);
  2. if (page)
  3. return page;
从而继续分配页面
以上反复调用了requeue()函数,

 该函数试图从一个页面管理区分配若干连续的内存页面。这是最基本的分配操作,其具

体代码如下:

  1. //指向要分配页面的管理区,order表示分配页面数为2的order次方
  2. static struct page * rmqueue(zone_t *zone, unsigned int order)
  3. {
  4. //area指向free_area的第order个元素
  5. free_area_t * area = zone->free_area + order;
  6. unsigned int curr_order = order;
  7. struct list_head *head, *curr;
  8. unsigned long flags;
  9. struct page *page;
  10. spin_lock_irqsave(&zone->lock, flags);
  11. do {
  12. head = &area->free_list;
  13. curr = memlist_next(head);
  14. if (curr != head) {
  15. unsigned int index;
  16. //获得空闲块的第 1 个页面的地址,如果这个地址是个无效的地址,就陷入 BUG()
  17. page = memlist_entry(curr, struct page, list);
  18. if (BAD_RANGE(zone,page))
  19. BUG();
  20. //从队列中摘除分配出去的页面块。
  21. memlist_del(curr);
  22. index = page - zone->zone_mem_map;
  23. if (curr_order != MAX_ORDER-1)
  24. //如果某个页面块被分配出去,就要在 frea_area 的位图中进行标记,这是通过调用 MARK_USED()宏来完成的。
  25. MARK_USED(index, curr_order, area);
  26. zone->free_pages -= 1UL << order;
  27. //如果分配出去后还有剩余块,就通过 expand()获得所分配的页块,而把剩余块链入适当的空闲队列中。
  28. page = expand(zone, page, index, order, curr_order, area);
  29. spin_unlock_irqrestore(&zone->lock, flags);
  30. set_page_count(page, 1);
  31. if (BAD_RANGE(zone,page))
  32. BUG();
  33. if (PageLRU(page))
  34. BUG();
  35. if (PageActive(page))
  36. BUG();
  37. return page;
  38. }
  39. curr_order++;
  40. area++;
  41. } while (curr_order < MAX_ORDER);//如果当前空闲队列没有空闲块,就从更大的空闲块队列中找。
  42. spin_unlock_irqrestore(&zone->lock, flags);
  43. return NULL;
  44. }
expand体现了伙伴算法
  1. /*
  2. zone指向已分配页块所在的管理区
  3. page指向一分配的页块
  4. index为一分配的页块在mem_map中的下标;
  5. low表示所需页面块的大小为2的low次方
  6. high表示从实际空闲队列中实际分配的页面块大小为2的high次方
  7. area指向要实际分配的页块
  8. */
  9. static inline struct page * expand (zone_t *zone, struct page *page,
  10. unsigned long index, int low, int high, free_area_t * area)
  11. {
  12. unsigned long size = 1 << high;//初始化为分配块的页面数
  13. while (high > low) {
  14. if (BAD_RANGE(zone,page))
  15. BUG();
  16. area--;
  17. high--;
  18. size >>= 1;
  19. memlist_add_head(&(page)->list, &(area)->free_list);
  20. /*然后调用 memlist_add_head()把刚分配出去的页面块又加入到低一档(物理块减半)的空闲队列中准备从剩下的一半空闲块中重新进
  21. 行分配*/
  22. //MARK_USED()设置位图
  23. MARK_USED(index, high, area);
  24. index += size;
  25. page += size;
  26. }
  27. if (BAD_RANGE(zone,page))
  28. BUG();
  29. return page;
  30. }
 
Slab的数据结构
对于预期频繁使用的内存区,可以创建一组
特定大小的专用缓冲区进行处理,以避免内碎片的产生。对于较少使用的内存区,可以创建
一组通用缓冲区(如 Linux 2.0 中所使用的 2 的幂次方)来处理,即使这种处理模式产生碎
片,也对整个系统的性能影响不大。

slab有2种数据结构:描述缓冲区的结构keme_cache_t,描述Slab的结构kmem_slab_t
slab是slab管理最基本的结构
  1. typedef struct slab_s {
  2. struct list_head list;
  3. unsigned long colouroff;//slab上着色区的大小
  4. void *s_mem; /*指向对象区的起点 */
  5. unsigned int inuse; /* 分配对象的个数 */
  6. kmem_bufctl_t free;//空闲对象链的第一个对象
  7. } slab_t;

 每个缓冲区管理一个Slab链表.Slab按序分3组,第一组全满的Slab(没空闲的对象).
第二组只有部分对象被分配,部分对象还空闲,最后一组slab的对象则完全空闲.
为了对slab有效管理,每个缓冲区都有一个轮转锁,在对链表进行修改时用这锁进行同步
  1. struct kmem_cache_s {
  2. /* 1) each alloc & free */
  3. /* full, partial first, then free */
  4. struct list_head slabs_full;
  5. struct list_head slabs_partial;
  6. struct list_head slabs_free;
  7. unsigned int objsize;原始的数据结构的大小.初始化为kemem_cache_t的大小
  8. unsigned int flags; /* constant flags */
  9. unsigned int num; //每个slab obj的个数
  10. spinlock_t spinlock;
  11. #ifdef CONFIG_SMP
  12. unsigned int batchcount;
  13. #endif
  14. /* 2) slab additions /removals */
  15. /* order of pgs per slab (2^n) */
  16. unsigned int gfporder;//gfporder 则表示每个 Slab 大小的对数,即每个 Slab 由 2 gfporder 个页面构成。
  17. /* force GFP flags, e.g. GFP_DMA */
  18. unsigned int gfpflags;
  19. size_t colour; /* 颜色数目 */
  20. unsigned int colour_off; /*颜色的偏移量 */
  21. unsigned int colour_next; /* 下一个slab将要使用的颜色 */
  22. kmem_cache_t *slabp_cache;
  23. unsigned int growing;
  24. unsigned int dflags; /* dynamic flags */
  25. /* constructor func */
  26. void (*ctor)(void *, kmem_cache_t *, unsigned long);
  27. /* de-constructor func */
  28. void (*dtor)(void *, kmem_cache_t *, unsigned long);
  29. unsigned long failures;
  30. /* 3) cache creation/removal */
  31. char name[CACHE_NAMELEN];
  32. struct list_head next;
  33. #ifdef CONFIG_SMP
  34. /* 4) per-cpu data */
  35. cpucache_t *cpudata[NR_CPUS];
  36. #endif
  37. …..
  38. };
定义了 kmem_cache_t并给部分域赋予了初值,该结构有3个队列分别指向满slab,半满slab,空闲slab.另外一next把所有的专用缓冲区连成一个链表
kmem_cache_t 相当于 Slab 的总控结构,
  1. static kmem_cache_t cache_cache = {
  2. slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full) ,
  3. slabs_partial: LIST_HEAD_INIT(cache_cache.slabs_partial),
  4. slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free) ,
  5. objsize: sizeof(kmem_cache_t),//原始的数据结构的大小.初始化为kemem_cache_t的大小
  6. flags: SLAB_NO_REAP,
  7. spinlock: SPIN_LOCK_UNLOCKED,
  8. colour_off: L1_CACHE_BYTES,
  9. name: "kmem_cache",
  10. };

 专用缓冲区的建立与撤销
  1. //缓冲区名 对象大小 所请求的着色偏移量
  2. kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t offset,
  3. unsigned long c_flags,//对缓冲区的设置标志,SLAB_HWCACHE_ALIGN:表示与第一个高速缓冲中的行边界对齐
  4. //指向对象指针 ,指向缓冲区
  5. void (*ctor) (void *objp, kmem_cache_t *cachep, unsigned long flags),//构造函数,一般为NULL
  6.  
  7. void (*dtor) (void *objp, kmem_cache_t *cachep, unsigned long flags))//析构函数一般为NULL
kmem_cache_create()函数要进行一系列的计算,以确定最佳的 Slab 构成。包括:每
个 Slab 由几个页面组成,划分为多少个对象;Slab 的描述结构 slab_t 应该放在 Slab 的外
面还是放在 Slab 的尾部;还有“颜色”的数量等等。并根据调用参数和计算结果设置
kmem_cache_t 结构中的各个域,包括两个函数指针 ctor 和 dtor。最后,将 kmem_cache_t
结构插入到 cache_cache 的 next 队列中。
kmem_cache_create()所创建的缓冲区中还没有包含任何 Slab,因此,
也没有空闲的对象。只有以下两个条件都为真时,才给缓冲区分配 Slab:
(1)已发出一个分配新对象的请求;
(2)缓冲区不包含任何空闲对象。
当这两个条件都成立时,Slab 分配模式就调用 kmem_cache_grow()函数给缓冲区分配
一个新的 Slab。其中,该函数调用 kmem_gatepages()从伙伴系统获得一组页面;然后又调用
kmem_cache_slabgmt()获得一个新的 Slab 结构; 还要调用 kmem_cache_init_objs()为新 Slab
中的所有对象申请构造方法(如果定义的话); 最后, 调用 kmem_slab_link_end()把这个 Slab
结构插入到缓冲区中 Slab 链表的末尾。
通用缓冲区
在内核中初始化开销不大的数据结构可以合用一个通用的缓冲区。通用缓冲区非常类似
于物理页面分配中的大小分区,最小的为 32,然后依次为 64、128、……直至 128KB(即 32
个页面),但是,对通用缓冲区的管理又采用的是 Slab 方式。从通用缓冲区中分配和释放缓
冲区的函数为:

  1. void *kmalloc(size_t size, int flags);
  2. Void kree(const void *objp);
因此,当一个数据结构的使用根本不频繁时,或其大小不足一个页面时,就没有必要给
其分配专用缓冲区,而应该调用 kmallo()进行分配。如果数据结构的大小接近一个页面,则
干脆通过 alloc_page()为之分配一个页面。
事实上,在内核中,尤其是驱动程序中,有大量的数据结构仅仅是一次性使用,而且所
占内存只有几十个字节, 因此, 一般情况下调用 kmallo()给内核数据结构分配内存就足够了。
另外,因为,在 Linux 2.0 以前的版本一般都调用 kmallo()给内核数据结构分配内存,因此,
调用该函数的一个优点是(让你开发的驱动程序)能保持向后兼容。

内核空间非连续内存区的管理
非连续内存处于3G到4G(虚拟地址)

 PAGE_OFFSET为3G.high_memory表示保存物理地址的最高值的变量.VMALLOC_START为非连续区的起始地址
在物理地址的末尾与第一个内存区之间插入了一个 8MB(VMALLOC_OFFSET)的区间这
是一个安全区,目的是为了“捕获”对非连续区的非法访问。出于同样的理由,在其他非连
续的内存区之间也插入了 4KB 大小的安全区。每个非连续内存区的大小都是 4096 的倍数。
描述非连续区的数据结构
  1. struct vm_struct {
  2. unsigned long flags;
  3. void * addr;//内存区的起始地址
  4. unsigned long size;//内存区大小+4096(安全区的大小)
  5. struct vm_struct * next;
  6. };
  7. struct vm_struct * vmlist;//非连续区组成一个单链表

Linux内核之内存管理完全剖析的更多相关文章

  1. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

  2. 24小时学通Linux内核之内存管理方式

    昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今天将会讲诉Linux如何追踪和管理用户空间进程的可用内 ...

  3. Linux内核之内存管理

    Linux内核之内存管理 Linux利用的是分段+分页单元把逻辑地址转换为物理地址; RAM的某些部分永久地分配给内核, 并用来存放内核代码以及静态内核数据结构; RAM的其余部分称动态内存(dyna ...

  4. Linux内核笔记——内存管理之slab分配器

    内核版本:linux-2.6.11 内存区和内存对象 伙伴系统是linux用于满足对不同大小块内存分配和释放请求的解决方案,它为slab分配器提供页框分配请求的实现. 如果我们需要请求具有连续物理地址 ...

  5. 深入理解Linux内核-内存管理

    内核如果给自己分配动态内存 动态内存:RAM的某些部分被永久打分配给内核,用来存放内核代码以及静态内核数据结构:剩余的部分被称为动态内存 连续物理内存区管理: 页框管理:1.页大小的选择,通常情况下主 ...

  6. Linux 内核开发 - 内存管理

    1.1什么是内存管理 内存管理是对计算机内存进行分配和使用的技术.内存管理主要存在于多任务的操作系统中,因为内存资源极其有限.须要在不同的任务之间共享内存,内存管理的存在就是要高效.高速的非配内存,并 ...

  7. Linux内核之 内存管理

    前面几篇介绍了进程的一些知识,从这篇开始介绍内存.文件.IO等知识,发现更不好写哈哈.但还是有必要记录下自己的所学所思.供后续翻阅,同时写作也是一个巩固的过程. 这些知识以前有文档涉及过,但是角度不同 ...

  8. Linux内核笔记——内存管理之块内存分配

    内核版本:linux-2.6.11 伙伴系统 伙伴系统是linux用于满足对不同大小块物理内存分配和释放请求的解决方案. 内存管理区 linux将物理内存分成三个内存管理区,分别为ZONE_DMA Z ...

  9. Linux内核之内存管理(4)--缺页处理程序

    本文主要解说缺页处理程序,凝视足够具体,不再解释. //以下函数将一页内存页面映射到指定线性地址处,它返回页面的物理地址 //把一物理内存页面映射到线性地址空间指定处或者说把线性地址空间指定地址add ...

随机推荐

  1. Maven简介与简单使用

    Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具.由于 Maven 的缺省构建 ...

  2. meate 标签使用介绍

    //禁止浏览器从本地计算机的缓存中访问内容 <meta http-equiv="pragma" content="no-cache"> //清楚缓存 ...

  3. Navi.Soft20.WinCE使用手册

    1.概述 1.1应用场景 随着物联网的普及,越来越多的制造商对货品从原料配备,加工生产,销售出库等环节的要求和把控越来越高.在此情况之下,传统的ERP软件已经无法满足现有的流程. 移动设备的应用,在很 ...

  4. swift 判断输入的字符串是否为数字

    // 判断输入的字符串是否为数字,不含其它字符 func isPurnInt(string: String) -> Bool { let scan: Scanner = Scanner(stri ...

  5. Linux 网络编程四(socket多线程升级版)

    //网络编程--客户端 #include <stdio.h> #include <stdlib.h> #include <string.h> #include &l ...

  6. Java系列:《Java核心技术 卷一》学习笔记,chapter11 记录日志

    11.5 日志记录 可以通过Loger.getGlobal().info(xxxx);的方式来记录log. 11.5.2 高级日志 1)通过一个包名来 创建一个新的日志记录器. private sta ...

  7. [CareerCup] 4.7 Lowest Common Ancestor of a Binary Search Tree 二叉树的最小共同父节点

    4.7 Design an algorithm and write code to find the first common ancestor of two nodes in a binary tr ...

  8. tabbar的自定义

    [self createCustomTabBar]; -(void)createCustomTabBar{    //创建一个UIImageView,作为底图    UIImageView *bgVi ...

  9. ASP.NET MVC 5 入门教程 (1) 新建项目

    文章来源: Slark.NET-博客园 http://www.cnblogs.com/slark/p/mvc-5-get-started-create-project.html 下一节:ASP.NET ...

  10. 破解windows server 2008 的登录密码。有效的

    今天拿到一块以前服务器上替换下来的老盘,里面还有系统.挂载到另外一台闲置服务器,发现密码忘记了, 结果拿出pe和以前修改xp和2003的系统那样去修改发现不行,不知道为什么,修改SAM文件明明提示成功 ...