基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文

第一章绪论

1.1 概述

为什么学习操作系统?这是一个的所有操作原理课上都会提及的一个问题:因为学习操作系统可以:

  • 学习一种思维、设计方式
  • 学会如何将事物进行抽象,使得计算机似乎有了一种魔力:
  • “无限”的 CPUs
  • “无限”的内存

这种无限的CPUs和无限的内存,得益现代操作系统的进程分时和虚拟内

存技术,这些技术很值得去进行透彻的学习研究。而要透彻的学习一个事物,总要结合抽象的原理和具体的实例,两者缺一不可。那么,在学习了操作系统原理之后,如何针对实例,用所学的原理进行实例的分析,把原理运用到实现细节上的研究?这就是本课题要解决的问题。

“UNIX系统非常成功。在写出这些文字的时候,全世界已经有超过3000 个UNIX系统在运行使用!”这是 S.R.Bourne 先生 1983 年说的一句话。时至今日,已有成千上万的 UNIX 系统运行在各个应用层次,在所有 UNIX 系统中, Solaris 以其强大的功能和健壮的稳定性而深受企业用户的青睐。同时,Solaris 还提供两款强大的操作系统内核监察工具: Dynamic Tracing (DTrace)和 Modular Debugger(MDB),为本文深入研究 Solaris 内核并在此基础上从不同的角度设计操作系统实验提供了可行性。

在 Solaris 系统中,虚拟内存系统是最为核心的系统,因为随着 Solaris 技术的不断完善,Solaris 虚拟内存系统日趋复杂,融合了多种技术,最成功的一种技术的应用便是应用面向对象的层层抽象,Solaris 最终把进程模型、文件系统和设备管理,都和虚拟内存系统结合到了一起,构成了 Solaris 内核结构,使得 Solaris 自身更为统一、简单、灵活、高效。

可见,设计并实现基于 Solaris 的虚拟内存实验,不仅对研究者,更是对未

来的学习者,都将是一件充满挑战性、又极富创造性,既有很强的研究价值,又有重要的现实意义的课题。

本文是几个月来本课题研究的总结,颇具创造性的运用DTrace、MDB工具,

并结合内核源码阅读,从不同的角度提出并实现了一些独特的实验设计方案,对 Solaris 内核结构、主要是虚拟内存技术有一个较为深入而全面研究与总结。

1.2 课题的研究现状

对于操作系统原理的实验设计与实现,在国内外,已经有很多各种类型、基于各种操作系统平台的实验。其中尤以Windows和Linux平台上的实验为最,前者是因为应用的广泛,后者是因为源码的开放。

由于 Solaris 大多是运行在服务器上,并且之前没有完全开源,因此,基于

Solaris 系统上的操作系统实验至今还没有完整的设计与实现。

虽然基于 Solaris 的操作系统实验还没有较为完整的设计与实现,但对于 DTrace和MDB工具的使用,已经有了相当的文档和资料——虽然这些文档资料大都是英文的。因此,可以说,这个课题是一种挑战,一种对新事物,新工具应用和设计的挑战;但更多的,应该说,这更是一种机会,一种研究设计新事物、并走在时代浪尖的机会。

归结起来,基于 Solaris 的操作系统实验设计现状和现拥有的条件,可以这

样描述:正是因为 Solaris 在服务器领域的广泛应用,和现在的源码完全开放,加上其提供的两款内核跟踪查看工具,使得基于 Solaris 的操作系统实验设计与实现变得有挑战性、又有可行性;既是研究工作,又具实际意义。更多资料请参阅文献[1] 、[6] 。

1.3 课题的来源和所做的工作

本课题是Open Solaris 开源项目的一部分。

所做的工作主要是研究理解 Solaris 内核结构,阅读分析 Solaris 内核源码,学习并创造性的运用DTrace和MDB工具,并在此基础上,从不同的角度设计并实现出基于 Solaris 的操作系统实验的虚拟内存管理部分,以期通过这些实验,能帮助学习和研究者能更好更快的理解操作系统原理、理解 Solaris 对于虚拟内存技术的实现。

1.4 本文的组织结构和相关说明

本文正文部分共四章:

第一章为绪论,主要是概括性的阐述课题的目的、意义、研究现状以及课题的来源、所做的工作等等内容。

第二章是基本理论简介,为后面的主体部分“操作系统的设计与实现”提供相关的理论基础,本章力求简练,对 Solaris 虚拟内存管理、DTrace和MDB 工具都只做了一些概述性的阐述。

