Linux c/c++编程--知识点(4)mmap用法和用法举例

文章原文:https://www.cnblogs.com/arnoldlu/p/8330785.html

https://blog.csdn.net/Holy_666/article/details/86532671

返回知识列表:


什么是 mmap()

mmap, 从函数名就可以看出来这是memory map, 即地址的映射, 是一种内存映射文件的方法, 将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

 

为什么使用 mmap()

Linux通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的内存。这样做的直观效果显而易见:内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须数据拷贝。举个例子理解一下,使用mmap方式获取磁盘上的文件信息,只需要将磁盘上的数据拷贝至那块共享内存中去,用户进程可以直接获取到信息,而相对于传统的write/read IO系统调用, 必须先把数据从磁盘拷贝至到内核缓冲区中(页缓冲),然后再把数据拷贝至用户进程中。两者相比,mmap会少一次拷贝数据,这样带来的性能提升是巨大的。

使用内存访问来取代read()和write()系统调用能够简化一些应用程序的逻辑。
在一些情况下,它能够比使用传统的I/O系统调用执行文件I/O这种做法提供更好的性能。
原因是:

正常的read()或write()需要两次传输:一次是在文件和内核高速缓冲区之间,另一次是在高速缓冲区和用户空间缓冲区之间。使用mmap()就不需要第二次传输了。对于输入来讲,一旦内核将相应的文件块映射进内存之后,用户进程就能够使用这些数据了;对于输出来讲,用户进程仅仅需要修改内核中的内容,然后可以依靠内核内存管理器来自动更新底层的文件。
除了节省内核空间和用户空间之间的一次传输之外,mmap()还能够通过减少所需使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另个一位于内核空间。当使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个进程正在同一个文件上执行I/O,那么它们通过使用mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。

 

如何使用mmap()

 #include <sys/mman.h>

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

mmap 会在被调用者的用户进程中的虚拟地址中创建一块映射区域。

 

Arguments Describes (参数描述)

  • 参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。
  • len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
  • prot 参数指定共享内存的访问权限。可取如下几个值的或:
    • PROT_READ(映射区域可被读) ,
    • PROT_WRITE (映射区域可被写),
    • PROT_EXEC (映射区域可被执行),
    • PROT_NONE(映射区域不可访问)。
  • flags由以下几个常值指定:
    • MAP_SHARED :对映射区域的写入会复制回文件内,而且允许其他映射该文件的进程共享,原来的文件会改变。
    • MAP_PRIVATE:对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)。对此区域做的任何修改都不会写回原来的文件内容。当共享的对象的虚拟存储区域为私有对象时,修改只会被本进程改变。
    • MAP_FIXED:如果参数addr所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此flag。
    • 其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
    • MAP_ANONYMOUS:建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
    • MAP_DENYWRITE:只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
    • MAP_LOCKED :将映射区域锁定住,这表示该区域不会被置换(swap)。
  • 参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。
  • ooffset 文件映射的偏移量,通常设置为0,代表从文件最前方开始映射,offset必须是分页大小的整数倍。

 

返回值

若映射成功则返回映射区域的內存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

 

错误码:

EBADF 参数fd 不是有效的文件描述符
EACCES 存取权限有误。如果是MAP_PRIVATE 情況下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
EINVAL 参数start、length 或offset有一个不合法。
EAGAIN 文件被锁住,或是有太多內存被锁住。
ENOMEM 內存不足。

错误码还有很多,大家可以通过 man mmap 查看详情。

 

系统调用mmap()用于共享內存的两种方式:

(1)使用普通文件提供的內存映射:

适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()

典型调用代码如下:

fd=open(name, flag, mode); if(fd<0) ...

ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 

通过mmap()实现共享內存的通信方式有许多特点和要注意的地方,可以参看《UNIX网络编程第二卷》

(2)使用特殊文件提供匿名內存映射:

适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,再父进程中先调用mmap(),然后调用用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标记即可。

  下面写一个demo:

#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
//#include "csapp.h"
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void mmapcopy(int fd, int size)
{
	void *bufp;
	//void * start_addr = 0;
	//start_addr = (void *)0x80000;
	bufp = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (bufp == (void *)-1){
        fprintf(stderr, "mmap: %s\n", strerror(errno));  //输出到标准错误输出
    }
	
	memcpy(bufp, "Linuxdd", 7);
	
	write(1, bufp, size);  // 输出到标准输出。
	munmap(bufp, size);
	return;
}

int main(int argc, char **argv)
{
	struct stat stat;
	if (argc != 2)
	{
		printf("error.\n");
		return -1;
	}
	//int fd = atoi(*argv[1]);
	//mmap()
	int fd = open(argv[1], O_RDWR, 0);  // O_RDWR 才能被读写
	if (fd < 0){
	    fprintf(stderr, "open: %s\n", strerror(errno));
        return -1;
    }
	fstat(fd, &stat);
	mmapcopy(fd, stat.st_size);
	//while(1);
	close(fd);
	return 0;
}

 

标准文件描述符
stdin = 0    标准输入
stdout = 1   标准输出
stderr = 2   标准错误输出

小实验: 可以将mmap调用处, 将 MAP_SHARED 改成 MAP_PRIVATE 看一下区别。同事,在cat 一下那个打开的文件。看一下文件内容是否改变了。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值