bootmem机制是在linux启动期间,buddy管理器,slab管理器没有初始化好时使用的内存管理方法。
之后系统起来之后,就交由buddy等方法来管理了。
在多核系统中,每个核都对应这个一个node_data结构体,其中就记录着这个核使用的内存,也包括了bootmem的信息。
讲解一个bootmem内存分配方法:
之后系统起来之后,就交由buddy等方法来管理了。
在多核系统中,每个核都对应这个一个node_data结构体,其中就记录着这个核使用的内存,也包括了bootmem的信息。
看一下node_data的结构体
#define LEVELS_PER_SLICE 128
struct slice_data {
unsigned long irq_enable_mask[2];
int level_to_irq[LEVELS_PER_SLICE];
};
struct hub_data {
cpumask_t h_cpus;
unsigned long slice_map;
unsigned long irq_alloc_mask[2];
struct slice_data slice[2];
};
struct node_data {
struct pglist_data pglist; //记录内存信息
struct hub_data hub;
cpumask_t cpumask;
};
#define NODE_DATA(n) (&__node_data[(n)]->pglist)
#define hub_data(n) (&__node_data[(n)]->hub)
struct bootmem_data;
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; //zone信息
struct zonelist node_zonelists[MAX_ZONELISTS]; //zone链表
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map;
#ifdef CONFIG_MEMCG
struct page_cgroup *node_page_cgroup;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
struct bootmem_data *bdata; //bootmem信息
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
spinlock_t node_size_lock;
#endif
unsigned long node_start_pfn; //物理地址开始
unsigned long node_present_pages; /*总的物理页数*/
unsigned long node_spanned_pages; /*总的物理大小,包括hole*/
int node_id; //node号
nodemask_t reclaim_nodes; /* Nodes allowed to reclaim from */
wait_queue_head_t kswapd_wait;
wait_queue_head_t pfmemalloc_wait;
struct task_struct *kswapd; /* Protected by lock_memory_hotplug() */
int kswapd_max_order;
enum zone_type classzone_idx;
#ifdef CONFIG_NUMA_BALANCING
/* Lock serializing the migrate rate limiting window */
spinlock_t numabalancing_migrate_lock;
/* Rate limiting time interval */
unsigned long numabalancing_migrate_next_window;
/*Number of pages migrated during the rate limiting time interval */
unsigned long numabalancing_migrate_nr_pages;
#endif
} pg_data_t;
typedef struct bootmem_data {
unsigned long node_min_pfn; //地址开始
unsigned long node_low_pfn; //地址结束
void *node_bootmem_map; // 位图信息存储位置
unsigned long last_end_off;//分配的最后一个页内的偏移
unsigned long hint_idx; //
struct list_head list; //用于链表
} bootmem_data_t;
//每个cpu都对应一个bootmem
bootmem_data_t bootmem_node_data[MAX_NUMNODES] __initdata;
//为每个cpu定义的node_data
struct node_data *__node_data[MAX_NUMNODES];
EXPORT_SYMBOL(__node_data);
bootmem的操作方法:
bootmem的操作函数:
#define alloc_bootmem(x) \
__alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_align(x, align) \
__alloc_bootmem(x, align, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_nopanic(x) \
__alloc_bootmem_nopanic(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages(x) \
__alloc_bootmem(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages_nopanic(x) \
__alloc_bootmem_nopanic(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_node(pgdat, x) \
__alloc_bootmem_node(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_node_nopanic(pgdat, x) \
__alloc_bootmem_node_nopanic(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages_node(pgdat, x) \
__alloc_bootmem_node(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_pages_node_nopanic(pgdat, x) \
__alloc_bootmem_node_nopanic(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT)
#define alloc_bootmem_low(x) \
__alloc_bootmem_low(x, SMP_CACHE_BYTES, 0)
#define alloc_bootmem_low_pages_nopanic(x) \
__alloc_bootmem_low_nopanic(x, PAGE_SIZE, 0)
#define alloc_bootmem_low_pages(x) \
__alloc_bootmem_low(x, PAGE_SIZE, 0)
#define alloc_bootmem_low_pages_node(pgdat, x) \
__alloc_bootmem_low_node(pgdat, x, PAGE_SIZE, 0)
讲解一个bootmem内存分配方法:
/**参数的作用
* __alloc_bootmem - allocate boot memory
* @size: size of the request in bytes
* @align: alignment of the region
* @goal: preferred starting address of the region
*
* The goal is dropped if it can not be satisfied and the allocation will
* fall back to memory below @goal.
*
* Allocation may happen on any node in the system.
*
* The function panics if the request can not be satisfied.
*/
void * __init __alloc_bootmem(unsigned long size, unsigned long align,
unsigned long goal)
{
unsigned long limit = 0;
return ___alloc_bootmem(size, align, goal, limit);
}
static void * __init ___alloc_bootmem(unsigned long size, unsigned long align,unsigned long goal, unsigned long limit)
{
void *mem = ___alloc_bootmem_nopanic(size, align, goal, limit);
if (mem)
return mem;
printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size);
panic("Out of memory");
return NULL;
}
static void * __init ___alloc_bootmem_nopanic(unsigned long size,
unsigned long align,
unsigned long goal,
unsigned long limit)
{
void *ptr;
restart:
ptr = alloc_bootmem_core(size, align, goal, limit);
if (ptr)
return ptr;
if (goal) {
goal = 0;
goto restart;
}
return NULL;
}
static void * __init alloc_bootmem_core(unsigned long size,
unsigned long align,
unsigned long goal,
unsigned long limit)
{
bootmem_data_t *bdata;
void *region;
//循环查找bdata_list,在bootmem中查找一个合适的内存区域
list_for_each_entry(bdata, &bdata_list, list) {
if (goal && bdata->node_low_pfn <= PFN_DOWN(goal))
continue;
if (limit && bdata->node_min_pfn >= PFN_DOWN(limit))
break;
//然后调用alloc_bootmem_bdata进行分配
region = alloc_bootmem_bdata(bdata, size, align, goal, limit);
if (region)
return region;
}
return NULL;
}
static void * __init alloc_bootmem_bdata(struct bootmem_data *bdata,unsigned long size, unsigned long align,unsigned long goal, unsigned long limit)
{
unsigned long fallback = 0;
unsigned long min, max, start, sidx, midx, step;
//如果size为0,报错
BUG_ON(!size);
BUG_ON(align & (align - 1));
//limit存在并且goal+size>limit报错
BUG_ON(limit && goal + size > limit);
//如果bdata的bootmem为空,则报错
if (!bdata->node_bootmem_map)
return NULL;
//获取最小,最大地址
min = bdata->node_min_pfn;
max = bdata->node_low_pfn;
//得到goal和limit的物理地址
goal >>= PAGE_SHIFT;
limit >>= PAGE_SHIFT;
if (limit && max > limit)
max = limit;
if (max <= min)
return NULL;
//设置步长,也就是在位图操作时,每次跳过几位
step = max(align >> PAGE_SHIFT, 1UL);
//goal为指定的查找区域的开始地址,在这里把goal的对齐格式更新后,赋值给start
if (goal && min < goal && goal < max)
start = ALIGN(goal, step);
else
start = ALIGN(min, step);
//得到查找区域相对于bdata开始位置的偏移,以及获取最大偏移
sidx = start - bdata->node_min_pfn;
midx = max - bdata->node_min_pfn;
//hint为上次分配的偏移
if (bdata->hint_idx > sidx) {
/*
* Handle the valid case of sidx being zero and still
* catch the fallback below.
*/
fallback = sidx + 1;
sidx = align_idx(bdata, bdata->hint_idx, step);
}
while (1) {
int merge;
void *region;
unsigned long eidx, i, start_off, end_off;
find_block:
//在位图中(node_bootmem_map)中查找下一个为零的地方,为零代表可用
sidx = find_next_zero_bit(bdata->node_bootmem_map, midx, sidx);
sidx = align_idx(bdata, sidx, step);
eidx = sidx + PFN_UP(size);
if (sidx >= midx || eidx > midx)
break;
for (i = sidx; i < eidx; i++)
if (test_bit(i, bdata->node_bootmem_map)) {
sidx = align_idx(bdata, i, step);
if (sidx == i)
sidx += step;
goto find_block;
}
//这里的start_off即为分配的物理地址开始
if (bdata->last_end_off & (PAGE_SIZE - 1) &&
PFN_DOWN(bdata->last_end_off) + 1 == sidx)
start_off = align_off(bdata, bdata->last_end_off, align);
else
start_off = PFN_PHYS(sidx);
//这里的merge是什么作用????????????????
merge = PFN_DOWN(start_off) < sidx;
//end_off分配的物理地址的结束地址
end_off = start_off + size;
//把end_off赋值给last_end_off;last_and_off表示上次分配的页内偏移,分配完,其为0
bdata->last_end_off = end_off;
//hint表示上次分配的页号
bdata->hint_idx = PFN_UP(end_off);
//把分配的地址设置为已配置
if (__reserve(bdata, PFN_DOWN(start_off) + merge,
PFN_UP(end_off), BOOTMEM_EXCLUSIVE))
BUG();
//region表示分配地址的开始,清零后返回
region = phys_to_virt(PFN_PHYS(bdata->node_min_pfn) +
start_off);
memset(region, 0, size);
kmemleak_alloc(region, size, 0, 0);
return region;
}
if (fallback) {
sidx = align_idx(bdata, fallback - 1, step);
fallback = 0;
goto find_block;
}
return NULL;
}