如有问题,欢迎一起讨论:-)
为了对页面管理机制作初步准备,Linux使用了一种叫bootmem分配器的机制,它仅仅用在系统引导时,为整个物理内存建立一个页面位图,位图建立开始于start_pfn,即内核映像终点_end,结束是max_low_pfn,主要用来管理0-896MB的范围。目的:在这个范围有的页面可能保留,有些页面可能是空洞,要搞清楚哪些物理页面是可以动态分配的。
typedef struct bootmem_data{
unsigned long node_boot_start; /*位图标识的第一个页面*/
unsigned long node_low_pfn; /*位图标识的最后的页面,也是直接访问物理页面的顶端*/
void *node_bootmem_map; /*位图*/
unsigned long last_offset; /*上一次分配的页内偏移*/
unsigned long last_pos; /*上一次分配的位图的位置*/
}bootmem_data_t;
mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL)
memsize成为下一个4的倍数(4为CPU的字长)。例如,假设有40个物理页面,因此,我们可以得出memsize为5个字节。所以,上面的操作就变为(5+(4-1))&~(4-1)即(00001000&11111100),最低的两位变为0,其结果为8。这就有效地使memsize变为4的倍数
#define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) 地址页面向上取整
#define PFN_DOWN(x) ((x) >> PAGE_SHIFT) 地址页面向下取整
max_pfn 对应的页框为地址最高的一个可用页框
max_low_pfn 对应的页框为可直接寻址的最后一个页框,属于低端内存
highstart_pfn 同max_low_pfn
highend_pfn 同max_pfn
max_low_pfn 对应的页框为可直接寻址的最后一个页框,属于低端内存
highstart_pfn 同max_low_pfn
highend_pfn 同max_pfn
1、 初始化:
bootmem_data_t *bdata = pgdat->bdata; pgdat是pg_data_t数据结构,它代表一片均匀、连续的内存,对于UMA,只有一个结点
mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL); 见前面笔记,为了向上取4倍数,位图字节对齐
bdata->node_bootmem_map = phys_to_virt(mapstart<<PAGE_SHIFT); mapstart为start_pfn,转换为虚地址,为了在后面直接使用其地址,进行初始化
bdata->node_boot_start = (start<<PAGE_SHIFT);
bdata->node_low_pfn = end; 初始化开始地址0x00000000,结束页面
memset(bdata->node_bootmem_map, 0xff, mapsize); 位图全部初始化为1,表示都保留使用
2、 free_bootmem() 这个函数把给定范围内的页面标记为空闲(即可用)
unsigned long eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE;
unsigned start = (addr + PAGE_SIZE -1) /PAGE_SIZE;
unsigned long sidx = start - (bdata->node_boot_start)/PAGE_SIZE;
for(i = sidx; i < eidx ; i++)
test_and_clear_bit(i, bdata->node_boot_mem_map);
3、reserve_bootmem()函数 函数用来保留一部分页面,其实直接可以在位图中置1
传入参数:bdata、addr、size
首先得到要进行保留的起始页面:
unsigned long sidx = (addr - bdata->node_boot_start)/PAGE_SIZE;
然后是size后的页面:
unsigned long eidx = (addr + size - bdata->node_boot_start + PAGE_SIZE - 1)/PAGE_SIZE;
然后进行循环进行置位:
for(i = sidx; i < eidx; i++)
if(test_and_set_bit(i, bdata->node_bootmem_map))
4、__alloc_bootmem函数,用来在节点上分配页面
传入参数:bdata, size, align, goal
goal是在分配时,尽量去主动选择页面,即在goal地址之上的,所以首先要判断这个值存在与否:
if(goal && (goal >= bdata->node_boot_start) && ((goal >> PAGE_SHIFT) < bdata->node_low_pfn))
preferred = goal - bdata->node_boot_start;
else
preferred = 0;
preferred = ((preferred + align - 1) & ~(align - 1)) >> PAGE_SHIFT; 根据align对齐得到物理地址
下面计算要分配的页面数
areasize = (size + PAGE_SIZE - 1)/PAGE_SIZE;
incr = align>>PAGE_SHIFT ? : 1; 根据对齐大小来选择增加值
restart_scan:
for(i = perferred; i < edix; i+=incr)
{
unsigned long j;
if(test_bit(i, bdata->node_bootmem_map))
continue;
首先,函数查找从perferred开始,第一个位图为0的,即空闲的页面,如果置1,则continue。
for(j = i + 1; j < i + areasize; j++)
{
if(j >= edix)
goto fail_block;
if(test_bit(j, bdata->node_bootmem_map))
goto fail_block;
}
函数要找到联系的areasize页面都是空闲的,如果不满足要求,就goto fail_block;
start = i;
goto found;
表示已经找到,从页面i开始
found:
if(align < PAGE_SIZE && bdata->last_offset && bdata->last_pos + 1 == start)
offset = (bdata->last_offset + align - 1) & ~(align - 1);
如果满足上面的,则offset 调整为align 对齐(注意这时候align < PAGE_SIZE),这样为了合并
下面应该知道了吧,猜想应该是,首先会判断所得出上次分配页面剩余的大小,然后和现在分配大小
进行比较,根据大小来分配不同页面
remaining_size = PAGE_SIZE - offset;
if(size < remaining_size)
{
areasize = 0;
bdata->last_offset = offset + size;
ret = phys_to_virt(bdata->last_pos * PAGE_SIZE + offset + bdata->node_boot_start);
}
如果size大小小于剩余大小,则不用分配页面,直接在当前页面中移动last_offset
else
{
remaining_size = size - remaining_size;
areasize = (remaining_size + PAGE_SIZE - 1)/PAGE_SIZE;
ret = phys_to_virt(bdata->last_pos * PAGE_SIZE + offset + bdata->node_boot_start);
bdata->last_pos = start + areasize - 1;
bdata->last_offset = remaining_size;
}
如果上面的if语句三个条件有一个不满足,或者都不满足,则直接修改bdata里面的数据即可:
else
{
bdata->last_pos = start + areasize - 1;
bdata->last_offset = size & ~PAGE_MASK;
ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);
}
#define PAGE_MASK (~(PAGE_SIZE - 1))
#define PAGE_SIZE (1UL<<PAGE_SHIFT)
#define PAGE_SHIFT 12