mmap虚拟映射(DMA机制)

一:顺便看看mmap的映射程序:(吧物理内存映射到用户空间)

直接提供驱动程序,内存映射提供用户程序直接访问设备内存的能力:意味着用户程序的一段内存与设备内存关联起来,

mmap的限制:必须以page_size为单位进行映射,内核只能在页表一级上对虚拟地址进行管理,直接访问内存数据的接口。

http://blog.csdn.net/zjujoe/archive/2009/05/15/4189612.aspx

mmap(caddr_t addr,size_t len ,int port, int flag, int fd ,off_t offset);len:映射到用户空间的字节数,offset被映射文件开头offset个字节开始算。在进程的虚拟空间中找一块VMA,将这块VMA进行映射,将这个VMA插入进程VMA的链表中,如果调用mmap,就先找到对应的VMA。mmap的机制就是建立页表,填充vma结构体中的vm_operations_struct指针,VMA就是描述一个虚拟内存区域。

驱动操作声明:int (*mmap) (struct file *, struct vm_area_struct *);

一些page结构体和虚拟地址之间进行转换:virt_to_page (逻辑地址转换为相应的page结构)

pfn_to_page(页帧号转换为page结构)

page_address(返回页的内核虚拟地址)

kmap(对于低端地址,返回页的逻辑地址)。

虚拟内存去(VMA)用于管理进程地址空间中不同区域的内核数据结构:备份的一个连续的虚拟内存地址范围。

查看进程的内存区域  查看:/proc/(pid)/maps其中的每个成员都与vm_area_struct结构中的成员相对应。系统通过创建一个表示该映射的心VMA作为相应。

struct vm_area_struct{ //用于描述一个虚拟的内存区域
    /* The first cache line has the info for VMA tree walking. */
    //vm所覆盖的地址范围

    unsigned long vm_start;        /* Our start address within vm_mm. */
    unsigned long vm_end;        /* The first byte after our end address
                       within vm_mm. */
    struct vm_area_struct *vm_next, *vm_prev;

    struct rb_node vm_rb;
    unsigned long rb_subtree_gap;

    /* Second cache line starts here. */

    struct mm_struct *vm_mm;    /* The address space we belong to. */
    pgprot_t vm_page_prot;        /* Access permissions of this VMA. */
    unsigned long vm_flags;        /* Flags, see mm.h. */    //vm的标志
    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } linear;
        struct list_head nonlinear;
        const char __user *anon_name;
    } shared;
    struct list_head anon_vma_chain; /* Serialized by mmap_sem &
                      * page_table_lock */
    struct anon_vma *anon_vma;    /* Serialized by page_table_lock */
    const struct vm_operations_struct *vm_ops; //操作函数

    /* Information about our backing store: */

//表示该区域中被映射的第一页的文件的位置
    unsigned long vm_pgoff;        /* Offset (within vm_file) in PAGE_SIZE。units, *not* PAGE_CACHE_SIZE */
    struct file * vm_file;        /* File we map to (can be NULL). */
    void * vm_private_data;        /* was vm_pte (shared mem) */ //保存自身数据

#ifndef CONFIG_MMU
    struct vm_region *vm_region;    /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
#endif
}

vm_area_struct :表示访问设备的虚拟地址,驱动程序只需要为该地址范围建立合适的页表,为了优化查找方式,内核维护了VMA的链表和树形结构,该结构体的许多成员是维护这个结构体的

建立页表的过程:*********(为一段物理内存创建页表):(页表:处理器必须使用某种机制,将虚拟地址转换为相应的物理地址,这种机制称为页表。)

方法一:int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
            unsigned long pfn, unsigned long size, pgprot_t prot)

vm_area_struct :将一定范围内的页映射到该区域,addr:重新映射时的起始用户虚拟地址,在(addr~addr+size)之间的虚拟地址建立页表,pfn:与物理内存对应的页帧号,虚拟内存要被映射到该物理内存上(主要需要的到该物理地址)实际是物理地址右移page_shift(4k),page_to_pfn(c->vm_pages)找到实际的页帧号,size:大小,port:新页的保护属性。

io_remap_pfn_range(vma, vaddr, pfn, size, prot)(是在phys_addr指向IO内存时使用)

这两个函数内容基本一样:为一段物理地址创建新的页表。(一次性全部建立页表)

方法二:nopage映射:

struct page * (*nopage)(struct vm_area_struct * area,unsigned long address, int *type);

