虚拟内存,地址空间,page cache <转>

分类: C |
本文的内容是零散的, 有待进一步完善. 我在"linux memory management"中对物理内存管理做了 bird-view 级别的描述, 可以与本文相互映照.
我们平时编写程序, 执行程序的时候, 总是会接触到"内存(memory)", "buffer", "cache" 这样的概念, 多少会在英文术语和中文翻译中迷失. 本文尝试建立对这些概念和其原理的初步认识.
问题
考虑 Linux 操作系统, 假设我们执行程序 /bin/more, 它读取某个大文件, 并"一页一页地"输出到控制台(stdout). 对此, 我有这些疑问:
- more 程序是否有自己的应用程序缓存(application buffer, user buffer),
- more 将文件从硬盘读入到内存的什么地方?
-
page cache
是什么, 在这个过程中有什么用? - 内存管理都是内核来做的?
- more 的输出是直接从某内核内存区域输出到 stdout 的吗?
-
more 程序中是否会数据从内核 copy 到 userland? 有
system buffer 吗?
Gustavo Duarte 的博文
要解决上面的问题, 不是一件容易的事情,
阅读书籍+源码一定可以做到, 然而定需要花费大量时间. 幸好在 google "page cache" 的时候,
我找到了
和本文关注点"内存"相关的一系列文章如下, 本文"严重"参考了这个系列:
- Getting Physical With Memory
- Anatomy of a Program in Memory
- How The Kernel Manages Your Memory
- Page Cache, the Affair Between Memory and Files
Anatomy of a Program in Memory
OS不让你直面物理内存, 它提供了
virtual memory 层.
http://static.duartes.org/img/blogPosts/kernelUserMemorySplit.pngcache
上图并不是说 kernel 要使用 1G 的物理内存, 而只是指 kernel 只有 1G 这一部分的 address space 可供映射物理内存.
Linux 中一个进程在虚拟内存中的经典布局为:
http://static.duartes.org/img/blogPosts/linuxFlexibleAddressSpaceLayout.pngcache
上图中 random stack offset 和 random mmap offset 等是随机值, 是为了防止恶意程序的, 因为如果 stack 的位置是固定的, 则恶意程序能够通过计算访问 stack, 库函数等.
略介绍该布局图各个部分的含义:
-
stack: 堆栈区, 一般程序员们都知道. Linux 中 "ulimit -s"
可以查看和设置堆栈最大值(RLIMIT_STACK), 当程序使用堆栈超过 RLIMIT_STACK 时,
会报
stack overflow 错误. -
memory mapping segment: 在这里 kernel 将硬盘文件的内容直接映射到内存, 任何程序都可以通过
mmap() 系统调用做这样的映射工作. Memory mapping 用在 file IO 中非常方便和高效,
因而被用于装载动态库. 用户也可以创建
匿名内存映射(anonymous memory mapping), 这样的映射没有对应的文件, 可以被用于映射程序数据. 在 Linux 中, 如果你通过 malloc() 请求一大块内存, C 库将创建一个匿名内存映射, 而不是使用 heap. 这里存在一个阈值 MMAP_THRESHOLD, 一般为 128 kb, 可以通过 mallopt() 调整. - heap: 提供 runtime 的内存分配, C 中可以通过 malloc() 家族申请 heap 内存, C++ 可以通过 new 关键字申请 heap 内存. heap 内存不足的时候, 可以通过 brk() 系统调用扩张.
- BSS segment: 用来存程序未初始化的 static 变量的. 这块内存是 anonymous 的.
- data segment: 存储初始化过的 static 变量和全局变量. 这块内存不是 anonymous 的, 它映射到程序二进制镜像的对应部分. 同时, 它是 private memory mapping 的, 意味着其变更不反应到硬盘文件.
- text segment: 只读区域, 映射程序的二进制镜像文件.
How The Kernel Manages Your Memory
了解了进程的虚拟地址空间内存布局之后, 我们来看看操作系统如何来管理内存.
Linux 中进程是 task_struct 结构的实例, 这个结构包含 struct mm_struct *mm 指针, 它管理上文提及的内存布局.
http://static.duartes.org/img/blogPosts/mm_struct.pngcache
mm 结构中还包含链表指针 struct
vm_area_struct *mmap, 这个指针对应的内存区域应该就是 memory mapping segment,
这个链表的每一项表示一个
http://static.duartes.org/img/blogPosts/memoryDescriptorAndMemoryAreas.pngcache
vm_area_struct
结构中包含指示读写权限的标志变量, 包含指示被映射文件路径的 vm_file. *mmap 链表由红黑树(mm_rb)管理,
以支持内核快速的查询.
cat /proc/pid_of_process/maps 则仅简单遍历该链表.
# vim test.cc
C^z to put it to background.
# ps aux | grep vim
root 18969 0.3 0.0 18288 3484 pts/3 T 19:19 0:00 vim test.cc
root 19114 0.0 0.0 4340 760 pts/3 S+ 19:20 0:00 grep vim
# cat /proc/18969/maps
00400000-005b8000 r-xp 00000000 08:01 213434 /bin/vim-normal // 共 3 行
007ca000-00915000 rw-p 00000000 00:00 0 [heap]
7f533930a000-7f533930e000 r-xp 00000000 08:01 106723 /lib64/libattr.so.1.1.0 // 共 4 行
7f533950f000-7f5339511000 r-xp 00000000 08:01 106781 /lib64/libdl-2.11.1.so // 共 4 行
7f5339713000-7f5339867000 r-xp 00000000 08:01 106798 /lib64/libc-2.11.1.so // 共 4 行
7f5339a6c000-7f5339a71000 rw-p 00000000 00:00 0
7f5339a71000-7f5339a78000 r-xp 00000000 08:01 106716 /lib64/libacl.so.1.1.0 // 共 4 行
7f5339c79000-7f5339cb7000 r-xp 00000000 08:01 106775 /lib64/libncurses.so.5.6 // 共 4 行
7f5339ec1000-7f5339f16000 r-xp 00000000 08:01 106790 /lib64/libm-2.11.1.so // 共 4 行
7f533a117000-7f533a136000 r-xp 00000000 08:01 106824 /lib64/ld-2.11.1.so
7f533a2a9000-7f533a2de000 r--s 00000000 08:08 385827 /var/run/nscd/passwd
7f533a2de000-7f533a31d000 r--p 00000000 08:09 5154103 /usr/lib/locale/en_US.utf8/LC_CTYPE
7f533a31d000-7f533a322000 rw-p 00000000 00:00 0
7f533a32d000-7f533a334000 r--s 00000000 08:09 32866478 /usr/lib64/gconv/gconv-modules.cache
7f533a334000-7f533a335000 rw-p 00000000 00:00 0
7f533a335000-7f533a336000 r--p 0001e000 08:01 106824 /lib64/ld-2.11.1.so
7f533a336000-7f533a337000 rw-p 0001f000 08:01 106824 /lib64/ld-2.11.1.so
7f533a337000-7f533a338000 rw-p 00000000 00:00 0
7fffba730000-7fffba745000 rw-p 00000000 00:00 0 [stack]
7fffba7ff000-7fffba800000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
memory pages
现在迎来另一个概念 "memory
page".
4GB 的虚拟地址空间被划分为 pages, 32 位的 x86 处理器支持 4KB, 2MB 和 4MB 的页大小. 一般
Linux 和 Windows 对于用户部分的虚拟地址空间都使用 4KB 的页大小, 则 3GB 的用户空间对应的页为:
http://static.duartes.org/img/blogPosts/pagedVirtualSpace.pngcache
正如前文所说,
虚拟内存和物理内存之间通过
http://static.duartes.org/img/blogPosts/x86PageTableEntry4KB.pngcache
物理地址空间被 kernel
划分为
http://static.duartes.org/img/blogPosts/physicalAddressSpace.pngcache
Linux 中 page frame 由
struct page 描述, 其中包含很多标志变量, 用以跟踪整个物理内存的状态.
物理内存的管理由
从虚拟内存到物理内存的映射, 可以由下面的简图展示:
http://static.duartes.org/img/blogPosts/heapMapped.pngcache
Page Cache, the Affair Between Memory and Files
OS 处理文件的时候面对两大难题: 1)
访问硬盘很慢; 2) the need to load file contents in physical memory once
and share the contents among programs.
page cache 机制用以解决这两个问题. 假设有一个程序 render 需要打开文件 scene.data, 并一次读取 512
bytes. kernel 处理流程图示:
http://static.duartes.org/img/blogPosts/readFromPageCache.pngcache
读取了 12KB 数据之后, render 的
heap 和对应的 page frames 图示:
http://static.duartes.org/img/blogPosts/nonMappedFileRead.pngcache
可以看到, 上图 page frames
中有几个 page cache 是重复的.
这是因为:
http://static.duartes.org/img/blogPosts/mappedFileRead.pngcache
当使用 file mapping 时, kernel 将程序的 virtual pages 直接映射到 page cache. file mapping 可以是 private 或 shared 的.
解答问题
回到本文开始的问题:
-
more 程序是否有自己的应用程序缓存(application buffer, user
buffer)
- more 程序位于 utils-linux 包中, 阅读其源代码, 发现它主要调用 C stdio 的 getc() 和 putchar(), 而 C 的标准 IO 库是有自己的缓存的, 我相信该缓存是属于我们这里说的 "user buffer", 或 "application buffer", 或"应用程序缓存".
-
more 将文件从硬盘读入到内存的什么地方?
- 根据上面 page cache 部分的描述, 文件应该是被读入到 page cache, 并从 page cache 拷贝到 user buffer.
-
page cache
是什么, 在这个过程中有什么用? - 此时该问题不言自明. 某些 page frames 用作 anonymous 内存, 某些 page frames 用作 page cache.
-
内存管理都是内核来做的?
- buddy memory allocation system. 应属于运行于内核态的代码.
-
more 的输出是直接从某内核内存区域输出到 stdout 的吗?
- 通过阅读代码, 应该不是这样的, 因为使用的是 getc() 和 putchar().
-
more 程序中是否会数据从内核 copy 到 userland? 有
system buffer 吗? -
应该是有的, page cache 到 user buffer. 此时还不知道 system buffer
是何物,
怀疑是否真有这个概念.
-
应该是有的, page cache 到 user buffer. 此时还不知道 system buffer
是何物,