计算机系统篇之虚拟内存(4):再探 mmap
Author: stormQ
Created: Friday, 13. November 2020 07:13AM
Last Modified: Tuesday, 17. November 2020 12:39PM
本文通过示例的方式研究了参数 length 和 offset 对 mmap 函数行为的影响,从而更好地理解内存映射。
参数length
不是页大小的整数倍的情形可以细分为两种:1)length
小于页大小;2)length
大于页大小,但不是页大小的整数倍。这两种情况在本质上没有什么区别。因此,我们以第一种情况作为研究示例,最终结论同样适用于第二种情况。
如果参数length
小于页大小时,我们通过研究以下问题来分析mmap
函数的真正行为。
实际创建的虚拟内存区域大小,是参数length
的大小还是其他的?
内存映射对象为普通文件时,修改该虚拟内存区域中大于length
的部分,是否可共享?
内存映射对象为普通文件时,修改该虚拟内存区域中大于length
的部分,是否会被同步到普通文件中?
研究过程:
step 0: 准备研究示例
该示例首先使用mmap
函数创建一个可读写、共享的虚拟内存区域,内存映射对象为 Linux 系统中的普通文件——a.txt。然后,写一些数据到该虚拟内存区域中大于length
的部分。最后,调用msync
函数显式地同步这些数据到 a.txt 文件中。
源码,vm3_main.cpp:
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
if (argc < 4)
{
printf("Usage, needed args: backed-file length offset\n");
return EXIT_FAILURE;
}
int fd = open(argv[1], O_RDWR);
if (-1 == fd)
{
perror("open failed, reason:");
return EXIT_FAILURE;
}
const auto length = atoi(argv[2]);
const auto offset = atoi(argv[3]);
auto addr = static_cast<int *>(mmap(NULL, length,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset));
if (MAP_FAILED == addr)
{
perror("mmap failed, reason:");
return EXIT_FAILURE;
}
close(fd);
printf("page size:%ld bytes\n", sysconf(_SC_PAGE_SIZE));
const auto end = length + 10;
for (int i = length; i < end; i++)
{
addr[i] = i;
}
printf("\n");
msync(addr, end, MS_SYNC);
return EXIT_SUCCESS;
}
编译:
$ g++ -o vm3_main vm3_main.cpp -g
查看 a.txt 文件的初始内容(ASCII 码,共 110 字节):
$ cat a.txt
1234512345
1234512345
1234512345
1234512345
1234512345
1234512345
1234512345
1234512345
1234512345
1234512345
使用od
命令查看 a.txt 的所有内容:
$ od -A x -tc a.txt
000000 1 2 3 4 5 1 2 3 4 5 \n 1 2 3 4 5
000010 1 2 3 4 5 \n 1 2 3 4 5 1 2 3 4 5
000020 \n 1 2 3 4 5 1 2 3 4 5 \n 1 2 3 4
000030 5 1 2 3 4 5 \n 1 2 3 4 5 1 2 3 4
000040 5 \n 1 2 3 4 5 1 2 3 4 5 \n 1 2 3
000050 4 5 1 2 3 4 5 \n 1 2 3 4 5 1 2 3
000060 4 5 \n 1 2 3 4 5 1 2 3 4 5 \n
00006e
注:最左侧一列为地址,选项-A x
表示地址显示为十六进制。默认情况下,地址显示的是八进制。选项-tc
表示文件内容以 ASCII 码显示,通过od
命令可以看出文件中所有字节的值,包括换行符,共 110 字节。
step 1: 查看新创建的虚拟内存区域
1)使用 gdb 运行可执行目标文件——vm3_main
$ gdb -q --args ./vm3_main ./a.txt 10 0
Reading symbols from ./vm3_main...done.
2)启动并执行到“调用mmap”的下一条语句处(对应源文件中的第 26 行)
(gdb) start
Temporary breakpoint 1 at 0x4007b5: file vm3_main.cpp, line 9.
Starting program: /home/test/vm/vm3_main ./a.txt 10 0
Temporary breakpoint 1, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:9
9 if (argc < 4)
(gdb) b 26
Breakpoint 2 at 0x400864: file vm3_main.cpp, line 26.
(gdb) c
Continuing.
Breakpoint 2, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:26
26 if (MAP_FAILED == addr)
断点被击中时,表示我们已完成了创建内存映射的过程。
3)查看进程的内存映射
(gdb) i proc mappings
process 17039
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/test/vm/vm3_main
0x600000 0x601000 0x1000 0x0 /home/test/vm/vm3_main
0x601000 0x602000 0x1000 0x1000 /home/test/vm/vm3_main
0x7ffff7a0d000 0x7ffff7bcd000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 0x4000 0x0
0x7ffff7dd7000 0x7ffff7dfd000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fd1000 0x7ffff7fd4000 0x3000 0x0
0x7ffff7ff6000 0x7ffff7ff7000 0x1000 0x0 /home/test/vm/a.txt
0x7ffff7ff7000 0x7ffff7ffa000 0x3000 0x0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffdd000 0x7ffffffff000 0x22000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
从上面可以看出,我们所创建的虚拟内存区域的详细信息,比如:起始地址为 0x7ffff7ff6000、大小为 0x1000、内存映射对象为 /home/test/vm/a.txt 文件且偏移量为 0。
另外,我们可以查看mmap
函数所返回的虚拟内存区域的起始地址,即局部变量addr
的值也确实是 0x7ffff7ff6000:
(gdb) p/x addr
$1 = 0x7ffff7ff6000
也就是说,我们新创建的虚拟内存区域的地址范围为[0x7ffff7ff6000, 0x7ffff7ff7000)
(前闭后开),大小为 0x1000 字节(即 4KB,也就是页面大小)。
因此,可以得出结论:如果参数length
不是页大小的整数倍时,mmap
函数实际创建的虚拟内存区域大小 = 不小于length
的页大小的最小整数倍。
step 2: 修改该虚拟内存区域中大于length
的部分,观察这些修改是否对其他进程可见
1)在另一个 shell 窗口中再次执行可执行目标文件——vm3_main(记为进程 B,步骤step 1
中的记为进程 A)
$ gdb -q --args ./vm3_main ./a.txt 10 0
Reading symbols from ./vm3_main...done.
(gdb) start
Temporary breakpoint 1 at 0x4007b5: file vm3_main.cpp, line 9.
Starting program: /home/test/vm/vm3_main ./a.txt 10 0
Temporary breakpoint 1, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:9
9 if (argc < 4)
(gdb) b 26
Breakpoint 2 at 0x400864: file vm3_main.cpp, line 26.
(gdb) c
Continuing.
Breakpoint 2, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:26
26 if (MAP_FAILED == addr)
2)分别查看进程 A 和进程 B 的虚拟内存区域中length
后面 40 字节(即 10 个int
类型的值)的内容
进程 A 中:
(gdb) x/10wd addr+length
0x7ffff7ff6028: 171258931 875770417 858927413 822752564
0x7ffff7ff6038: 892613426 875770417 842074677 825570355
0x7ffff7ff6048: 892613426 858927370
进程 B 中:
(gdb) x/10wd addr+length
0x7ffff7ff6028: 171258931 875770417 858927413 822752564
0x7ffff7ff6038: 892613426 875770417 842074677 825570355
0x7ffff7ff6048: 892613426 858927370
可以看出,此时(即两个进程都未对各自的虚拟内存区域的内容进行修改)两个进程的各自虚拟内存区域中length
后面 40 字节的内容是相同的。
3)进程 B 继续执行到“写一些数据到其虚拟内存区域中length
后面 40 字节”的下一条语句处(对应源文件中的第 40 行),而进程 A 保持不动
进程 B 中:
(gdb) b 40
Breakpoint 3 at 0x4008d7: file vm3_main.cpp, line 40.
(gdb) c
Continuing.
page size:4096 bytes
Breakpoint 3, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:40
40 printf("\n");
4)再次查看进程 B 和进程 A 的虚拟内存区域中length
后面 40 字节(即 10 个int
类型的值)的内容
进程 B 中:
(gdb) x/10wd addr+length
0x7ffff7ff6028: 10 11 12 13
0x7ffff7ff6038: 14 15 16 17
0x7ffff7ff6048: 18 19
可以看出,进程 B 确实已经修改了其虚拟内存区域中length
后面 40 字节的内容。
进程 A 中:
(gdb) x/10wd addr+length
0x7ffff7ff6028: 10 11 12 13
0x7ffff7ff6038: 14 15 16 17
0x7ffff7ff6048: 18 19
可以看出,进程 A 的虚拟内存区域中length
后面 40 字节的内容与进程 B 的相同。但此时,进程 A 还未对其虚拟内存区域中length
后面 40 字节的内容进行修改。
因此,可以得出结论:如果参数length
不是页大小的整数倍且内存映射对象为普通文件时,一个进程修改该虚拟内存区域中大于length
的部分,对其他也映射该区域的进程可见。
注意: 这里的所修改的大于length
的部分仍在该虚拟内存区域的地址范围之内。如果超出了这个范围,可能会产生 coredump。
step 3: 调用msync
函数显式地将进程 B 中所修改的数据进行同步操作,观察 a.txt 中是否有这些修改
1)在调用msync
函数前,查看 a.txt 文件的当前内容(十进制)
$ od -tu4 a.txt
0000000 875770417 858927413 822752564 892613426
0000020 875770417 842074677 825570355 892613426
0000040 858927370 842085684 10 11
0000060 12 13 14 15
0000100 16 17 18 19
0000120 842085684 171258931 875770417 858927413
0000140 822752564 892613426 875770417 2613
0000156
从上面可以看出,内核已经自动地将进程 B 所修改的数据同步到 a.txt 中了。
因此,可以得出结论:如果参数length
不是页大小的整数倍且内存映射对象为普通文件时,一个进程修改该虚拟内存区域中大于length
的部分,这些被修改的内容会被内核同步到内存映射文件中。
注意: 上述示例中大于length
的部分还未超出内存映射文件的大小范围。
于是,可以很自然地引申出这样一个问题:如果所修改的虚拟内存区域超出内存映射文件的大小范围时,那么默认情况下这些被修改的内容还会被内核同步到内存映射文件中吗?让我们继续研究。
2)在进程 B 中,修改其虚拟内存区域中起始地址为 addr+27,长度为 4 字节的内容(其中两个字节超出了内存映射文件的大小范围)
(gdb) p/x *(addr+27)=0x12345678
$10 = 0x12345678
再次查看 a.txt 文件的当前内容(十进制,且将每个元素看作 4 字节):
$ od -tu4 a.txt
0000000 875770417 858927413 822752564 892613426
0000020 875770417 842074677 825570355 892613426
0000040 858927370 842085684 10 11
0000060 12 13 14 15
0000100 16 17 18 19
0000120 842085684 171258931 875770417 858927413
0000140 822752564 892613426 875770417 22136
0000156
从上面的结果中可以看出,a.txt 文件中的最后一个值被修改了。
查看 a.txt 文件的当前内容(十六进制,且将每个元素看作 1 字节):
$ od -A x -tx1 a.txt
000000 31 32 33 34 35 31 32 33 34 35 0a 31 32 33 34 35
000010 31 32 33 34 35 0a 31 32 33 34 35 31 32 33 34 35
000020 0a 31 32 33 34 35 31 32 0a 00 00 00 0b 00 00 00
000030 0c 00 00 00 0d 00 00 00 0e 00 00 00 0f 00 00 00
000040 10 00 00 00 11 00 00 00 12 00 00 00 13 00 00 00
000050 34 35 31 32 33 34 35 0a 31 32 33 34 35 31 32 33
000060 34 35 0a 31 32 33 34 35 31 32 33 34 78 56
00006e
从上面的结果中可以看出,只有 0x56、0x78 这两个字节的内容被同步到了 a.txt 文件。上述程序是在字节序为小端的机器上运行的。所以,起始地址为 addr+27 的后面 4 字节的内容依次为(从低地址到高地址):0x78、0x56、0x34、0x12。也就是说,0x34、0x12 这两个字节对应的地址超出了内存映射文件的大小范围。
因此,可以得出结论:如果所修改的虚拟内存区域超出内存映射文件的大小范围时,那么默认情况下这些被修改的内容不会被内核同步到内存映射文件中。
研究结论:
如果参数length
不是页大小的整数倍时,mmap
函数的真正行为:
实际创建的虚拟内存区域大小 = 不小于length
的页大小的最小整数倍。
内存映射对象为普通文件时,一个进程修改该虚拟内存区域中大于length
的部分,对其他也映射该区域的进程可见。
内存映射对象为普通文件时,一个进程修改该虚拟内存区域中大于length
的部分(隐含着未超出该虚拟内存区域的地址范围),可以分为两种情形:
大于length
的部分且未超过内存映射文件的大小范围,那么这些修改会被内核自动地同步到普通文件中。
大于length
的部分且已超过内存映射文件的大小范围,那么这些修改不会被内核自动地同步到普通文件中。
注意: 这两个结论的默认前提是内存映射文件的大小一直保持不变。比如:未对内存映射文件进行文件截断操作。
我们通过研究以下问题来分析参数offset
取不同值时,mmap
函数的真正行为。
参数offset
的取值小于 0 时,mmap
函数的行为?
参数offset
的取值等于 0 时,mmap
函数的行为?
参数offset
的取值等于 1 时(即不是页面大小的整数倍),mmap
函数的行为?
参数offset
的取值等于 4096 时(即正好是页面大小的整数倍),mmap
函数的行为?
参数offset
的取值等于内存映射文件的大小减去 1 时,mmap
函数的行为?
参数offset
的取值等于内存映射文件的大小时,mmap
函数的行为?
参数offset
的取值大于内存映射文件的大小时,mmap
函数的行为?
研究过程:
step 0: 准备研究示例
研究示例源码采用上述的 vm3_main.cpp。
step 1: 研究参数offset
的取值小于 0 时,mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 -1
mmap failed, reason:: Invalid argument
从上面结果中可以看出,参数offset
的取值小于 0 时,mmap
函数执行失败,错误信息为Invalid argument
(参数无效)。
step 2: 研究参数offset
的取值等于 0 时,mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 0
page size:4096 bytes
从上面结果中可以看出,当参数offset
的取值小于 0 时,mmap
函数执行成功,并且无运行时错误。
step 3: 研究参数offset
的取值等于 1 时(即不是页面大小的整数倍),mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 1
mmap failed, reason:: Invalid argument
从上面结果中可以看出,当参数offset
的取值等于 1 时(即不是页面大小的整数倍),mmap
函数也会执行失败,错误信息为Invalid argument
(参数无效)。
step 4: 研究参数offset
的取值等于 4096 时(即正好是页面大小的整数倍),mmap
函数的行为
这里我们可以分成 4 种情况:1)内存映射文件的大小 < 页大小;2)内存映射文件的大小 = 页大小;3)内存映射文件的大小 > 页大小,且是页大小的整数倍;4)内存映射文件的大小 > 页大小,但不是页大小的整数倍。
1)内存映射文件的大小 < 页面大小时
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 4096
page size:4096 bytes
Bus error (core dumped)
从从上面结果中可以看出,这种情况下mmap
函数没有报错,即执行成功了。但进程在运行时会崩溃,原因是收到了SIGBUS
信号。
下面我们分析下是哪行语句导致了SIGBUS
信号的产生。
先复现SIGBUS
信号产生时的现场:
$ gdb -q --args ./vm3_main ./a.txt 1 4096
Reading symbols from ./vm3_main...done.
(gdb) start
Temporary breakpoint 1 at 0x4007b5: file vm3_main.cpp, line 9.
Starting program: /home/test/vm/vm3_main ./a.txt 1 4096
Temporary breakpoint 1, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:9
9 if (argc < 4)
(gdb) b 26
Breakpoint 2 at 0x400864: file vm3_main.cpp, line 26.
(gdb) c
Continuing.
Breakpoint 2, main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:26
26 if (MAP_FAILED == addr)
(gdb) n
31 close(fd);
(gdb)
33 printf("page size:%ld bytes\n", sysconf(_SC_PAGE_SIZE));
(gdb)
page size:4096 bytes
35 const auto end = length + 10;
(gdb)
36 for (int i = length; i < end; i++)
(gdb)
38 addr[i] = i;
(gdb)
Program received signal SIGBUS, Bus error.
0x00000000004008d3 in main (argc=4, argv=0x7fffffffdae8) at vm3_main.cpp:38
38 addr[i] = i;
查看局部变量addr
和i
的值:
(gdb) p/x addr
$1 = 0x7ffff7ff6000
(gdb) p/d i
$2 = 1
查看进程的内存空间分布:
(gdb) i proc mappings
process 28476
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/test/vm/vm3_main
0x600000 0x601000 0x1000 0x0 /home/test/vm/vm3_main
0x601000 0x602000 0x1000 0x1000 /home/test/vm/vm3_main
0x602000 0x623000 0x21000 0x0 [heap]
0x7ffff7a0d000 0x7ffff7bcd000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 0x4000 0x0
0x7ffff7dd7000 0x7ffff7dfd000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fd1000 0x7ffff7fd4000 0x3000 0x0
0x7ffff7ff6000 0x7ffff7ff7000 0x1000 0x1000 /home/test/vm/a.txt
0x7ffff7ff7000 0x7ffff7ffa000 0x3000 0x0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffdd000 0x7ffffffff000 0x22000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
从上面的结果中可以看出,mmap
函数的返回值(即局部变量addr
的值)为新创建的虚拟内存区域的起始地址 0x7ffff7ff6000。但是,由于offset
的值超过了内存映射文件的大小,即该地址在内存映射文件中没有对应的部分。因此,导致了SIGBUS
信号的产生。
mmap(2) — Linux manual page 上关于这种情况(即SIGBUS
信号产生的原因)是这么描述的:Attempted access to a portion of the buffer that does not correspond to the file (for example, beyond the end of the file, including the case where another process has truncated the file).
2)内存映射文件的大小 = 页大小时
此处,a.txt 文件的大小为 4096 字节。
在 Linux 系统中,我们可以通过$ truncate --size=<file-size-bytes> <file>
命令很容易地创建一个大小为file-size-bytes
的文件(文件内容默认填充全是二进制零)。比如:命令$ truncate --size=4096 test
,用于创建一个名称为test
,大小为 4097 字节,内容全是二进制零的文件。
$ ./vm3_main ./a.txt 1 4096
page size:4096 bytes
Bus error (core dumped)
这种情况下,也会产生SIGBUS
信号,从而导致进程崩溃。
3)内存映射文件的大小 > 页大小,且是页大小的整数倍时
此处,a.txt 文件的大小为 8192 字节。
$ ./vm3_main ./a.txt 1 4096
page size:4096 bytes
这种情况下,mmap
执行成功了,并且没有运行时错误。
4)内存映射文件的大小 > 页大小,但不是页大小的整数倍时
此处,a.txt 文件的大小为 8193 字节。
$ ./vm3_main ./a.txt 1 4096
page size:4096 bytes
这种情况下,mmap
也执行成功了,并且没有运行时错误。
step 5: 研究参数offset
的取值等于内存映射文件的大小减去 1 时,mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 109
mmap failed, reason:: Invalid argument
从上面结果中可以看出,当参数offset
的取值 = 内存映射文件的大小 - 1,且不是页大小的整数倍时,mmap
函数也会执行失败,错误信息为Invalid argument
(参数无效)。
step 6: 研究参数offset
的取值等于内存映射文件的大小时,mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 110
mmap failed, reason:: Invalid argument
从上面结果中可以看出,当参数offset
的取值等于内存映射文件的大小时,mmap
函数也会执行失败,错误信息为Invalid argument
(参数无效)。
step 7: 研究参数offset
的取值大于内存映射文件的大小时,mmap
函数的行为
此处,a.txt 文件的大小为 110 字节。
$ ./vm3_main ./a.txt 1 111
mmap failed, reason:: Invalid argument
从上面结果中可以看出,当参数offset
的取值大于内存映射文件的大小时,mmap
函数也会执行失败,错误信息为Invalid argument
(参数无效)。
研究结论:
mmap
函数中,参数offset
的取值必须同时满足以下两种条件:
参数offset
取值必须是页大小的整数倍(隐含了该值必须 >= 0)。否则,mmap
函数会执行失败,且错误信息为Invalid argument
(参数无效)。
参数offset
取值不能 >= 内存映射文件的大小。否则,虽然mmap
函数可以成功创建虚拟内存区域,但在运行时访问该虚拟内存区域会产生SIGBUS
信号,从而导致进程崩溃退出。
最后,给出计算传递给mmap
函数的实参offset
的方法(需要包含头文件#include <unistd.h>
):
const auto true_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
注:
offset
是用户传入的参数,true_offset
作为最终传递给mmap
函数的值。
sysconf(_SC_PAGE_SIZE)
表示获取系统的页大小(单位:Bytes)。
offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
的作用:将变量offset
低页大小所占用的 bit 数
位的内容全部置为零,即结果为不大于offset
的页大小的最大整数倍。
下一篇:计算机系统篇之虚拟内存(8):理解 glibc malloc 的工作原理(上)