Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15498869
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: LINUX

2007-08-04 18:13:37

浅析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;//返回申请到的空间虚拟地址
}

阅读(3545) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~