1. 内存映射

所谓的内存映射就是把物理内存映射到进程的地址空间之内,这些应用程序就可以直接使用输入输出的地址空间,从而提高读写的效率。Linux提供了mmap()函数,用来映射物理内存。

在驱动程序中,应用程序以设备文件为对象,调用mmap()函数,内核进行内存映射的准备工作,生成vm_area_struct结构体,然后调用设备驱动程序中定义的mmap函数。

2. 映射的种类

把同一个物理地址映射为虚拟地址有两种方法,第一种是mmap()函数将物理地址映射到进程的虚拟地址空间中去,第二种方法为ioremap()函数映射到内核虚拟地址上的方法。

应用程序中的mmap函数:

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);

start 映射到进程空间的虚拟地址

length 映射空间的大小

prot 映射到内存的读写权限

flags flags可取MAP_SHARED,MAP_PRIVATE,MAP_FIXED,如果是MAP_SHARED,此进程对映射空间的内容修改会影响到其它的进程,即对其它的进程可见,而MAP_PRIVATE,此进程修改的内容对其它的进程不可见

fd  要映射文件的文件标识符

offset 映射文件的位置,一般从头开始。而在设备文件中,表示映射物理地址的起始地址

设备驱动程序的mmap函数:

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

首先调用应用程序的mmap函数,然后内核进行适当处理之后,进行相应的内存映射,即生成vm_area_struct结构体,然后传递给设备驱动程序的mmap函数。

关于vma中的一些参数说明:

(1)unsigned long vm_start 映射到进程空间的起始地址

(2)unsigned long vm_end   映射到进程空间的结束地址

(3)unsigned long vm_flags  即包含在应用程序中mmap中的flags值,如VM_READ,VM_WRITE,VM_SHARED,VM_EXEC

(4)unsigned long vm_pgoff 映射到物理内存的偏移量

mmap映射的方法:

有两种方法建立页表,一次性建立页表,可以调用函数remap_pfn_range和每次建立一个页的页表,调用函数nopage。

remap_pfn_range:

这个函数的功能是一次性建立新的页表去映射物理地址。

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

返回值:映射成功时返回0,否则返回一个错误的负数代码。

vma 物理地址被映射到的虚拟内存区域

virt_addr 被映射到进程空间的起始虚拟地址。页表建立的范围在virt_addr到virt_addr+size

pfn 对应物理地址的页框号,一般是vma->vm_pgoff域。

size 被映射区域的字节大小

prot  vma->vm_page_prot

nopage:

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

vm_area_struct:虚拟内存区域

address:发生page fault的进程空间的虚拟地址

type  page fault的处理类型

get_page(struct page* pageptr);

增加被映射页的使用次数。

3. remap_pfn_range与nopage的区别

(1)remap_pfn_range一次性建立页表,而nopage通过缺页中断找到内核虚拟地址,然后通过内核虚拟地址找到对应的物理页

(2)remap_pfn_range函数只对保留页和物理内存之外的物理地址映射,而对常规RAM,remap_pfn_range函数不能映射,而nopage函数可以映射常规的RAM。

4. 例子

下面的例子分别采用remap_pfn_range与nopage建立内存映射

驱动程序memap.c:

#include #include #include #include #include #include #include #include #include #include #include #include #include #define SHARE_MEM_COUNT 4

#define SHARE_MEM_SIZE (PAGE_SIZE*SHARE_MEM_COUNT)

MODULE_LICENSE("GPL");

static char* reserve_virt_addr;

static int major;

char* share_memory=NULL;

int mmapdrv_open(struct inode*,struct file* filp); //驱动程序的open函数

int mmapdrv_release(struct inode*,struct file* filp);//驱动程序的release函数

int mmapdrv_mmap(struct file* file,struct vm_area_struct*vma);//驱动程序中的mmap函数

void simple_vma_open(struct vm_area_struct* vma );//vm_operations_struct对vma(虚拟内存区域)打开函数

void simple_vma_close(struct vm_area_struct *vma);//vma的关闭函数

struct page* simple_vma_nopage(struct vm_area_struct* vma,unsigned long address,int *type);//nopage映射

