linux中bootmem分析

bootmem机制是在linux启动期间,buddy管理器,slab管理器没有初始化好时使用的内存管理方法。
之后系统起来之后,就交由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;
}




猜你喜欢

转载自blog.csdn.net/u010383937/article/details/78596635
今日推荐