大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。 Jetbrains全系列IDE稳定放心使用
Linux 虚拟存储管理分析
摘 要: 本文通过解剖 Linux 操作系统的虚拟存储管理机制,说明了 Linux 虚拟存储的特点、虚拟存储器的实现方法,并基于 Linux Kernel Source 1.0 ,详细分析有关虚拟存诸管理的主要数据结构之间的关系。
关键字: Linux 操作系统 内存管理 虚拟存储
Linux操作系统是一种多用户多任务、支持多种平台的开源的类 Unix 操作系统,其支持多种平台,在服务端可与其它商用类 Unix 系统媲美,在客户端则向 Windows 系列发出了强有力的挑战。自 Linux 诞生以来,发展迅猛,已经受到了全球开源社区和许多商业科技巨头的大力支持以及政府、教育机构、科研单位的重视。本文介绍了 Linux 虚拟存储技术的特点,并结合操作系统原理和 Linux 内核源码,通过分析虚拟存储管理所需的主要数据结构及其相互关系,来更深入地了解 Linux 虚拟存储管理机制。本文所援引的 Linux 内核源代码版本为1.0 。
Linux 虚拟存储概述
虚拟存储器
在实存储器的管理模式中,要求作业在运行前全部装入内存,之后就一直驻留在内存中直到运行结束,其中某些程序并没有一直处于运行状态,却长期占用着内存资源,从而降低了内存的利用率,为此,引入了虚拟存储器。
虚拟存储器并不以物理的方式存在,而是从逻辑上对内存容量进行扩充,提供了一个比真实内存空间大得多的逻辑地址空间的逻辑存储器。虚拟存储技术把用户地址空间和实际的存储空间区分开,在程序运行时通过动态重定位的地址映射机制将逻辑地址转换为物理地址。
所谓的“动态重定位”是指在目标程序执行过程中,在 CPU 访问内存之前,由硬件地址映射机构来完成将要访问的指令或数据的逻辑地址向内存的物理地址的转换。由于这种地址转换是在程序执行期间随着每条指令的数据访问自动连续地进行,所以称为“动态重定向”。
在 386 保护模式下, Linux 可以提供的逻辑地址空间为 4GB 。 Linux 的全局描述符表 定义了如何分配逻辑地址空间:0GB – 3GB 分配为用户空间,用户进程可以直接访问;3GB – 4GB 分配为内核空间,用户进程不能访问。
段页式存储管理
Linux 中的内存管理技术采用的是段页式虚存技术。它将一个进程中的程序、数据、堆栈分成若干“段”来处理,每段有一个 8 字节的段描述符,指出该段的起始地址、长度和存取权限等信息,这些段描述符的集合构成段描述符表,通过一个寄存器指出该表的起始位置。为了便于段长的动态变化,每段分为若干页,将需要的内容以页面为单位调入内存的物理块中,暂不执行的页面仍留在外存,以保证比实际内存容量需求更大的进程能够正常使用内存。
Linux 的分段机制
Linux 的分段机制就是将线性地址空间分段,利用这些段来存储代码和数据,通过对段的保护来提供一种对数据或代码的保护。根据每个段的作用和存储内容的不同,分为三类进程段:代码段、数据段和堆栈段;两类系统段:TSS 段(任务状态段)和LDT 段(局部描述符表段)。
在保护模式下,逻辑地址空间可达 4GB 。从逻辑地址到线性地址的转换由分段机制管理。段寄存器 CS 、 DS 、 ES 、 SS 、 FS 或 GS 标识一个段。这些段寄存器作为段选择器,用来选择该段的描述符。
进程使用的是 48 位的逻辑地址,其中高 16 位是段选择符,低 32 位是段内的偏移量。通过段选择符在 GDT (全局描述符表)或 LDT (局部描述符表)中索引相应的段描述符,以得到该段的基地址,再加上偏移量得到逻辑地址对应的线性地址。然后通过分页地址的转换,将线性地址转换为物理地址,最后通过物理地址访问内存。
如图 1
所示,为分段逻辑地址到线性地址的转换图。
Linux 的分页机制
分页机制是在段机制之后进行的,它进一步将线性地址转换为物理地址。 Linux 一般使用 4K 字节大小的页( PAGE_SIZE , include/linux/page.h ,通过修改 PAGE_SHIFT 定义的左移位数来修改页面大小),且每页的起始地址都被 4K 整除。因此, Linux 把 4GB 的线性地址空间划分为 1M 个页面,采用了两级表结构。
两级表的第一级表称为页目录,存储在一个 4K 字节的页中,页目录表共有 1K 个表项,每个表项为 4 个字节,线性地址最高的 10 位( 22-31 位)用来产生第一级表索引,由该索引得到的表项中的内容定位了二级表中的一个表的地址,即下级页表所在的内存块号。
第二级表称为页表,存储在一个 4K 字节页中,它包含了 1K 字节的表项,每个表项包含了一个页的物理地址。二级页表由线性地址的中间 10 位( 12-21 位)进行索引,定位页表表项,获得页的物理地址。页物理地址的高 20 位与线性地址的低 12 位形成最后的物理地址。
如图 2
所示,为两级页表的地址转换图。
Linux 进程与段页式管理
每当启动一个新的进程, Linux 都为其创建一个进程控制块( task_struct , include/linux/sched.h )。在创建过程中,每个进程(根据需要)创建并初始化新页目录,设置页目录基地址寄存器,在 GDT 中添加进程对应的 TSS 项和 LDT 项,创建并初始化该进程的 LDT 。
Linux 采用“按需调页”的原则来分配内存页面,执行进程的页面总会在外存与内存之间不断交换,从而避免页表过多占用存储空间。创建一个进程时页面分配的情况大致是这样的:进程控制块( 1 页),内存态堆栈( 1 页),页目录( 1 页),页表(需要的 n 页)。在进程以后执行的执行中,再根据需要逐渐分配更多的内存页面。
Linux 交换空间
交换空间是在外存中开辟一定的空间来临时存放从内存中调出的页面,其存储区域自然也是按页划分的。 Linux 采用了块设备和交换文件两种形式来保存换出的页面,但是这两种形式的内部结构是一致的。有时候,为了优化系统性能,会定义不止 1 个交换空间,因而 Linux 实现了并行管理多个交换空间,这些交换空间均定义在同一个数据结构中( swap_info_struct[MAX_SWAPFILES] , mm/swap.c ,其中 MAX_SWAPFILES 定义为最大的交换空间数量)。
Linux 虚存管理的数据结构模型
Linux 虚存管理模型
根据虚拟内存抽象模型,每个进程都可以互不干扰的使用所有虚拟地址。进程的虚拟内存空间被划分为小的虚拟内存区域来使用。每个内存区域是一段具有相同属性的虚拟地址空间。 Linux 用 vm_area_struct ( include/linux/mm.h )来描述一个虚拟内存区域。一个进程的所有内存区域组织成一个双向链表。进程用了一个指向 vm_area_struct 链表的指针,来描述进程虚拟内存空间的一个区域,包括对该区域的起始和终止地址的描述。进程可以通过 vm_operation_struct ( include/linux/mm.h )对这些区域进行操作。
当加载关于进程虚拟地址空间的页面时,一系列的 vm_area_struct 将自动生成,每一个 vm_area_struct 描述进程的一部分,如执行代码、数据等。 Linux 支持了多数标准的虚拟内存操作,如读取、关闭、共享、缺页等。一旦 vm_area_struct 结构生成,就可以通过该结构中的指向 vm_operation_struct 的指针进行虚拟内存操作了。
如图 3
所示,为虚存管理数据结构之间的关系。
vm_area_struct
Linux 采用了虚拟存储区域的方式来管理虚拟存储空间,一个虚拟存储区域是某个进程的一段虚拟存储空间,该结构由 vm_are_struct 定义。
struct vm_area_struct {
struct task_struct * vm_task; /* VM area parameters */
/* 虚存区的起始地址 */
unsigned long vm_start;
/* 虚存区的终止地址 */
unsigned long vm_end;
/* 进程对应于虚存区的访问权限 */
unsigned short vm_page_prot;
/* linked list,在链表中指向下一个虚拟内存区域 */
struct vm_area_struct * vm_next;
/* linked list,在链表中指向共享区域 */
struct vm_area_struct * vm_share;
/* 指向虚存区所在文件的 inode 结构,若不涉及文件,则为 NULL */
struct inode * vm_inode;
/* 虚存区相对于文件或设备在 inode 结构中的偏移量 */
unsigned long vm_offset;
/* 指向 vm_operation_struct 结构 */
struct vm_operations_struct * vm_ops;
};
vm_operation_struct
Linux 对于虚存区的操作定义在 vm_operation_struct 数据结构中,通过在 vm_area_struct 结构中使用指针 vm_ops 来确定该虚存区可以进行的一系列操作。
struct vm_operations_struct {
/* 打开操作,当内核生成一个虚存区后或者当虚存区被复制后,就用该命令打开。 */
void (*open)(struct vm_area_struct * area);
/* 关闭操作,当内核销毁一个虚存区时,就调用该命令。 */
void (*close)(struct vm_area_struct * area);
/* 处理缺页异常,当进程访问一个不属于内存的有效页面时,就会调用该命令,
/ * 返回该页的物理地址。 */
void (*nopage)(int error_code,
struct vm_area_struct * area, unsigned long address);
/* 处理写保护异常,当往一个被保护的页面上写入数据时,就会调用该命令。 */
void (*wppage)(struct vm_area_struct * area, unsigned long address);
int (*share)(struct vm_area_struct * from, struct vm_area_struct * to, unsigned long address);
/* 取消映射操作,当内核取消虚存区的部分或者全部映射时,调用该命令;
/ * 当取消全部映射后,内核就会自动调用 close() 进行关闭操作。 */
int (*unmap)(struct vm_area_struct *area, unsigned long, size_t);
};
总结
Linux 是一个功能强大的实际的操作系统,相对于操作系统原理,每个技术环节都有其自身的特点。限于篇幅,本文仅简单介绍 Linux 在虚拟存储管理上的技术特点与实现的数据结构之间的关系,作为深入探讨 Linux 虚拟存储管理源码细节的入门性文章,希望能起到抛砖引玉的作用。
参考资料
((1) Andrew S.Tanenbaum , Modern Operating Systems(Second Edition) ,机械工业出版社, 2002
(2)Abhishek Nayani 、 Mel Gorman 、 Rodrigo S.de Castro , Memory Management in Linux , 2002
(3) 刘胤杰、岳浩等, Linux 操作系统教程,机械工业出版社, 2004
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/189420.html 原文链接:https://javaforall.cn
【正版授权,激活自己账号】:
Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】:
官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...