struct page* simple_vma_nopage1(struct vm_area_struct* vma,unsigned long address,int *type);

static int simple_nopage_mmap(struct file* filp,struct vm_area_struct *vma);//驱动程序中的mmap与上面的mmap可选择其一

static struct file_operations mmapdrv_fops={

owner:THIS_MODULE,

mmap:simple_nopage_mmap,

open: mmapdrv_open,

release:mmapdrv_release,

};

static struct vm_operations_struct simple_remap_vm_ops={

.open=simple_vma_open,

.close=simple_vma_close,

.nopage=simple_vma_nopage1,

};

static int __init memc_init(void){

if((major=(register_chrdev(0,"mapdrv",&mmapdrv_fops)))<0){

printk("register mapdrv failure/n");

return -EIO;

}

printk("register success,major=%d/n",major);

share_memory=vmalloc(SHARE_MEM_SIZE);//通过vmalloc分配内存,返回的是内核虚拟地址,然后将物理内存映射到进程的虚拟地址空间上去

int lp;

for(lp=0;lpsprintf(share_memory+PAGE_SIZE*lp,"Test %d",lp);

}

return 0;

}

static void __exit memc_exit(void){

if(reserve_virt_addr){

iounmap(reserve_virt_addr);

}

unregister_chrdev(major,"mapdrv");

return;

}

int mmapdrv_open(struct inode* inode,struct file* filp){

//MOD_INC_USE_COUNT;

return 0;

}

int mmapdrv_release(struct inode* inode,struct file* filp){

//MOD_DEC_USE_COUNT;

return(0);

}

//remap_pfn_range一次性建立页表进行映射

int mmapdrv_mmap(struct file* filp,struct vm_area_struct *vma){

printk("vm_pgoff=0x%lx/n",vma->vm_pgoffvm_start);//进程地址空间的起始地址

printk("vm_end=0x%lx/n",vma->vm_end);//进程地址空间的结束地址

printk("vm_flags=0x%lx/n",vma->vm_flags);

unsigned long physical=vma->vm_pgoffvm_end-vma->vm_start; //映射的空间长度

vma->vm_flags|=VM_RESERVED;//remap只能对VM_RESERVED和物理内存之外的内存进行映射

vma->vm_flags|=VM_IO;

if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,size,PAGE_SHARED)){

printk("remap page range failed/n");

return -ENXIO;

}

printk("remap page range success/n");

return 0;

}

//nopage映射

static int simple_nopage_mmap(struct file* filp,struct vm_area_struct *vma){

unsigned long offset=vma->vm_pgoff=__pa(high_memory)||(filp->f_flags&O_SYNC)){

vma->vm_flags|=VM_IO;

}

vma->vm_flags|=VM_RESERVED;

vma->vm_ops=&simple_remap_vm_ops;//当发生page fault时会调用nopage函数进行缺页处理

simple_vma_open(vma);

return 0;

}

