内核的bootmem内存分配器【转】

转自:http://blog.csdn.net/zmxiangde_88/article/details/8041040

版权声明:本文为博主原创文章,未经博主允许不得转载。

在内核启动期间,伙伴系统内存管理器还没有建立之前,内核此时也要分配内存以及创建一些用于建立内存管理等机制的数据结构,此时内存分配和管理就是由bootmem内存分配器来完成的。

bootmem的建立要求就是简单,越简单越好,因为一旦伙伴系统建立之后,就不需要bootmem了,因此对性能和通用性等要服从一切从简的原则。在了解这个分配器之后,就会知道它真的很简单。

该分配器使用一个位图来管理页,位图比特位的数目与系统中物理内存页的数目相同,比特位为1时,表示这个页已经分配,为0时,表示当前指示的页是空闲的。在需要分配内存时,分配器扫描整个位图,直到找到一个能够提供足够连续页的位置。

下面分析一下这个分配器。

一,前提

在这个分配器被建立之前,先了解一下内核此时是一个什么样的状态,主要说内存方面的。

内存在检测系统可用内存之后,被存入一个数组之中,其结构如下:

[cpp] view plain copy

  1. struct e820map{
  2. _u32 nr_map;
  3. struct e820entry map[E820MAX];
  4. }
  5. struct e820entry{
  6. _u64 addr;
  7. _u64 size;
  8. _u32 type;
  9. } __attribute__((packed));

在用中断检测可用内存之后,内存被存入e820map e820变量中,然后根据这个数组,确定下面一些值:

  1. min_low_pfn :表示RAM 中在内核映像后第一个可用页框的页框号,因为内存映像所占用的页肯定不能用于分配的。
  2. max_pfn:表示最后一个可用页框的页框号,包括可端内存。
  3. max_low_pfn:被内核直接映射的,低地址内存的最后一个页框的页框号。

在确定好了这些值之后,调用setup_bootmem_allocator函数,完成bootmem分配的建立工作,这是本博文的重点。

二,数据结构

用于维护该分配器的数据结构如下

[cpp] view plain copy

  1. typedef struct bootmem_data {
  2. unsigned long node_boot_start;
  3. unsigned long node_low_pfn;
  4. void *node_bootmem_map;
  5. unsigned long last_offset;
  6. unsigned long last_pos;
  7. unsigned long last_success; /* Previous allocation point.  To speed
  8. * up searching */
  9. struct list_head list;
  10. } bootmem_data_t;

node_boot_start:这个字段保存了系统中第一个页的编号,这一般都是0;

node_low_pfn:可以被直接管理的物理地址空间中最后一页的编号;

node_bootmem_map:指向分配位图的内存指针,前面说这种分配方式主要是维护一个大的位图。

last_offset:上一次分配的页内的偏移,上一次分配的页的编号由last_pos指定,而last_offset指定个页内已经分配的偏移。

last_success:指定位图中上一次成功分配内存的位置,下一个分配从这里开始分配。

list:因为内存并不是都是连续的,对于不连续的内存,系统需要多个bootmem分配器,所有的分配器都保存一个链表中,表头由一个全局变量指定。

[cpp] view plain copy

  1. void __init setup_bootmem_allocator(void)
  2. {
  3. unsigned long bootmap_size;
  4. /*
  5. * Initialize the boot-time allocator (with low memory only):
  6. */
  7. bootmap_size = init_bootmem(min_low_pfn, max_low_pfn);

调用init_bootmem函数完成初始化,这个函数会调用init_bootmem_core函数完成初始化。

[cpp] view plain copy

  1. unsigned long __init init_bootmem(unsigned long start, unsigned long pages)
  2. {
  3. max_low_pfn = pages;
  4. min_low_pfn = start;
  5. return init_bootmem_core(NODE_DATA(0), start, 0, pages);
  6. }

这里的两个参数需要注意,是由min_low_pfn和max_low_pfn传递而来,表示低内存域的最小和最大的页帧编号。

在下面的函数实现中,可以看到mapstart的值就是min_log_pfn,end的值是max_low_pfn,而start的值为0,这个值要赋给bootmem_data_t结构的node_boot_start字段,参数中的NODE_DATA(0),表示当前内存结点。

[cpp] view plain copy

  1. static unsigned long __init init_bootmem_core(pg_data_t *pgdat,
  2. unsigned long mapstart, unsigned long start, unsigned long end)
  3. {
  4. bootmem_data_t *bdata = pgdat->bdata;
  5. unsigned long mapsize;
  6. bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart));//内存的最开始,由分配位图占用一部分
  7. bdata->node_boot_start = PFN_PHYS(start);
  8. bdata->node_low_pfn = end;//注意这个赋值
  9. link_bootmem(bdata);
  10. /*
  11. * Initially all pages are reserved - setup_arch() has to
  12. * register free RAM areas explicitly.
  13. */
  14. mapsize = get_mapsize(bdata);
  15. memset(bdata->node_bootmem_map, 0xff, mapsize);
  16. return mapsize;
  17. }

