setup_arch()函数分析3-request_standard_resources

来源:互联网 发布:ping网络命令怎么输入 编辑:程序博客网 时间:2024/04/30 01:07
文章来源:http://gliethttp.cublog.cn
在分析request_standard_resources前先来看linux对挂接在4G总线空间上的设备实体的管理方式-resource结构体
  一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,linux是怎么管理所有的这些外部"物理地址范围段",进而给用户和linux自身一个比较好的观察4G总线上挂接的一个个设备实体的简洁、统一级联视图的呢?
  linux采用struct resource结构体来描述一个挂接在cpu总线上的设备实体(32位cpu的总线地址范围是0~4G)
struct resource结构体定义在include/linux/ioport.h
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};
include/linux/types.h
typedef u32 phys_addr_t;
typedef phys_addr_t resource_size_t;

resource->name描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切;
resource->start描述设备实体在cpu总线上的线性起始物理地址;
resource->end描述设备实体在cpu总线上的线性结尾物理地址;
resource->flag描述这个设备实体的一些共性和特性的标志位;
只需要了解一个设备实体的以上4项,linux就能够知晓这个挂接在cpu总线的上的设备实体的基本使用情况,也就是[resource->start,resource->end]这段物理地址现在是空闲着呢,还是被什么设备占用着呢?
  linux会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段[resource->start,resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体,进而避免设备之间出现对同一总线物理地址段的重复引用,而造成对唯一物理地址的设备实体二义性.
  以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不是linux所想的,linux需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,这就需要链在一起,因此resource结构体提供了另外3个成员,
resource->parent描述管理本设备实体的父类resource指针
resource->sibling描述比本设备实体物理地址大的下一个设备实体的resource指针[这是一个单向链表]
resource->child描述本设备实体可能还要更详细的描述本设备实体内部的一些物理地址区段,来让linux能够更清楚本设备实体的内部的级联关系,甚至可以将本设备实体内部的具有特色的区段的给它七个名字标示一下
有这样一个保证:由来request_resource()函数提供这个保证的实现
resource->child是所有resource->sibling里边地址最小的,resource->sibling大之,resource->sibling大大之,以此类推,所以resource->sibling永远链接着比resource自己的resource->end地址值大的下一个设备实体.
比如:request_resource(&iomem_resource, res);将res设备实体挂接到iomem_resource设备实体上,request_resource()函数会会在[iomem_resource.start,iomem_resource.end]之间找到[res->.start,res->end](对于request_resource()函数的具体实现,我们后面分析)区间设备实体res是否可以占用,如果可以,那么将把res顺序加入到单向链表sibling的相应位置,如果已经被某个设备实体占用了,那么request_resource()将返回正在使用[res->.start,res->end]该段区间或部分区间的设备实体B的resource指针,进而res可以决定是否把自己作为B的孩子插到B的线性空间视图中.
  通过request_resource()函数,我们可以将打算占用[start,end]总线地址空间的设备实体,登记到供linux内核观察4G总线上设备占用情况的单向观察链表resource中,进而避免对已经被占用的总线地址空间段,再分配给另一个新来的设备实体.
  Linux把基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。I/O Region仍然是一种I/O资源,因此它仍然可以用resource结构类型来描述。Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。
如:struct resource iomem_resource = { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
现在我们再来看看request_standard_resources函数,它定义在arch/arm/kernel/setup.c
static void __init request_standard_resources(struct meminfo *mi, struct machine_desc *mdesc)
{
    struct resource *res;
    int i;

    kernel_code.start   = virt_to_phys(_text);  内核代码段开始
    kernel_code.end     = virt_to_phys(_etext - 1);
    kernel_data.start   = virt_to_phys(_data);  内核数据段开始
    kernel_data.end     = virt_to_phys(_end - 1);
    _text,_etext,_data,_end参见arch/arm/kernel/vmlinux.lds.S 链接脚本
    for (i = 0; i < mi->nr_banks; i++) {
        if (mi->bank[i].size == 0)
            continue;
         //总线上的所有memory
        res = alloc_bootmem_low(sizeof(*res));  //申请res存储空间
        res->name  = "System RAM";  //该设备实体的名称
        res->start = mi->bank[i].start; //该设备实体地址段
        res->end   = mi->bank[i].start + mi->bank[i].size - 1;
        res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;

        request_resource(&iomem_resource, res); //将该设备实体登记注册到总线空间链表

        if (kernel_code.start >= res->start &&
            kernel_code.end <= res->end)
//如果kernel_code在这个memory内,那么将kernel_code这个比较有特性的设备实体登记注册到res[该memory实体]设备实体下,来详细描述res内部地址空间中一些比较有特色的设备实体的分布情况,进而使linux能够给用户提供一个更加详细,充实的cpu外围总线设备的级联图解情况.
//res就像一个倒挂的树,res是树根,kernel_code是倒挂在res这个树根下的一根枝儿.
//                树根
//    .............................res..........................   
//    |                          |
//    |                          [..kernel_data..]//又生出来一根枝儿
//    [..kernel_code..]//生出来一根枝儿
            request_resource(res, &kernel_code); //将kernel_code设备实体登记注册到res空间
        if (kernel_data.start >= res->start &&
            kernel_data.end <= res->end)
            request_resource(res, &kernel_data); //将kernel_code设备实体登记注册到res空间
    }
//到这里所有总线上的外部内存都已经登记注册了,这些空间已经被告知linux---占用了.
    if (mdesc->video_start) { //将video,液晶之类的设备实体占用的总线地址空间段,进行登记注册.
        video_ram.start = mdesc->video_start;
        video_ram.end   = mdesc->video_end;
        request_resource(&iomem_resource, &video_ram);
    }

    /*
     * Some machines don't have the possibility of ever
     * possessing lp0, lp1 or lp2
     */
//登记注册以下io口,这些io口在linux系统运行起来之后,只能由如下的设备使用,不能他用.
    if (mdesc->reserve_lp0)
        request_resource(&ioport_resource, &lp0);
    if (mdesc->reserve_lp1)
        request_resource(&ioport_resource, &lp1);
    if (mdesc->reserve_lp2)
        request_resource(&ioport_resource, &lp2);
}

request_resource()函数的具体实现,它定义在kernel/resource.c
int request_resource(struct resource *root, struct resource *new)
{
    struct resource *conflict;

    conflict = request_resource_conflict(root, new);//调用request_resource_conflict
    return conflict ? -EBUSY : 0;
}
request_resource_conflict实现,同文件下
struct resource *request_resource_conflict(struct resource *root, struct resource *new)
{
    struct resource *conflict;

    write_lock(&resource_lock);
    conflict = __request_resource(root, new);调用__request_resource
    write_unlock(&resource_lock);
    return conflict;
}
__request_resource实现,同文件下
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
    resource_size_t start = new->start;
    resource_size_t end = new->end;
    struct resource *tmp, **p;

    if (end < start)
        return root;
    if (start < root->start)
        return root;
    if (end > root->end)
        return root;
    p = &root->child;  //root下的第一个链表元素*p.[child链表是以I/O资源物理地址从低到高的顺序排列的]
    for (;;) {
        tmp = *p;
        if (!tmp || tmp->start > end) {
            new->sibling = tmp;
            *p = new;
/*可以从root->child=null开始我们的分析考虑,此时tmp=null,那么第一个申请将以!tmp条件满足而进入,这时root->child的值为new指针,new->sibling = tmp = null;当第二次申请发生时:如果tmp->start > end成立,那么,root->child的值为new指针,new->sibling = tmp;这样就链接上了,空间分布图如:child=[start,end]-->[tmp->start,tmp->end](1);如果条件tmp->start > end不成立,那么只能是!tmp条件进入那么,root->child的值不变,tmp->sibling = new;new->sibling = tmp = null这样就链接上了,空间分布图如:child=[child->start,child->end]-->[start,end](2);当第三次申请发生时:如果start在(2)中的[child->end,end]之间,那么tmp->end < start将成立,继而continue,此时tmp = (2)中的[start,end],因为tmp->start < end,所以继续执行p = &tmp->slibing = null,因为tmp->end > start,所以资源冲突,返回(2)中的[start,end]域综上的两个边界值情况和一个中间值情况的分析,可以知道代码实现了一个从地地址到高地址的顺序链表模型图:childe=[a,b]-->[c,d]-->[e,f],此时有一个[x,y]需要插入进去,tmp作为sibling指针游动tmp指向child=[a,b],tmp指向[a,b],当tmp->start>y时,插入后的链接图为:child=[x,y]-->[a,b]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp tmp指向[c,d],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[x,y]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp,tmp指向[e,f],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[c,d]-->[x,y]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp,tmp指向null,插入后的链接图为:child=[a,b]-->[c,d]-->[e,f]-->[x,y]-->null;顺利的达到了检测冲突,顺序链接的目的*/
            new->parent = root;
            return NULL;
        }
        p = &tmp->sibling;
        if (tmp->end < start)
            continue;
        return tmp;
    }
}

原创粉丝点击