使用:get_page(page *)用来增加返回的内存页的计数。

几种映射方式:

1:重新映射特定的IO区域:

eg: cma_obj->paddr >> PAGE_SHIFT页对齐

    ret = remap_pfn_range(vma, vma->vm_start, cma_obj->paddr >> PAGE_SHIFT,
    vma->vm_end - vma->vm_start, vma->vm_page_prot);

2:重新映射内核虚拟地址:(一般也用这种方式)

内核虚拟地址:是通过vmalloc这样的函数返回的地址,,是一个映射到内核页表的虚拟地址,在内核空间中采用

page = vmalloc_to_page((void *)addr);来获得相应的物理配置page结构体,addr是虚拟的内核地址。

ret = vm_insert_page(vma, start, page)将单个页插入vma结构体中去。

二:直接IO访问:内核缓冲了大多数IO操作,在一定程度上分割了用户空间和实际空间,,有时候需要直接对用户空间缓冲区执行IO操作,直接进行数据传输,

直接访问IO的缺点eg:所需的资源非常大,而没有使用缓冲io优势,直接IO需要write系统调用写同步执行,否则应用程序不知道什么时候才能再次执行它的缓冲区(在每个写操作写完不能停止应用程序,导致关闭程序缓慢),

long get_user_pages(struct task_struct *tsk, struct mm_struct *mm,unsigned long start, unsigned long nr_pages, int write,
        int force, struct page **pages, struct vm_area_struct **vmas)

不常用:所以具体的作用就不怎么看了。

三:直接内存访问:(DMA是一种硬件机制,它容许外围设备和主内存之间直接传输他们的IO数据),二不需要系统处理器的参与。

1:分配dma缓冲区:主要问题:分配内存大于一页,必须占据连续的物理页,(一般采用的都是物理地址)并不是所有的内存都适用于DMA内存(一些系统的高端地址不能使用dma),

分配内存:一般使用GFP_DMA标志调用kmalloc或者get_free_pages(GFP_DMA)(大内存)。设备不支持分散聚集操作的情况下,DMA缓冲区在物理上必须是连续的

2:总线地址:驱动程序连接到总线接口上,DMA的硬件使用总线地址地址,程序使用虚拟地址,总线是从设备上看到的地址,物理地址是从CPU的MMU控制器外围角度看到的内存地址,一般来说总线地址为物理地址,但是通过桥接电路连接,会将IO地址映射为不同的物理地址。

使用内核逻辑地址和总线地址进行了简单的转换(一般不常用:只做了解):

unsigned long virt_to_bus(volatile void *address);

void* virt_to_bus(unsigned long);

3:DMA层映射:

是要分配的dma缓冲区为该缓冲区生成的,设备可访问的地址的组合,(注意缓存一致性的问题,eg:当设备使用dma从内存中读取数据时,在处理器缓存中的任何改变必须立刻得到刷新。)

dma映射的结构体:dma_addr_t来表示总线地址,dma_addr_t类型的变量对驱动程序是不透明的

根据dma缓冲区期望的保留时间的长短:映射方式可以分为:

一致性dma映射:存在于驱动的生命周期。(可同时被cpu和外围设备访问)

a:分配以页为单位

dma_alloc_writecombine(fbi->dev, map_size,  &map_dma, GFP_KERNEL);分配出的内存不使用缓存,但是用写缓冲

dma_alloc_coherent //都不使用缓冲

void *dma_alloc_coherent(struct device *dev, size_t size,  dma_addr_t *dma_handle, gfp_t gfp)
{
    void *ret;
    int order = get_order(size);
    /* ignore region specifiers */
    gfp &= ~(__GFP_DMA | __GFP_HIGHMEM);

    if (dma_alloc_from_coherent(dev, size, dma_handle, &ret))
        return ret;

    if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
        gfp |= GFP_DMA;

    ret = (void *)__get_free_pages(gfp, order);

    if (ret != NULL) {
        memset(ret, 0, size);
        *dma_handle = virt_to_phys(ret);
    }
    return ret;
}

返回值内核的虚拟地址。相关的总线地址返回保存在map_dma中。

b:分配以池为单位:

创建dma池

 dma_pool *dma_pool_create(const char *name, struct device *dev,size_t size, size_t align, size_t boundary)

分配dma池:void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags,dma_addr_t *handle)

流式dma映射:为单独的操作建立流式映射。

dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size,
              enum dma_data_direction direction)

分散聚集映射:(以后再看)。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值