在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:
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. */
/*
* 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 apace)和后备存储(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
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;
其关系图:
反向映射的引入
所谓反向映射是相对于从虚拟地址到物理地址的映射,反向映射是从物理页面到虚拟地址空间VMA的反向映射。反向映射通常记为rmap。RMAP能否实现的基础是通过struct anon_vma、struct anon_vma_chain和sturct vm_area_struct建立了联系,通过物理页面反向查找到VMA。
反向映射的需求来源:
当Linux系统内存不足时, swap子系统会释放一些页面, 交换到交换设备中, 以空出多余的内存页。虚拟内存的理念就是通过页表来维护虚拟地址到物理地址的映射。但是, 页表是种单向映射, 即通过虚拟地址查找物理地址很容易, 但反之通过物理地址查找虚拟地址则很麻烦。
同时,由于fork的 copy-on-write机制,导致一个物理page可能被多个进程的vma同时映射。
当Linux系统内存不足时, swap子系统会释放一些页面, 交换到交换设备中, 以空出多余的内存页。虚拟内存的理念就是通过页表来维护虚拟地址到物理地址的映射。但是, 页表是种单向映射, 即通过虚拟地址查找物理地址很容易, 但反之通过物理地址查找虚拟地址则很麻烦。这种问题在共享内存的情况下更加严重。而swap子系统在释放页面时就遇到这个问题, 对于特定页面(物理地址), 要找到映射到它的页表项(PTE)
, 并修改PTE, 以使其指向交换设备中的该页的位置。在2.4之前的内核中, 这是件费时的工作, 因为内核需要遍历每一个进程的所有页表, 以找出所有映射该页的页表项。
解决这一问题的做法是引入**反向映射(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 page
的mapping
字段, 因为对于匿名页, mapping
为null
, 不指向后备空间。复用方法是利用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:
/*
* The copy-on-write semantics of fork mean that an anon_vma
* can become associated with multiple processes. Furthermore,
* each child process will have its own anon_vma, where new
* pages for that process are instantiated.
*
* This structure allows us to find the anon_vmas associated
* with a VMA, or the VMAs associated with an anon_vma.
* 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.
*/
struct anon_vma_chain {
struct vm_area_struct *vma;
struct anon_vma *anon_vma;
struct list_head same_vma; /* locked by mmap_sem & page_table_lock */
struct list_head same_anon_vma; /* locked by anon_vma->lock */
};
每个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_vma
和vma
。 (蓝色)链表是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
这个名字, 它就像个粘合剂, 也像个链条, 把初始时父,子进程关联的vma
和anon_vma
链接起来, 当子进程通过COW拥有自己的匿名页后, 会发生解链, 以分冶策略各自管理, 从而使得在解除一页映射时, 减少了父进程遍历的vma
数目, 也减少了相应的锁冲突, 因而提高了效率。
vm_flags
权限
参考:
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