Linux系统调用mmap文件映射与malloc简单模拟实现指南

mmap介绍

  mmap是一个linux系统调用接口,作用是让进程把打开的文件映射到自己的地址空间内,给用户返回虚拟地址,往后操作文件就不用做系统调用了,可以通过虚拟地址对文件操作。

  普通的文件访问方法是通过fd走系统调用,用户与内核间会频繁来回数据拷贝。文件创建出来,缓冲区也能申请好,只要把地址空间与缓冲区映射,就能通过虚拟地址访问。这有点类似共享内存,或者说这就是另一种共享内存,再来一个进程同样可以映射,就完成了不同进程数据共享。

所以mmap不仅可以实现单纯文件操作,也能完成共享内存的功能。

mmap的使用

c 复制代码
#include <sys/mman.h> //需要包含的头文件
void *mmap(void *addr
          , size_t length
          , int prot
          , int flags
          ,int fd, off_t offset
);

返回值:

  • 成功:返回虚拟地址起始地址
  • 失败:返回MAP_FAILED(即-1的强转)

参数:

  • addr:mmap可以指明具体地址,传入NULL让系统给我们分配(推荐)
  • length:要映射的文件的大小。(不一定从偏移量为0开始访问,length可调整。length是系统内存页的整数倍,内部会上下取整,和共享内存一样)
  • prot:权限设置
    • PROT_READ:可读
    • PROT_WRITE:可写
    • ... ...
  • flags:映射类型
    • MAP_SHARED:在使用虚拟地址访问时,会同步到文件
    • MAP_PRIVATE:不与文件挂钩,单纯的开辟空间使用,和malloc一样效果
  • fd:文件描述符(文件的唯一性标识),选择要映射的文件描述符。
  • offset:文件映射起始偏移字节,映射区间为 [offset, offset + length)

注意:在使用mmap操作文件时,它没打开文件功能,也没有自动扩容功能,所以要先打开文件,然后根据需要进行文件大小调整。

关闭文件映射:

c 复制代码
int munmap(void *addr, size_t length);

返回值:

  • 成功:0
  • 失败:-1

参数:

  • addr:文件的起始地址
  • length:文件大小

示例代码:

c 复制代码
#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PAGE_SIZE 4096
int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        std::cerr << "Usage: " << argv[0] << " filename" << std::endl;
        return 1;
    }
    // 1. 打开文件
    int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        std::cout<<"file open fail"<<std::endl;
        return 2;
    }
    // 2. 调整文件大小
    if(::ftruncate(fd, PAGE_SIZE)==-1)
    {
        std::cout<<"ftruncate fail"<<std::endl;
        return 3;
    }
    //3. 文件映射
    char* shmaddr = (char*)::mmap(NULL,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(shmaddr==MAP_FAILED)
    {
        std::cout<<"mmap fail"<<std::endl;
        return 4;
    }
    //4. 操作文件
    for(char c='a';c<='z';c++)
    {
        shmaddr[c-'a']=c;
        sleep(1);
    }
    //5. 关闭文件映射
    if(::munmap(shmaddr,PAGE_SIZE)==-1)
    {
        std::cout<<"munmap fail"<<std::endl;
        return 5;
    }
    ::close(fd);
    return 0;
}

输出结果:

打开文件查看内容(vim log.txt)

文件并未被使用的空间会被0填充(@^的ASCII值就是0)

注意:正常情况下需要获取一下文件大小。手动调整文件大小(特别是空文件),mmap不会扩容,只是对既定的文件映射,不像write一样自动扩容。

模拟实现malloc

  以上讲的都是map_shared映射类似,而map_private创建私有的地址空间,给自己进程使用,这种映射方式也叫匿名映射。和文件就没有任何关系了,malloc底层调用有时候使用的就是mmap。

  将权限设为可读可写,文件描述符设置为负一,设置标志位MAP_PRIVATE | MAP_ANONYMOUS作用是创建一个与任何文件都无关的、匿名的内存映射。它分配的内存是被初始化为全零的。

示例代码:

cpp 复制代码
#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void* my_malloc(int size)
{
    void* ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    if(ptr==MAP_FAILED)
    {
        std::cout<<"my_malloc fail"<<std::endl;
        return nullptr;
    }
    return ptr;
}

void my_free(void* ptr, int size)
{
    if(munmap(ptr, size)==-1) 
    {
        std::cout<<"my_free fail"<<std::endl;
    }
}
int main()
{
    int size = 1024;
    char* ptr = (char*)my_malloc(size);
    if(!ptr) return 1;
    for(char c = 'a'; c<='z'; c++)
    {
        ptr[c-'a'] = c;
        std::cout<<ptr[c-'a']<<" ";
    }
    std::cout<<std::endl;
    my_free(ptr, size);
    return 0;
}

输出结果:

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!🎉

相关推荐
ajassi20001 小时前
AI语音智能体开发日记(十八)智能体服务器xiaozhi-esp32-server源码部署指南
运维·服务器·人工智能·ai·ai编程
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
linux 进程信号(下)
linux·运维·服务器
skywalk81631 小时前
在FreeBSD系统的linux仿真环境下安装和使用kiro 这个亚马逊的AI agent
linux·人工智能·freebsd·kiro
Mikko71 小时前
Linux 磁盘满了但 du 找不到?LVM 扩容 df 不变?fstab 写错开不了机?三台真机实测磁盘排查
linux·运维·centos
yumgpkpm2 小时前
Acceldata ODP 3.3.6.4 vs CDP Private Cloud Base 7.3.2 对比
大数据·运维·服务器·hadoop·华为·zookeeper·hbase
Madison-No72 小时前
多语言聊天大模型--测试报告
linux·git·python·selenium·jmeter·自动化·postman
Liuqy-052 小时前
特殊进程——孤儿、僵尸、守护进程
linux·运维·服务器
李纲明2 小时前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
流浪0012 小时前
Linux系统篇47——线程(十二) POSIX 信号量与环形队列,把判空判满提前到访问之前
linux·操作系统·线程·信号量·环形队列
我不会插花弄玉2 小时前
4.string类型【由浅入深-redis】
数据库·redis·缓存