内核解读之内存管理(12)进程虚拟内存管理 vm_area_struct 与反向映射

Posted 奇妙之二进制

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核解读之内存管理(12)进程虚拟内存管理 vm_area_struct 与反向映射相关的知识,希望对你有一定的参考价值。

在32位的系统上,线性地址空间可达到4GB,这4GB一般按照3:1的比例进行分配,也就是说用户进程享有前3GB线性地址空间,而内核独享最后1GB线性地址空间。由于虚拟内存的引入,每个进程都可拥有3GB的虚拟内存,并且用户进程之间的地址空间是互不可见、互不影响的,也就是说即使两个进程对同一个地址进行操作,也不会产生问题。在前面介绍的一些分配内存的途径中,无论是伙伴系统中分配页的函数,还是slab分配器中分配对象的函数,它们都会尽量快速地响应内核的分配请求,将相应的内存提交给内核使用,而内核对待用户空间显然不能如此。用户空间动态申请内存时往往只是获得一块线性地址的使用权,而并没有将这块线性地址区域与实际的物理内存对应上,只有当用户空间真正操作申请的内存时,才会触发一次缺页异常,这时内核才会分配实际的物理内存给用户空间。

用户进程的虚拟地址空间包含了若干区域,这些区域的分布方式是特定于体系结构的,不过所有的方式都包含下列成分:

  • 可执行文件的二进制代码,也就是程序的代码段
  • 存储全局变量的数据段
  • 用于保存局部变量和实现函数调用的栈
  • 环境变量和命令行参数
  • 程序使用的动态库的代码
  • 用于映射文件内容的区域

cat /proc/<pid>/maps可以查看某一进程的所有映射区间。