PFN_PHYS宏是将页帧的编号转换为对应的页内的物理地址,该操作通过左移页内偏移的位数来达到。

[cpp] view plain copy

  1. #define PFN_PHYS(x) ((x) << PAGE_SHIFT)//在这里是左移12位

init_bootmem_core完成以下操作:

  1. 给内存结点的bdata域,就是bootmem_data_t类型字段的值赋值。一个内存结点的数据结构中包含由这个分配器指针。
  2. 将bootmem_data_t添加到全局变量为表头的链表上,这个通过调用link_bootmem完成,将其添加到分局变量bdata_list上。

    [cpp] view plain copy

    1. static void __init link_bootmem(bootmem_data_t *bdata)
    2. {
    3. bootmem_data_t *ent;
    4. if (list_empty(&bdata_list)) {
    5. list_add(&bdata->list, &bdata_list);
    6. return;
    7. }
    8. /* insert in order */
    9. list_for_each_entry(ent, &bdata_list, list) {
    10. if (bdata->node_boot_start < ent->node_boot_start) {
    11. list_add_tail(&bdata->list, &ent->list);
    12. return;
    13. }
    14. }
    15. list_add_tail(&bdata->list, &bdata_list);
    16. }
  3. get_mapsize函数计算分配器中可用的内存页所需的BIT位数,就是一个页占用一个BIT的话,需要多少个bit位,然后按字对齐。注意这个函数会将从第0页开始进行管理,但是第0页至少已经被作为内核映射了。

    [cpp] view plain copy

    1. static unsigned long __init get_mapsize(bootmem_data_t *bdata)
    2. {
    3. unsigned long mapsize;
    4. unsigned long start = PFN_DOWN(bdata->node_boot_start);
    5. unsigned long end = bdata->node_low_pfn;
    6. mapsize = ((end - start) + 7) / 8;
    7. return ALIGN(mapsize, sizeof(long));
    8. }
  4. 调用memset将所有的页标识为已经使用。接下来肯定要完成哪些页能够被用来分配内存,

三,释放可用的内存

在前面已经初始化了一个位图,该位图的位置从min_low_pfn开始占用,其实就是被内核映射之后的第一个页。但是标记了所有的内存页都是已经被使用了,这时系统中就不存在可用的内存了,需要从刚标识的内存位图中释放一些潜在的、可用的内存。这通过调用register_bootmem_low_pages函数完成。

