Linux内存管理: 物理内存的释放(回收).为物理页面抬棺

前情提要:

  1. 地址转换
  2. 物理页面的分配

终于到了物理内存的释放. 内存页面如生命一般. 有生有死.
接下来我们就要为物理页面抬棺收尸了.

1.要点: 如何为兄弟抬棺回收?

分配时跟谁分开的, 回收时要跟他一起才能被释放 双生小鬼

2.代码分析

  • 函数调用关系:
    在这里插入图片描述
  • __free_pages_ok函数流程图

Linux内存管理: 物理内存的释放(回收).为物理页面抬棺_第1张图片不是很清楚, 可私信要清楚的版本.如果我还存了的话

  • 代码
static void __free_pages_ok (struct page *page, unsigned int order)
{
	unsigned long index, page_idx, mask, flags;
	free_area_t *area;
	struct page *base;
	zone_t *zone;

	
	if (PageLRU(page)) /*
		检测该page是否在page lists中
			如果在, 就删除
		*/
		lru_cache_del(page);
	if (page->buffers) // 如果page被用作磁盘块缓存时
		BUG();
	if (page->mapping) //如果page有指向的inode
		BUG();

	/*
		检测 ((page - mem_map) < max_mapnr) 是否超过page管理单元总数
			page 是否在有效的 [mem_map,mem_mep+max_mapnr]范围内
	*/
	if (!VALID_PAGE(page))
		BUG();
	if (PageSwapCache(page))
		BUG();
	if (PageLocked(page))
		BUG();
	if (PageLRU(page))
		BUG();
	if (PageActive(page))
		BUG();
	/*
	释放page标志位, 进而释放该order页
	*/
	page->flags &= ~((1<<PG_referenced) | (1<<PG_dirty));
	
	/*
	current : 当前运行的进程
	current在i386上定义为
		static inline struct task_struct * get_current(void)
		{
			struct task_struct *current;
			__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
			return current;
		}
		#define current get_current()
	*/ 
	if (current->flags & PF_FREE_PAGES)
		//若当前进程的标志位为每个进程物理页框释放
		goto local_freelist;
back_local_freelist:

	zone = page->zone; //指向该page属于的zone
	mask = (~0UL) << order; // mask = 页面指数order的掩码
	base = zone->zone_mem_map; // 该zone的所有4k页面的起始mem_map虚拟地址
	/*
	*减运算
	page - base //(同类型结构体)表示从base到page 一共有多少个 sturct page 这样的结构体,即一共有多少个页,
	计算该page是第几个页, 即page在所有页中的页帧号       
	*/
	page_idx = page - base; 

	if (page_idx & ~mask) // 该page一定是(1<
		BUG();//不是则报错
	index = page_idx >> (1 + order);// 使用buddy算法,求得该page对应的map管理位图值

	/*
	* 加运算
	zone->free_area 所在地址加order个 free_area_t	结构体大小结构块之后的最终地址
	即free_area[order]
	*/
	area = zone->free_area + order; 

	spin_lock_irqsave(&zone->lock, flags);//上锁

	zone->free_pages -= mask; // 将释放的空闲页数目, 加入到该zone的free_pages
	//
	/*
	每次mask<<= 1 
	当mask = -512 此时while(0) 退出
	*/
	while (mask + (1 << (MAX_ORDER-1))) { //这里判断具体见示例程序
		struct page *buddy1, *buddy2;

		if (area >= zone->free_area + MAX_ORDER) 
			//若area 越界,即free_area数组下标越界
			BUG();
		/*
		对 index进行异或运算, 返回0, 表示伙伴不在当前area内, 或者伙伴忙, 或者伙伴在其他area空闲着
		*/
		if (!__test_and_change_bit(index, area->map))
			/*
			 * the buddy page is still allocated.
			 */
			break;
		/*
			根据expend的分配规则寻找
				分配时跟哪个页框分开的,
				回收时就找它合并
		*/
		buddy1 = base + (page_idx ^ -mask); // 伙伴的page单元,通过^运算, 一下子找到伙伴的页帧号
		buddy2 = base + page_idx; //自己的page单元
		/*
		确保两个page单元都是合法的
		#define BAD_RANGE(zone,x) 
			(((zone) != (x)->zone) ||  //非本page对应的zone
			(((x)-mem_map) < (zone)->zone_start_mapnr) ||  // 小于该zone管理的起始偏移
			(((x)-mem_map) >= (zone)->zone_start_mapnr+(zone)->size)) / 超过该zone->size 管理上限偏移
		*/
		if (BAD_RANGE(zone,buddy1)) 
			BUG();
		if (BAD_RANGE(zone,buddy2))
			BUG();
		//list_del
		memlist_del(&buddy1->list); // 伙伴 buddy1 从空闲链表删除
		mask <<= 1; // 去 order+1 执行上面同样的 buddy合并算法
		area++; // 下一个高位 area
		index >>= 1; // 计算位图管理位索引号
		page_idx &= mask; //更新page_idx,即更新为合并之后的page_idx
	}
	
	/*
	#define memlist_add_head list_add
		将经过Buddy合并后的页, 添加到相应area的free_list中
	*/
	memlist_add_head(&(base + page_idx)->list, &area->free_list);

	spin_unlock_irqrestore(&zone->lock, flags);//释放锁
	return;

local_freelist:
	if (current->nr_local_pages)// 如果已经有local_pages, 将page释放到area->free_list中
		goto back_local_freelist; 
	if (in_interrupt()) // 在中断中, 也需要将page释放到area->free_list中
		goto back_local_freelist;		
	
	//释放到 current->local_pages中
	list_add(&page->list, &current->local_pages);
	page->index = order; // 标记该page后面还有多少可用页面
	current->nr_local_pages++;// 当前进程持有的物理页帧递增
}
  • 提示
    函数中的while (mask + (1 << (MAX_ORDER-1)))可能会令人迷惑.我写了一个小demo.
