Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15531190
  • 博文数量: 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-20 08:48:28

linux2.4.19下__ioremap和get_vm_area的粗略理解

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

  对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB.进程的4GB内存空间被人为的分为两个部分--用户空间与内核空间.用户空间地址分布从0到3GB(PAGE_OFFSET,在at91rm9200中它等于0xC0000000),3GB到4GB为内核空间.
  内核空间中,从3G到VMALLOC_START这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等),比如我们使用的vmware虚拟系统内存是160M,那么3G~3G+160M这片内存就应该映射物理内存.在物理内存映射区之后,就是vmalloc区域.对于160M的系统而言,VMALLOC_START位置应在3G+160M附近(在物理内存映射区与VMALLOC_START期间还存在一个8M的hole来防止跃界),VMALLOC_END的位置接近4G(最后位置系统会保留AT91C_IO_VIRT_BASE~0xFFFFFFFF作为at91rm9200的IO寄存器操作专用,实际过程是将0xFFFA0000 .. 0xFFFFFFFF寄存器物理地址,线性映射到0xFEFA0000 .. 0xFF000000虚拟地址,所以AT91C_IO_VIRT_BASE=0xFEFA0000,以后直接使用#define AT91_IO_P2V(x)    ((x) - AT91C_IO_PHYS_BASE + AT91C_IO_VIRT_BASE);就可以将物理地址直接线性的转换为虚拟地址).
  kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址,转换过程只是将虚拟地址减去3G,与之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址.
 而vmalloc申请的内存则位于VMALLOC_START~VMALLOC_END之间,与物理地址没有简单的转换关系,虽然在逻辑上它们也是连续的,但是在物理上它们不要求连续
  几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址.根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:
 (1)I/O映射方式(I/O-mapped)
  典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元.
 (2)内存映射方式(Memory-mapped)
  RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分.此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令.
  但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源.
  一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定.但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源.Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB).

1.结构体
  1.1>struct vm_struct {
     unsigned long flags;
     void * addr;
     unsigned long size;
     struct vm_struct * next;
     };
2.驱动程序引用
fb_info.RegAddr = (unsigned char*) ioremap_nocache(S1D_PHYSICAL_REG_ADDR,S1D_PHYSICAL_REG_SIZE);

#define ioremap_nocache(off,sz)        __arch_ioremap((off),(sz),1)
#define __arch_ioremap(off,sz,nocache)                \
 ({                                \
    unsigned long _off = (off), _size = (sz);        \
    void *_ret = (void *)0;                    \
    if (iomem_valid_addr(_off, _size))            \
        _ret = __ioremap(iomem_to_phys(_off),_size,0);    \
    _ret;                            \
 })
#define iomem_valid_addr(iomem,size) (1)
#define iomem_to_phys(iomem)         (iomem)

所以ioremap_nocache函数最后还是等效于 直接调用 __ioremap函数

3.__ioremap函数分析[/arch/arm/mm/Ioremap.c]
void * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)
{
    void * addr;
    struct vm_struct * area;
    unsigned long offset, last_addr;
//size=0或者32位数据wraparound了将返回null
    last_addr = phys_addr + size - 1;
    if (!size || last_addr < phys_addr)
        return NULL;
//#define PAGE_ALIGN(addr)      (((addr)+PAGE_SIZE-1)&PAGE_MASK)
//页面对齐,最后经过页规整之后,size的值将是页大小的整倍数,即:size % PAGE_SIZE将等于0
    offset = phys_addr & ~PAGE_MASK;
    phys_addr &= PAGE_MASK;
    size = PAGE_ALIGN(last_addr) - phys_addr;

    area = get_vm_area(size, VM_IOREMAP);
    if (!area)
        return NULL;
    addr = area->addr;
    if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr >> PAGE_SHIFT, size, flags)) {//remap_area_pages待读
        vfree(addr);
        return NULL;
    }
    return (void *) (offset + (char *)addr);
}
//#define __va(x)               ((void *)__phys_to_virt((unsigned long)(x)))

//#define TASK_SIZE             (0xc0000000)        3GB
//#define AT91_SDRAM_BASE       (0x20000000)        sdram起始地址


//#define PAGE_OFFSET           TASK_SIZE
//#define PHYS_OFFSET           (AT91_SDRAM_BASE)
//#define __virt_to_phys(vpage) ((vpage) - PAGE_OFFSET + PHYS_OFFSET)//用来线性转换3GB~VMALLOC_START-8M虚拟内存地址到物理内存SDRAM地址
//#define __phys_to_virt(ppage) ((ppage) + PAGE_OFFSET - PHYS_OFFSET)//将物理内存AT91_SDRAM_BASE~meminfo.end地址线性转换成3GB~VMALLOC_START-8M的虚拟地址

//在arch/arm/mm/Init.c的void __init mem_init(void)中有high_memory = (void *)__va(meminfo.end);这么一句,
//在arch/arm/kernel/Setup.c的void __init setup_arch(char **cmdline_p)命令行解释函数中,调用函数:
//parse_cmdline(struct meminfo *mi, char **cmdline_p, char *from);实现了meminfo的填充,其中如果用户通过uboot之类
//的参数mem=31M方式传递了内存大小,那么由linux上电默认的内存大小32M将会被mem=31M代替,这样最后的1M物理内存就被闲置了,可以
//作DMA专用,也可以通过参数@0x20000000指定外部sdram的起始地址,一般不需要,linux上电默认的地址就是sdram的起始地址了.

//#define VMALLOC_OFFSET          (8*1024*1024)
//#define VMALLOC_START           (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))
//#define VMALLOC_VMADDR(x)       ((unsigned long)(x))
//#define VMALLOC_END             (AT91C_IO_VIRT_BASE-1)
//从上面可以看出vmalloc的VM区域VMALLOC_START是从high_memory+8M空间以后开始的,high_memory到VMALLOC_START之间的8M内存漏洞系统用来捕获
//任何超过内存区域的访问操作,vmalloc()函数多创建的最后一个4k空间漏洞也是出于同样的考虑.
struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
{
    unsigned long addr;
    struct vm_struct **p, *tmp, *area;
    area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
    if (!area)
        return NULL;
//人为的多申请4k空间作为hole,用来捕获内存越界异常
    size += PAGE_SIZE;
    addr = VMALLOC_START;
    write_lock(&vmlist_lock);
//vmlist是按虚拟地址从低到高的顺序链接成的扫描链
//这部分的实现方式和上一篇文章《linux下request_mem_region的粗略理解》[http://blog.chinaunix.net/u1/38994/showart_324267.html]异曲同工
    for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
        if ((size + addr) < addr)
            goto out;
        if (size + addr <= (unsigned long) tmp->addr)
            break;
        addr = tmp->size + (unsigned long) tmp->addr;
        if (addr > VMALLOC_END-size)
            goto out;
    }
//按虚拟地址从低到高的顺序,插入area自身,挤占原有*p.
    area->flags = flags;
    area->addr = (void *)addr;
    area->size = size;
    area->next = *p;
    *p = area;
    write_unlock(&vmlist_lock);
    return area;
out:
    write_unlock(&vmlist_lock);
    kfree(area);
    return NULL;
}

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

liubaifu2008-12-14 14:22:24

呵呵!!讲的很好啊!学长!!我也是桂电的哦