[cpp] view plain copy

  1. register_bootmem_low_pages(max_low_pfn);
  2. /*
  3. * Reserve the bootmem bitmap itself as well. We do this in two
  4. * steps (first step was init_bootmem()) because this catches
  5. * the (very unlikely) case of us accidentally initializing the
  6. * bootmem allocator with an invalid RAM area.
  7. */
  8. reserve_bootmem(__pa_symbol(_text), (PFN_PHYS(min_low_pfn) +
  9. bootmap_size + PAGE_SIZE-1) - __pa_symbol(_text));
  10. /*
  11. * reserve physical page 0 - it‘s a special BIOS page on many boxes,
  12. * enabling clean reboots, SMP operation, laptop functions.
  13. */
  14. reserve_bootmem(0, PAGE_SIZE);
  15. /* reserve EBDA region, it‘s a 4K region */
  16. reserve_ebda_region();
  17. /* could be an AMD 768MPX chipset. Reserve a page  before VGA to prevent
  18. PCI prefetch into it (errata #56). Usually the page is reserved anyways,
  19. unless you have no PS/2 mouse plugged in. */
  20. if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
  21. boot_cpu_data.x86 == 6)
  22. reserve_bootmem(0xa0000 - 4096, 4096);
  23. #ifdef CONFIG_SMP
  24. /*
  25. * But first pinch a few for the stack/trampoline stuff
  26. * FIXME: Don‘t need the extra page at 4K, but need to fix
  27. * trampoline before removing it. (see the GDT stuff)
  28. */
  29. reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
  30. #endif
  31. #ifdef CONFIG_ACPI_SLEEP
  32. /*
  33. * Reserve low memory region for sleep support.
  34. */
  35. acpi_reserve_bootmem();
  36. #endif
  37. #ifdef CONFIG_X86_FIND_SMP_CONFIG
  38. /*
  39. * Find and reserve possible boot-time SMP configuration:
  40. */
  41. find_smp_config();
  42. #endif
  43. numa_kva_reserve();
  44. #ifdef CONFIG_BLK_DEV_INITRD
  45. if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
  46. unsigned long ramdisk_image = boot_params.hdr.ramdisk_image;
  47. unsigned long ramdisk_size  = boot_params.hdr.ramdisk_size;
  48. unsigned long ramdisk_end   = ramdisk_image + ramdisk_size;
  49. unsigned long end_of_lowmem = max_low_pfn << PAGE_SHIFT;
  50. if (ramdisk_end <= end_of_lowmem) {
  51. reserve_bootmem(ramdisk_image, ramdisk_size);
  52. initrd_start = ramdisk_image + PAGE_OFFSET;
  53. initrd_end = initrd_start+ramdisk_size;
  54. } else {
  55. printk(KERN_ERR "initrd extends beyond end of memory "
  56. "(0x%08lx > 0x%08lx)\ndisabling initrd\n",
  57. ramdisk_end, end_of_lowmem);
  58. initrd_start = 0;
  59. }
  60. }
  61. #endif
  62. reserve_crashkernel();
  63. }

下面是register_bootmem_low_pages的代码。

[cpp] view plain copy

  1. void __init register_bootmem_low_pages(unsigned long max_low_pfn)
  2. {
  3. int i;
  4. if (efi_enabled) {
  5. efi_memmap_walk(free_available_memory, NULL);
  6. return;
  7. }
  8. for (i = 0; i < e820.nr_map; i++) {
  9. unsigned long curr_pfn, last_pfn, size;
  10. /*
  11. * Reserve usable low memory
  12. */
  13. if (e820.map[i].type != E820_RAM)
  14. continue;
  15. /*
  16. * We are rounding up the start address of usable memory:
  17. */
  18. curr_pfn = PFN_UP(e820.map[i].addr);
  19. if (curr_pfn >= max_low_pfn)
  20. continue;
  21. /*
  22. * ... and at the end of the usable range downwards:
  23. */
  24. last_pfn = PFN_DOWN(e820.map[i].addr + e820.map[i].size);
  25. if (last_pfn > max_low_pfn)
  26. last_pfn = max_low_pfn;
  27. /*
  28. * .. finally, did all the rounding and playing
  29. * around just make the area go away?
  30. */
  31. if (last_pfn <= curr_pfn)
  32. continue;
  33. size = last_pfn - curr_pfn;
  34. free_bootmem(PFN_PHYS(curr_pfn), PFN_PHYS(size));
  35. }
  36. }

前面说了由BIOS的中断给我们提供了可用的内存区列表,并且存在变量e820中,那么我们应该要将e820中标识的可用的内存列表都在内存分配器中标识为可用的内存。这个函数就是完成这个功能的,它通过对列表的遍历,找到每一个可用的内存域所在的页,然后标识该页可用。标记为可用是通过调用free_bootmem函数完成的。

