mmap()和fmemopen()的使用

本文介绍了mmap和fmemopen两个Linux系统调用的功能及使用方法。mmap函数能够将文件映射到内存中,允许进程像读取内存一样对文件进行操作;fmemopen函数则实现了将内存当作文件使用的功能。

今天Mayuyu遇到了两个比较有意思的函数,即mmap()fmemeopen()函数。

 

先来看看mmap()函数,本函数的头文件为#include <unistd.h>#include <sys/mman.h>。函数原型如下

 

 

   

 

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

错误代码:

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

 

mmap()是用来将某个文件内容映射到内存中,对该内存区域的取值即是直接对该文件内容的读写。在内存中建立页表项,但mmap()调用并没有立即在页表中建立页表项,只有使用到某个地址空间时才会给此页面分配空间。由于涉及到页面置换,需要有一定的物理内存做支撑,如果内存太小,那么刚置换入内存中的页面又被置换出到磁盘上,mmap的性能大大降低。

 

mmap()的存在主要是为用户程序随机访问大文件提供了一个方便的操作方法。同时为不同进程共享大批量数据提供高效的手段。对特大文件的处理提供了一种有效的方法。

 

 

示例:

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	struct stat st;

	//打开文件
	int fd = -1;
	if((fd = open("File", O_RDONLY)) == -1)
	{
		printf("open File error!\n");
		return -1;
	}

    //根据文件描述符取出文件状态
	if(fstat(fd, &st) == -1)
	{
		printf("get st error!\n");
		return -1;
	}

    //进行映射
	void *start = NULL;
	if((start = mmap(start, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
	{
		printf("mmap error!\n");
		return -1;
	}

	printf("%s\n", (char *)start);

    //解除映射关系
	if(munmap(start, st.st_size) == -1)
	{
		printf("munmap error!\n");
		return -1;
	}
	close(fd);

	return 0;
}

 

上面有一个函数为fstat(fd, st),成功返回0,失败返回-1。它是根据文件描述符fd获取文件状态st的,而st是一个结构体,记录了文件的很多状态信息,这个结构体定义如下

 

 

其实还有一个函数的作用跟fstat()差不多,即stat()。不同的是stat()的第一个参数表示的是文件名,而不是文件描述符。

 

mmap()系统调用并不是完全为了共享内存而设计的,它本身提供了一种不同于普通文件的访问方式,进程可以像读取内存那样对普通文件进行操作,当然mmap()是共享内存最主要应用之一。

 

 

还有几个与mmap()系统调用有关的函数如下

 

(1)munmap()函数

 

    函数原型为 :

 

    该调用在进程地址空间解除一个映射关系,addr是调用mmap()返回的地址,len是映射区的大小,当解除映

    射关系后,对原来映射地址的访问将会导致段错误。此函数解除成功返回0,否则返回-1

 

(2)msync()函数

 

    函数原型为 :

 

    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执

    行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。


    addr: 文件映射到进程空间的地址
    len:  映射空间的大小
    flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC/ MS_INVALIDATE

    其中:
   

    取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成;
    取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;
    取MS_INVALIDATE(通知使用该共享区域的进程,数据已经改变)时,在共享内容更改之后,使得文件的其

    他映射失效,从而使得共享该文件的其他进程去重新获取最新值

    此函数成功则返回0,失败则返回-1

 

(3)madvise()函数

 

    在调用mmap()时内核只是建立了逻辑地址到物理地址的映射表,并没有映射任何数据到内存。 在你要访问数

    据时内核会检查数据所在分页是否在内存,如果不在,则发出一次缺页中断,linux默认分页为4K,可以想象

    读一个将近2G的电影文件要发生多少次中断。解决办法如下

 

    将madvise()mmap()搭配起来使用,在使用数据前告诉内核这一段数据我要用,将其一次读入内存,避免

    中断。madvise()这个函数可以对映射的内存提出使用建议,从而提高内存,大大改善性能。

 

    函数原型 :

 

    参数说明: addr  为mmap()调用获得的映射地址空间首地址。

             len   为映射空间的大小。

             behav 关于这个参数,详情请点这里。

 

    执行成功返回0,否则返回-1。使用方法如下

 

代码:

<strong><span style="font-size:18px;">#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	struct stat st;

	//打开文件
	int fd = -1;
	if((fd = open("File", O_RDONLY)) == -1)
	{
		printf("open File error!\n");
		return -1;
	}

    //根据文件描述符取出文件状态
	if(fstat(fd, &st) == -1)
	{
		printf("get st error!\n");
		return -1;
	}

    //进行映射
	void *start = NULL;
	if((start = mmap(start, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
	{
		printf("mmap error!\n");
		return -1;
	}

	//使用advise()调用
	if(madvise(start, st.st_size, MADV_WILLNEED | MADV_SEQUENTIAL) == -1)
	{
		printf("madvise error!\n");
		return -1;
	}

	printf("%s\n", (char *)start);

    //解除映射关系
	if(munmap(start, st.st_size) == -1)
	{
		printf("munmap error!\n");
		return -1;
	}
	close(fd);

	return 0;
}
</span></strong>


 

关于mmap()调用Mayuyu已经介绍了很多了,接下来介绍另一个系统调用,即fmemopen()

 

mmap()调用是将文件映射到内存中,也就是说通过mmap()调用可以把文件当内存使用。而fmemopen()调用正好相反,通过fmemopen()调用可以把内存当文件使用。接下来将会详细介绍fmemopen()调用。

 

 

头文件:#include <stdio.h>

 

函数原型 :

 

参数说明:这个比较明显,就不说了,后面用实例说明即可。

 

这个应用场合较多,比如有些文件不支持内存操作,但是支持文件操作的。

 

代码:

#include <string.h>
#include <stdio.h>

static char buff[] = "Mayuyu is from Japan";

int main(int argc, char **argv)
{
    int len = strlen(buff);
	FILE *fd = fmemopen(buff, len, "r");
	if(fd == NULL)
	{
		printf("get file error!\n");
		return -1;
	}

    char ch;
	while((ch = fgetc(fd)) != EOF)
		printf("%c", ch);
	puts("");

	fclose(fd);

	return 0;
}


 

### mmap 函数的使用方法 `mmap` 是一种用于创建内存映射文件区域的系统调用,在 POSIX 兼容的操作系统(如 Linux macOS)中广泛使用。它允许进程将文件或其他对象映射到其地址空间,从而可以通过操作内存来读写文件内容。 以下是 `mmap` 的基本语法及其参数说明: #### 基本语法 ```c void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` - **`addr`**: 请求的起始地址。通常设置为 `NULL`,让操作系统决定合适的地址[^1]。 - **`length`**: 映射区域的长度(字节单位)。必须是非零值[^2]。 - **`prot`**: 指定访问权限,可以是以下标志的组合: - `PROT_READ`: 可读。 - `PROT_WRITE`: 可写。 - `PROT_EXEC`: 可执行。 - `PROT_NONE`: 不可访问。 - **`flags`**: 控制行为的标志位,常见选项有: - `MAP_SHARED`: 修改会反映回文件。 - `MAP_PRIVATE`: 创建私有的副本,修改不会影响原文件。 - **`fd`**: 文件描述符,表示要映射的文件。如果不需要关联文件,则应指定特殊值(如 `-1`),并配合其他标志使用。 - **`offset`**: 文件中的偏移量,必须是对页大小取整的结果[^3]。 成功返回指向已映射区间的指针;失败则返回 `(void *) -1` 并设置 errno 来指示错误原因。 --- ### 示例代码 下面是一个简单的 C 程序示例,展示如何利用 `mmap` 将一个文件的内容映射到内存中,并对其进行读写操作。 ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main() { const char *filename = "example.txt"; int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); exit(EXIT_FAILURE); } // 设置文件大小以便有足够的数据供映射 ftruncate(fd, 4096); void *mapped_region = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (mapped_region == MAP_FAILED) { perror("mmap"); close(fd); exit(EXIT_FAILURE); } // 向映射区域写入一些数据 snprintf(mapped_region, 4096, "Hello from mmap!"); printf("Mapped content: %s\n", (char*) mapped_region); munmap(mapped_region, 4096); // 解除映射关系 close(fd); // 关闭文件描述符 return EXIT_SUCCESS; } ``` 此程序完成如下功能: 1. 打开名为 `"example.txt"` 的文件或者新建该文件; 2. 使用 `ftruncate()` 调整文件尺寸至至少一页(这里设定了4KB),确保有足够的空间被映射; 3. 利用 `mmap()` 把这段范围内的文件内容加载进内存; 4. 对这个新建立起来的虚拟存储器位置进行字符串赋值以及打印验证; 5. 清理工作包括解除映射(`munmap`)关闭文件句柄(close). --- ### 注意事项 当不再需要某个由 `mmap` 返回的内存区间时,应该显式地通过调用 `munmap(addr,length)` 来释放资源。否则可能导致未定义的行为发生,比如尝试再次映射同一段地址可能会失败等问题出现[^4]. 另外需要注意的是,尽管上面例子演示了共享模式下的情况 (`MAP_SHARED`), 如果希望更改只存在于当前进程中而不同步更新原始文件的话,则需改用复制方式即设定成 `MAP_PRIVATE`. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值