赞
踩
一:顺便看看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)
分散聚集映射:(以后再看)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。