#include
#define MAX_ORDER 10
int main(){
    unsigned long mask= (~0UL) << 1 ;
    
    while( mask+(1<<(MAX_ORDER-1))) {
        mask<<=1;
        printf("unsigned mask = %lu\n",mask);
        printf("signed mask = %ld\n",mask);
    }
    return 0;
}

结果如下
Linux内存管理: 物理内存的释放(回收).为物理页面抬棺_第2张图片

自旋锁&信号量

  • 自旋锁
--自旋锁
		自旋锁不会引起调用者睡眠,且自旋锁保持期间是抢占失效的
--自旋锁API
		1.spin_lock(lock)
			只有获得锁才返回, (获得不到锁,将自旋直到自旋锁的持有者释放)
		2.spin_unlock(lock)
			释放自旋锁
		3.spin_trylock(lock)
			尽力获得自旋锁lock.
			if 立即能获得锁
				获得锁并返回真
			else
				立即返回假
		4.spin_lock_irqsave(lock, flags)
			#define spin_lock_irqsave(lock, flags)		do { local_irq_save(flags);       spin_lock(lock); } while (0)
				获得自旋锁的同时也把标志寄存器的值保存到flags中并失效本地中断
		5.spin_unlock_irqrestore(lock, flags)
			#define spin_unlock_irqrestore(lock, flags)	do { spin_unlock(lock);  local_irq_restore(flags); } while (0)
				释放自旋锁的同时也恢复标志寄存器的值为变量flags保存的值, 与spin_lock_irqsave配对使用
  • 信号量
	--信号量
		会导致调用者睡眠,因此只在进程上下文使用,可被抢占
		适合于保持时间较长的情况

感想

现在,逻辑地址到物理地址的转换,物理内存分配与回收就算写完了.
希望我的经验可以帮到你.
页面转换不算难, 不过细节问题还是值得注意的.
比如说GDT, LDT放的位置,当然是专门的硬件了经典解决方案.
buddy系统的分析也是不少, 但是我还是要查好多资料.

虽然说我写的不算很详细, 但是应付学习一下操作系统课程的代码. 还算可以的. 下一篇会有一个简短的要点总结. 提分要素

参考

1、蒋静.操作系统原理·技术与编程.北京:机械工业出版社.2004
2、余华兵.Linux内核深度解析基于ARM64架构的Linux4.x内核.北京:人民邮电出版社.2019
3、河秦.王洪涛.Linux2.6内核标准教程.北京: 人民邮电出版社.2008
4、linux内核分析-内存管理
5、Linux_虚拟地址、线性地址和物理地址的转换
6、linux内核研究笔记4(do_wp_page参数
7、逆向映射的演进
8、页框的回收
9、The Buddy System Algorithm

你可能感兴趣的:(linux)