$ sudo cat /proc/1/maps 
[sudo] password for developer: 
55725a3000-55726ce000 r-xp 00000000 b3:01 917543                         /lib/systemd/systemd
55726de000-5572718000 r--p 0012b000 b3:01 917543                         /lib/systemd/systemd
5572718000-5572719000 rw-p 00165000 b3:01 917543                         /lib/systemd/systemd
558e959000-558eb63000 rw-p 00000000 00:00 0                              [heap]
7f80000000-7f80021000 rw-p 00000000 00:00 0 
7f80021000-7f84000000 ---p 00000000 00:00 0 
7f88000000-7f88021000 rw-p 00000000 00:00 0 
7f88021000-7f8c000000 ---p 00000000 00:00 0 
7f8c7a9000-7f8c7aa000 ---p 00000000 00:00 0 
7f8c7aa000-7f8cfaa000 rw-p 00000000 00:00 0 
7f8cfaa000-7f8cfab000 ---p 00000000 00:00 0 
7f8cfab000-7f8d7ae000 rw-p 00000000 00:00 0 
7f8d7ae000-7f8d856000 r-xp 00000000 b3:01 918344                         /lib/aarch64-linux-gnu/libm-2.27.so
7f8d856000-7f8d865000 ---p 000a8000 b3:01 918344                         /lib/aarch64-linux-gnu/libm-2.27.so
7f8d865000-7f8d866000 r--p 000a7000 b3:01 918344                         /lib/aarch64-linux-gnu/libm-2.27.so
7f8d866000-7f8d867000 rw-p 000a8000 b3:01 918344                         /lib/aarch64-linux-gnu/libm-2.27.so
7f8d867000-7f8d880000 r-xp 00000000 b3:01 918341                         /lib/aarch64-linux-gnu/libudev.so.1.6.9
7f8d880000-7f8d88f000 ---p 00019000 b3:01 918341                         /lib/aarch64-linux-gnu/libudev.so.1.6.9
7f8d88f000-7f8d890000 r--p 00018000 b3:01 918341                         /lib/aarch64-linux-gnu/libudev.so.1.6.9
7f8d890000-7f8d891000 rw-p 00019000 b3:01 918341                         /lib/aarch64-linux-gnu/libudev.so.1.6.9
7f8d891000-7f8d8a2000 r-xp 00000000 b3:01 918337                         /lib/aarch64-linux-gnu/libgpg-error.so.0.22.0
7f8d8a2000-7f8d8b1000 ---p 00011000 b3:01 918337                         /lib/aarch64-linux-gnu/libgpg-error.so.0.22.0
7f8d8b1000-7f8d8b2000 r--p 00010000 b3:01 918337                         /lib/aarch64-linux-gnu/libgpg-error.so.0.22.0
7f8d8b2000-7f8d8b3000 rw-p 00011000 b3:01 918337                         /lib/aarch64-linux-gnu/libgpg-error.so.0.22.0
7f8d8b3000-7f8d8bc000 r-xp 00000000 b3:01 918236                         /lib/aarch64-linux-gnu/libjson-c.so.3.0.1
7f8d8bc000-7f8d8cb000 ---p 00009000 b3:01 918236                         /lib/aarch64-linux-gnu/libjson-c.so.3.0.1
7f8d8cb000-7f8d8cc000 r--p 00008000 b3:01 918236                         /lib/aarch64-linux-gnu/libjson-c.so.3.0.1
7f8d8cc000-7f8d8cd000 rw-p 00009000 b3:01 918236                         /lib/aarch64-linux-gnu/libjson-c.so.3.0.1
7f8d8cd000-7f8d8d4000 r-xp 00000000 b3:01 551785                         /usr/lib/aarch64-linux-gnu/libargon2.so.0
7f8d8d4000-7f8d8e3000 ---p 00007000 b3:01 551785                         /usr/lib/aarch64-linux-gnu/libargon2.so.0
7f8d8e3000-7f8d8e4000 r--p 00006000 b3:01 551785                         /usr/lib/aarch64-linux-gnu/libargon2.so.0
7f8d8e4000-7f8d8e5000 rw-p 00007000 b3:01 551785                         /usr/lib/aarch64-linux-gnu/libargon2.so.0
7f8d8e5000-7f8d94a000 r-xp 00000000 b3:01 918410                         /lib/aarch64-linux-gnu/libdevmapper.so.1.02.1
7f8d94a000-7f8d95a000 ---p 00065000 b3:01 918410                         /lib/aarch64-linux-gnu/libdevmapper.so.1.02.1
7f8d95a000-7f8d95b000 r--p 00065000 b3:01 918410                         /lib/aarch64-linux-gnu/libdevmapper.so.1.02.1
7f8d95b000-7f8d95f000 rw-p 00066000 b3:01 918410                         /lib/aarch64-linux-gnu/libdevmapper.so.1.02.1
7f8d95f000-7f8d960000 rw-p 00000000 00:00 0 
7f8d960000-7f8d964000 r-xp 00000000 b3:01 918353                         /lib/aarch64-linux-gnu/libattr.so.1.1.0
7f8d964000-7f8d973000 ---p 00004000 b3:01 918353                         /lib/aarch64-linux-gnu/libattr.so.1.1.0
7f8d973000-7f8d974000 r--p 00003000 b3:01 918353                         /lib/aarch64-linux-gnu/libattr.so.1.1.0
7f8d974000-7f8d975000 rw-p 00004000 b3:01 918353                         /lib/aarch64-linux-gnu/libattr.so.1.1.0
7f8d975000-7f8d979000 r-xp 00000000 b3:01 918310                         /lib/aarch64-linux-gnu/libcap-ng.so.0.0.0
7f8d979000-7f8d988000 ---p 00004000 b3:01 918310                         /lib/aarch64-linux-gnu/libcap-ng.so.0.0.0
7f8d988000-7f8d989000 r--p 00003000 b3:01 918310                         /lib/aarch64-linux-gnu/libcap-ng.so.0.0.0
7f8d989000-7f8d98a000 rw-p 00004000 b3:01 918310                         /lib/aarch64-linux-gnu/libcap-ng.so.0.0.0
7f8d98a000-7f8d990000 r-xp 00000000 b3:01 918406                         /lib/aarch64-linux-gnu/libuuid.so.1.3.0
7f8d990000-7f8d99f000 ---p 00006000 b3:01 918406                         /lib/aarch64-linux-gnu/libuuid.so.1.3.0
7f8d99f000-7f8d9a0000 r--p 00005000 b3:01 918406                         /lib/aarch64-linux-gnu/libuuid.so.1.3.0
7f8d9a0000-7f8d9a1000 rw-p 00006000 b3:01 918406                         /lib/aarch64-linux-gnu/libuuid.so.1.3.0
7f8d9a1000-7f8d9a4000 r-xp 00000000 b3:01 918240                         /lib/aarch64-linux-gnu/libdl-2.27.so
7f8d9a4000-7f8d9b4000 ---p 00003000 b3:01 918240                         /lib/aarch64-linux-gnu/libdl-2.27.so
7f8d9b4000-7f8d9b5000 r--p 00003000 b3:01 918240                         /lib/aarch64-linux-gnu/libdl-2.27.so
7f8d9b5000-7f8d9b6000 rw-p 00004000 b3:01 918240                         /lib/aarch64-linux-gnu/libdl-2.27.so
7f8d9b6000-7f8da17000 r-xp 00000000 b3:01 918323                         /lib/aarch64-linux-gnu/libpcre.so.3.13.3
7f8da17000-7f8da26000 ---p 00061000 b3:01 918323                         /lib/aarch64-linux-gnu/libpcre.so.3.13.3
7f8da26000-7f8da27000 r--p 00060000 b3:01 918323                         /lib/aarch64-linux-gnu/libpcre.so.3.13.3
7f8da27000-7f8da28000 rw-p 00061000 b3:01 918323                         /lib/aarch64-linux-gnu/libpcre.so.3.13.3
7f8da28000-7f8da3f000 r-xp 00000000 b3:01 918397                         /lib/aarch64-linux-gnu/libpthread-2.27.so
7f8da3f000-7f8da4e000 ---p 00017000 b3:01 918397                         /lib/aarch64-linux-gnu/libpthread-2.27.so
7f8da4e000-7f8da4f000 r--p 00016000 b3:01 918397                         /lib/aarch64-linux-gnu/libpthread-2.27.so
7f8da4f000-7f8da50000 rw-p 00017000 b3:01 918397                         /lib/aarch64-linux-gnu/libpthread-2.27.so
7f8da50000-7f8da54000 rw-p 00000000 00:00 0 
7f8da54000-7f8da6f000 r-xp 00000000 b3:01 550388                         /usr/lib/aarch64-linux-gnu/liblz4.so.1.7.1
7f8da6f000-7f8da7e000 ---p 0001b000 b3:01 550388                         /usr/lib/aarch64-linux-gnu/liblz4.so.1.7.1
7f8da7e000-7f8da7f000 r--p 0001a000 b3:01 550388                         /usr/lib/aarch64-linux-gnu/liblz4.so.1.7.1
7f8da7f000-7f8da80000 rw-p 0001b000 b3:01 550388                         /usr/lib/aarch64-linux-gnu/liblz4.so.1.7.1
7f8da80000-7f8da9f000 r-xp 00000000 b3:01 918356                         /lib/aarch64-linux-gnu/liblzma.so.5.2.2
7f8da9f000-7f8daae000 ---p 0001f000 b3:01 918356                         /lib/aarch64-linux-gnu/liblzma.so.5.2.2
7f8daae000-7f8daaf000 r--p 0001e000 b3:01 918356                         /lib/aarch64-linux-gnu/liblzma.so.5.2.2
7f8daaf000-7f8dab0000 rw-p 0001f000 b3:01 918356                         /lib/aarch64-linux-gnu/liblzma.so.5.2.2
7f8dab0000-7f8dae0000 r-xp 00000000 b3:01 918359                         /lib/aarch64-linux-gnu/libidn.so.11.6.16
7f8dae0000-7f8daf0000 ---p 00030000 b3:01 918359                         /lib/aarch64-linux-gnu/libidn.so.11.6.16
7f8daf0000-7f8daf1000 r--p 00030000 b3:01 918359                         /lib/aarch64-linux-gnu/libidn.so.11.6.16
7f8daf1000-7f8daf2000 rw-p 00031000 b3:01 918359                         /lib/aarch64-linux-gnu/libidn.so.11.6.16
7f8daf2000-7f8daf8000 r-xp 00000000 b3:01 553538                         /usr/lib/aarch64-linux-gnu/libip4tc.so.0.1.0
7f8daf8000-7f8db07000 ---p 00006000 b3:01 553538                         /usr/lib/aarch64-linux-gnu/libip4tc.so.0.1.0
7f8db07000-7f8db08000 r--p 00005000 b3:01 553538                         /usr/lib/aarch64-linux-gnu/libip4tc.so.0.1.0
7f8db08000-7f8db09000 rw-p 00006000 b3:01 553538                         /usr/lib/aarch64-linux-gnu/libip4tc.so.0.1.0
7f8db09000-7f8dbae000 r-xp 00000000 b3:01 918396                         /lib/aarch64-linux-gnu/libgcrypt.so.20.2.1
7f8dbae000-7f8dbbd000 ---p 000a5000 b3:01 918396                         /lib/aarch64-linux-gnu/libgcrypt.so.20.2.1
7f8dbbd000-7f8dbbf000 r--p 000a4000 b3:01 918396                         /lib/aarch64-linux-gnu/libgcrypt.so.20.2.1
7f8dbbf000-7f8dbc4000 rw-p 000a6000 b3:01 918396                         /lib/aarch64-linux-gnu/libgcrypt.so.20.2.1
7f8dbc4000-7f8dbc8000 r-xp 00000000 b3:01 918411                         /lib/aarch64-linux-gnu/libcap.so.2.25
7f8dbc8000-7f8dbd8000 ---p 00004000 b3:01 918411                         /lib/aarch64-linux-gnu/libcap.so.2.25
7f8dbd8000-7f8dbd9000 r--p 00004000 b3:01 918411                         /lib/aarch64-linux-gnu/libcap.so.2.25
7f8dbd9000-7f8dbda000 rw-p 00005000 b3:01 918411                         /lib/aarch64-linux-gnu/libcap.so.2.25
7f8dbda000-7f8dc1a000 r-xp 00000000 b3:01 918423                         /lib/aarch64-linux-gnu/libcryptsetup.so.12.2.0
7f8dc1a000-7f8dc2a000 ---p 00040000 b3:01 918423                         /lib/aarch64-linux-gnu/libcryptsetup.so.12.2.0
7f8dc2a000-7f8dc2b000 r--p 00040000 b3:01 918423                         /lib/aarch64-linux-gnu/libcryptsetup.so.12.2.0
7f8dc2b000-7f8dc2d000 rw-p 00041000 b3:01 918423                         /lib/aarch64-linux-gnu/libcryptsetup.so.12.2.0
7f8dc2d000-7f8dc34000 r-xp 00000000 b3:01 918385                         /lib/aarch64-linux-gnu/libacl.so.1.1.0
7f8dc34000-7f8dc43000 ---p 00007000 b3:01 918385                         /lib/aarch64-linux-gnu/libacl.so.1.1.0
7f8dc43000-7f8dc44000 r--p 00006000 b3:01 918385                         /lib/aarch64-linux-gnu/libacl.so.1.1.0
7f8dc44000-7f8dc45000 rw-p 00007000 b3:01 918385                         /lib/aarch64-linux-gnu/libacl.so.1.1.0
7f8dc45000-7f8dc53000 r-xp 00000000 b3:01 918328                         /lib/aarch64-linux-gnu/libapparmor.so.1.4.2
7f8dc53000-7f8dc62000 ---p 0000e000 b3:01 918328                         /lib/aarch64-linux-gnu/libapparmor.so.1.4.2
7f8dc62000-7f8dc63000 r--p 0000d000 b3:01 918328                         /lib/aarch64-linux-gnu/libapparmor.so.1.4.2
7f8dc63000-7f8dc64000 rw-p 0000e000 b3:01 918328                         /lib/aarch64-linux-gnu/libapparmor.so.1.4.2
7f8dc64000-7f8dc76000 r-xp 00000000 b3:01 918336                         /lib/aarch64-linux-gnu/libkmod.so.2.3.2
7f8dc76000-7f8dc86000 ---p 00012000 b3:01 918336                         /lib/aarch64-linux-gnu/libkmod.so.2.3.2
7f8dc86000-7f8dc87000 r--p 00012000 b3:01 918336                         /lib/aarch64-linux-gnu/libkmod.so.2.3.2
7f8dc87000-7f8dc88000 rw-p 00013000 b3:01 918336                         /lib/aarch64-linux-gnu/libkmod.so.2.3.2
7f8dc88000-7f8dca4000 r-xp 00000000 b3:01 918415                         /lib/aarch64-linux-gnu/libaudit.so.1.0.0
7f8dca4000-7f8dcb3000 ---p 0001c000 b3:01 918415                         /lib/aarch64-linux-gnu/libaudit.so.1.0.0
7f8dcb3000-7f8dcb4000 r--p 0001b000 b3:01 918415                         /lib/aarch64-linux-gnu/libaudit.so.1.0.0
7f8dcb4000-7f8dcb5000 rw-p 0001c000 b3:01 918415                         /lib/aarch64-linux-gnu/libaudit.so.1.0.0
7f8dcb5000-7f8dcbf000 rw-p 00000000 00:00 0 
7f8dcbf000-7f8dccb000 r-xp 00000000 b3:01 918319                         /lib/aarch64-linux-gnu/libpam.so.0.83.1
7f8dccb000-7f8dcda000 ---p 0000c000 b3:01 918319                         /lib/aarch64-linux-gnu/libpam.so.0.83.1
7f8dcda000-7f8dcdb000 r--p 0000b000 b3:01 918319                         /lib/aarch64-linux-gnu/libpam.so.0.83.1
7f8dcdb000-7f8dcdc000 rw-p 0000c000 b3:01 918319                         /lib/aarch64-linux-gnu/libpam.so.0.83.1
7f8dcdc000-7f8dd1c000 r-xp 00000000 b3:01 918401                         /lib/aarch64-linux-gnu/libblkid.so.1.1.0
7f8dd1c000-7f8dd2c000 ---p 00040000 b3:01 918401                         /lib/aarch64-linux-gnu/libblkid.so.1.1.0
7f8dd2c000-7f8dd30000 r--p 00040000 b3:01 918401                         /lib/aarch64-linux-gnu/libblkid.so.1.1.0
7f8dd30000-7f8dd31000 rw-p 00044000 b3:01 918401                         /lib/aarch64-linux-gnu/libblkid.so.1.1.0
7f8dd31000-7f8dd32000 rw-p 00000000 00:00 0 
7f8dd32000-7f8dd7c000 r-xp 00000000 b3:01 918350                         /lib/aarch64-linux-gnu/libmount.so.1.1.0
7f8dd7c000-7f8dd8b000 ---p 0004a000 b3:01 918350                         /lib/aarch64-linux-gnu/libmount.so.1.1.0
7f8dd8b000-7f8dd8d000 r--p 00049000 b3:01 918350                         /lib/aarch64-linux-gnu/libmount.so.1.1.0
7f8dd8d000-7f8dd8e000 rw-p 0004b000 b3:01 918350                         /lib/aarch64-linux-gnu/libmount.so.1.1.0
7f8dd8e000-7f8dd8f000 rw-p 00000000 00:00 0 
7f8dd8f000-7f8ddaf000 r-xp 00000000 b3:01 918257                         /lib/aarch64-linux-gnu/libselinux.so.1
7f8ddaf000-7f8ddbe000 ---p 00020000 b3:01 918257                         /lib/aarch64-linux-gnu/libselinux.so.1
7f8ddbe000-7f8ddbf000 r--p 0001f000 b3:01 918257                         /lib/aarch64-linux-gnu/libselinux.so.1
7f8ddbf000-7f8ddc0000 rw-p 00020000 b3:01 918257                         /lib/aarch64-linux-gnu/libselinux.so.1
7f8ddc0000-7f8ddc2000 rw-p 00000000 00:00 0 
7f8ddc2000-7f8ddf3000 r-xp 00000000 b3:01 918349                         /lib/aarch64-linux-gnu/libseccomp.so.2.4.3
7f8ddf3000-7f8de02000 ---p 00031000 b3:01 918349                         /lib/aarch64-linux-gnu/libseccomp.so.2.4.3
7f8de02000-7f8de1b000 r--p 00030000 b3:01 918349                         /lib/aarch64-linux-gnu/libseccomp.so.2.4.3
7f8de1b000-7f8de1c000 rw-p 00049000 b3:01 918349                         /lib/aarch64-linux-gnu/libseccomp.so.2.4.3
7f8de1c000-7f8de22000 r-xp 00000000 b3:01 918326                         /lib/aarch64-linux-gnu/librt-2.27.so
7f8de22000-7f8de31000 ---p 00006000 b3:01 918326                         /lib/aarch64-linux-gnu/librt-2.27.so
7f8de31000-7f8de32000 r--p 00005000 b3:01 918326                         /lib/aarch64-linux-gnu/librt-2.27.so
7f8de32000-7f8de33000 rw-p 00006000 b3:01 918326                         /lib/aarch64-linux-gnu/librt-2.27.so
7f8de33000-7f8dfb6000 r-xp 00000000 b3:01 917552                         /lib/systemd/libsystemd-shared-237.so
7f8dfb6000-7f8dfc6000 ---p 00183000 b3:01 917552                         /lib/systemd/libsystemd-shared-237.so
7f8dfc6000-7f8e050000 r--p 00183000 b3:01 917552                         /lib/systemd/libsystemd-shared-237.so
7f8e050000-7f8e052000 rw-p 0020d000 b3:01 917552                         /lib/systemd/libsystemd-shared-237.so
7f8e052000-7f8e054000 rw-p 00000000 00:00 0 
7f8e054000-7f8e193000 r-xp 00000000 b3:01 918299                         /lib/aarch64-linux-gnu/libc-2.27.so
7f8e193000-7f8e1a3000 ---p 0013f000 b3:01 918299                         /lib/aarch64-linux-gnu/libc-2.27.so
7f8e1a3000-7f8e1a7000 r--p 0013f000 b3:01 918299                         /lib/aarch64-linux-gnu/libc-2.27.so
7f8e1a7000-7f8e1a9000 rw-p 00143000 b3:01 918299                         /lib/aarch64-linux-gnu/libc-2.27.so
7f8e1a9000-7f8e1ad000 rw-p 00000000 00:00 0 
7f8e1b3000-7f8e1d4000 rw-p 00000000 00:00 0 
7f8e1d4000-7f8e1f1000 r-xp 00000000 b3:01 918295                         /lib/aarch64-linux-gnu/ld-2.27.so
7f8e1f1000-7f8e1ff000 rw-p 00000000 00:00 0 
7f8e1ff000-7f8e200000 r--p 00000000 00:00 0                              [vvar]
7f8e200000-7f8e201000 r-xp 00000000 00:00 0                              [vdso]
7f8e201000-7f8e202000 r--p 0001d000 b3:01 918295                         /lib/aarch64-linux-gnu/ld-2.27.so
7f8e202000-7f8e204000 rw-p 0001e000 b3:01 918295                         /lib/aarch64-linux-gnu/ld-2.27.so
7ffd202000-7ffd223000 rw-p 00000000 00:00 0                              [stack]