[cpp] view plain copy

  1. void __init free_bootmem(unsigned long addr, unsigned long size)
  2. {
  3. free_bootmem_core(NODE_DATA(0)->bdata, addr, size);
  4. }
  5. static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr,
  6. unsigned long size)
  7. {
  8. unsigned long sidx, eidx;
  9. unsigned long i;
  10. /*
  11. * round down end of usable mem, partially free pages are
  12. * considered reserved.
  13. */
  14. BUG_ON(!size);
  15. BUG_ON(PFN_DOWN(addr + size) > bdata->node_low_pfn);
  16. if (addr < bdata->last_success)
  17. bdata->last_success = addr;
  18. /*
  19. * Round up the beginning of the address.
  20. */
  21. sidx = PFN_UP(addr) - PFN_DOWN(bdata->node_boot_start);
  22. eidx = PFN_DOWN(addr + size - bdata->node_boot_start);
  23. for (i = sidx; i < eidx; i++) {
  24. if (unlikely(!test_and_clear_bit(i, bdata->node_bootmem_map)))
  25. BUG();
  26. }
  27. }

这个函数其实是释放内存的函数,就是释放从addr开始的内存页

  1. 先检查所需要释放的内存是否超过了分配器的最大内存页。
  2. 然后要注意的是:这里只释放内存中的整页。由sidx和eidx的计算可以知道,它会计算完全包含在该内存中的、将被释放的页,如果只有部分包含在内存区中的页是被忽略的。这个过程肯定是有风险的,如果页包含在两个不同的内存区中,那么连续释放这些内存区,却无法释放这个页的内存,在释放所有的页后,分配器无法知道这个页是否还在使用,也没办法再去释放它了,因为这个页的状态一直都是标记为使用。所以这个分配器是简单的,但它是可行的,为什么呢?因为它只用于系统初始化期间,并且系统初始化期间所分配的大多数内存都会一直被使用,直到这个分配器停止使用,新的分配器开始工作,就是伙伴系统了。
  3. 调用test_and_clear_bit函数将这个BIT位标记为0。

四,预留内存

在上面把系统的可用内存都标记为可用,但此时系统正在使用一些内存,需要把这些内存相应的标记出来,这通过调用reserve_bootmem函数完成。

[cpp] view plain copy

  1. #define reserve_bootmem(addr, size) \
  2. reserve_bootmem_node(NODE_DATA(0), (addr), (size))
  3. void __init reserve_bootmem_node(pg_data_t *pgdat, unsigned long physaddr,
  4. unsigned long size)
  5. {
  6. reserve_bootmem_core(pgdat->bdata, physaddr, size);
  7. }
  8. static void __init reserve_bootmem_core(bootmem_data_t *bdata, unsigned long addr,
  9. unsigned long size)
  10. {
  11. unsigned long sidx, eidx;
  12. unsigned long i;
  13. /*
  14. * round up, partially reserved pages are considered
  15. * fully reserved.
  16. */
  17. BUG_ON(!size);
  18. BUG_ON(PFN_DOWN(addr) >= bdata->node_low_pfn);
  19. BUG_ON(PFN_UP(addr + size) > bdata->node_low_pfn);
  20. sidx = PFN_DOWN(addr - bdata->node_boot_start);
  21. eidx = PFN_UP(addr + size - bdata->node_boot_start);
  22. for (i = sidx; i < eidx; i++)
  23. if (test_and_set_bit(i, bdata->node_bootmem_map)) {
  24. #ifdef CONFIG_DEBUG_BOOTMEM
  25. printk("hm, page %08lx reserved twice.\n", i*PAGE_SIZE);
  26. #endif
  27. }
  28. }

这个函数和前面的有些类似,就是将对应的页标记为已经使用。注意这个内存区域的计算,和前面的计算是相反的,前面是计算完全包含在内存中的内存页,这个计算是被完全包含在内存页中那些内存页。

五,内核分配内存

内核提供一些函数,用于向bootmem分配器索要内存,提供了很多系列的接口,如alloc_bootmem(size)、alloc_bootmem_pages(size)等等,这些接口最终调用__alloc_bootmem函数完成分配。

[cpp] view plain copy

  1. void * __init __alloc_bootmem(unsigned long size, unsigned long align,
  2. unsigned long goal)
  3. {
  4. void *mem = __alloc_bootmem_nopanic(size,align,goal);
  5. if (mem)
  6. return mem;
  7. /*
  8. * Whoops, we cannot satisfy the allocation request.
  9. */
  10. printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size);
  11. panic("Out of memory");
  12. return NULL;
  13. }