第三章是本文的重点,全章内容占了本文的三分之二,也是本课题所做的主要工作的体现。全章一共设计并实现了四个实验,每个实验都从多个不同的角度设计并实现了不同的方案。有关本章实验的组织安排和内容阐述在3.1节都有更为详细的说明。

第四章是本文正文的最后一章,主要是写本课题所取得的成绩、不足、完善、和建议。

紧接着是致谢与参考文献,最后是附录,由于在做课题时拿到的文档资料大都是英文的,因此,为了自己也为了后续研究者的方便,我做了比较多的翻译工作,附录里的只是一部分,更多的关于《DTrace动态跟踪指南》的翻译我已经做成电子书附在电子文档里。

本课题所编写的所有脚本程序、命令系列和涉及到的源码文件、实验结果等文件都可以在提交的电子文档中,文件的注释请见相关的说明 readme 文件。

第二章基本理论简介

2.1  Solaris 虚拟内存管理概述
2.1.1 虚拟内存技术概述

操作系统似乎具有一种魔力,他使得一个计算机系统似乎有“无限”的内存。操作系统的这种魔力来源于一项让人激动人心的技术——虚拟内存技术!虚拟内存技术的核心思想是利用大容量的外储存器的实际储存地址扩充为“虚拟内存”的逻辑地址,产生一个比有限的实际内存空间大得多的、逻辑的虚拟内存空间,从而可以使程序大小大于实际内存可用储存空间的进程也可以运行,突破了内存大小限制可运行程序大小的瓶颈,大大增强了系统的处理能力。

在获得“无限”的内存空间的同时,虚拟内存技术还利用“程序局部性原理”,即只把当前执行或马上需要的代码、数据装入内存,而不用全部,从而使得在获得巨大内存空间的同时,又几乎不影响进程运行的速度。

虚拟内存技术由于其成熟性和高效性,如今已广泛应用于各种操作系统的

储存策略之中。早期的UNIX版本仅使用可变划分,而未使用虚拟储存技术,后来随着技术的逐步发展和成熟,现今的Linux和 Solaris 都采用分页式虚拟储存管理。

随着面向对象技术的发展,虚拟内存技术也逐步融合了这一技术。采用段式管理,段本来是硬件体系结构的“发明”,而随着虚存技术的发展,段更成了虚拟内存最重要的一个对象,在 Solaris 系统中,段的对象更使得进程、文件系统和虚拟内存系统很好的融合到了一起。关于段在 Solaris 中的重要地位,在

3.4.4小节有较为详细的论述。

2.1.2   Solaris 虚拟内存技术

虚拟内存技术可以认为是 Solaris 最为核心的系统,他把进程系统和文件系统,都和虚拟内存系统结合到了一起,构建了 Solaris 内核系统。

2-

1

Solaris

内核组件关系图

从图 2-1 可以看出,虚拟内存系统建立在硬件 HAT 结构和设备驱动之上,同时为文件系统、进程调度、以及各种资源管理子系统提供统一的抽象层。

这种抽象层使得虚拟内存系统在 Solaris 中处于核心地位,而它的建立有依赖于三个重要的对象:段、虚结点和页(segments, vnodes, and pages)。

页是物理内存的分割单位。在 Solaris x86 32位系统中,Solaris 把内存分成4K的大小的页来实行内存的管理。每个物理内存与一个物理页号相对应,其与虚拟地址的映射管理由HAT硬件来实现。

段是虚拟地址空间的逻辑单位。Solaris 系统中有各种段,seg_vn段(与进程相关的段都属于此类)、seg_kmem、seg_map等等,每一种段都有自己的段驱动程序,实现着自己的各种功能。

虚结点vnode是一种新的虚文件对象,他实质上是物理内存中的一个数据结构。Virtual Node(vnode)和Virtual File System(VFS)结合起来,使得所有的文件和文件系统的管理都有了统一的接口,而他们只需根据文件和文件系统的类型调用不同的文件系统功能。如图2-2所示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第1张图片

图 2-2 Solaris 中 VFS/Vnode 体系

段、虚结点和页构成了 Solaris 虚拟内存的核心。将 Solaris 将内存管理代