由此可以看到进程的虚拟内存空间会被分成不同的若干区域,每个区域都有其相关的属性和用途,一个合法的地址总是落在某个区域当中的,这些区域也不会重叠。在linux内核中,这样的区域被称之为虚拟内存区域(virtual memory areas),简称 VMA。一个vma就是一块连续的线性地址空间的抽象,它拥有自身的权限(可读,可写,可执行等等) ,每一个虚拟内存区域都由一个相关的 struct vm_area_struct 结构来描述。

从进程的角度来讲,VMA 其实是虚拟空间的内存块,一个进程的内存资源由多个内存块组成,所以,一个进程的描述结构 task_struct 中首先包含Linux的内存描述符 struct mm_struct 结构。

struct task_struct 
.......
  struct mm_struct *mm;
.......
;

在 mm_struct 中进而包含了 vm_area_struct :

struct mm_struct 
.......
  struct maple_tree mm_mt;
.......


一个进程的每个 VMA 块都会链接到mm_struct中的maple_tree。maple_tree是一颗什么样的树,我没有精力分析。内核文档说,它是一颗b数,用于存储非重叠的数。

接下来看看这次的主角 struct vm_area_struct:

下面我们会将vm_area_struct简写成vma

struct vm_area_struct 
	/* The first cache line has the info for VMA tree walking. */

	unsigned long vm_start;		/* Our start address within vm_mm. */ // 该虚拟地址的起始地址
	unsigned long vm_end;		/* The first byte after our end address // 该虚拟地址的结束地址
					   within vm_mm. */

	struct mm_struct *vm_mm;	/* The address space we belong to. */ // 该vma所属的mm_struct, 通过mm_struct我们能找到该vma所属的进程

	/*
	 * Access permissions of this VMA.
	 * See vmf_insert_mixed_prot() for discussion.
	 */
	pgprot_t vm_page_prot;  // 此VMA的访问权限
	unsigned long vm_flags;		/* Flags, see mm.h. */

	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap interval tree.
	 * 对于具有address_space和后备存储(backing store)的区域,
	 * 链接到address_space->i_mmap间隔树,或者链接到address_space-> i_mmap_nonlinear列表中的vma。
	 *
	 */
	struct 
		struct rb_node rb;
		unsigned long rb_subtree_last;
	 shared;

	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 * 
	 */
	struct list_head anon_vma_chain; /* Serialized by mmap_lock &
					  * page_table_lock */
	struct anon_vma *anon_vma;	/* Serialized by page_table_lock */

	/* Function pointers to deal with this struct. */
	const struct vm_operations_struct *vm_ops; //  用于处理此结构体的函数指针

	/* Information about our backing store: */ // 所谓的后背存储就是指该内存映射的文件
	unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units */
	struct file * vm_file;		/* File we map to (can be NULL). */
	void * vm_private_data;		/* was vm_pte (shared mem) */