这个函数也是__alloc_bootmem_nopanic函数的一个前端。

[cpp] view plain copy

  1. void * __init __alloc_bootmem_nopanic(unsigned long size, unsigned long align,
  2. unsigned long goal)
  3. {
  4. bootmem_data_t *bdata;
  5. void *ptr;
  6. list_for_each_entry(bdata, &bdata_list, list) {
  7. ptr = __alloc_bootmem_core(bdata, size, align, goal, 0);
  8. if (ptr)
  9. return ptr;
  10. }
  11. return NULL;
  12. }

这个函数会实际的分配,之前我们知道多个分配器被一个全局变量链接到一个链表上,这里会遍历整个链表,调用__alloc_bootmem_core来分配内存,这个函数比较长,函数所需要参数有

  1. 分配器指针,表示从这个分配器上分配。
  2. 分配的大小。
  3. 对齐方式,如果需要分配页,此时分配的对齐方式就是页对齐了。
  4. goal,表示从哪开始扫描这个分配器的位图。
  5. 内存分配的限制,表示不能分配limit指定的后面的页。

[cpp] view plain copy

  1. void * __init
  2. __alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,
  3. unsigned long align, unsigned long goal, unsigned long limit)
  4. {
  5. unsigned long offset, remaining_size, areasize, preferred;
  6. unsigned long i, start = 0, incr, eidx, end_pfn;
  7. void *ret;
  8. if (!size) {
  9. printk("__alloc_bootmem_core(): zero-sized request\n");
  10. BUG();
  11. }
  12. BUG_ON(align & (align-1));
  13. if (limit && bdata->node_boot_start >= limit)
  14. return NULL;
  15. /* on nodes without memory - bootmem_map is NULL */
  16. if (!bdata->node_bootmem_map)
  17. return NULL;
  18. end_pfn = bdata->node_low_pfn;
  19. limit = PFN_DOWN(limit);
  20. if (limit && end_pfn > limit)
  21. end_pfn = limit;
  22. eidx = end_pfn - PFN_DOWN(bdata->node_boot_start);
  23. offset = 0;
  24. if (align && (bdata->node_boot_start & (align - 1UL)) != 0)
  25. offset = align - (bdata->node_boot_start & (align - 1UL));
  26. offset = PFN_DOWN(offset);
  27. /*
  28. * We try to allocate bootmem pages above ‘goal‘
  29. * first, then we try to allocate lower pages.
  30. */
  31. if (goal && goal >= bdata->node_boot_start && PFN_DOWN(goal) < end_pfn) {
  32. preferred = goal - bdata->node_boot_start;
  33. if (bdata->last_success >= preferred)
  34. if (!limit || (limit && limit > bdata->last_success))
  35. preferred = bdata->last_success;
  36. } else
  37. preferred = 0;
  38. preferred = PFN_DOWN(ALIGN(preferred, align)) + offset;
  39. areasize = (size + PAGE_SIZE-1) / PAGE_SIZE;
  40. incr = align >> PAGE_SHIFT ? : 1;
  41. restart_scan:
  42. for (i = preferred; i < eidx; i += incr) {
  43. unsigned long j;
  44. i = find_next_zero_bit(bdata->node_bootmem_map, eidx, i);
  45. i = ALIGN(i, incr);
  46. if (i >= eidx)
  47. break;
  48. if (test_bit(i, bdata->node_bootmem_map))
  49. continue;
  50. for (j = i + 1; j < i + areasize; ++j) {
  51. if (j >= eidx)
  52. goto fail_block;
  53. if (test_bit(j, bdata->node_bootmem_map))
  54. goto fail_block;
  55. }
  56. start = i;
  57. goto found;
  58. fail_block:
  59. i = ALIGN(j, incr);
  60. }
  61. if (preferred > offset) {
  62. preferred = offset;
  63. goto restart_scan;
  64. }
  65. return NULL;
  66. found:
  67. bdata->last_success = PFN_PHYS(start);
  68. BUG_ON(start >= eidx);
  69. /*
  70. * Is the next page of the previous allocation-end the start
  71. * of this allocation‘s buffer? If yes then we can ‘merge‘
  72. * the previous partial page with this allocation.
  73. */
  74. if (align < PAGE_SIZE &&
  75. bdata->last_offset && bdata->last_pos+1 == start) {
  76. offset = ALIGN(bdata->last_offset, align);
  77. BUG_ON(offset > PAGE_SIZE);
  78. remaining_size = PAGE_SIZE - offset;
  79. if (size < remaining_size) {
  80. areasize = 0;
  81. /* last_pos unchanged */
  82. bdata->last_offset = offset + size;
  83. ret = phys_to_virt(bdata->last_pos * PAGE_SIZE +
  84. offset +
  85. bdata->node_boot_start);
  86. } else {
  87. remaining_size = size - remaining_size;
  88. areasize = (remaining_size + PAGE_SIZE-1) / PAGE_SIZE;
  89. ret = phys_to_virt(bdata->last_pos * PAGE_SIZE +
  90. offset +
  91. bdata->node_boot_start);
  92. bdata->last_pos = start + areasize - 1;
  93. bdata->last_offset = remaining_size;
  94. }
  95. bdata->last_offset &= ~PAGE_MASK;
  96. } else {
  97. bdata->last_pos = start + areasize - 1;
  98. bdata->last_offset = size & ~PAGE_MASK;
  99. ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);
  100. }
  101. /*
  102. * Reserve the area now:
  103. */
  104. for (i = start; i < start + areasize; i++)
  105. if (unlikely(test_and_set_bit(i, bdata->node_bootmem_map)))
  106. BUG();
  107. memset(ret, 0, size);
  108. return ret;
  109. }