码和数据结构分为平台无关和平台相关部分。页管理平台相关部分位于HAT层,平台无关的部分通过vnode和段相关联。Solaris 的进程地址空间由逻辑段组成,而这些段通过vnode和物理页相映射,为进程提供统一的地址空间。Solaris 以段、虚结点和页这三个对象为核心,形成了 Solaris 虚存管理的层层抽象,最终构架出了整个虚拟内存系统。Solaris 虚拟内存层次结构如图2-3所示。关于段、页和vnode的关系在本文的主体部分第三章还会有更多的阐述。

2-

3

Solaris

虚拟内存层次结构图

从概述中可以看到,Solaris 虚拟内存技术的实现非常复杂,涉及到许多的其他

内核组件和大量的数据结构。要理解 Solaris 虚拟内存技术,需要理解内核结构,需要阅读内核源码,还需要 DTrace 和 MDB 这两款内核跟踪查看工具,更需要结合这几种方式,设计并实现出不同角度的操作系统实验——这正是本课题所有解决的主题。

2.2  DTrace 简介

Dtrace(Dynamic Tracing 动态跟踪技术)是一项基于 Solaris™操作系统的综合动态跟踪构架,它提供一个强大的工具,以允许管理员、开发人员和维护人员精确的解决系统和用户程序行为所遇到的任何问题。在软件开发系统中使用该工具是安全的,且不必重启系统或应用程序。

在本课题中,主要使用 Dtrace 工具来跟踪内核的某些重要模块的实现。

本节仅对 DTrace 的使用做一个简要的阐述。关于 DTrace 的详细阐述请参

考[2] 。DTrace 动态地修改操作系统内核和用户进程,以记录特别位置的数据,称为 probe(探针)。probe 就是位置或活动,DTrace 可将其与请求绑定在一起,以执行一组活动,如记录堆栈追踪、时间戳或函数的实参。probe 就像遍布于 Solaris 系统中各个感兴趣位置的可编程传感器。DTrace probe 来自一组称为

“provider”的内核模块,每个模块都执行某种特殊方法的来创建 probe。

一种称为“D”的脚本语言,专门为动态追踪而设计的。使用 D 语言,很容易编写启动 probe、收集信息及处理信息的脚本。DTrace 的组件如图 2-4 所示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第2张图片

图 2-4 DTrace 组件结构图

D 语言语法是与 C 相似,由 probe 描述、predicate 和 action 组成: prode description

/predicate/{actions}

在执行D脚本时,在probe description中描述的probe就被启动了。当

probe被激活且predicate计算为真时,就会执行动作语句。

2.3  MDB 简介

MDB(Modular Debugger模块化调试器) 是一个新的可扩展公用程序,用于活动操作系统、操作系统故障转储、用户进程、用户进程信息转储以及对象文件的低级调试和编辑。有了MDB,便可以动态观察内核的状态、设置断点,修改内核数据。图2-5是MDB的架构图:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第3张图片

图 2-5 MDB 架构图

本课题使用MDB工具,主要用来查看系统内核的各种数据结构和执行状态。

像 DTrace 一样,本小节只对 MDB 做一个简要的阐述,详细资料请参阅[3] 。在 shell 中输入#MDB –k便可以对启动MDB来查看内核数据。MDB的语法结构

是:[address][,count] command [modifiers],address:默认为当前地址,默认为十六进制。count是指command执行的次数,默认是1次;command可以是各种命令,如“/”中得到地址处的内核内容,=是进行地址计算。modifiers 是一些修改选项。如 x 表示 16 位,C 表示按字符打印。

为了保持论文的完整性和可读性,凡在本文第一次涉及到MDB和DTrace

命令时,都会做较为详细的注释,以方便论文的阅读和理解。

第三章基于 Solaris 的虚拟内存实验设计与实现

3.1 本章内容结构与实验的组织安排
3.1.1 本章内容概述

UNIX的最初建立是为解决两个问题:一是进程,一是文件。而随着 Solaris 技术的不断完善,进程模型和文件系统都应用了先进的OO技术,建立在了一个统一的对象上,这个对象就是虚拟内存。

Solaris 虚拟内存系统使得进程可以运行、编码在一个统一的地址空间,而不用考虑实际物理内存的大小。可以给应用程序一个简便的内存编程模式,以使得开发人员不需要知道底层内在相关的硬件是如何安排的。

本章的主要内容是,在 Solaris 的开源之机,阅读 Solaris 源码,并结合运