#ifdef CONFIG_SWAP
	atomic_long_t swap_readahead_info;
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
	struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
 __randomize_layout

挑几个关键成员分析:

anon_vma和anon_vma_chain是为实现反向映射而引入的,我们先了解下反向映射。

反向映射的引入

所谓反向映射是相对于从虚拟地址到物理地址的映射,我们叫正向映射。所谓正向映射,就是在已知虚拟地址和物理地址(或者page number、page struct)的情况下,为地址映射建立起完整的页表的过程。例如,进程分配了一段VMA之后,并无对应的page frame(即没有分配物理地址),直到程序访问了这段VMA之后,产生异常,由内核为其分配物理页面并建立起所有的各级的translation table。通过正向映射,我们可以将进程虚拟地址空间中的虚拟页面映射到对应的物理页面(page frame)。

反向映射是从物理页面到虚拟地址空间VMA的反向映射。在已知page frame的情况下(可能是PFN、可能是指向page descriptor的指针,也可能是物理地址,内核有各种宏定义用于在它们之间进行转换),找到映射到该物理页面的虚拟页面们。由于一个page frame可以在多个进程之间共享,因此**逆向映射的任务是把分散在各个进程地址空间中的所有的page table entry全部找出来。**反向映射通常记为rmap。

由于fork的 copy-on-write(COW)机制,导致一个物理page可能被多个进程的vma同时映射。父进程fork了子进程,将自身的mm_struct拷贝一份给子进程,父子进程拥有一份完全一样的页表和虚拟地址空间。在子进程没有写的动作之前,内核是不会分配新的page frame的,因此父子进程共享一个物理页面。