这个函数的中心思想是从指定的位置开始扫描位图,如果找到了满足分配要求的,就马上分配。执行以下一些操作:

  1. 从goal所指定的位置开始扫描位图,找到满足条件的空闲内存区域。
  2. 如果目标页紧接着上一次分配的页,即bootmem_data->last_pos所指定的页,那么内核会检查bootmem_data->last_offset,判断这次请求的内存能不能从上一个页开始分配。
  3. 新分配的页在位图对应的比特位设置为1,最后一页的数目也保存在bootmem_data->lastpos中,如果这个页没有完全分配,那么保存分配的偏移至last_offset中,否则将这个值表示为0。注意,如果分配的时候,要求是页对齐的,此时不能从偏移开始分配,页要从新页开始分配。
  4. 分配成功之后,返回分配的内存域的起始地址。

六,内存的释放

内存的释放在前面已经介绍过了,通过调用free_bootmem函数完成,前面也说了,这个函数一般很少使用。原因也是上面的原因。

七,停用bootmem分配器

这个分配器因为简单,所以用于系统的初期,在伙伴系统开始接手内存管理工作之后,这个分配器的历史使命就完成了,此时需要停止这个分配器。可以通过函数free_all_bootmem等来完成。基本的步骤是:

  1. 扫描分配器中的页位图,释放没有使用的页,此时释放页是通过调用_free_pages_bootmem函数,为什么呢?因为这时释放的内存需要释放到伙伴系统中,而不是这个分配器中,_free_pages_bootmem函数会调用__free_pages函数,而后者是伙伴系统的标准内核接口函数,在这释放之后,伙伴系统就可以使用刚刚被从这个分配器中释放的页了。
  2. 然后释放分配器本身的内存占用,其实主要是位图所占用的内存。

    [cpp] view plain copy

    1. page = virt_to_page(bdata->node_bootmem_map);
    2. count = 0;
    3. idx = (get_mapsize(bdata) + PAGE_SIZE-1) >> PAGE_SHIFT;
    4. for (i = 0; i < idx; i++, page++) {
    5. __free_pages_bootmem(page, 0);
    6. count++;
    7. }
    8. total += count;
    9. bdata->node_bootmem_map = NULL;
0
时间: 2024-10-18 08:24:25

内核的bootmem内存分配器【转】的相关文章

[转]Linux内核最新的连续内存分配器(CMA)——避免预留大块内存

http://blog.csdn.net/21cnbao/article/details/7309757 在我们使用ARM等嵌入式Linux系统的时候,一个头疼的问题是GPU,Camera,HDMI等都需要预留大量连续内存,这部分内存平时不用, 但是一般的做法又必须先预留着.目前,Marek Szyprowski和Michal Nazarewicz实现了一套全新的Contiguous Memory Allocator.通过这套机制,我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才

