浅谈 Linux 的内存管理

匿名内存映射

  在 Linux 中,如果使用malloc()分配一块很大的内存 (大于128KB),这时malloc()不会直接从 heap 上分配内存,而是会调用mmap()创建匿名的内存映射,那么使用mmap()分配内存有何优缺点呢?

  • 使用mmap()分配内存时,不会产生内存碎片。因为当这块内存不需要时,可以直接调用munmap()释放它,这样内存就会直接返回给操作系统。
  • 在 Linux 中,一个页面的大小是 4KB,所以mmap()分配的内存会按照 4KB 的大小对齐。通常来说,分配小块内存并不会用到mmap(),因为mmap()分配的内存是 4KB 的整数倍,如果不能充分利用,就会造成内存的浪费。
  • 和在 heap 上分配内存相比,使用mmap()分配内存时将带来比较大的开销,所以mmap()不适合频繁地调用,通常只有当分配的内存比较大时,才会使用到mmap()

  使用mmap()分配内存有两种形式,一种是私有的内存映射,另一种是共享的内存映射。如果创建的是私有的内存映射,那么在fork()之后,尽管子进程也可以访问这块内存,但是 copy-on-write 会保证父进程和子进程对这块内存的修改不会被彼此看见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
int main()
{
// 创建私有的内存映射, 512 KB
size_t sz = 512 * 1024;
void *p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED)
{
perror("mmap");
exit(EXIT_FAILURE);
}
int *addr = p;
int n = fork();
if (n == 0)
{
*addr = 100;
printf("Child set value to %d\n", *addr);
if (munmap(p, sz) == -1)
{
perror("munmap");
exit(EXIT_FAILURE);
}
}
else if (n > 0)
{
wait(NULL); // 等待子进程退出
printf("Parent get value %d\n", *addr);
if (munmap(p, sz) == -1)
{
perror("munmap");
exit(EXIT_FAILURE);
}
}
else
{
perror("fork");
exit(EXIT_FAILURE);
}
return 0;
}

  从程序的输出可以看到,子进程对内存的修改不会被父进程看到:

1
2
Child set value to 100
Parent get value 0


  但如果mmap()创建的是共享的内存映射,那么在fork()之后,父进程和子进程对这块内存的修改,对彼此来说都是可见的,因此共享的内存映射可以用来作为进程间通信的一种方式。我们可以试着修改上面的程序,改用共享的内存映射:

1
2
void *p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);

  这时运行程序,可以看到子进程修改了内存,对父进程来说是可见的:

1
2
Child set value to 100
Parent get value 100

参考资料