共享内存的存在也会导致一个物理page被多个进程的vma同时映射。

RMAP能否实现的基础是通过struct anon_vma、struct anon_vma_chain和sturct vm_area_struct建立了联系,通过物理页面反向查找到VMA。

反向映射的需求来源:

虚拟内存的理念就是通过页表来维护虚拟地址到物理地址的映射。但是, 页表是种单向映射, 即通过虚拟地址查找物理地址很容易, 但反之没办法通过物理地址查找虚拟地址。

建立逆向映射机制主要是为了方便页面回收(比如swap机制)。当页面回收机制启动之后,如果回收的page frame是位于内核中的各种内存cache中(例如 slab内存分配器),那么这些页面其实是可以直接回收,没有相关的页表操作。如果回收的是用户进程空间的page frame,那么在回收之前,内核需要对该page frame进行unmapping的操作,即找到所有的page table entries,然后进行对应的修改操作。当然,如果页面是dirty的,我们还需要一些必要的磁盘IO操作。

在早期的Linux内核版本中,是没有反向映射的,那个时候为了找到一个物理页面对应的页表项就需要遍历系统中所有mm组成的链表,然后对每一个mm再遍历每一个VMA,然后查找这个VMA是否映射了这个页面,这个过程极其漫长且低效,有时候不得不遍历完所有的mm才能找到映射到这个页面的所有PTE。

