linux内存映射

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

内存映射分为2种:

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

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

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

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

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

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

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

异常信号:

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

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

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

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

二、函数接口

1.创建映射

1.创建映射

#include <sys/mman.h>

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

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

length:映射内存的字节数。

prot:权限保护:PORT_NONE(无法访问),PORT_READ(可读),PORT_WRITE(可写),

length:映射内存的字节数。

prot:权限保护:PROT_NONE(无法访问),PORT_READ(可读),PORT_WRITE(可写),

PORT_EXEC(可执行).

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

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

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

2.解除映射

#include <sys/mman.h>

int munmap(void *addr,size_t length);

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

length:要解除的字节数。

3.同步映射区

#include <sys/mman.h>

int msync(void *addr, size_t length, int flags);

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

length:要同步的字节长度。

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

三.文件映射实例

复制代码
 1 /**
 2  * @file mmap_file.c
 3  */
 4 
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <fcntl.h>
 9 #include <signal.h>
10 #include <unistd.h>
11 #include <sys/mman.h>
12 
13 #define MMAP_FILE_NAME "a.txt"
14 #define MMAP_FILE_SIZE 10
15 
16 void err_exit(const char *err_msg)
17 {
18     printf("error:%s\n", err_msg);
19     exit(1);
20 }
21 
22 /* 信号处理器 */
23 void signal_handler(int signum)
24 {
25     if (signum == SIGSEGV)
26         printf("\nSIGSEGV handler!!!\n");
27     else if (signum == SIGBUS)
28         printf("\nSIGBUS handler!!!\n");
29     exit(1);
30 }
31 
32 int main(int argc, const char *argv[])
33 {
34     if (argc < 2)
35     {
36         printf("usage:%s text\n", argv[0]);
37         exit(1);
38     }
39 
40     char *addr;
41     int file_fd, text_len;
42     long int sys_pagesize;
43 
44     /* 设置信号处理器 */
45     if (signal(SIGSEGV, signal_handler) == SIG_ERR)
46         err_exit("signal()");
47     if (signal(SIGBUS, signal_handler) == SIG_ERR)
48         err_exit("signal()");
49 
50     if ((file_fd = open(MMAP_FILE_NAME, O_RDWR)) == -1)
51         err_exit("open()");
52 
53     /* 系统分页大小 */
54     sys_pagesize = sysconf(_SC_PAGESIZE);
55     printf("sys_pagesize:%ld\n", sys_pagesize);
56 
57     /* 内存只读 */
58     //addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ, MAP_SHARED, file_fd, 0);
59     
60     /* 映射大于文件长度,且大于该文件分页大小 */
61     //addr = (char *)mmap(NULL, sys_pagesize + 1, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
62 
63     /* 正常分配 */
64     addr = (char *)mmap(NULL, MMAP_FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
65     if (addr == MAP_FAILED)
66         err_exit("mmap()");
67 
68     /* 原始数据 */
69     printf("old text:%s\n", addr);
70 
71     /* 越界访问 */
72     //addr += sys_pagesize + 1;
73     //printf("out of range:%s\n", addr);
74 
75     /* 拷贝新数据 */
76     text_len = strlen(argv[1]);
77     memcpy(addr, argv[1], text_len);
78 
79     /* 同步映射区数据 */
80     //if (msync(addr, text_len, MS_SYNC) == -1)
81     //    err_exit("msync()");
82 
83     /* 打印新数据 */
84     printf("new text:%s\n", addr);
85 
86     /* 解除映射区域 */
87     if (munmap(addr, MMAP_FILE_SIZE) == -1)
88         err_exit("munmap()");
89 
90     return 0;
91 }
复制代码

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.匿名映射的使用跟上面的文件映射差不多。这里不再给例子。


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值