用 Solaris 提供的两款强大的跟踪调试工具DTrace、MDB,用实验的形式,对 Solaris 操作系统的虚拟内存管理模式、组织、实现和性能分析做一个详细的解剖,以加深对操作系统原理,尤其是 Solaris 系统的虚拟内存技术有一个全新的认识,最终达到深入理解操作系统原理的目的。

3.1.2 本章组织结构

本章的组织结构:要研究一个操作系统的虚拟内存管理,可以从下面几个

角度进行考虑:(四个角度分别对应四个实验)

  1. 从进程的角度。从一个程序员编写应用程序所用的虚拟地址所在的虚拟空间开始,跟踪查看 Solaris 进程虚拟地址空间的结构及实现。
  2. 从操作系统内核角度。结合特定的体系结构,(本实验用的是 Intel x86 32 位体系)来看操作系统是如何实现对虚存和物理内存的有效管理与映射。
  3. 从进程运行角度。进程的运行是建立在不断的解析指令,而指令所涉及的虚存对应的物理内存又不在主存中,从而发生页故障。因此,分析进程运行时所产生的页故障及其处理的实现,也是理解虚存技术的关键。 4) 从系统宏观的角度。在已经对 Solaris 虚存管理和实现有比较深入的了解

和认识之后,跳出这些微观的实现细节,对 Solaris 的宏观信息,如物理内存,进程虚拟内存、扫描速度、页交换等进行整体的统计和性能分析。

最后一节,是根据四个实验,从三个不同的角度,总结 Solaris 虚拟内存的特点。

同时,为了使得本文的逻辑更为严密统一,分析更为透彻有有条理,在每个实验中,都会下列小节:

问题描述与分析——提出具体问题,分析问题,为具体的设计方案的提出多个角度、思路上的分析。

实验设计与方案——根据上小节的对问题的不同角度的分析,提出相应方案来解决问题,主要包括如何利用 DTrace 来怎样编写相应脚本,如何使用 MDB 命令系列来查看结果,以及 DTrace 和 MDB 如何结合起来,才能更有效的解决提出的问题。

实验的实现与步骤——根据已经提出的实验方案,对实验进行逐步详细的实现,并取得数据用来分析总结。

结果分析与总结——分析实验结果,结合源码阅读、内核分析,对问题进行最终的解决和总结。

实验的完善与扩展——由于时间有限,加上只是第一次接触 Solais 系统,需要阅读大量的相关资料文档(这些文档大多都是英文的),每个实验都不可能做到面面俱到,本小节主要是提出尚待解决的问题,和还可以利用的角度和方法,并总结我自己目前对这个解决这个大问题的经验,以待后续更深更全面的研究。

3.2 跟踪查看 Solaris 进程虚空间的结构及实现
3.2.1 问题描述与分析

从一个程序设计者的角度看,他不关心底层物理内存地址的排列,只知道对一个虚拟地址(在32位机上,就是一个32位的虚地址)进行编程,那么,操作系统是如何组织这“庞大”的地址空间(在32位系统中,这个地址空间是

4G)?在程序执行时,即做为一个进程运行时,又是如何创建和维护这个地址空间的?

从“算法与数据结构”的角度和原理上看,要跟踪一个系统进程虚拟空间的结构(数据结构及其组织关系)及实现(算法实现),可以从两个大的方面来进行:

一是从数据结构的组织和相互包含、引用关系入手,看虚拟空间结构;二是从函数调用的角度,来分析是如何“动态”生成和实现地址空间的。

想从这两个角度获得操作内核的数据,在一般的操作系统中,都几乎是不

可能的。幸运的是,在 Solaris 系统中,提供了与之相对应的两款强大的工具,来支持对操作系统内核的深入跟踪与查看。

与这两个角度相对应,本实验结合MDB和DTrace工具来进行:即,用MDB 工具来层层追踪 Solaris 虚拟内存实现所涉及到的一些关键数据结构;同时,用

DTrace工具来对其中的一些数据结构和技术是如何实现进行跟踪。

3.2.2 实验设计与方案

和上面“问题分析”中两个角度相对应,本实验采取下以解决方案:

方案一:利用MDB查看数据结构

利用MDB工具,进入进程内核,从proc结构开始跟踪,进入地址空间结构as,探询 Solaris 地址空间的管理。再从as出发,对Solars段结构进行查看,并主要查看vnode的段驱动程序seg_vn的实现和匿名内存结构的实现。在下小节“实现步骤”中,有详细的命令系列和注释。本方案对应着“实现步骤”小节中的1)、2)、3)、4)、5)步骤。