系统中的所有进程地址空间被串联成一个链表,链表头是init_mm,系统中所有的进程地址空间都挂在这个链表中。

  • 首先沿着init_mm,对每一个进程地址空间进行扫面
  • 在扫面一个进程地址空间的时候,对属于该进程地址空间中的每一个VMA进行扫面,如果命中了进程1的PTE,由于该page知识被进程1使用,那么可以直接unmap并回收该page

对于共享页面,就不能进行这么简单处理了,当扫面到进程1的时候,如果符合条件,那么我们就会unmap该page,解除它和进程A的关系。当然,这个时候不能回收该pgae,因为进程X还在使用该page,直到扫面到进程x,并完成unmap操作,该物理页面才能真正的回收
对于这种方式,存在很多问题,例如到底要扫面多少进程才能停止呢?对效率和性能都会是一个很大的影响。后来发现这个问题,只需要在物理页面的Page结构体重增加一个指针的方式解决,通过这个指针来找到一个描述映射这个页的所有PTE结构,这对于反向映射查找所有的PTE易如反掌,但是却带来了内存浪费问题。

接着在2.6的内核中,内核的大神们想到服用page结构体中的mapping字段,然后通过红黑树的方式来组织所有映射这个页的VMA,形成匿名页和文件页的反向映射。

解决这一问题的做法是引入反向映射(reverse mapping)这一概念。该做法就是为每一个内存页(struct page)维护一个数据结构, 其中包含所有映射到该页的PTE, 这样在寻找一个内存页的反向映射时只要扫描这个结构即可, 大大提高了效率。这正是Rik van Riel的做法, 他在struct page中增加了一个pte_chain指针字段, 它是一个指向所有映射到该页的PTE的链表指针。

当然, 它是有代价的。

  • 每个struct page都增加了一个指针, 而系统中每个内存页都对应一个struct page结构, 这意味着相当数量的内存被用来维护这个字段。而struct page是重要的内核数据结构, 存放在有限的低端内存中, 增加一个字段浪费了大量的保贵低端内存, 而且, 当物理内存很大时, 这种情况更突出, 这引起了**伸缩性(scalability)**问题。
  • 其它一些需要操作大量页面的函数慢下来了。fork()系统调用就是一个。由于Linux采取**写时复制(COW, Copy On Write)**的语义, 意味着新进程共享父进程的页表, 这样, 进程地址空间内的所有页都新增了一个PTE指向它, 因此, 需要为每个页新增一个反向映射, 这显著地拖慢了速度。

基于对象的反向映射

这种代价显然是不能容忍的, 于是, Dave McCracken提出了一个叫做**基于对象的反向映射(object-based reverse mapping)**的解决方案。他的观察是, 前面所述的代价来源于反向映射字段的引入, 而如果存在可以从struct page中获取映射到该页面的所有页表项, 这个字段就不需要了, 自然不需要付出这些代价。他确实找到了一种方法。

Linux的用户态内存页大致分两种使用情况:

  • 其中一大部分叫做文件后备页(file-backed page), 顾名思义, 这种内存页的内容关联着后备存储系统中的文件, 比如程序的代码, 比如普通的文本文件, 这种内存页使用时一般通过上述的mmap系统调用映射到地址空间中, 并且, 在内存紧张时, 可以简单地丢弃, 因为可以从后备文件中轻易的恢复。
  • 一种叫匿名页(anonymous page), 这是一种普通的内存页, 比如栈或堆内存就属于这种, 这种内存页没有后备文件, 这也是其称为匿名的缘故。

Dave的方案中的对象指的就是第一种内存页的后备文件。他通过后备文件对象, 以迂回的方式算出PTE,在本文中就不做过多的介绍。

匿名页的反向映射

Dave的方案只解决了第一种内存页的反向映射, 于是, Andrea Arcangeli顺着Dave的思路, 给出了匿名页的反向映射解决方案。

如前所述, 匿名页没有所谓的后备文件, 但是, 匿名页有个特点, 就是它们都是私有的, 而非共享的(比如栈, 椎内存都是独立每个进程的, 非共享的)。这意味着, 每一个匿名内存页, 只有一个PTE关联着它, 也就是只有一个vma关联着它。Andrea的方案是复用struct pagemapping字段, 因为对于匿名页, mappingnull, 不指向后备空间。复用方法是利用C语言的union, 在匿名页的情况下,mapping字段不是指向struct address_space的指针, 而是指向关联该内存页的唯一的vma。由此, 也可以方便地计算出PTE来。

匿名页面反向映射图解

文件页反向映射图解

但是, 事情并不是如此简单。当进程被fork复制时, 前面已经说过, 由于COW的语义, 新进程只是复制父进程的页表, 这意味着现在一个匿名页有两个页表指向它了, 这样, 上面的简单复用mapping字段的做法不适用了, 因为一个指针, 如何表示两个vma呢。

Andrea的做法就是多加一层。新创建一个struct anon_vma结构, 现在mapping字段是指向它了, 而anon_vma中, 不出意料的, 包含一个链表, 链接起所有的vma。每当进程fork一个子进程, 子进程由于COW机制会复制父进程的vma, 这个新vma就链接到父进程中的anon_vma中。这样, 每次unmap一个内存页时, 通过mapping字段指向的anon_vma, 就可以找到可能关联该页的vma链表, 遍历该链表, 就可以找到所有映射到该匿名页的PTE。

这也有代价, 那就是

  • 每个struct vm_area_struct结构多了一个list_head结构字段用以串起所有的vma
  • 需要额外为anon_vma结构分配内存。

但是, 这种方案所需要的内存远小于前面所提的在每个struct page中增加一个反向映射字段来得少, 因此是可以接受的。

以上, 便介绍完了anon_vma结构的来由和作用。

anon_vma_chain

anon_vma结构的提出, 完善了反向映射机制, 一路看来, 无论是效率还是内存使用, 都有了提升, 应该说是很完美的一套解决方案。但现实不断提出难题。一开始提到的Rik van Riel就举了一种工作负载(workload)的例子来反驳说该方案有缺陷。