void simple_vma_open(struct vm_area_struct *vma){

printk(KERN_NOTICE "simple VMA open virt %lx,phys %lx/n",vma->vm_start,vma->vm_pgoff

void simple_vma_close(struct vm_area_struct *vma){

printk(KERN_NOTICE "Simple VMA close./n");

}

//simple_vma_nopage是通过物理地址找到page,而simple_vma_nopage1通过内核虚拟地址找到page, 即vmalloc返回的内核虚拟地址

struct page* simple_vma_nopage(struct vm_area_struct* vma,unsigned long address,int *type){

printk("call nopage method/n");

struct page* pageptr;

unsigned long offset=vma->vm_pgoff

long physaddr=address-vma->vm_start+offset;

//address是缺页进程地址空间的虚拟地址,vm_start是进程地址空间的起始映射地址,address-

vma->vm_start+offset要映射的物理地址

unsigned long pageframe=physaddr>>PAGE_SHIFT;

printk("offset is %lx, physaddr is %lx,pageframe is %lx/n",offset,physaddr,pageframe);

if(!pfn_valid(pageframe))

return NOPAGE_SIGBUS;

pageptr=pfn_to_page(pageframe);//根据页框号,得到page

if(type)

*type=VM_FAULT_MINOR;

printk("pageptr is %lx/n",(unsigned long)pageptr);

return pageptr;

}

//首先根据page fault的进程地址空间的address找到内核虚拟地址,然后根据内核虚拟地址,即vmalloc返回的地址找到相对应的page

//对于address-vma->vm_start地址范围的内容是通过vmalloc()+address-

vma->vm_start找到相应的页取得的,所以对于用户空间address-vma->vm_start存储的内容就是

vmalloc()+address-vma->vm_start对应页的内容

struct page* simple_vma_nopage1(struct vm_area_struct* vma,unsigned long address,int *type){

struct page *page;

unsigned long offset1;

void *page_ptr;

unsigned long offset=vma->vm_pgoffvm_start+offset;

unsigned long pageframe=physaddr>>PAGE_SHIFT;

printk("vm_pgoff

is %lx, PAGE_SHIFT is %lx,PAGE_SIZE is %lx,offset is %lx, physaddr is

%lx,pageframe is %lx/n",vma->vm_pgoff,PAGE_SHIFT,(unsigned

long)PAGE_SIZE,offset,physaddr,pageframe);

offset1=address-vma->vm_start; //映射的进程地址空间的偏移

if(offset1>=SHARE_MEM_SIZE)return NOPAGE_SIGBUS;

page_ptr=share_memory+offset1;//对应的缺页的内核虚拟地址

printk("address

is %lx,vma->vm_start is %lx,offset1 is %lx,share_memory is

%lx,page_ptr is %lx/n",address,vma->vm_start,offset1,(unsigned long

)share_memory,(unsigned long)page_ptr);

page=vmalloc_to_page(page_ptr);

get_page(page);//增加该页的使用计数

if(type) *type=VM_FAULT_MINOR;

return page;

}

module_init(memc_init);

module_exit(memc_exit);

测试程序:

test.c

#include #include #include #include #include #include #include #define SHARE_MEM_COUNT 4

#define SHARE_MEM_SIZE (4096*SHARE_MEM_COUNT)

int main(){

int fd;

char *data;

int loop;

fd=open("/dev/mapdrv",O_RDWR|O_NDELAY);

if(fd>=0){

data=(char*)mmap(0,SHARE_MEM_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

printf("data is %lx/n",(unsigned long)data);

printf("[%s]/n",data+4096*loop);

}

munmap(data,SHARE_MEM_SIZE);

close(fd);

}

return 0;

}

运行:

(1)将memap.c与test.c文件放到/usr/src/kernels/linux-2.6.20/drivers/char目录下。

并在Makefile文件中添加obj-m  +=memap.o

(2)返回到/usr/src/kernels/linux-2.6.20下make.

(3)插入模块insmod memap.ko,然后 mknod /dev/globalvar c 252 0

252是动态生成的major值。

(4)编译test gcc -o test test.c

(5)./test,可以打印出写入的值。

[Test 0]

[Test 1]

[Test 2]

[Test 3]

总结:

1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效

率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page

fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规

内存映射,只能对保留的内存与物理内存之外的进行映射。

2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如

kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三

个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函

数是将物理地址映射为内核虚拟地址。

3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的

mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通

过remap_pfn_range函数来完成。

