一步一图带你深入理解 Linux 虚拟内存管理(11)


一步一图带你深入理解 Linux 虚拟内存管理

文章插图
还有我们在 《从 Linux 内核角度看 IO 模型的演变》一文中介绍到,socket 相关的操作接口定义在 inet_stream_ops 函数集合中 , 负责对上给用户提供接口 。而 socket 与内核协议栈之间的操作接口定义在 struct sock 中的 sk_prot 指针上,这里指向 tcp_prot 协议操作函数集合 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
对 socket 发起的系统 IO 调用时,在内核中首先会调用 socket 的文件结构 struct file 中的 file_operations 文件操作集合 , 然后调用 struct socket 中的 ops 指向的 inet_stream_opssocket 操作函数,最终调用到 struct sock 中 sk_prot 指针指向的 tcp_prot 内核协议栈操作函数接口集合 。
5.7 虚拟内存区域在内核中是如何被组织的在上一小节中,我们介绍了内核中用来表示虚拟内存区域 VMA 的结构体 struct vm_area_struct ,并详细为大家剖析了 struct vm_area_struct 中的一些重要的关键属性 。
现在我们已经熟悉了这些虚拟内存区域,那么接下来的问题就是在内核中这些虚拟内存区域是如何被组织的呢?
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
我们继续来到 struct vm_area_struct 结构中,来看一下与组织结构相关的一些属性:
struct vm_area_struct { struct vm_area_struct *vm_next, *vm_prev; struct rb_node vm_rb;struct list_head anon_vma_chain; struct mm_struct *vm_mm; /* The address space we belong to. */unsigned long vm_start;/* Our start address within vm_mm. */unsigned long vm_end;/* The first byte after our end addresswithin vm_mm. *//** Access permissions of this VMA.*/pgprot_t vm_page_prot;unsigned long vm_flags;struct anon_vma *anon_vma;/* Serialized by page_table_lock */struct file * vm_file;/* File we map to (can be NULL). */unsigned long vm_pgoff;/* Offset (within vm_file) in PAGE_SIZEunits */void * vm_private_data;/* was vm_pte (shared mem) *//* Function pointers to deal with this struct. */const struct vm_operations_struct *vm_ops;}在内核中其实是通过一个 struct vm_area_struct 结构的双向链表将虚拟内存空间中的这些虚拟内存区域 VMA 串联起来的 。
vm_area_struct 结构中的 vm_next ,vm_prev 指针分别指向 VMA 节点所在双向链表中的后继节点和前驱节点 , 内核中的这个 VMA 双向链表是有顺序的,所有 VMA 节点按照低地址到高地址的增长方向排序 。
双向链表中的最后一个 VMA 节点的 vm_next 指针指向 NULL,双向链表的头指针存储在内存描述符 struct mm_struct 结构中的 mmap 中,正是这个 mmap 串联起了整个虚拟内存空间中的虚拟内存区域 。
struct mm_struct {struct vm_area_struct *mmap;/* list of VMAs */}在每个虚拟内存区域 VMA 中又通过 struct vm_area_struct 中的 vm_mm 指针指向了所属的虚拟内存空间 mm_struct 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
我们可以通过 cat /proc/pid/maps 或者 pmap pid 查看进程的虚拟内存空间布局以及其中包含的所有内存区域 。这两个命令背后的实现原理就是通过遍历内核中的这个 vm_area_struct 双向链表获取的 。
内核中关于这些虚拟内存区域的操作除了遍历之外还有许多需要根据特定虚拟内存地址在虚拟内存空间中查找特定的虚拟内存区域 。
尤其在进程虚拟内存空间中包含的内存区域 VMA 比较多的情况下,使用红黑树查找特定虚拟内存区域的时间复杂度是 O( logN ) ,可以显著减少查找所需的时间 。
所以在内核中,同样的内存区域 vm_area_struct 会有两种组织形式,一种是双向链表用于高效的遍历,另一种就是红黑树用于高效的查找 。
每个 VMA 区域都是红黑树中的一个节点 , 通过 struct vm_area_struct 结构中的 vm_rb 将自己连接到红黑树中 。
而红黑树中的根节点存储在内存描述符 struct mm_struct 中的 mm_rb 中:
struct mm_struct {struct rb_root mm_rb;}
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
6. 程序编译后的二进制文件如何映射到虚拟内存空间中经过前边这么多小节的内容介绍,现在我们已经熟悉了进程虚拟内存空间的布局,以及内核如何管理这些虚拟内存区域,并对进程的虚拟内存空间有了一个完整全面的认识 。
现在我们再来回到最初的起点,进程的虚拟内存空间 mm_struct 以及这些虚拟内存区域 vm_area_struct 是如何被创建并初始化的呢?
一步一图带你深入理解 Linux 虚拟内存管理

推荐阅读