方案二:利用 DTrace 跟踪地址空间创建

利用DTrace工具,跟踪进程创建的详细过程,并对创建时as结构的建立,

多个关键函数之间的调用、以及参数之间的传递做一个跟踪、记录和分析。本方案与步骤6)相对应。

方案三:结合 MDB 与 DTrace 分析匿名段

同时利用DTrace和MDB工具,对 Solaris 系统中的一种最重要的段驱动 segvn(与进程相关的所有的段都是由此种段驱动产生)所产生的内存映像文件段的数据结构及其实现做一个实例分析,本方案与步骤4)(结构)、7)(实现)相对应。

3.2.3 实验实现与步骤

1): 查看进程结构:proc

启动MDB后,可以利用::ps来查看所有进程的入口地址,在其中选择 mdb进程来做为示例,结果如下:(为了实验的方便和论文的简洁,删除了一些不关心的结果,同时把重要的数据用灰色底纹标出,并在一些关键结果后做了相关的注释):

[root@David /]#mdb -k

> ::ps !grep mdb

d55a2040

                     R      1063      1062      1063      1062                     0 0x42004000mdb

/*d55a2040为进程的入口*/

> d55a2040::print struct proc

{ p_exec = 0xd5e1ca80

p_as = 0xd5af5a28  /*这是地址空间as结构的地址*/ p_lockp = 0xd3993300

………

::print命令打印出进程的proc结构,在本实验中,最为关心的是地址空间as数据结构,下面将利用p_as这个地址来对as 结构进行详细观察。

2): 查看虚存地址空间结构:as

利用上面的as 指针,打印出as结构的内容,使用下面命令:

> d55a2040::print proc_t p_as

                     p_as = 0xd5af5a28                   /*这个地址和上面proc结构中的p_as相同*/

> 0xd5af5a28::print struct as

{

……

a_hat = 0xd7baaf18

a_userlimit = 0xd2800000

a_seglast

= 0xd5d7fca0 a_size = 0x135f000 a_segtree = {……

}

}

从这个结构的数据中可以看出:as 结构有一个 AVL 树(a_segtree)来维护进程的所有段,而这些段正是 Solaris 虚拟内存逻辑结构的基本对象

其中有一个 a_seglast 成员,是指向所有段中的最后一个段的。可以对

他进行考察:

> d5d7fca0::print struct seg {

s_base = 0xd1080000

s_size = 0x8000

s_as = 0xd5af5a28

s_tree = {

avl_child = [ 0, 0 ]

avl_parent = 0xd5d7fc24

avl_child_index = 0 avl_balance = 0 }

s_ops = segvn_ops

s_data = 0xd5d7eca0

}

可以看到该段中含有一个指向 as 结构的指针和指向 avl 树中的父结点指针。可以沿着他的父指针来在 avl 树中寻找和遍历进程所有的段。

a_segtree = { avl_root = 0xd5c753f4

当然,也可以从入手,但和上面一

样,这也牵涉到 avl 树的数据结构问题。

为了更方便直接的研究 Solaris 进程的段,还可以用下面两条指令:

> d55a2040::pmap

/* d55a2040 为前面已查到的进程入口地址*/

             SEG              BASE

SIZE

RES PATH

d5ee0b00 08044000

16k

16k [ anon ]

d5f3eb70 08050000

352k

/usr/bin/i86/mdb

                               d7c30eb8 080b8000            16k               8k /usr/bin/i86/mdb

d6921510 080bc000

8384k

8352k [ anon ]

                               d5d7fca0 d1080000            32k            32k [ anon ]

或者还可以利用 walk 来查找 seg 结构:

> d55a2040::print proc_t p_as |::walk seg |::seg

                               d5ee0b00    8044000          4000 d5f31b18 segvn_ops

d5f3eb70

8050000

58000 d5f3db18 segvn_ops

                               d7c30eb8    80b8000          4000 d7c31a00 segvn_ops

选择d5f3eb70的segvn段和d6921510的匿名段进行下面的实验:

3): 查看段结构:seg

d5f3eb70::print struct    seg

>

{ s_base = 0x8050000 s_as = 0xd5af5a28 s_tree = {

}

s_ops = segvn_ops

s_data = 0xd5f3db18

}

