Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15531105
  • 博文数量: 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-06-24 10:55:36

 

linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析

文章来源:http://gliethttp.cublog.cn

接续前一篇文章《linux2.4.19下__ioremap和get_vm_area的粗略理解》未完成的部分-remap_area_pages,进行进一步阐述:
  虚拟内存vm技术,采用分页机制,使得进程可以使用比实际物理内存大甚多的空间;linux的地址有三种:
<1>物理地址
   就是没有启用mmu之前cpu操作的地址,:sdram的总线地址等.
<2>线性地址:
   不采用分页机制,可以通过简单的加、减一个基地址方式,完成vm地址和物理地址的转换,:物理内存sdram的虚拟地址位于3G~vmalloc_start,可以
   通过virt_to_phys和phys_to_virt两个函数实现虚拟地址和sdram物理地址的直接线性转换,另一例子就是at91rm9200控制寄存器的控制,同样直接使用
   AT91_IO_P2V(AT91C_BASE_SYS)将物理寄存器AT91C_BASE_SYS线性的映射成mmu下的虚拟地址(AT91_IO_P2V在include/asm-arm/arch-at91rm9200/Hardware.h中定义).
<3>逻辑地址,也被称为虚拟地址:逻辑地址和物理地址都被linux进行了分页管理,两者的页大小可以不同,但是为了管理上的方便,一般at91rm9200处理器下的linux系统将逻
   辑地址和物理地址均划分大小为4k的页面,逻辑地址经过vm技术的2级(at91rm9200处理器对应的linux系统采用二级mmu映射机制)表转换后最终映射到的真正使用的物理地
   址空间,at91rm9200处理器最终操作的还是总线地址,也就是物理地址,所以linux系统的工作之一就是将线性地址和逻辑地址,于是linux需要首先建立vm的2级转换表;

  这之前我们先来了解一下mmu的分页机制:mmu可以对4种大小进行映射管理,
1)微页:构成1k的存储区
2)小页:构成4k的存储区
3)大页:构成64k的存储区
4):构成1M的存储区(arch/arm/kernel/head-armv.S启动代码中就暂用4个节映射,完成mmu的正常启动)
  at91rm9200处理器采用了2级小页mmu管理方式,因此存在dir页目录4k[虚拟地址的31~20共12位],每个dir页目录项存放着第2级pte页表的基地址
dir页目录项内容如下:
bit 31 ... 10 9 ... 0
    | 页表基地址 | mmu控制域 |
pte页表项内容如下:
bit 31 ... 12 11 ... 0
    | 小页基地址 | mmu控制域 |
经过pte之后,pte表项内容的31~12位和虚拟地址的低12位组合成最终32位物理地址.
虚拟地址的结构:
bit 31 ... 20 19 ... 12 11 ... 0
    | 第一级表索引 | 第二级表索引 | 页索引[偏移量] |
举个例子来说明2级小页mmu的结构,先假设虚拟地址为addr,那么dir目录项的索引号为:addr>>20;该dir目录索引号对应的目录项的内容31~10位作为pte的
基地址,addr的19~12位作为相对该pte基址的偏移,:pte页表项,将pte页表项的31~12位和addr的11~0位偏移量组合成最终的物理地址,所以通过以上分析我们可以
总结出如下:dir共4k个,每个挂带1M数据,该1M数据又由256个pte分开管理,每个pte对应1个页4k,也就是1个dir页目录含有1个pte页表基地址对应256个pte页表项,1个页表项对应1个4k空间,可以用如下示意图简易的表示:
dir+0000->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k
dir+0001->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k
...
dir+4094->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k
dir+4095->pte+000->4k
       |->pte+001->4k
       |->pte+002->4k
        ...
       |->pte+253->4k
       |->pte+254->4k
       |->pte+255->4k

所以通过上述的2级小页mmu就轻松的实现了4k*256*4k=4G虚拟空间到物理空间的无缝转换.[注意:2级小页mmu结构中,dir和pte表项中的地址均为物理地址]
  通过上面的mmu的转换路径分析,我们大体掌握了mmu的机理,现在转入正题,来看看__ioremap函数中remap_area_page子函数体源码:

static int
remap_area_pages(unsigned long address, unsigned long pfn,
         unsigned long size, unsigned long flags)
{
    int error;
    pgd_t * dir;
    unsigned long end = address + size;

    pfn -= address >> PAGE_SHIFT;
//#define PGDIR_SHIFT           20
//#define pgd_index(addr)      ((addr) >> PGDIR_SHIFT)
//#define pgd_offset(mm, addr) ((mm)->pgd+pgd_index(addr))
//dir=address对应的高12值[0~4095]
    dir = pgd_offset(&init_mm, address);
    flush_cache_all();
    BUG_ON(address >= end);
    spin_lock(&init_mm.page_table_lock);
    do {
        pmd_t *pmd;
//at91rm9200处理器采用2级小页mmu方式,所以pmd_alloc将直接返回dir,即:pmd = dir;
//对于采用3级页表的系统来说,pmd_alloc将会向dir目录项填入pmd的物理基地址
        pmd = pmd_alloc(&init_mm, dir, address);
        error = -ENOMEM;
        if (!pmd)
            break;
//remap_area_pmd函数将中间页pmd填入pte内容,因为采用的是2级小页mmu方式,所以pmd=dir;
        if (remap_area_pmd(pmd, address, end - address,
                     pfn + (address >> PAGE_SHIFT), flags))
            break;
        error = 0;
//address+1M,如果size=5.2M,那么会连续占用接下的5个dir区域
        address = (address + PGDIR_SIZE) & PGDIR_MASK;
        dir++;
    } while (address && (address < end));
    spin_unlock(&init_mm.page_table_lock);
    flush_tlb_all();
    return error;
}

static inline int
remap_area_pmd(pmd_t * pmd, unsigned long address, unsigned long size,
     unsigned long pfn, unsigned long flags)
{
    unsigned long end;
    pgprot_t pgprot;
//只对address的低1M地址空间进行pte创建
    address &= ~PGDIR_MASK;
    end = address + size;
//#define PGDIR_SHIFT 20
//#define PGDIR_SIZE (1UL << PGDIR_SHIFT) 1级目录以1M字节为dir++自加单位
    if (end > PGDIR_SIZE)
        end = PGDIR_SIZE;
    pfn -= address >> PAGE_SHIFT;
    BUG_ON(address >= end);
    pgprot = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | flags);
    do {
//pte_alloc将获取256个连续pte的空间,并将地址指针指向这256个pte地址空间的最后一个地址处
        pte_t * pte = pte_alloc(&init_mm, pmd, address);
        if (!pte)
            return -ENOMEM;
//remap_area_pte为256个连续的pte填充pfn物理地址
        remap_area_pte(pte, address, end - address, pfn + (address >> PAGE_SHIFT), pgprot);
//因为是2级小页mmu方式,所以PMD_SIZE = PGD_SIZE = 1M
//又因为外层dir已经做了size总大小的处理,所以pmd这里,将只对1M空间进行pte内容填充,也就是while循环仅仅执行一次
        address = (address + PMD_SIZE) & PMD_MASK;
        pmd++;
    } while (address && (address < end));
    return 0;
}

以下为一些宏函数的出处和简单定义:
#define pmd_populate(mm,pmdp,pte) \
    do { \
        unsigned long __prot; \
        if (mm == &init_mm) \
            __prot = _PAGE_KERNEL_TABLE; \
        else \
            __prot = _PAGE_USER_TABLE; \
        set_pmd(pmdp, __mk_pmd(pte, __prot)); \
    } while (0)

//函数__mk_pmd用来把pte数值赋给pmd单元
//函数pte_alloc
static inline pmd_t __mk_pmd(pte_t *ptep, unsigned long prot)
{
    unsigned long pte_ptr = (unsigned long)ptep;
    pmd_t pmd;
//#define PTRS_PER_PTE 256
    pte_ptr -= PTRS_PER_PTE * sizeof(void *);
    pmd_val(pmd) = __virt_to_phys(pte_ptr) | prot;
    return pmd;
}
include/asm-arm/cpu-multi32.h:#
include/asm-arm/cpu-single.h中有如下定义:
#define cpu_set_pgd cpu_fn(CPU_NAME,_set_pgd)
#define cpu_set_pmd cpu_fn(CPU_NAME,_set_pmd)
#define cpu_set_pte cpu_fn(CPU_NAME,_set_pte)
而cpu_fn定义如下:
#ifdef __STDC__
#define __cpu_fn(name,x) cpu_##name##x
#else
#define __cpu_fn(name,x) cpu_/**/name/**/x
#endif
#define cpu_fn(name,x) __cpu_fn(name,x)
所以最后at91rm9200处理器cpu_set_pgd,cpu_set_pmd和cpu_set_pte对应的函数原型为:
cpu_set_pgd -- cpu_arm920_set_pgd
cpu_set_pmd -- cpu_arm920_set_pmd
cpu_set_pte -- cpu_arm920_set_pte
以上三个函数位于arch/arm/mm/proc-arm920.S

include/asm-arm/proc-armv/Pgtable.h
#define PTRS_PER_PTE 256  //第二级,表示有256个表项,每个表项描述4K的二级页面
#define PTRS_PER_PMD 1    //因为at91rm9200处理器不需要该级,所以此处忽略
#define PTRS_PER_PGD 4096 //第一级,表示有4K个表项,每个表项描述1M的一级页面
include/asm-arm/pgtable.h中
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];

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