Linux之mmap函数简介
本文主要讲述
mmap
函数的使用,与驱动中mmap
函数的实现mmap
怎么使用,怎么实现,为什么mmap
可以减少额外的拷贝?
下面简单详情。
一、 mmap
的使用
#include <sys/mman.h>void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off );int munmap(void *addr, size_t length);
函数属于系统调用级别,负责内存映射。
形容
mmap
把文件或者者设施映射到内存。这个函数在调用进程的虚拟地址空间中创立一块映射区域。
addr
指定映射区域的首地址。假如 addr
是 NULL
,那么由内核来选择一个地址来创立映射的区域,否则创立的时候会尽可能地使用 addr
的地址。在linux系统中,创立映射的时候应该是在下一个页面的边界创立, addr
是 NULL
的时候,程序的可移植性最好。
len
指定文件被映射的长度, 或者者映射区域的长度。
offset
指定从文件的哪个偏移位置开始映射。=offset= 必需是页面大小的整数倍页面的大小可以由 sysconf(_SC_PAGE_SIZE)
来返回.
prot
指定内存的保护模式(具体参见 man
), flags
指定区域在不同进程之间的共享方式,以及区域能否同步到相应的文件等等(具体参见 man
).
这个函数返回新创立的页面的地址。
munmap
取消 address
指定地址范围的映射。以后再引用取消的映射的时候就会导致非法内存的访问。
这里 address
应该是页面的整数倍, length
指定取消映射的地址长度。
成功的时候这个函数返回0, 失败的时候,两者都返回-1.
举例
//hello hello hello/*程序功能: * 1)主要测试mmap和munmap的2)简单的write * 具体为: * 在命令中分别指定文件名,映射的长度,映射的起始地址. * 将文件映射到内存中 * 把映射到内存中的内容用write写到标准输出。 * 注意,这里没有对越界进行检测。 * */#include <sys/mman.h>//mmap#include <unistd.h>//sysconf#include <fcntl.h>//file open#include <stdio.h>//printfint main(int argc, char *argv[]){ if(argc != 4) { write(STDOUT_FILENO,"hello\n",6); printf("usage:%s \n",argv[0]); return 1; } char *filename = argv[1];//1)指定文件 printf("the file to be mapped is:%s\n",filename); int fd = open(filename,O_RDONLY); int offset = atoi(argv[2]);//2)指定映射起始地址(页面的整数倍) printf("start offset of file to be mapped is:%d\n",offset); printf("page size is:%ld\n",sysconf(_SC_PAGE_SIZE)); int realOffset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);//转换成页面的整数倍 printf("real start offset of file to be mapped is:%d\n",realOffset); int length = atoi(argv[3]);//3)指定映射长度 printf("the length to be map is:%d\n",length); int realLen = length+offset-realOffset;//实际写入的字节数 printf("the real length to be map is:%d\n",realLen); //mmap的参数分别是: //NULL,让内核自己选择映射的地址;realLen指定映射的长度; //PROT_READ只读;MAP_PRIVATE不和其余的进程之间共享映射区域,数据也不写入对应的文件中; //realOffset映射文件的起始地址(页面的整数倍)。 char *addr = mmap(NULL, realLen,PROT_READ,MAP_PRIVATE,fd,realOffset);//4)开始映射 //关闭打开的文件,实际程序退出的时候会自动关闭。 //关闭文件之后,相应的映射内存依旧存在,映射的内存用munmap关闭。 close(fd); //write的参数分别是: //STDOUT_FILENO:文件形容符号(这里是标准输出) //addr,将要写入文件的内容的地址 //realLen,写入的长度,长度以addr作为起始地址 write(STDOUT_FILENO,addr,realLen);//将映射的内容写到标准输出 munmap(addr,realLen);//5)关闭映射的内存 //write(STDOUT_FILENO,addr,realLen);//不能使用了 printf("\n");}
二、 mmap
实现
驱动中有对 mmap
的具体实现。客户调用 mmap
系统调用函数之后,最终会调用到驱动中的 mmap
函数接口。下面是一个例子:
static int commdrv_mmap(struct file* file, struct vm_area_struct* vma){ long phy_addr; unsigned long offset; unsigned long size; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); vma->vm_flags |= VM_LOCKED; offset = vma->vm_pgoff << PAGE_SHIFT;/*XXX assume is 12*/ size = vma->vm_end - vma->vm_start; if(BUF0_OFF == offset) { phy_addr = PHYS_BASE0; } else if(BUF1_OFF == offset) { phy_addr = PHYS_BASE1; } else if(START_OFF == offset) { phy_addr = PHYS_BASE; } else { return -ENXIO; } /*phy_addr must be 4k *n*/ if(remap_pfn_range(vma, vma->vm_start, phy_addr >> PAGE_SHIFT, size, vma->vm_page_prot)) { return -ENXIO; } return 0;}
对于以上代码,vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
表示要映射的内存是非cached的,这样不会存在缓存中的数据和实际数据不一致的情况,但是速度会比cached的要慢。
offset
表示要映射的数据偏移,来自客户空间的 mmap
调用,在这里进行判断,尽管一般的文件就将这个偏移量做为文件偏移了,其实这个 offset
的含义,由驱动自己解释,不肯定就是字节偏移,驱动根据这个偏移量来决定映射哪块内存。
size
表示要映射的内存的大小。
remap_pfn_range(vma, vma->vm_start, phy_addr >> PAGE_SHIFT, size, vma->vm_page_prot)
表示将根据被映射的物理地址,以及虚拟起始地址,和大小等信息,将相应的部分映射到客户空间。
其中参数vma直接来自 commdrv_mmap
函数的参数, phy_addr
是要映射的设施的物理地址(必需是页对齐的),只有一些的信息自定义,大多来自外部。最后映射的地址,通过客户调用的 mmap
函数返回,客户可以直接操作。
三、 mmap
优点
mmap
实现了将设施驱动在内核空间的部分地址直接映射到客户空间,使得客户程序可以直接访问和操作相应的内容。减少了额外的拷贝,而一般的 read
, write
函数尽管表面上直接向设施写入,其实还需要进行一次拷贝。
例如,下面是某个设施驱动中的的 write
实现,当外面客户程序调用 write
系统调用向相应设施文件写之后,最终会进入到这个函数进行真正的读取所需操作。
static ssize_t commdrv_write(struct file* filp, char __user* buf, size_t count, loff_t* ppos){ char* wbuf; wbuf = (char*)vmalloc(count); if(!wbuf) { return 0; } ret = copy_from_user(wbuf, (char __user*)buf, count); if(0 != ret) { vfree(wbuf); return 0; } .....do others things with wbuf...... vfree(wbuf); return count;}
由上面的代码可知,客户传入的数据指针 buf
,在驱动中(也就是内核空间)不能直接访问,必需使用 copy_from_user
将其拷贝到内核空间的一块内存,而后才能进行后续的操作(内核中不能不经过 copy_from_user
,直接访问客户传下来的指针 buf
的地址的内容)。而 mmap
,使得将内核空间直接映射到了客户空间,让客户空间通过返回的指针直接访问,这样内核和客户空间直接操作同样的内存。也就是说,假如不使用 mmap
,那么因为在内核空间的代码,和外面客户空间的代码对应的地址空间不同,这样内核空间和客户空间不能互相访问其指针;假如想要访问,对方指针的内容,那么只能通过 copy_from_user
之类的函数先将其数据拷贝到内核空间(相应的 read
一般使用 copy_to_user
可以将内核空间内的指针数据拷贝给客户空间的指针所指)再访问。除非直接将内存映射,否则肯定要拷贝才能访问客户空间数据。
四、其它
参考: http://blog.chinaunix.net/uid-9525959-id-3063123.html
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Linux之mmap函数简介