内存的“段”对象管理是一个面对对象技术渗入内存管理的典型例子。这种技术允许内存和设备都映像到虚拟内存地址空间,从而有了统一的接口,且对象结构可以允许不同的段有不同的行为。这行为都被封装为一组函数,而段结构中的 s_ops 便是指向这些函数的入口。

这个段中另外一个重要的成员数据是 s_data,它指向一个特定段的数据结构,这个数据结构包含了一个段的大部分信息,包括类型、页、vnode 信息等等。下面集中到这个数据成员来对 vnode 段进行更深入的研究:

4): 查看 segvn_data 结构和 vnode 结构

> 0xd5f3db18::print struct    segvn_data

{

offset = 0

vp = 0xd5e1ca80

anon_index = 0 amp = 0

vpage = 0

}

每个段都分了很多个连续的逻辑虚拟页,和不连续的物理页相联系,这些物理页都统一组织在一个 vnode 对象上。而这里的 vp 便是指向这个 vnode 对象的指针;如果这是一个匿名段,那么也会通过 amp 指针最终指向一个 vnode 结构。

下面利用这个指针查看 vnode 结构数据:

> d5e1ca80::print struct vnode

{ v_data = 0xd5e1d598 v_pages = 0xfddc6e10

…………

v_path = 0xd5ce0e78 "/usr/bin/i86/mdb"

} >

d5f3eb70 08050000 352k

v_path 成员说明这个 v node 是映射着执行文件"/usr/bin/i86/mdb",这与前面在“查看虚存地址空间数据结构:as(address space)”小节里使用命令:“> d55a2040::pmap ”查看的结果“

/usr/bin/i86/mdb”是一致的。

这是一个代码段,它映射到一个具体的文件系统中的可执行文件,

一旦创建了文件的内存映像,文件系统将处理绝大部分映射文件的工作。而 vnode 的段驱动程序 seg_vn 的功能则相当简单——绝大部分都是在创建和删除映像期间进行。

在 Solaris 系统中,还有另一种重要的段——匿名段,组织着大量不与文件虚结点 vnode 直接相关联的页的段,堆、栈和写入时拷贝都要用到匿名内存。既然没有 vnode 直接和他们关联,那他们是如何把虚拟地址空间映射到物理地址空间的?

5): 查看匿名段的 anon 结构

d6921510

080bc000

8384k

8352k [ anon ]

在前面在“查看虚存地址空间数据结构:as(address space)”小节里使用命令:“> d55a2040::pmap ”在查看的结果中选择这个段:

来作为研究匿名内存实现的入口。

> d6921510::print struct seg

{

s_as = 0xd5af5a28

……

s_data = 0xd691f7c0

}

进入 segvn_data 数据结构:

> d691f7c0::print struct segvn_data

{ offset = 0 vp = 0

anon_index = 0

amp = 0xd69231e0

}

在匿名段的 s_data 数据中,vp 和 offset 均为 0,表明没有直接的 vnode 与之相关联,如何解决匿名内存段的虚拟内存页和物理内存映射的问题,并和代码段等非匿名段形成一种统一的接口?

Solaris 用了另外一个指针成员来解决这个问题,即 amp 指针,当且

amp =

0xd69231e0

),进入 struct

> d69231e0::print struct anon_map

