mmap,mprotect,msync与munmap函数

本文介绍了Linux系统中的mmap函数,包括MAP_ANONYMOUS匿名映射的使用,文件IO映射,共享内存映射以及mmap在设备操作中的作用。mmap允许进程直接访问设备驱动,减少数据拷贝,提高效率。通过mmap,父子进程可以共享内存进行通信,并通过remap_pfn_range等方法建立页表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数原型:

#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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值