前面的匿名页反向映射机制在解除一页映射时, 通过访问anon_vma访问vma链表, 遍历整个vma链表, 以查找可能映射到该页的PTE。但是, 这种方法忽略了一点: 当进程fork而复制产生的子进程中的vma如果发生了写访问, 将会分配新的匿名页, 把该vma指向这个新的匿名页, 这个vma就跟原来的那个匿名页没有关系了, 但原来的vma链表却没反映出这种变化, 从而导致了对该vma不必要的检查。 Rik举的例子正是对这种极端情况的描述。

Rik采取的方案是又增加一层, 新增了一个结构叫anon_vma_chain:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIuH79Oj-1673606970761)(/home/syrius/.config/Typora/typora-user-images/image-20230113151814047.png)]

以下是他的提交日志:

mm: change anon_vma linking to fix multi-process server scalability issue

The old anon_vma code can lead to scalability issues with heavily forking
workloads.  Specifically, each anon_vma will be shared between the parent
process and all its child processes.

In a workload with 1000 child processes and a VMA with 1000 anonymous
pages per process that get COWed, this leads to a system with a million
anonymous pages in the same anon_vma, each of which is mapped in just one
of the 1000 processes.  However, the current rmap code needs to walk them
all, leading to O(N) scanning complexity for each page.

This can result in systems where one CPU is walking the page tables of
1000 processes in page_referenced_one, while all other CPUs are stuck on
the anon_vma lock.  This leads to catastrophic failure for a benchmark
like AIM7, where the total number of processes can reach in the tens of
thousands.  Real workloads are still a factor 10 less process intensive
than AIM7, but they are catching up.

This patch changes the way anon_vmas and VMAs are linked, which allows us
to associate multiple anon_vmas with a VMA.  At fork time, each child
process gets its own anon_vmas, in which its COWed pages will be
instantiated.  The parents' anon_vma is also linked to the VMA, because
non-COWed pages could be present in any of the children.

This reduces rmap scanning complexity to O(1) for the pages of the 1000
child processes, with O(N) complexity for at most 1/N pages in the system.
 This reduces the average scanning cost in heavily forking workloads from
O(N) to 2.

The only real complexity in this patch stems from the fact that linking a
VMA to anon_vmas now involves memory allocations.  This means vma_adjust
can fail, if it needs to attach a VMA to anon_vma structures.  This in
turn means error handling needs to be added to the calling functions.

A second source of complexity is that, because there can be multiple
anon_vmas, the anon_vma linking in vma_adjust can no longer be done under
"the" anon_vma lock.  To prevent the rmap code from walking up an
incomplete VMA, this patch introduces the VM_LOCK_RMAP VMA flag.  This bit
flag uses the same slot as the NOMMU VM_MAPPED_COPY, with an ifdef in mm.h
to make sure it is impossible to compile a kernel that needs both symbolic
values for the same bitflag.

Some test results:

Without the anon_vma changes, when AIM7 hits around 9.7k users (on a test
box with 16GB RAM and not quite enough IO), the system ends up running

\\>99% in system time, with every CPU on the same anon_vma lock in the
pageout code.

With these changes, AIM7 hits the cross-over point around 29.7k users.
This happens with ~99% IO wait time, there never seems to be any spike in
system time.  The anon_vma lock contention appears to be resolved.

anon_vma和anon_vma_chain

struct anon_vma 
	struct anon_vma *root;		/* Root of this anon_vma tree */
	struct rw_semaphore rwsem;	/* W: modification, R: walking the list */
	/*
	 * The refcount is taken on an anon_vma when there is no
	 * guarantee that the vma of page tables will exist for
	 * the duration of the operation. A caller that takes
	 * the reference is responsible for clearing up the
	 * anon_vma if they are the last user on release
	 */
	atomic_t refcount;

	/*
	 * Count of child anon_vmas. Equals to the count of all anon_vmas that
	 * have ->parent pointing to this one, including itself.
	 *
	 * This counter is used for making decision about reusing anon_vma
	 * instead of forking new one. See comments in function anon_vma_clone.
	 */
	unsigned long num_children;
	/* Count of VMAs whose ->anon_vma pointer points to this object. */
	unsigned long num_active_vmas;

	struct anon_vma *parent;	/* Parent of this anon_vma */

	/*
	 * NOTE: the LSB of the rb_root.rb_node is set by
	 * mm_take_all_locks() _after_ taking the above lock. So the
	 * rb_root must only be read/written after taking the above lock
	 * to be sure to see a valid next pointer. The LSB bit itself
	 * is serialized by a system wide lock only visible to
	 * mm_take_all_locks() (mm_all_locks_mutex).
	 */

	/* Interval tree of private "related" vmas */
	struct rb_root_cached rb_root;
;
struct anon_vma_chain 
	struct vm_area_struct *vma;
	struct anon_vma *anon_vma;
	struct list_head same_vma;   /* locked by mmap_lock & page_table_lock */ 
	struct rb_node rb;			/* locked by anon_vma->rwsem */
	unsigned long rb_subtree_last;
;

anon_vma,简单说,链接物理page和vma的桥梁,简称av;

其关系图:

每个anon_vma_chain(AVC)维护两个链表

  • same_vma:与给定vma相关联的所有anon_vma
  • same_anon_vma:与给定anon_vma相关联的所有vma

最初,我们有一个进程与一个匿名vma

这里,“AV”是anon_vma,“AVC”是上面看到的anon_vma_chain。 AVC直接通过指针链接到anon_vmavma。 (蓝色)链表是same_anon_vma链表,而(红色)链表是same_vma链表。

想象一下,这个进程进行了fork操作,导致子进程复制了vma; 现在有了一个孤立的新vma

内核需要将此vma链接到父进程的anon_vma中; 这需要添加一个新的anon_vma_chain

