浅析armlinux-setup_arch()->alloc_bootmem_low_pages()函数5-1
文章来源:http://gliethttp.cublog.cn
建议首先参考《浅析armlinux2_4_19启动程序[head-armv.s文件]》[http://gliethttp.cublog.cn] //---------------------------------------- //include/linux/Bootmem.h->alloc_bootmem_low_pages //#define alloc_bootmem_low_pages(x) \ // __alloc_bootmem((x), PAGE_SIZE, 0) //mm/Bootmem.c->__alloc_bootmem() void * __init __alloc_bootmem (unsigned long size, unsigned long align, unsigned long goal) { pg_data_t *pgdat = pgdat_list;//在我的at91rm9200开发板上pgdat_list=NODE_DATA(0)=&discontig_node_data[0] void *ptr; while (pgdat) { //pgdat->bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);//位图页帧号转为系统用的虚拟地址 //pgdat->bdata->node_boot_start = (start << PAGE_SHIFT);//本map所处物理内存起始地址 //pgdat->bdata->node_low_pfn = end;//本map所处物理内存结束地址 //具体情形请参见《浅析armlinux-setup_arch()->bootmem_init()函数4》 if ((ptr = __alloc_bootmem_core(pgdat->bdata, size,align, goal))) return(ptr);//返回清0后的起始虚拟地址 pgdat = pgdat->node_next; //如果当前node内存空间区域没有符合要求的内存,那么去下一个node内存空间区域申请 } //提出的请求方式不正确,否则我的天,把所用内存都用光了 printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size); panic("Out of memory"); return NULL; } //返回申请到空间的起始虚拟地址,如果申请不到,则系统halt //为了尽量利用内存,使用如下的计算方法,对本次分配的内存, //尽量使用上一次分配时最后一页内剩余内存空间中的last_offset~PAGE_SIZE个字节空闲区域 //但我想对于last_pos+1==start,这种连续分配是有好处的,但是当 //内存释放,出现内存间隙时,对于释放出大块连续的空闲内存,下面的算法效果还不错 //但是如果释放的都是小页,那就怎么理想了. //不过话又说回来,对于只分配2字节的数据就占用1页空间,我们还是能忍受的,而且没有其他好的解决方案时,也只能忍受. //如下算法,只是在剩余的PAGE_SIZE-2字节空间进行发挥, //如果实在发挥不了,它也并不对linux系统有多少负面影响[gliethttp 2007-08-04] //在node[0]的内存区间[0x20000000,0x22000000]上申请size大小,align字节对齐的内存空间 static void * __init __alloc_bootmem_core (bootmem_data_t *bdata, unsigned long size, unsigned long align, unsigned long goal) { unsigned long i, start = 0; void *ret; unsigned long offset, remaining_size; unsigned long areasize, preferred, incr; //bdata->node_boot_start为当前node内存区间对应物理内存起始页帧号 //bdata->node_low_pfn为当前node内存区间对应的结尾地址 //所以eidx对应当前node内存区间占用页总数,我的at91rm9200开发板eidx=32M>>PAGE_SHIFT, //当然也根据u-boot传递的实际内存大小有关 unsigned long eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT); if (!size) BUG();//没人会和linux开这种玩笑--申请0字节 if (align & (align-1))//2,4,8,16,32,...等对齐方式 BUG(); offset = 0;//假定不进行对齐调整 if (align && //假如bdata->node_boot_start=0x20008,那么对于align=4对齐, //offset=物理起始内存和align之间还差多少个字节 (bdata->node_boot_start & (align - 1UL)) != 0) offset = (align - (bdata->node_boot_start & (align - 1UL))); //align对齐方式需要在node_boot_start物理起始内存的基础上加上offset个页 //这样调整后的物理内存地址是第一个align对齐的物理内存地址[gliethttp] offset >>= PAGE_SHIFT; if (goal && (goal >= bdata->node_boot_start) && //goal为开发人员指定的本次size大小数据分配,需要在goal为起始地址的物理内存之后 //这个goal开始地址是如下获取size大小数据的首选区域 //所以goal需要在当前node物理内存区间内 ((goal >> PAGE_SHIFT) < bdata->node_low_pfn)) { //ok,指定了goal,计算goal与node_boot_start的偏移地址 //那么preferred就是由开发人员指定的成功获取size大小空闲页概率系数比较大的首选区域 preferred = goal - bdata->node_boot_start; } else preferred = 0; //计算preferred首选偏移地址为开始地址,之后对应的第一个align对齐的物理页帧号 preferred = ((preferred + align - 1) & ~(align - 1)) >> PAGE_SHIFT; //将这些页帧偏移量都统计到以node_boot_start地址为基址上,所以 //以node_boot_start地址为基址的align和goal的综合页首选地址页偏移量preferred=preferred+offset[gliethttp] preferred += offset; areasize = (size+PAGE_SIZE-1)/PAGE_SIZE;//size字节数转换成页个数 incr = align >> PAGE_SHIFT ? : 1;//incr=align >> PAGE_SHIFT,如果结果incr=0,那么incr=1; //所以incr决定是按1“页”增加还是按incr=align >> PAGE_SHIFT个页增加 restart_scan://检测尝试,查看所需要分配的大小是否能够满足,如果满足找到对应的起始页i值 for (i = preferred; i < eidx; i += incr) {//首先搜索preferred首选页,以incr步进 //直到找到areasize的大小连续空闲页或者i < eidx才退出本for循环[gliethttp] //incr为1的搜索是最全面的搜索方式,他将以每一个页为起始页,计算随后的areasize个连续页是否空闲 //对于align对齐的方式,可能因为边界与边界之间的交界问题,而丢失本是连续的页却因align规则而不能获取的机会 unsigned long j; //include/asm-arm/Bitops.h->test_bit() //static inline int test_bit(int nr, const void * addr) //{ // return ((unsigned char *) addr)[nr >> 3] & (1U << (nr & 7)); //} if (test_bit(i, bdata->node_bootmem_map))//检测i页是否已经被占用,1:占用;0:可以使用 continue; for (j = i + 1; j < i + areasize; ++j) { if (j >= eidx) goto fail_block;//如果已经到达当前node内存区间的内存上限,fail_block if (test_bit (j, bdata->node_bootmem_map)) //如果在随后的第j页时,出现了被占用情况,表明以i为起始页,不能有areasize个连续页可用,fail_block goto fail_block; } start = i;//ok,以i为起始页的后续areasize个连续页都空闲[gliethttp 2007-08-04] goto found; fail_block:;//继续扫描 } if (preferred) { //如果首选地址页获取失败,那么从普通的offset页开始,继续扫描 //offset <= preferred,所以我们希望在[offset,preferred]之间能够成功获取 //当然[preferred,eidx]已经在前面搜索过了, //但是如果[offset,preferred]之间也不能成功获取,那kernel岂不是halt在这里了,反复去找,又一次次不能找到??? preferred = offset; goto restart_scan; } return NULL; found: if (start >= eidx) BUG(); //为了尽量利用内存,使用如下的计算方法,对本次分配的内存, //尽量使用上一次分配时最后一页内剩余内存空间中的last_offset~PAGE_SIZE个字节空闲区域 //但我想对于last_pos+1==start,这种连续分配是有好处的,但是当 //内存释放,出现内存间隙时,对于释放出大块连续的空闲内存,下面的算法效果还不错 //但是如果释放的都是小页,那就怎么理想了. //不过话又说回来,对于只分配2字节的数据就占用1页空间,我们还是能忍受的,而且没有其他好的解决方案时,也只能忍受. //如下算法,只是在剩余的PAGE_SIZE-2字节空间进行发挥, //如果实在发挥不了,它也并不对linux系统有多少负面影响[gliethttp 2007-08-04] if (align <= PAGE_SIZE && bdata->last_offset && bdata->last_pos+1 == start) { offset = (bdata->last_offset+align-1) & ~(align-1); if (offset > PAGE_SIZE)//这里align<=PAGE_SIZE,防止align=0 BUG(); remaining_size = PAGE_SIZE-offset;//start的前一连续页,start-1,剩余可用字节数 if (size < remaining_size) { areasize = 0; // last_pos unchanged bdata->last_offset = offset+size;//调整本页空闲偏移量 ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset + bdata->node_boot_start);
//呵呵,将第last_pos页中offset~offset+size空间作为此次申请空间 } else {//size>=remaining_size时,就要调整last_pos对应的页帧号了 remaining_size = size - remaining_size; //前一页start-1中分配出remaining_size字节数据之后还剩余size - remaining_size需要分配[这些字节是页对齐的] //计算这些剩余数据需要areasize页 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;//调整last_pos bdata->last_offset = remaining_size; //因为remaining_size是页对齐后的字节数,所以remaining_size&~PAGE_MASK就是页内偏移 } bdata->last_offset &= ~PAGE_MASK;//计算出页内偏移 } else { bdata->last_pos = start + areasize - 1;//对应末页 bdata->last_offset = size & ~PAGE_MASK;//相应末页可用字节的页内偏移量 ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start);//线性转为虚拟地址 } //好了,该真正的对页位图进行占用设置了 for (i = start; i < start+areasize; i++) if (test_and_set_bit(i, bdata->node_bootmem_map)) BUG(); //arch/arm/lib/test_and_set_bit.S->test_and_set_bit() //ENTRY(test_and_set_bit) // add r1, r1, r0, lsr #3//获取第r0个页对应的位图管理字节地址 // and r3, r0, #7//字节内偏址 // mov r0, #1 // save_and_disable_irqs ip, r2//禁用中断,同时ip=cpsr // ldrb r2, [r1] // tst r2, r0, lsl r3 // orr r2, r2, r0, lsl r3//r2的第r3位置位,成功清0后Z=0,因为硬件故障未能成功那么Z=1,返回后会halt系统 // strb r2, [r1]//回写 // restore_irqs ip//恢复之前的cpsr状态 // moveq r0, #0//如果因为硬件故障导致不能清0,那么r0=0;清0失败 // RETINSTR(mov,pc,lr)//返回 //arch/arm/lib/memset.S->memset(),对虚拟地址ret进行清0 //对memset的理解可参见《浅析armlinux-__arch_clear_user内存拷贝函数之3》[http://gliethttp.cublog.cn] memset(ret, 0, size); return ret;//返回申请到的空间虚拟地址 }
|