linux usb ga驱动详解,Linux设备驱动之内存映射相关推荐

  1. linux环境下ps命令行,Linux下ps命令详解 Linux下ps命令的详细使用方法

    Linux下ps命令详解 Linux上进程有5种状态:1. 运行(正在运行或在运行队列中等待) 2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 3. 不可中断(收到信号不唤醒和不可运 ...

  2. linux摄像头V4L2 subdev,linux 摄像头驱动 详解linux 摄像头驱动编写

    想了解详解linux 摄像头驱动编写的相关内容吗,feixiaoxing在本文为您仔细讲解linux 摄像头驱动的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:linux,摄像头驱动,下面 ...

  3. linux中dd命令详解,Linux中DD命令详解

    Linux中DD命令详解 1.dd命令简介 功能:把指定的输入文件拷贝到指定的输出文件中,并且在拷贝过程中可以进行格式转换.可以用该命令实现DOS下的diskcopy命令的作用.先用dd命令把软盘上的 ...

  4. linux的vi命令详解,Linux上Vi命令详解

    Linux下Vi命令详解 补充一点:在vi中使用命令的方法是:冒号+命令+回车,如:q 回车代表退出.================================================= ...

  5. linux中create命令详解,linux中 pmap 命令详解

    通过查看帮助,返回了如下信息: Usage: pmap [options] pid [pid ...] Options: -x, --extended show details -X show eve ...

  6. linux中which命令详解,Linux下which命令使用详解(转)

    我们经常在linux要查找某个文件,但不知道放在哪里了,可以使用下面的一些命令来搜索: which 查看可执行文件的位置. whereis 查看文件的位置. locate 配合数据库查看文件位置. f ...

  7. linux中的ps fx命令,Linux下ps命令详解 Linux下ps命令的详细使用方法

    Linux下ps命令详解 1. 运行(正在运行或在运行队列中等待) 2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断 ...

  8. linux 命令行 time,详解Linux time 命令的使用

    1.命令简介 time 用于统计命令执行所消耗的时间及相关系统资源等信息.time 可以显示的资源有四大项,分别是: Time resources Memory resources IO resour ...

  9. linux怎么授权目录,详解Linux如何将一个文件夹的所有内容授权给某一个用户?...

    详解Linux如何将一个文件夹的所有内容授权给某一个用户? 发布时间:2020-09-14 15:23:51 来源:脚本之家 阅读:138 作者:冰阔落 栏目:服务器 [问题分析] 我们可以使用cho ...

  10. linux下awk命令详解,Linux文件处理awk命令-linux awk命令详解-嗨客网

    Linux文件处理awk命令详解教程 Linux awk命令说明 awk 是一个强大的文本分析工具,相对于 awk 有 3 个不同版本: awk.nawk 和 gawk,未作特别说明,一般指 gawk ...

最新文章

  1. html插入不规则表格,如何构建HTML表格中的不规则形状?
  2. xmake 新增对 Qt 编译环境支持,用自己最喜爱的编辑器去开发 Qt 程序
  3. Gartner:2019年七大AI科技趋势,百万行业将颠覆!
  4. python web界面整合 tail_如何使用Python Tornado在HTTP上实现“ tail”?
  5. c php aes加密解密,php的AES加密解密
  6. Spring Boot 学习笔记(三)Spring boot 中的SSM
  7. mysql odb驱动_odb C++访问mysql数据库,从安装到写入
  8. SoapUI接口测试断言
  9. 千图成像!祝可爱的小伙伴们圣诞快乐!给女朋友一个惊喜吧!
  10. mac系统如何连接服务器地址,mac如何远程连接服务器地址
  11. 使用 R 构建复杂设计调查加权(Survey-Weighted) Cox 模型的列线图
  12. 微信小程序 live-player 实时音视频播放 组件
  13. 你的人生,就是从这一刻开始毁掉的
  14. CentOS,Ubuntu,Linux下安装yasm-1.3.0详细说明
  15. 中国为什么要买美国国债
  16. 机器学习面试题1~60
  17. 网关 Gateway
  18. 点到线段直线的距离, 直线与直线的关系 直线与线段的关系
  19. Python opencv 伪彩色保存的结果和plt.imshow(blur_heatMap_jet)的结果截然相反 Python opencv如何imshow一张图片
  20. 深度学习环境搭建之七_Ubuntu安装微信、QQ、百度网盘

热门文章

  1. when and where is getControllerName called
  2. JS Event handler example - event capture and bubble
  3. SAP权限对象_SAP_APP
  4. Document Builder: 如何分析document template里某个字段未被web service填充的问题
  5. Action Framework- Table PPFSFMLRU
  6. cannot create employee - HR check BP_BUPA_CHECK_HR_IS_ACTIVE
  7. Eclipse里代码自动完成 auto completion的快捷键设置
  8. 如何关闭SAP Fiori的病毒扫描设
  9. SAP Marketing cloud里的campaign管理
  10. Java入门算法(动态规划篇1:初识动规)