请注意,新的AVC已被添加到same_anon_vma链表中。 新的vma也需要自己的anon_vma

现在还有另一个anon_vma_chain链接在新的anon_vma中。 新的AVC已被添加到same_vma链表中。

此刻,根据上图,可以验证anon_vma_chain(AVC)中两个链表的作用。

The “same_vma” list contains the anon_vma_chains linking all the anon_vmas associated with this VMA.
The “same_anon_vma” list contains the anon_vma_chains which link all the VMAs associated with this anon_vma.

当子进程写内存页时,发生COW, 子进程的vma将指向自己匿名页, 同时, 这个新的匿名页指向子进程的anon_vma(此时same_anon_vma链与same_vma链解除)。

这样, 在解除一页映射时, 对于子进程自己的匿名页, 只要遍历子进程自己的anon_vma下的vma链表即可; 拥有大量子进程的父进程对于共享的页(未发生COW), 则按原来的方法遍历, 对于子进程自己的匿名页,父进程则不需要访问对应的vma,这样大大减少了父进程需要遍历的vma

再看anon_vma_chain这个名字, 它就像个粘合剂, 也像个链条, 把初始时父,子进程关联的vmaanon_vma链接起来, 当子进程通过COW拥有自己的匿名页后, 会发生解链, 以分冶策略各自管理, 从而使得在解除一页映射时, 减少了父进程遍历的vma数目, 也减少了相应的锁冲突, 因而提高了效率。

vm_flags

权限

参考:

https://lwn.net/Articles/383162/

https://www.cnblogs.com/arnoldlu/p/8335483.html
https://blog.csdn.net/youzhangjing_/article/details/127640564
https://blog.csdn.net/qq_38654981/article/details/127061582

http://www.wowotech.net/memory_management/reverse_mapping.html

https://blog.csdn.net/weixin_30794499/article/details/98049560

https://blog.csdn.net/u012489236/category_9614673.html

5Vrfd5owFP5rfJwHEkB8bG27PaxnO6cP6556UoiSFYgLser++gVJBBK01II6Zx9KLkkg37357o8wgJNk9ZmheXRPQxwPgBWuBvBmAIDtOSPxL5esC8nIswrBjJFQdioFD+QPlkLVbUFCnNU6ckpjTuZ1YUDTFAe8JkOM0WW925TG9afO0QwbgocAxab0Bwl5VEh91yrlXzCZRerJtiXvJEh1loIsQiFdVkTwdgAnjFJeXCWrCY5z8BQuxbi7HXe3L8ZwytsMCL79joL0ht3f8a/wN3mavPjTT3KWVxQv5ILly/K1QoDRRRrifBJrAK+XEeH4YY6C/O5S6FzIIp7EomWLSzkdZhyvdr6nvV29MBtME8zZWnRRAxRg0mLssWwvS/yVKKpAD5ROkFT5bDt1iYq4kMC8AyTgvo0SDoXZyCZlPKIzmqL4tpRe13Es+3yldC7R+4U5X8s9gBacNmGbP2g/suK96IIFeN+K5E5CbIb5nn6jZk0xHCNOXuvv0T3sBupJ8jHrnJI4ntCYss1YGLrYDx0hzzijL7hyxwfP0PP6sectIbxhz05f5gwNXDnKXp4ECAtBoDrAYuG8jmIdrZSmWINWilBMZqloBgIoLOTXOYxEUOuVvJGQMNxsjSa11RXbhR6gxiuuqQevQQ+wLz0458e9wD+Qe0FfIHkNJHDxpgrG7nmZqorGLscDjlp6QP+DHnAz9IoxtK50mFOS8qwy8/dcUOofgrr+VTR8t6O/Y1sf6m/7lmYixRuXBrNd+uE2NGrayYm5i8/fo+ssCZS63mDJ3ranf36exHE0ExuZFOY3YGT3FvaMDZBekyexHnTx7sRxNV3AU7sTM+0UusBp2CkZTN38L5fTlFfkxa+JJLzN77QboD/QzXzqNQ+lEOuWg88LdmCdGnYz3To5ObtjzTZtE6QtIlWU3N7Y2TazoTmjAc6yXEgYX6B8jgQnVD7tIqna04oFjqmYrYc8jvmata8sQoyIRQMrJs8sv846JZAQYX8aNBJF4OPnaU97oIGfG/eA3xvUZq67set/H1vHb4ntuDdszewjRBxdALawoURzZGzN9GM4HF4wR3uaBhq853HDaTO1MdAXwfVVfhyWwxijLCNBXRd1lPCK8Ed5J7/+WZHfrKqNtWqkYiWP1cbPaqMctGmta9ropKCjcoo3Kzr2jkONivbcptBHyt5X+TFLO9r2dS3NKoqFylH7ajhaoOvox18FEMZEXZVzVKDSr9HZBxidfUSjA/+E0TlaVOl6hxrdSJvIPrLRneF5tb4RYUPMftxKlwLlfyqvtEW9NycMzDz/PyivwFNXtUCLU9QOYp/hyD0k/Bm6x/NFbU+0CoBOFv9op/CebhltXRHUDmFcPS/f4Yp2n8V17qzMfF4wQsffsJwXHYCGk4Xj0kGLo7B30oHa2fb7draiEatGI/tZpEM28FuywWmTIT2cPDgZ0sumnSVDoll+oll0Lz90hbd/AQ==

以上是关于内核解读之内存管理(12)进程虚拟内存管理 vm_area_struct 与反向映射的主要内容,如果未能解决你的问题,请参考以下文章

内核解读之内存管理(12)进程虚拟内存管理 vm_area_struct 与反向映射

内核解读之内存管理(9) 第一个mm_struct

内核源码解读之内存管理(9) 第一个mm_struct

内核解读之内存管理页分配器伙伴系统介绍

内核解读之内存管理页分配器伙伴系统介绍

内核解读之内存管理页分配器伙伴系统介绍