虚拟内存视角下的Linux进程 & 动态内存分配 - CS:APP 第九章

虚拟内存视角下的Linux进程 & 动态内存分配

Linux的虚拟内存系统

Linux为每个进程提供了一个虚拟地址空间。而这些地址空间被分为两部分:

  • 进程虚拟内存部分
  • 内核虚拟内存部分

进程虚拟内存部分我们已经很熟悉了,按照地址从低到高,他们分别是:

用户栈
%rsp →
共享库的内存映射区域
brk →
运行时的堆(通过malloc分配的)
未初始化的数据(.bss)
已初始化的数据(.data)
0x400000 → 代码(.text)

内核虚拟内存可分为两部分:

  • 内核中的代码和数据及数据结构
  • 被映射到此的物理内存(Linux将一组连续的虚拟内存(大小等于系统DRAM总量)映射到物理内存,为内核提供一种便利的方法来访问物理内存中的任何位置)
与进程相关的数据结构(页表、task和mm结构、内核栈) 每个进程都不相同
物理内存 每个进程都相同
内核代码和数据 每个进程都相同

image-20220917111815524

Linux如何组织虚拟内存

Linux将虚拟内存组成成一些区域的集合。一个区域就是已分配的虚拟内存片段

这些片段通过一个结构链表被组织起来。(task_struct->mm_struct->mmap)

image-20220917111745313

用户级内存映射

Linux 可以使用mmap函数来创建新的虚拟内存,并将对象映射到这些区域中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <unistd.h>
#include <sys/mman.h>

void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
/*
将文件描述符fd所表示的文件,从offset处开始的length字节大小的数据,以prot的访问权限和flags的方式,映射到最好再虚拟内存start处开始的区域

prot:
PROT_EXEC 该页面的区域可以被执行
PROT_READ 该页面区域可读
PROT_WRITE ... 可写
PROT_NONE ... 不能被访问

flags:
MAP_ANON 被映射的对象是一个匿名对象
MAP_PRIVATE 是一个私有对象,使用写时复制机制
MAP_SHARE 是一个共享对象
*/

int munmap(void *start, size_t length); // 删除从虚拟地址start开始处的length字节,接下来对这些地址的引用将会导致段错误

例子:将任意磁盘文件赋值到stdout

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
#include <csapp.h>

void mmapcopy(int fd, int size)
{
char *bufp;

// load to virtual memory
bufp = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
Write(STDOUT_FILENO, bufp, size); // write to stdout
return ;
}

int main(int argc, char *argv[])
{
struct stat stat;
int fd;

if (argc != 2) {
printf("usage: %s <filename>\n", argv[0]);
exit(0);
}

// open file
fd = Open(argv[1], O_RDONLY, 0);
fstat(fd, &stat); // get detailed message of file
mmapcopy(fd, stat.st_size); // call mmapcopy
exit(0);
}