Linux内存管理: 物理内存的分配与回收

接上文
大段代码警告
本文主要讲物理页面的分配以代码为主
有以下四个函数

  1. __alloc_pages
  2. rmqueue
  3. expand
  4. balance_classzone

请主要关注前三个函数. 特别是rmqueueexpand函数.

物理页面的分配

Linux内存管理: 物理内存的分配与回收_第1张图片核心流程: __alloc_pages → \rightarrow rmqueue → \rightarrow expend

__alloc_pages

Linux内存管理: 物理内存的分配与回收_第2张图片


/*
 * This is the 'heart' of the zoned buddy allocator:
 * 函数参数
	1.gfp_mask : 分配请求标志
	2.order : 大小, 根据order 分配 2^order个物理页帧
	3.zonelist : 指向node的node_zonelists某个元素, 里面指定了寻找要分配物理页的zone次序
 * 函数功能:
	分配2^order个页面
 * 函数返回值
    返回物理页帧描述符page, page指向页面块中第一个页面的起始地址。
 */
struct page * __alloc_pages(unsigned int gfp_mask, unsigned int order, zonelist_t *zonelist)
{
	unsigned long min;
	zone_t **zone, * classzone;
	struct page * page;
	int freed;
	/*
	1.zone指向zones数组, 
	2.classzone指向zones数组第一个元素
	3.根据order计算实际分配页数(最少分配一个)
	*/
	zone = zonelist->zones; 
	classzone = *zone; 
	min = 1UL << order; //1UL代表 unsigned long(无符号长整型数)
	/*
	从第一个zone开始, 将请求大小与zone水位比较.
	如果zone中的空闲页数大于 水位 pages_low+min
		那么调用rmqueue分配物理页,
			分配成功,返回第一个物理页的描述符地址
		否则, 查询下一个zone
	*/
	for (;;) {
		zone_t *z = *(zone++); // 相当于zone_t *z = *(zone[i])
		if (!z)
			// 循环结束, 即遍历完数组,退出循环
			break;

		min += z->pages_low; //累加计算水位
		if (z->free_pages > min) { // 空闲页面足够多, 即 z->free_pages > ((1UL<< order) + z->pages_low + 之前pages->low的累加和)
			page = rmqueue(z, order);// 从z中分配 2^order个页帧
			if (page) // 分配成功
				return page; //返回物理页帧描述符page, page指向页面块中第一个页面的起始地址。
		}
		
	}
	/*
	上面分配未成功,即各zone都超过水位pages_low了,设置classzone的need_balance标志
	同时唤醒 kswapd, 期待它能将某些物理页交换出去
	*/
	classzone->need_balance = 1;
	mb();
	if (waitqueue_active(&kswapd_wait)) 
		//唤醒 kswapd
		wake_up_interruptible(&kswapd_wait);

	/*
	1.kswapd唤醒后, 重新检查有没有空闲页
	2.重新顺序遍历zonelist中的各个zone
	3.如果zone的空闲页数大于请求大小+水位pages_min,调用rmqueue分配
		其中如果请求标志为不可等, 那么将水位暂时除以4再比较
	4.若分配成功,则返回首页描述符
	*/
	zone = zonelist->zones;
	min = 1UL << order;
	for (;;) {
		unsigned long local_min;
		zone_t *z = *(zone++); // 相当于zone_t *z = *(zone[i])
		if (!z)
			// 循环结束, 即遍历完数组,退出循环
			break;

		local_min = z->pages_min;
		if (!(gfp_mask & __GFP_WAIT)) //请求标志不可等待
			local_min >>= 2; // local_min=(local_min/4)
		min += local_min; //累加水位
		if (z->free_pages > min) { //足够的空闲页面
			page = rmqueue(z, order);// 从z中分配 2^order个页帧
			if (page)
				//分配成功,返回page, page指向页面块中第一个页面的起始地址。
				return page;
		}
	}
/* here we're in the low on memory slow path */
rebalance:
/*
	上述分配未成功, 检查当前进程标志,
	如果具有PF_MEMALLOC 或 PF_MEMDIE其中的一个, 直接调用rmqueue分配
	通常情况下, 进程标志为PF_MEMALLOC  或 PF_MEMDIE的 可能是 kswapd, oom killers
*/
	if (current->flags & (PF_MEMALLOC | PF_MEMDIE)) { 
		// 具有PF_MEMALLOC 或 PF_MEMDIE其中的一个, 直接调用rmqueue分配
		zone = zonelist->zones; //zones数组
		for (;;) {
			zone_t *z = *(zone++);//遍历数组
			if (!z)
				//数据遍历完成,跳出循环
				break;
			//直接从z中分配 2^order个页帧
			page = rmqueue(z, order);
			if (page)
				//分配成功,返回page, page指向页面块中第一个页面的起始地址。
				return page;
		}
		//分配不成功, 返回NULL
		return NULL;
	}