仅当为匿名段时, amp 非空。利用指向匿名映像的指针(

anon_map 结构:

{ size = 0x20000000

ahp = 0xd4ef4e48

swresv = 0 refcnt = 0x1

}

其中(struct anon_hdr *)ahp 是指向匿名内存数组的头指针,继续往下

看:

> 0xd4ef4e48::print struct anon_hdr

{

size = 0x20000 array_chunk = 0xd4d2fa00

flags = 0

}

array_chunk= 0xd4d2fa00

是匿名内存数组,选择其中的第一个数据,却

数组的入口地址进行跟踪:

>    0xd4d2fa00::print struct anon

{ an_vp = 0xd5ec7000

an_pvp = 0xd7c24000 an_off = 0xd5ece000

an_poff = 0

}

这时有两对数据结构,一对 an_vp 和 an_off,一对是 an_pvp 和 an_poff。

如果前者不为 0,则表明这个匿名段在物理内存中,如果后者不为 0,则表明这个匿名段已经被交换到交换设备。顺着 vp 找到 vnode,并可以查看其中的内容:

> d5ec7000::print struct vnode

{

v_path = 0xd7bf8c80

至此,已经用MDB对 Solaris 虚存的实现所涉及到的一些关键数据结构进行

了层层深入的跟踪研究。剩下的工作就是利用这些实验得来的数据,配合一些图表和源码的阅读,来对 Solaris 虚存的实现来做一个系统的总结,这个工作将会在下个小节“结果分析与总结”中详细讨论。

6): 跟踪进程地址空间的创建

  • 要跟踪一个函数所做的工作,最直接的办法莫过于跟踪他调用的函数,

而 Solaris 内核提供的函数都含有一个 entry 和一个 return 探针,利用这

些探针,便可以完成跟踪任务。

  • 这个脚本很简单,除了利用了 entry 和 return 探针外,还利用了一个变量self->in,用来控制只匹配并激活cfork函数间的函数探针。由于

是本文的第一个 D 脚本,有必要做些简单的分析解释:

/* 有了这行,就可以不用在终端显式的调用dtrace命令来执行*/

#!/usr/sbin/dtrace –s

/*这个选项使得DTrace的输出体现出函数调用关系*/

#pragma D option flowindent cfork:entry

                            /execname == $$1/            /*用户可以指定进程名*/

{

printf("fork start:\n");

self->in = 1; /* self->in = 1;时激活的probe都是在cfork函数中,否则不被激活*/

} cfork:return /self->in == 1/

{

self->in = 0;  /*“关闭”开关,并退出*/ exit(0);

}

/*激活cfork内的所有函数入口探针和返回探针*/ entry /self->in == 1/

{ } return /self->in == 1/

{

}

  • 将上述脚本保存为 fork.d 文件,同时修改其属性为可执行,再在终端

#./fork.d bash >fork.txt,

运行以下命令;结果输入到 fork.txt 文件中,以

便分析。

  • 下面是文件中保存的运行结果:(删除了一些不关心的结果)
    1. -> cfork fork start
    2. | cfork:entry

                           0        -> getproc

                           0        <- getproc

1

-> as_dup

                           0            -> as_alloc

1

<- as_alloc

1

-> seg_alloc

1

<- seg_alloc

                           0              -> segvn_dup

                           0              <- segvn_dup

0

-> anonmap_alloc

0

<- anonmap_alloc

                           0                -> anon_dup

                           0                <- anon_dup

                           0            <- segvn_dup

                           0            -> hat_dup

    1. <- hat_dup
    2. <-as_dup

                           0        -> forklwp

    1. <- forklwp
    2. <- cfork

7): 跟踪内存文件映像的建立

  • 步骤 4)仅对一个 segvn 段的数据结构进行了查看,但如果想知道这个段是如何创建的,则需要更多的研究。
  • 对于文件,Solaris 操作系统支持调用 mmap 系统调用来把文件映像到进程地址空间。下面结合使用 DTrace 工具来编写跟踪脚本,来对其中的细节做一个粗略的分析。
  • 这个 D 脚本和步骤 6 中的很相似,详细文件见提交的电子文档,下面是运行结果:
  • 运行结果:(删除了一些不关心的结果),对运行结果的分析请见 3.2.4

./traceMMap.d

mdb

的步骤 5)。运行命令:

                                                       1      | mmap:entry

                                                       1        -> smmap32

                                                       1            -> smmap_common

                                                       1                -> fop_map

                                                       1                    -> lo_map

                                                       1                        -> fop_map

-> ufs_map

1

                                                       1                                -> map_addr

                                                       1                                    -> map_addr_proc

                                                       1                                        -> as_gap

                                                       1                                        <- as_gap

                                                       1                                    <- map_addr_proc

                                                       1                                <- map_addr

1

-> as_map

1

-> seg_alloc

 

1

<- seg_alloc

 

1

-> segvn_create

 

                                                       1                                        -> hat_map

                                                       1                                        <- hat_map

                                                       1                                        -> fop_addmap

                                                       1                                        <- fop_addmap

1

<- segvn_create

1

<- as_map

1

<- ufs_map

                                                       1                        <- fop_map

                                                       1                    <- lo_map

                                                       1                <- fop_map

                                                       1        <- smmap32

                                                       1    <= mmap

3.2.4 结果分析与总结

1)Solaris 进程虚拟地址空间结构从上面实验步骤 1)、 2)、 3)中对 proc、as 和 seg 的数据结构的跟踪察看实验可以看出:Solaris 进程中虚存的这几个关键的地址空间数据结构的

关系如图 3-1 所示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第4张图片

