现在的位置: 首页 > 综合 > 正文

mmap方法实现物理内存到用户虚拟地址的映射

2013年09月03日 ⁄ 综合 ⁄ 共 2635字 ⁄ 字号 评论关闭

内核空间内存管理:

物理内存被划分成struct
page
来进行管理。然后把所有page划分成不同的struct
zone
Linux中使用了三种zone

  1. ZONE_DMA;

  2. ZONE_NORMAL;

  3. ZONE_HIGHMEM;

内核中获取内核虚拟内存有三种途径:

  1. 获取页:

如果你需要用到struct
page
,则:

struct
page *alloc_pages(gfp_mask, order);

struct
page *alloc_page(gfp_mask);

void
*page_address(struct page *page)

第一个页的内核虚拟地址 =
page_address(alloc_page(GFP_KERNEL, 3))
--分配8个页

第一个页的内核虚拟地址 =
page_address(alloc_page(GFP_KERNEL))
--分配一个页

如果用不到struct
page
,则:

unsigned
long __get_free_pages(gfp_mask, order);

unsigned
long __get_free_page(gfp_mask);

  1. kmalloc

分配以字节为单位的一块内核内存,且分配的内存物理上连续。返回内核虚拟地址。

  1. vmalloc

分配以字节为单位的一块内核内存,但分配的内存物理上不连续。返回内核虚拟地址。

4
slab
分配器

其实就是kmalloc() 分配连续的内核虚拟地址,用于小内存分配。

__get_free_page() 分配连续的内核虚拟地址,用于整页分配。

前者基于slab,两者最终都使用__alloc_page()来返回物理页的page结构。其实他们都是在“物理内存映射区”进行内存分配,其内核虚拟地址与物理地址仅相差一个常量。

这种内核虚拟地址和vmalloc分配(经过页表映射)的内核虚拟地址不一样,也称为逻辑地址。他们与物理地址之间的转换也很简单:

/include/asm/memory.h(page.h中包含了memory.h)

#define __pa(x)            __virt_to_phys((unsigned
long)(x))
   #define __va(x)            ((void *)__phys_to_virt((unsigned long)(x)))

static inline unsigned long virt_to_phys(void *x){
          return __virt_to_phys((unsigned long)(x));
    }
   static inline void *phys_to_virt(unsigned long x){
          return (void *)(__phys_to_virt((unsigned long)(x)));
   }

#define
__virt_to_phys(x)    ((x) - PAGE_OFFSET + PHYS_OFFSET)

   #define __phys_to_virt(x)    ((x) - PHYS_OFFSET + PAGE_OFFSET)

都是通过__virt_to_phys()和__phys_to_virt()两个宏的简单实现。

另外还有:

#define page_to_pfn(page)
   #define pfn_to_page(pfn)

#define virt_to_page(kaddr) 等其他一些地址转换函数。

 

用户空间直接访问设备内存:

内存映射mmap()可以提供给 用户程序 直接访问 设备内存 的能力。

[转载]【原】mmap方法实现物理内存到用户虚拟地址的映射

映射一个设备就是通过建立页表,将用户空间的一段虚拟内存空间和设备内存关联起来。但是并不是所有设备都能够进行mmap映射。比如串口和其他面向流的设备就不行,并且mmap还有一个限制:必须以PAGE_SIZE为单位进行映射。

mmap方法是file_operations结构的一部分,用户空间系统调用原型为:

mmap
(cadaar_t addr, size_t len, int prot, int flags, int fd, off_t offset)

但是文件操作声明如下:

int
(*mmap) (struct file *filp, struct vm_area_struct *vma)

注:vm_area_struct 是描述进程地址空间的基本管理单元。vma中就需要包含用于访问设备虚拟地址的信息,因此大量工作是在内核中完成的。

实现mmap主要就是为用户虚拟地址建立到物理地址的页表,并将vma->vm_ops替换为一系列的新操作就可以了。

两种建立页表的方法:

  1. 一次全部建立:

int remap_pfn_range(struct
vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

int io_remap_page_range(struct
vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);

这两个函数的功能是负责为一段物理地址创建新的页表。前者在pfn指向实际系统的RAM的时候使用,后者在phys_addr指向IO内存的时候使用。


(1)
remao_pfn_range
限制:只能访问保留页/超出物理内存的物理页。

因此这个函数不允许重映射常规地址。因为常规地址受内存管理单元的管理,可能会出现被换出内存等不安全现象。remap_pfn_range无法处理RAM:基于内存的设备无法简单地实现mmap,因为其设备内存是通用的RAM,而不是IO内存。但是对于任何需要将RAM映射到用户空间的驱动程序来说可以通过nopage来达到目的。

(2) 但是这个函数可以映射设备IO内存(包括高端PCI缓冲区和ISA内存等)/ 内存中的保留页,如x86系统中的64k-1M的范围,如果想把kmalloc等申请的常规地址映射到用户空间,使用mem_map_reserve()把相应的内存设置为保留即可。不过对于常规内存还是建议使用nopage

(3) 访问常规内存具体方法如下:
    一般情况是你的驱动分配一块内存,然后在驱动的mmap中使用这块内存的物理地址转成页帧号, 再调用remap_pfn_range

抱歉!评论已关闭.