	/*
	没有足够的空闲物理页满足请求
	如果请求标志又是不可等, 直接返回NULL
	*/
	/* Atomic allocations - we can't balance anything */
	if (!(gfp_mask & __GFP_WAIT))
		//没有足够的物理页帧满足请求, 且请求标志是不可等待
			//直接返回NULL
		return NULL;

	//1.尝试将classzone中的物理页释放
	page = balance_classzone(classzone, gfp_mask, order, &freed);
	if (page) 
		//1.1释放成功, 返回page
		return page;

	//1.2 这里即,balance_classzone释放未成功
	/*
	再次查看水位 pages_min,如果空闲页数够了,调用rmqueue分配
	再次查看原因, 上面操作可能释放了某些物理页
	*/
	zone = zonelist->zones;
	min = 1UL << order;
	for (;;) { 
		zone_t *z = *(zone++); //遍历数组
		if (!z)
			//遍历完成
			break;
		//累加水位
		min += z->pages_min;
		if (z->free_pages > min) {
			//有足够的空闲页面, 分配2^order个页面
			page = rmqueue(z, order);
			if (page)
				//返回page, page指向页面块中第一个页面的起始地址。
				return page;
		}
	}

	/*请求太大了 kswapd操作代价很高, 即不分配连续的大块内存 
	Don't let big-order allocations loop */
	if (order > 3)
		return NULL;

	

	/*等待CPU调度, 跳转到rebalance执行代码 
	Yield for kswapd, and try again */
	current->policy |= SCHED_YIELD; //调度策略为等待调度
	__set_current_state(TASK_RUNNING); //设置当前进程为运行状态
	schedule(); //进行调度
	goto rebalance;//回到rebalance标志再次分配页框
}


rmqueue

还记得free_area数组吗?
Linux内存管理: 物理内存的分配与回收_第3张图片

static struct page * rmqueue(zone_t *zone, unsigned int order){
	free_area_t * area = zone->free_area + order; // 根据order将area指向相应的内存分组
	unsigned int curr_order = order;
	struct list_head *head, *curr;
	unsigned long flags;
	struct page *page;
	spin_lock_irqsave(&zone->lock, flags); //上锁
	do {
		//指定的order没有页面了, 依次向高order的area遍历

		// head指向free_list头部, curr指向第一个物理页
		head = &area->free_list;
		curr = memlist_next(head);

		if (curr != head) { // 还有页面可以分配
			unsigned int index;
			//free_list不空, 从list中得到描述符
			page = memlist_entry(curr, struct page, list);
			//1.如果该物理页非法, 出错
			if (BAD_RANGE(zone,page))
				BUG();
			//2.否则将该物理页从free_list中删除
			memlist_del(curr);
			
			index = page - zone->zone_mem_map; //得到当前 page在zone_mem_map数组中的下标
			if (curr_order != MAX_ORDER-1)
				/*如果不是在最大那组分配的, 将area的map相应位设为1*/
				MARK_USED(index, curr_order, area);
			zone->free_pages -= 1UL << order; //减少被分配的页数

			//把大块连续物理内存分配到 小的连续的物理内存
			page = expand(zone, page, index, order, curr_order, area); 
			spin_unlock_irqrestore(&zone->lock, flags); //释放锁

			set_page_count(page, 1); //设置count为1,即page的引用计数为1
			//对page进行合法性检查
			if (BAD_RANGE(zone,page))
				BUG();
			if (PageLRU(page)) //page在active链表或者inactive链表中
				BUG();
			if (PageActive(page)) //page在active链表中
				BUG();
			return page;	
		}
		/*
		若该组没有找到连续的物理页来满足分配,尝试从更大的组中分配,
		重复遍历直到遍历完最大组*/
		curr_order++;
		area++;
	} while (curr_order < MAX_ORDER);
	spin_unlock_irqrestore(&zone->lock, flags); //释放锁
	//一直没有找到, 返回NULL
	return NULL;
}

expend

大块连续物理内存拆分为小块物理内存