Linux内核工程导论——内存管理(一)

Linux内存管理 概要 物理地址管理 很多小型操作系统,例如eCos,vxworks等嵌入式系统,程序中所采用的地址就是实际的物理地址.这里所说的物理地址是CPU所能见到的地址,至于这个地址如何映射到CPU的物理空间的,映射到哪里的,这取决于CPU的种类(例如mips或arm),一般是由硬件完成的.对于软件来说,启动时CPU就能看到一片物理地址.但是一般比嵌入式大一点的系统,刚启动时看到的已经映射到CPU空间的地址并不是全部的可用地址,需要用软件去想办法映射可用的物理存储资源到CPU地址空间.

linux内核探索之内存管理(二):linux系统中的内存组织--结点、内存域和页帧

本文主要参考<深入linux内核架构>(3.2节)及Linux3.18.3内核源码 概述:本文主要描述了内存管理相关的数据结构:结点pg_data_t.内存域struct zone以及页帧(物理页):struct page ,以及该结构相关的一些基本概念. 1. 概述 内存划分为接点,每个结点关联到系统中的一个处理器,在内核中表示为pg_data_t. 各个结点又划分为内存域,比如DMA内存域,高端内存域,普通内存域. 内核内存域的宏: enum zone_type { #ifdef CONF

[转]STL的内存分配器

题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面,很多C/C++书籍中都使用技巧的介绍.另一类是内存管理的实现,如linux内核的slab分配器,STL中的allocator实现,以及一些特定于某种对象的内存管理等.最近阅读了一些内存管理实现方面的资料和源码,整理了一下,汇编成一个系列介绍一些常用的内存管理策略. 1. STL容器简介 STL提供

STL中的内存分配器原理

题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面,很多C/C++书籍中都使用技巧的介绍.另一类是内存管理的实现,如linux内核的slab分配器,STL中的allocator实现,以及一些特定于某种对象的内存管理等.最近阅读了一些内存管理实现方面的资料和源码,整理了一下,汇编成一个系列介绍一些常用的内存管理策略. 1. STL容器简介 STL提供

Nah Lock: 一个无锁的内存分配器

概述 我实现了两个完全无锁的内存分配器:_nalloc 和 nalloc.  我用benchmark工具对它们进行了一组综合性测试,并比较了它们的指标值. 与libc(glibc malloc)相比,第一个分配器测试结果很差,但是我从中学到了很多东西,然后我实现了第二个无锁分配器,随着核数增加至30,测试结果线性提高.核数增加至60,测试结果次线性提高,但是仅比tcmalloc好一点. 想要安装,输入命令: git clone ~apodolsk/repo/nalloc,阅读 README文档.

[转]linux内核分析笔记----内存管理

转自:http://blog.csdn.net/Baiduluckyboy/article/details/9667933 内存管理,不用多说,言简意赅.在内核里分配内存还真不是件容易的事情,根本上是因为内核不能想用户空间那样奢侈的使用内存. 先来说说内存管理.内核把物理页作为内存管理的基本单位.尽管处理器的最小可寻址单位通常是字,但是,内存管理单元MMU通常以页为单位进行处理.因此,从虚拟内存的交代来看,页就是最小单位.内核用struct  page(linux/mm.h)结构表示系统中的每个

Linux内核中常见内存分配函数

1.原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分别为: l   页全局目录(Page Global Directory) l   页上级目录(Page Upper Directory) l   页中间目录(Page Middle Directory) l   页表(Page Table) 页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录

转:内核中的内存申请:kmalloc、vmalloc、kzalloc、kcalloc、get_free_pages

在内核模块中申请分配内存需要使用内核中的专用API:kmalloc.vmalloc.kzalloc.kcalloc.get_free_pages;当然,设备驱动程序也不例外;对于提供了MMU功能的处理器而言,Linux提供了复杂的内存管理系统,使得进程所能访问到的地址空间可以达到4GB;而这4GB的空间又被划分为两个部分:0GB~3GB(PAGE_OFFSET,x86中的值是0xC0000000)的区域被用作进程的用户空间,3GB~4GB的区域被用作内核空间;在内核空间中,从3GB到vmallo