图 3-1 Solaris 进程虚拟地址空间结构 as 示意图

其中,段的更为详细的结构图为图 3-2 所示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第5张图片

图 3-2 Solaris 段的接口示意图

2): Solaris 段与 vnode 的关系

从上面步骤 4)实验数据,可得到 Solaris 非匿名 segvn 段与 vnode 的关系图

3-3 所示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第6张图片

图 3-3 Solaris 中非匿名 seg_vn 段与 Vnode 关系图

3): 匿名内存段与 vnode

和其他的非匿名 segvn 段不同,匿名内存是指那些不直接和 vnode 关联的页。这些页主要用作进程的堆、栈和写入时拷贝页。从上面的 MDB 数据结构跟踪中可以发现:Solaris 匿名内存的实现分了两层:anon 层 和 swapfs 文件系统。最后最终要指向物理内存中的 vnode 或者是交换空间中的 vnode (这各区别在步骤 5)中有说明),他们的结构关系可以用图 3-4 来表示:

基于Solaris的操作系统实验设计与实现-含程序源代码、论文、实验数据1、实验脚本、实验命令和实验结果文件夹:内含所有实验的实验脚本、实验命令、实验结果。2、四个实验分别在不同的文件夹中,每个文_第7张图片

图 3-4 Solaris 匿名内存段数据结构图

图 3-4:Solaris 匿名内存段数据结构图

4): 虚拟内存地址空间 as 结构的建立

分析步骤6)中所得的数据,可以得到一个进程在被创建时,是如何建立as 结构的。

  • 首先是 cfork 的参数:cfork(isvfork=0,isfork1=1),说明这个调用不是 vfork,后者是 fork 的一个变种,他产生的子进程的地址空间与父进程完全一致,即不进行任何段和代码的复制。
  • fork 的第一步,是获取合法的进程号 pid,这是一个有限的资源,在 getproc 函数中完成。
  • 紧接着,进行 as 结构的复制,这是进程中内存相关的复制中最主要的部分,其后便是页表的复制。
  • 在 as 结构的复制中,工作是这样进行的,先分配一个 as 结构,再遍历整个父进程的 as 结构,把其中的每个段的内容都复制过去。当然不同的段会使用不同的复制函数,在保留的结果中,以 segvn 段中的匿名段为例:可以看到,段的复制和 as 的复制相似,也是先分配,成功后一页一页的复制。
  • 当所有的段和页表都复制完毕,一个新进程的地址空间结构 as 便这样创建成功了。

5): vnode 段的内存文件映像的建立

  • 分析步骤 7)中的 Dtrace 脚本执行的结果,可以看出,Solaris 函数在进行文件映射时,主要是调用 mmap 这个系统调用来进行,这个系统调用做的工作是根据这个调用的参数,来确定是文件内存映射,于是调用 ufs_map 函数,在 ufs_map 函数中,主要是通过 as_map()函数来完成映射。但这个函数又不知调用哪个段驱动来完成段的创建,怎么办呢?从上面可以看出,他调用了 segvn_create()函数来完成任务,而且采用了一个技巧,即用 segvn_create()函数指针做为参数,(这样才可以在 as_map() 函数中,根据参数的不同来调用不同段驱动提供的 segvn_create()函数),这个技巧最重要的是利用了一个专门的数据结构来完成两者的参数传递。这就是 struct segvn_crargs * argsp。
  • 通过源码阅读,可以看到 as_map 的函数原型是:int as_map(struct as *as, caddr_t addr, size_t size, int (*crfp)(), void *argsp)
  • 也就是说:当一个文件要被 mmap()映像到地址空间时,地址空间映射函数 as_map()把 segvn_create()(vnode 段驱动程序的构造器)函数指针作为参数进行调用。而 segvn_create 反过来又调入 seg_vn 段驱动程序以创建映射。其中的关键是一个数据结构 struct segvn_crargs * argsp,传入的 argsp 指针,在 segvn_create 函数中会被解释成 struct segvn_crargs * 来完成这种特殊的参数传递(struct segvn_crargs *a = (struct segvn_crargs

*)argsp;这行代码在 segvn_create 函数中)

另外,还可以利用这个 DTrace 脚本所跟踪到的地址,再结合 MDB 来查看相关的数据结构的变化:要映射的段的虚节点内容为:(这里可看到要映射的文件的路径

你可能感兴趣的:(linux,运维,服务器)