/*
*Buddy 核心函数
*函数功能:
	当指定的order,low无页面可以分配的时候
	将从order大的area一次一半拆分为 order小的area
*函数参数
	low : 目标order 
	high : 当前free_area数组的下标 
	page : order为high的页面组
	index: page组在order为high的area中的位图索引
*/
static inline struct page * expand (zone_t *zone, struct page *page,
	 unsigned long index, int low, int high, free_area_t * area)
{
	unsigned long size = 1 << high;//high对应的连续物理内存的页框数

	while (high > low) {
		//从高order到低order遍历 for(int i=high;i>low;i--)
		if (BAD_RANGE(zone,page)) //合法性检查
			BUG();
		area--; // 此时high对应的low area
		high--; //从高order 向low order遍历
		size >>= 1;//大块物理内存拆分为小块物理内存(一次一半)
		memlist_add_head(&(page)->list, &(area)->free_list); //将拆分的头一个组加入低级别的area
		
		MARK_USED(index, high, area);// 在此低级别的area中反转状态
		index += size; //低 order的位图索引
		page += size; //留下同一对的后一个组分配给用户
	}
	if (BAD_RANGE(zone,page))//合法性检查
		BUG();
	//返回物理页帧描述符
	return page;

}

balance_classzone

分配不出来了, 需要平衡一下
Linux内存管理: 物理内存的分配与回收_第4张图片

static struct page * balance_classzone(zone_t * classzone, unsigned int gfp_mask, unsigned int order, int * freed)
{
	struct page * page = NULL;
	int __freed = 0;
	// 1.不允许等待
	if (!(gfp_mask & __GFP_WAIT))
		goto out;//2.跳转到out, 退出函数
	//3.若在中断中, 出错
	if (in_interrupt())
		BUG();
	current->allocation_order = order; //设置当前进程的order
	current->flags |= PF_MEMALLOC | PF_FREE_PAGES; //设置current的标志位
	// 直接从classzone中释放页
	__freed = try_to_free_pages(classzone, gfp_mask, order);
	//再次清除标志位, 来保证free functions 不会继续把paes加入local_pages链表 
	current->flags &= ~(PF_MEMALLOC | PF_FREE_PAGES);

	//判断pages是否在local_pages链表
	if (current->nr_local_pages) {
		//在local_pages链表
		struct list_head * entry, * local_pages;
		struct page * tmp;
		int nr_pages;

		//从链表头开始
		local_pages = &current->local_pages;
		if (likely(__freed)) { //分支预测优化...
			// pages 被try_to_free_pages_zones()释放
			/* pick from the last inserted so we're lifo */
			entry = local_pages->next;
			do { /* 循环, 直到找到一个tmp->index==order,且
					tmp->zone等于classzone*/
				//从链表中获得页帧
				tmp = list_entry(entry, struct page, list);
					/*
					1.找到想要的的页面
					2.判断tmp->index与order,且tmp属于正确的zone
					*/
				if (tmp->index == order && memclass(tmp->zone, classzone)) {
					list_del(entry); // 从链表中删除
					current->nr_local_pages--; //包含页帧减1
					set_page_count(tmp, 1); // 设置tmp的count为1,准备释放
					page = tmp;// 设置page准备返回, tmp要释放在local_pages中剩下的页帧

					//保证释放的安全性
					if (page->buffers) // 如果page存在缓冲区
						BUG();
					if (page->mapping)// 如果page存在文件映射
						BUG();
					if (!VALID_PAGE(page)) //如果page失效
						BUG();
					if (PageSwapCache(page)) //page属于交换区
						BUG();
					if (PageLocked(page)) //如果page被上锁
						BUG();
					if (PageLRU(page)) //如果page在active链表或者inactive链表中
						BUG();
					if (PageActive(page))//如果page在active链表
						BUG();
					if (PageDirty(page))//如果page为脏页
						BUG();

					break;
				}
				//循环, 找到下一个page继续检查
			} while ((entry = entry->next) != local_pages);
		}
		//得到准备释放页帧的数量
		nr_pages = current->nr_local_pages;
		/* free in reverse order so that the global order will be lifo */
		//遍历完local_pages链表
		while ((entry = local_pages->prev) != local_pages) {
			// 将entry从链表删除
			list_del(entry);
			//从entry中得到页帧
			tmp = list_entry(entry, struct page, list);
			//释放页帧
			__free_pages_ok(tmp, tmp->index);
			/*
			如果nr_pages==0 但是此时list中还有物理页帧,
				说明有人向local_pages中加入页帧,所以调用BUG(),
			即如果nr_pages小于0,即有人向local_pages加入页帧,
				所以要调用BUG()
			*/
			if (!nr_pages--)
				BUG();
		}
		//表示页帧都被释放了
		current->nr_local_pages = 0;
	}
out:
 	//更新freed,说明释放页与否
	*freed = __freed;
	return page;//返回根据order与classzone找到的页帧
}

你可能感兴趣的:(linux)