讲解一下Linux内存映射

内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

一.概述

内存映射是在调用进程的虚拟地址空间创建一个新的内存映射。

内存映射分为2种:

1.文件映射:将一个普通文件的全部或者一部分映射到进程的虚拟内存中。映射后,进程就可以直接在对应的内存区域操作文件内容!

2.匿名映射:匿名映射没有对应的文件或者对应的文件时虚拟文件(如:/dev/zero),映射后会把内存分页全部初始化为0。

当多个进程映射了同一个内存区域时,它们会共享物理内存的相同分页。通过fork()创建的子进程也会继承父进程的映射副本!!!

如果多个进程都会同一个内存区域操作时,会根据映射的特性,会有不同的行为。映射特征可分为私有映射和共享映射:

1.私有映射:映射的内容对其他进程不可见。对于文件映射来说,某一个进程在映射内存中改变文件的内容不会反映到被映射的底层文件中。内核会使用copy-on-write(写时复制)技术来解决这个问题:只要有一个进程修改了分页中的内容,内核会为该进程重新创建一个新的分页,并将需要修改的内容复制到新分页中。

2.共享映射:某一个进程对共享的内存区域操作都对其他进程可见!!!对于文件映射,操作的内容会反映到底层文件中。

注意:进程执行exec()调用后,先前的内存映射会丢失,而fork()创建的子进程会继承父进程的,映射的特征(私有和共享)也会被继承。

异常信号:

1.当映射内存的属性设置只读时,如果进行写操作会产生SIGSEGV信号。

2.当映射内存的字节数大于被映射文件的大小,且大于该文件当前的内存分页大小时。如果访问的区域超过了该文件分页大小,会产生SIGBUS信号。

有点绕口,举个简单的例子:假设内核维护的内存分页是4k(一般都是4k,4096字节),一个普通文件a.txt的大小是10字节。如果创建一个映射内存为4097字节,并映射该文件。此时,因为a.txt的大小用一个分页就可以完全映射,10字节远小于一个分页的4096字节,所以内核只会给它一个分页。内存地址是从0开始,0-9区间的内容对应a.txt文件的数据,我们也是可以访问10-4095的区间。但如果访问4096区间时,已经超过一个分页的大小了,此时会产生SIGBUS信号!!!

等会我们用个简单的例子演示下这2个异常。

二.函数接口

1.创建映射

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

addr:映射后要存放的虚拟内存地址。如果是NULL,内核会自动帮你选择。

length:映射内存的字节数。

prot:权限保护:PROT_NONE(无法访问),PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行)。

flags:映射特征:MAP_PRIVATE(私有),MAP_SHARED(共享),MAP_ANONYMOUS。还有一些其他的可查询man手册。

fd:要映射的文件描述符。

offset:文件的偏移量,如果为0,且length为文件长度,代表映射整个文件。

2.解除映射

#include  
2
3 int munmap(void *addr, size_t length);

addr:要解除内存的起始地址。如果addr不在刚刚映射区域的开始位置,解除一部分后内存区域可能会分成两半!!!

length:要解除的字节数。

3.同步映射区

#include  
2
3 int msync(void *addr, size_t length, int flags);

addr:要同步的内存起始地址。

length:要同步的字节长度。

flags:MS_SYNC(执行同步文件写入),此操作内核会把内容直接写到磁盘。MS_ASYNC(执行异步文件写入),此操作内核会先把内容写到内核的缓冲区,某个合适的时候再写到磁盘。

三.文件映射实例

/**
\* @file mmap_file.c
*/

\#include  
\#include  
\#include  
\#include  
\#include  
\#include  
\#include  

\#define MMAP_FILE_NAME "a.txt"
\#define MMAP_FILE_SIZE 10

void err_exit(const char *err_msg)
{
 printf("error:%s\n", err_msg);
 exit(1);
}

