函数原型:
#include <sys/mman.h> /* 参数addr用于指定映射存储区的起始地址,通常将其设置为0,这表示由系统选择该映射区的起始地址; 参数fd指定要被映射文件的描述符; 参数len是映射字节数; 参数off是要映射字节在文件中的起始偏移量;另外,off和addr的值必须是页面大小的整数倍页面的大小可以由sysconf(_SC_PAGE_SIZE)来返回. 参数prot: PROT_READ 映射区可读 PROT_WRITE 映射区可写 PROT_EXEC 映射区可执行 PROT_NONE 映射区不可访问 参数flags: MAP_PRIVATE 私有,对映射区的写操作会导致创建映射文件的一个私有副本; MAP_SHARED 对映射区的写操作直接修改原始文件,多个进程对同一个文件的映射是共享的, 一个进程对映射的内存做了修改,另一个进程也会看到这种变化; MAP_FIXED 返回值必须等于addr,不利于移植性 MAP_ANONYMOUS 匿名映射,此时忽略fd,且映射区域无法与其它进程共享, 这个选项一般用来扩展heap MAP_DENYWRITE 只允许对应射区域的写入操作,其他对文件直接写入的操作将会被拒绝。 MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。 mmap调用成功时返回该映射区的起始地址。 */ void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off); /* mprotect函数可以更改一个现存映射存储区的权限; */ int mprotect(void *addr, size_t len, int prot); /* msync函数的作用是把共享存储映射区中被修改的页冲洗到被映射的文件中。如果映射是私有的(MAP_PRIVATE),不修改被映射的文件; flags参数: MS_ASYNC 异步写 MS_SYNC 同步写 MS_INVALIDATE 从文件中读回数据 */ int msync(void *addr, size_t len, int flags); /*munmap函数的作用是解除存储映射区,关闭描述符并不解除映射区*/ int munmap(void *addr, size_t len);
调用fork函数,子进程继承存储映射区(子进程复制父进程的地址空间,而存储映射区是该地址空间的一部分),但调用exec之后的程序则不继承此存储映射区
MAP_ANONYMOUS的用途:
常规创建mmap映射区有一点不方便的地方,那就是每次建立映射区都需要依赖一个文件才能实现,通常为了建立映射区需要open一个临时文件,创建mmap好了之后再unlink、close比较麻烦。这时候可以直接使用匿名映射来代替,使用 MAP_ANONYMOUS(匿名的);
int *ptr = (int *) mmap(NULL, N * sizeof(int), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); // fd=0,没有关联任何文件
文件IO映射
使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap(); fd = open(path, O_RDWR); start = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); strncpy((char*)start, "say hi", 6); munmap(start, sb.st_size);
映射普通文件,进程会在自己的地址空间新增一块空间,空间大小由len参数指定;
注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。
使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;
由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。
注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
共享内存映射
mmap设备操作
mmap
实现了将设备驱动在内核空间的部分地址直接映射到用户空间,使得用户程序可以直接访问和操作相应的内容,减少了额外的拷贝;
如果不使用 mmap
,那么由于在内核空间的代码,和用户空间的代码对应的地址空间不同,这样内核空间和用户空间不能互相访问其指针;如果想要访问,对方指针的内容,那么只能通过 copy_from_user
之类的函数先将其数据拷贝到内核空间(相应的 read
一般使用 copy_to_user
可以将内核空间内的指针数据拷贝给用户空间的指针所指)再访问。除非直接将内存映射,否则一定要拷贝才能访问用户空间数据。
mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调用;
int (*mmap) (struct file*, struct vm_area_struct *);
mmap建立页表的两种方法:
1、 使用remap_pfn_range一次建立所有页表;
2、 使用nopage VMA方法每次建立一个页表;
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
vma: 虚拟内存区域指针
virt_addr: 虚拟地址的起始值
pfn: 要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到,这个宏定义是12,就是除以4KB
size: 要映射的区域的大小
prot: VMA的保护属性
int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
//用virt_to_phys将虚拟地址dev->data转换为物理地址
if (remap_pfm_range(vma, vma->vma-start, virt_to_phys(dev->data)>>PAGE_SHIFT,
size, vma->vm_page_prot))
{
return –EAGAIN;
}
return 0;
}