安防通讯百科

您现在的位置是:首页 > IT通信百科 > 正文

IT通信百科

MMAP,mmap的使用

root012023-04-22IT通信百科66

mmap 即 memory map,也就是内存映射。

mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap映射内存必须是页面大小的整数倍,面向流的设备不能进行mmap,mmap的实现和硬件有关。

映射条件:

mmap()必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。

头文件:

函数原型:

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

int munmap(void* start,size_t length);

参数说明:

start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。

length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理

prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

PROT_EXEC //页内容可以被执行

PROT_READ //页内容可以被读取

PROT_WRITE //页可以被写入

PROT_NONE //页不可访问

flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

MAP_DENYWRITE //这个标志被忽略。

MAP_EXECUTABLE //同上

MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。

MAP_FILE //兼容标志,被忽略。

MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。

offset:被映射对象内容的起点。

返回值

成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值

EACCES:访问出错

EAGAIN:文件已被锁定,或者太多的内存已被锁定

EBADF:fd不是有效的文件描述词

EINVAL:一个或者多个参数无效

ENFILE:已达到系统对打开文件的限制

ENODEV:指定文件所在的文件系统不支持内存映射

ENOMEM:内存不足,或者进程已超出最大内存映射数量

EPERM:权能不足,操作不允许

ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志

SIGSEGV:试着向只读区写入

SIGBUS:试着访问不属于进程的内存区

特点:

使用说明:

适用场景:

mmap 的适用场景实际上非常受限,在如下场合下可以选择使用 mmap 机制:

用Mindjet Maps软件,操作如下:

工具:iPhone 8

操作系统:iOS 14.4.1

程序:Mindjet Maps 2.91

1、直接在手机桌面上,点击软件进入,如下图所示。

你有在 Go 程序中使用 syscall.Mmap 吗?答案很可能是肯定的,只是你不知道而已。因为你的程序直接或间接的依赖包会使用 syscall.Mmap,毕竟众所周知的:mmap 要比常规的 I/O 操作快。我们现在来看一下到底是不是这样。

mmap 是一个系统调用,将文件内容直接映射到内存地址空间。mmap 之后,你就可以像访问内存一样对文件内容进行读写。这样就不需要使用比较重的系统调用去对文件内容进行读写了。

使用系统调用操作文件,进程会在内核态和用户态之间频繁切换,而且数据还要在用户态和内核态之间来回拷贝。而 mmap 后,整个数据的读写都在用户态完成,不会进入内核态,同时也少了一次数据拷贝。是不是觉得很完美?其实不是的。

程序访问 mmap 返回的内存地址空间会发生什么?有两种场景:

你可能会说,那正常的使用 read/write 访问冷数据,也会有同样的问题;也会触发缺页中断,唯一不同的是把内存访问换成了一个系统调用。

的确是这样,但是让我们来看一下 Go 的运行时机制。

Go 的 goroutine 是运行在 OS threads(操作系统线程)之上的。最多可以有 GOMAXPROCS 个 goroutine 并行 的运行在 OS thread 上。其他就绪的 goroutine 会一直等待,直到运行中的 goroutine 发生了阻塞、出让、或者系统调用。goroutine 会因为 I/O、channel、mutex 而阻塞,会因为函数调用、内存分配、调用 runtime.Gosched 而出让。 Goroutine 并不会因为缺页中断而阻塞!

再强调一次,goroutine 不会因为缺页中断而发生阻塞或出让,因为它对 Go 运行时是不可见的。 那当一个 goroutine 通过 mmap 访问到冷数据时,会发生什么呢?它会让你的程序卡在那里很长很长时间。在这期间,它还是会持续占用你的 OS thread,所以其他就绪的 goroutine 因为受到 GOMAXPROCS 的限制,只能排队。这就导致 CPU 的利用率很低。如果 GOMAXPROCS 个 goroutine 同时访问 mmap 文件的冷数据,会发生什么?整个程序会彻底地 Hang 住,直到 OS 完成了这些 goroutine 触发的缺页中断。

监控请求延迟和 CPU 利用率:

这些程序在程序访问 page cache 中的数据时,是没有任何问题的。page cache 的大小受内存大小限制。所以这些程序只有在被 mmap 的文件很大时(超出内存大小),才会出现卡住的现象。在低负载场景,或者存储设备较快时(比如 SSD),不太容易注意到程序出现卡顿。

当 mmap 文件小于内存空间时,以下场景也会出现卡顿:

尽量避免在 Go 程序中使用 mmap,因为它可能让你的程序 Hang 住。

发表评论

评论列表

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~