/* 信号处理器 */
void signal_handler(int signum)
{
 if (signum == SIGSEGV)
   printf("\nSIGSEGV handler!!!\n");
 else if (signum == SIGBUS)
   printf("\nSIGBUS handler!!!\n");
 exit(1);
}

int main(int argc, const char *argv[])
{
 if (argc printf("usage:%s text\n", argv[0]);
   exit(1);
 }

 char *addr;
 int file_fd, text_len;
 long int sys_pagesize;

 /* 设置信号处理器 */
 if (signal(SIGSEGV, signal_handler) == SIG_ERR)
   err_exit("signal()");
 if (signal(SIGBUS, signal_handler) == SIG_ERR)
   err_exit("signal()");

 if ((file_fd = open(MMAP_FILE_NAME, O_RDWR)) == -1)
   err_exit("open()");

 /* 系统分页大小 */
 sys_pagesize = sysconf(_SC_PAGESIZE);
 printf("sys_pagesize:%ld\n", sys_pagesize);

 /* 内存只读 */
 //addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ, MAP_SHARED, file_fd, 0);
 
 /* 映射大于文件长度,且大于该文件分页大小 */
 //addr = (char *)mmap(NULL, sys_pagesize + 1, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);

 /* 正常分配 */
 addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
 if (addr == MAP_FAILED)
   err_exit("mmap()");

 /* 原始数据 */
 printf("old text:%s\n", addr);

 /* 越界访问 */
 //addr += sys_pagesize + 1;
 //printf("out of range:%s\n", addr);

 /* 拷贝新数据 */
 text_len = strlen(argv[1]);
 memcpy(addr, argv[1], text_len);

 /* 同步映射区数据 */
 //if (msync(addr, text_len, MS_SYNC) == -1)
 //  err_exit("msync()");

 /* 打印新数据 */
 printf("new text:%s\n", addr);

 /* 解除映射区域 */
 if (munmap(addr, MMAP_FILE_SIZE) == -1)
   err_exit("munmap()");

 return 0;
}

1.首先创建一个10字节的文件:

1 $:dd if=/dev/zero of=a.txt bs=1 count=10

2.把程序编译运行后,依次执行2写入:

可以看到本机的分页大小是4096字节。第一次写入9个字节,原来用dd命令创建的文件为空,old text为空。第二次写入4个字节,只覆盖了最前面的1234。

3.验证可访问现有分页的内存。写入超过10字节的数据:

上面我们写入了17个字节,虽然64行的mmap()映射了MMAP_FILE_SIZE=10字节。但从输入new text可以看出,我们���然可以访问10字节后面的内存,因为该数据都在一个分页(4096)里面。cat查看a.txt后,只有前10个字节写入了a.txt。

4.验证SIGSEGV信号。把64行注释调,58行打开,设置映射属性为只读,编译后访问:

设置只读属性后,第77行有写操作。我们自定义的信号处理器就捕捉到了该信号。如果没有自定义信号处理器,终端就会输出Segmentation fault

5.验证SIGBUS信号。用61行的方法来映射内存。映射了一个分页大小再加1字节的内存,并放开72,73行的代码,让指针指向一个分页后的区域。编译后运行:

SIGBUS信号被自定义处理器捕捉到了。如果没有自定义信号处理器,终端就会输出Bus error

四.匿名映射

匿名映射有2种方式:

1.指定mmap()的flags参数为MAP_ANONYMOUS,在linux上当指定这个值后会忽略fd参数的值。不过在有的UNIX上还需要把fd指定为-1。

2.把/dev/zero当做文件描述符打开,从/dev/zero读取数据时它会给你提供无穷无尽的0,向它写数据,它会丢弃。丢弃这点跟/dev/null一样,只是/dev/null不跟你提供数据。

3.匿名映射的使用跟上面的文件映射差不多。这里不再给例子。

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/209127.html<

(0)
运维的头像运维
上一篇2025-04-09 08:02
下一篇 2025-04-09 08:04

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注