Linux进程地址空间

进程地址空间

内核除了管理本身的内存之外,还必须管理用户空间中进程的内存,称这个内存为进程地址空间。系统中所有进程之间以虚拟方式共享内存。

1、地址空间

进程地址空间由进程可寻址的虚拟内存组成,内核允许进程使用这种虚拟内存中的地址。每个进程都有一个32位或者64位的平坦地址空间,空间大小取决于体系结构。一些操作系统提供了段地址空间。通常情况下,每个进程都有唯一的这种平坦地址空间,一个进程的地址空间与另一个进程的地址空间即使有相同的内存地址,实际上彼此互不相关,称这样的进程位线程。

进程只能访问有效内存区域内的内存地址。每个内存区域也具有相关权限。如果一个进程访问了不在有效范围的内存区域,或以不正确方式访问了有效地址,内核会终止该进程,并返回“段错误”。进程地址空间中的任何有效地址都只能位于唯一的区域,这些内存区域不能互相覆盖。在执行过程中,每个不同的内存片段都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等。

2、内存描述符

内核使用mm_struct结构体表示进程的地址空间。其中两个域mmapmm_rb这两个不同数据结构体描述的对象是相同的:该地址空间中的全部内存区域。但前者以链表形式存放而后者以红黑树形式存放。mmap结构体作为链表,能简单、高效地遍历所有元素;而mm_rb结构体作为红黑树,更适合搜索指定元素。

3、虚拟内存区域

内存区域由vm_area_struct结构体描述,在内核中也称作虚拟内存区域。该结构体描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,另外,相应的操作也都一致。

可以通过内存描述符中的mmapmm_rb域之一访问内存区域。这两个域各自独立地指向与内存描述符相关的全体内存区域对象。其实,它们包含完全相同的vm_area_struct结构体指针,仅仅组织方法不同。mmap域使用单独的链表链接所有的内存区域对象。每一个vm_area_struct结构体通过自身的vm_next被连入链表,所有的区域按地址增长的方向排序,mmap指向链表中第一个内存区域,链中最后一个结构体指针指向空。mm_rb使用红黑树连接所有的内存区域对象。mm_rb域指向红黑树根节点,地址空间中每一个vm_area_struct结构体通过自身的vm_rb连接到树中。链表用于需要遍历全部节点的时候,而红黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

4、操作内存区域

内核时常需要在某个内存区域上执行一些操作,比如某个指定地址是否包含在某个内存区域中。为了方便执行这类对于内存区域的操作,内核定义了很多辅助函数。

//找到一个给定的内存地址属于哪一个内存区域,该函数返回第一个包含addr或者首地址大于
//addr的内存区域,否则返回NULL
struct vm_area_struct * find_vma(struct mm_struct *mm, unsigned long addr);

//find_vma_prev()工作方式同find_vma(),但返回第一个小于addr的VMA, 
//pprev存放指向先于addr的VMA指针
struct vm_area_struct * find_vma_prev(struct mm_struct *mm, unsigned long addr,
                                      struct vm_area_struct **pprev);

//find_vma_intersection()函数返回第一个和指定地址区间相交的VMA
static inline struct vm_area_struct * 
find_vma_intersection(struct mm_struct *mm, unsigned long strat_addr, 
                      unsigned long end_addr);

5、创建及删除地址空间

内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新的VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,两个区间将合并为一个。如果不能合并,就需要创建一个VMA。在用户空间可以通过mmap()系统调用获取do_mmap()功能。

内核使用do_mummap()函数从特定的地址空间中删除指定地址区间。munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,和系统调用mmap()作用相反。

6、页表

内存映射就是将虚拟内存地址映射到物理地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址之间的映射关系。页表实际上存储在CPU的内存管理单元MMU中,这样处理器就可以直接通过硬件找出要访问的内存。

当进程访问的虚拟地址在页表中查询不到时,就会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

内存映射的最小单位是页,页的大小一般为4KB,每一次内存映射,都需要关联4KB或4KB整数倍的内存空间。4KB大小的页一个问题就是页表会很大,为了解决页表项过多的问题,提供了两种机制,多级页表和大页。

Linux用的是四级页表来管理内存页,虚拟地址被分为5个部分,前四个表项用于选择页,最后一个索引表示页内偏移。

大页,即比普通页更大的内存块,常见的大小有2MB和1GB。大页通常用在使用大量内存的进程上,如Oracle、DPDK等。

posted @   fallen_sky  阅读(260)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示