前情提要:
终于到了物理内存的释放. 内存页面如生命一般. 有生有死.
接下来我们就要为物理页面抬棺收尸了.
分配时跟谁分开的, 回收时要跟他一起才能被释放 双生小鬼
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, ¤t->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;
}
--自旋锁
自旋锁不会引起调用者睡眠,且自旋锁保持期间是抢占失效的
--自旋锁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