OSKit的部件对象模型 第一章
作 者: 洪宁
导师:陈朔鹰
第一章 OSKIT介绍
1.1 OSKIT概述
OSKIT 是美国犹它大学计算机科学系FLUX研究组编写的一套用于架构操作系统内核、服务器和其他OS级软件的框架及模块化的部件和库程序。OSKIT的编写者认为,操作系统中有很大一部分模块是系统必须的,但并不是开发者所感兴趣的,例如系统装入模块,各种标准驱动模块等。使用OSKIT的目的就是使操作系统的开发者集中精力开发他们操作系统中有特色的,或者他们感兴趣的部分,而不必考虑一些繁琐而乏味的细节。为了达到这个目的,OSKit在设计时借用了COM的思想,把操作系统的各个部分设计成尽量独立的COM模块,以方便操作系统的开发者使用或替换。因此,当开发人员使用这套工具时,可以把它当作一个完整的操作系统来使用,也可以根据需要使用其中的一部分,它还可以作为一套动态链接库,由操作系统及支持程序对它进行调用。
1.2 OSKIT的组成
OSKIT把给开发者提供的功能分为三个部分:接口、函数库和部件库。
OSKIT的接口非常清晰,而且是面向对象的,它采用了部件对象模型(COM)的架构 ,在文章的后续部分会对OSKIT中的COM接口的组织和结构进行详细的讨论。
OSKIT的函数库使用传统的C语言,并采用面向函数的方式向用户提供服务。例如基本的C库函数,这些函数易于使用和控制,目标操作系统能很方便的使用其中的某个函数,而且各个函数之间的依赖关系已被缩减至最小。这些函数库中很少使用OSKit自己的COM接口,取而代之的是在普通的C头文件中定义面向函数的接口。这种设计策略为操作系统设计人员提供了最大程度的支持和灵活性,以适合任何特殊的操作系统环境。
下面是OSKIT目前提供的主要函数库列表,以及它们的简介:
liboskit_c:一个简单的最小限度的C库,它在一个受限制的OS环境中提供了通用的C库支持,并使对环境和其他模块的依赖达到最小限度。在需要一个更完整的、类POSIX的C函数库的情况下,可以使用FreeBSD的C函数库,甚至是用自己编写的函数库来代替这个最小化的C库。
liboskit_kern:建立一个基本的操作系统内核运行环境所需的内核支持代码,该函数库为陷入和中断等提供缺省的处理程序。这个库包括很多在写核心代码时非常有用的通用工具,如访问某个CPU寄存器,建立并维护页表,在不同的处理器模式间转换等。
liboskit_smp:内核支持代码,这个库可建立一个便于操作系统使用的多处理器系统环境。
liboskit_com:处理COM接口的工具函数和一套通用的包装部件。
liboskit_dev:这个库提供了驱动程序和由其它操作系统引入的部件(如网络、文件系统)在OSKit中使用时所需要的转换函数的缺省实现,这些函数工作在OSKit驱动程序框架之中。这些函数的缺省实现是为使用了liboskit_kern库的简单内核所设计的,开发者在必要时可以重写部分或全部函数。
OSKIT的部件库提供了一个一般情况下更高层的功能,这种功能以一种更加标准、精心修整的、面向对象的\"黑盒\"方式设计。尽管OSKit的\"部件\"缺省情况下也是被打包成为普通的链接库,但它们的结构则是被设计成面向对象的,而不是传统的面向函数的方式。与OSKit的函数库相比,部件库通常只对外开放一些相关的公用调用接口,而不是大量的功能。例如,在Linux和BSD驱动程序部件库,每一个完整的驱动程序仅实现成为一个单独的函数调用,这个调用用来初始化并注册驱动程序。目标系统通过OSKit的面向对象的COM接口来与这些部件进行交互,这允许很多部件及部件的实例共存,并以操作系统开发人员定义的方式交互。这种设计的策略有利于将已有系统(如BSD和Linux)中的代码并入OSKit,那时,隐藏原有环境的细节要比提供灵活性更重要。
下面是目前OSKit所提供的主要部件库的概要。
liboskit_posix:增加对用POSIX构造的系统通常会实现的系统调用的支持。例如:open、read和write等。这些POSIX操作被映射到相应的OSKit COM接口上。最小化的C库和FreeBSD C库都依赖POSIX库来提供所需要的系统级操作。
liboskit_freebsd_c:源于FreeBSD的完整的类POSIX C库,提供了对单线程和多线程的支持。在需要支持类POSIX函数或线程安全时,这个库可以用来替换最小化的C库。
liboskit_freebsd_m:完整的标准数学库。可以提供浮点数支持。
liboskit_fsnamespace:文件系统名字空间库,为应用程序提供了命名接口,可以将多种部件的绝对和相对路径名转换成为oskit_file和oskit_dir的COM对象。
liboskit_rtld:运行时链接、装入库,允许ELF格式的OSKit内核装入共享库。
liboskit_lmm:一个灵活的存储管理库,它可以管理物理内存或虚拟内存。
liboskit_amm:地址映射管理库,用于管理系统资源。
liboskit_svm:简单虚存库,它使用AMM 库定义了一个简单的用于单独地址空间的虚存接口,提供内存保护和为块设备如一个硬盘分区进行分页。
liboskit_threads:这个库提供了多线程的内核支持,包括POSIX线程、同步、调度和栈保护。
liboskit_diskpart:一个能够识别多种常见的磁盘分区方案并生成一个完整映射的通用库。这个库提供了一个简单的方法让操作系统可以找到它所\"感兴趣的\"磁盘分区。
liboskit_fsread:一个简单只读文件系统解释库,它可用于不同的文件系统,包括BSD FFS、Linux ext2fs和MINIX的文件系统。它通常与liboskit_diskpart一起使用,为操作系统提供从硬盘或软盘上读取程序和数据的功能。这个库也可用于构造装入程序。
liboskit_exec:一个通用的可执行程序解释器和装入器,它支持流行的可执行程序格式,如a.out和ELF。
liboskit_linux_fs:对Linux内核2.0.29版的文件系统部分代码进行包装。
liboskit_netbsd_fs:对NetBSD 1.2版的文件系统部分代码进行包装。
liboskit_memfs:一个简单的基于内存的文件系统,对外提供了标准的OSKit文件系统接口。
liboskit_freebsd_net:对FreeBSD 2.1.7.1版网络代码的包装。
liboskit_linux_dev:对Linux内核2.0.29版的设备驱动程序进行包装。将其包装在OSKit的设备驱动架构中。
liboskit_freebsd_dev:对FreeBSD 2.1.7.1版的设备驱动程序进行包装。将其包装在OSKit的设备驱动架构中。
1.3 OSKIT的设计原则
尽管当前OSKIT的目标平台是X86体系,但将来它会被移植到其它的体系,如StrongArm。
OSKIT的接口被尽可能地设计成便于移植。尽管很大一部分的OSKit代码是与平台相关的,但它很多的功能接口都是通用的,并且可以在很多其它的体系结构或平台上使用。例如,OSKit的设备驱动程序接口是完全可以移植的。
OSKIT在每一个库中记录模块间的依赖信息。对于函数库,这意味着要标明不同的函数间的依赖关系;对于部件库,这意味着要标明每一个库中不同部件集之间的依赖关系。
1.4 OSKIT的配置
OSKit遵从GNU的配置、编译和安装习惯:可以参考源代码根目录中的INSTALL文件,在该文件中有使用GNU配置文件的通用方法。简单的说,需要运行OSKit根目录中的配置文件,这个文件将会估计出当前系统的类型以及各种所需工具(如C编译器)的位置。OSKit可以配置成在它自己的目录中编译,只要在OSKit源码的根目录中用./configure命令就可以了。或者可以将OSKit编译并安装到一个单独的目标目录中,如果需要这样,只要在那个新的目录中运行配置文件就可以了。例如,当源文件存在NFS分区上时,使用一个单独的目标目录,可让你把目标文件放在本地的硬盘上。
此外,OSKit的配置还可以保留多份(当然是在不同的目录中),每一套配置有自己的目标文件,但共享一份代码。
当需要为另一种体系结构对OSKit进行交叉编译时,需要指定宿主机(需要运行OSKit的机器)的类型和编译机(编译OSKit的机器)的类型,这可以用configure中的-build=machine和-host=machine选项来实现。由于OSKit是一个单独的软件包,并且不使用任何它自己以外的包含文件和库文件,因此宿主机的操作系统部件与OSKit的配置并没有直接的关联。然而,在配置脚本运行时会需要以对宿主机的指定作为一个前缀来寻找各种交叉编译工具。例如,如果指定\"-host=i486-linux\",配置脚本会去寻找名为i486-inux-gcc、i486-linux-ar和i486-linux-ld等的编译工具。在其它工具中,具体使用哪一个工具,由目标文件的格式来决定(如ix86-linux-*工具可以建立ELF格式,而ix86-mach-*工具建立a.out格式)。
OSKit的配置脚本支持很多基本的选项,要得到一份完整的选项列表,可以运行./configure -help。除了这些基本的命令之外,配置脚本还支持下面的这些OSKit特有的选项:
-enable-debug:在编译时使用这个选项,将会把调试信息包含进来,这会影响一些运行的性能,但当错误有可能发生时,会提高检测错误的速度。
-enable-profiling:生成剖析版的OSKit库,为了与习惯的标准保持一致,剖析版的库将会有-p作为后缀。
-disable-asserts:不使用assert()调用
-enable-unixexamples:生成在Unix的用户态下运行OSKit的一些部件的支持代码和例子程序,目前支持FreeBSD和Linux。
-enable-doc:在编译时生成OSKit的文档。这样做除了要花费很多时间以外,还需要LaTex和dvips。已经生成的.ps和.html格式的文档在源码目录中。
在开始实际编译OSKit之前,可以做以下的一些事情忽略那些不需要的部分,使编译工作完成的更快一些:
修改,使其只包括需要的行。除了可以让编译速度更快以外,对于某些不兼容的硬件,也需要这样做,以防止死机。
修改,使其只包括需要的驱动程序。
修改,使其只包含需要的文件系统。
修改源码根目录下的GNUmakefile,设定SUBDIRS,使其只有需要的目录。只有在对OSKit有了一些使用经验之后并且知道你需要哪些目录以后,才可以这样做。直接在GNUmakefile.in中指定subdirs也可以达到这样的目的,那样做的好处是防止以后再次进行配置时覆盖曾经做过的修改。
设定环境变量CFLAGS为-O1 -pipe。这会让编译器在编译时所做的优化工作比较少,并且在编译器不把临时文件写到内存虚拟的文件系统时提高编译的速度。
1.5 OSKIT的编译
目前,编译OSKit需要以下的工具:
GNU make
GNU CC (gcc) 2.95.2版。较新版本的gcc和egcs应当也可以使用,但没有测试过。
GNU binutils 2.8.x或2.9.1 with BFD 2.9.1。
要编译OSKit,可以到源码的根目录(或者是用户指定的独立的目标目录)中运行GNU make(在Linux系统上是make,在BSD系统上是gmake)。请注意OSKit需要GNU make,不能用其它的make工具来代替它。为了防止混淆,OSKit的makefile都命名为GNUmakefile,而不是Makefile,这样的话,当用户错误地运行了其它的make工具时,它将报告找不到makefile的错误,而不是一些其它的错误。
要想只编译或重新编译OSKit的一部分代码(例如某个库),只需进入放置该部分的目标目录,然后在该目录中运行GNU make。根目录中的GNUmakefile实际上不做任何事情,只是在其它各个目录中递归调用make命令。一些OSKit目录的编译需要其它的部分首先被编译,例如核心的例子在它们需要的OSKit库被编译之前是无法编译的。但OSKit的大部分库都可以在完全不需要其它库的情况下被编译。
当OSKit编译好以后,用户可以用\"make install\"命令来安装它。缺省的情况下,这些库会被安装到/usr/local/lib中,而头文件会被安装到/usr/local/include中,除非用户在配置时使用了-prefix选项来指定目标目录。所有的OSKit头文件都安装在oskit/子目录中(例如/usr/local/include/oskit),这使它们不会和已经存在的任何头文件冲突。即使是OSKit库被安装在主库的目录中(如/usr/local/lib),所有的OSKit库文件都有前缀oskit_,这可避免与其它不相关的库相混淆。例如,OSKit的最小化C库命名为liboskit_c.a,而不是libc.a,这使用户可以在同样的目录中安装一个实际的C库。
标准的make 变量如CFLAGS和LDFLAGS被OSKit的Build工具所使用,但是在OSKit的makefile中没有定义,它们可以在命令行上被使用。例如,你可以用命令\"make CFLAGS=\"-save-temps\"\"来使GCC把它编译过程中产生的中间文件留在目标目录中(我们内部规定,OSKit的makefile中的变量都以OSKIT_作为前缀。)
1.6 OSKIT的使用
要使用OSKit,可以在开发核心时使用OSKit提供的部件库、函数库,并在编译时将所使用的库链接到核心映象中。在OSKit的examples目录下有许多的例子核心,开发者可以通过查看以及运行这些核心来学习OSKit中各种库和映象Makefiels的编写方法。
OSKit中的库都进行了很好的设计,用户可以根据需要替换其中的任何一个库,由于在OSKit的文档中对每个库与其它库的依赖关系进行了详细的说明,因此用户可以很轻松的替换掉某个部件,而不会带来麻烦。实际上,在很多情况下,特别是在某些函数库中,为了有效的使用OSKit,是必须要替换掉其中的一些函数或者符号的。要重载某个库中的函数或者符号,只要在核心或应用程序中再定义一遍就可以了,链接程序会确保去使用所定义的函数和符号。OSKIT设计者强烈建议使用链接的方法去替换掉OSKit的某个部件,而不是直接修改OSKit的源码(除了修改源码中的Bug)。保持自己的核心和OSKit分开,会使升级到新版本的OSKit更加容易。
当完成核心的编写工作后,使用gcc中的-c选项把核心源码编译成一个或多个.o文件,然后创建Gnumakerules文件,把写好的.o文件与核心中使用的其它库文件按gcc的规范写入这个文件中。运行make,这些文件会被链接成一个核心。
启动核心有几种方法:当OSKit配置成在Linux或其它的使用ELF可执行文件结构的主机上使用时,就可以使用OSKIT提供的mklinuximage 将核心制作成一个标准的可以从LILO或其它Linux引导程序引导的内核映象。当OSKit配置成在Mach或BSD系统上工作时,就可以使用OSKIT提供的mkbsdimage建立一个a.out格式的映象,这个映象可以从任何BSD或Mach的引导程序引导。需要注意的是mkbsdimage 需要GNU ld能够正常的工作,在BSD系统上,通常都是不使用GNU的ld的,用户必须要自己手动编译并安装GNU的ld。当目标机以DOS 为基础(如i386-msdos或i386-moss)时,就要使用mkdosimage。和mkbsdimage一样,mkdosimage也需要GNU的ld,必须要首先安装GNU的ld,并且将其配置成为可以交叉编译MS-DOS的文件,然后mkdosimage才能正常的工作。此外,OSKIT还提供了mkmbimage脚本,与其它的脚本不同,它不会做任何的转换,它只是允许将内核与其它的文件合成一个MultiBoot(有关Multiboot的问题,请见第五章)映象,这个映象可以和MultiBoot引导程序(如GRUB和NetBoot)一起使用。如果在你的系统上安装了Perl,就可以用一个同样功能的程序mkmb2来代替它,这个程序工作的更快一些。它的用处和mkmbimage是一样的。
当用OSKIT制作的核心启动时可以附加命令行参数,不同的引导程序可以将它们转化成自己的命令行格式,OSKIT对命令行参数规定了格式:
progname [ -]
注意,如果没有\"-\"的话,所有的参数都将被传递给main函数。
缺省的OSKit Multiboot启动代码会把这些字符串转换成为C风格的argv/argc参数,环境变量数组,和记录在全局变量oskit_bootargv/osit_bootargc中的启动参数。
argv/argc和环境变量数组会传递给main程序,后者通常作为一个名为envp的第三参数。oskit_bootargv/oskit_bootargc中的引导参数会被缺省的OSKit控制台启动代码解释,并且下面的标志有着特殊的意义:
-h:使用串口作为控制台,参考-f标志。具体使用的串口由libkern中的base_console.c中的变量cons_com_port来控制。
-d:激活通过串口使用GDB,具体使用的串口由libkern中的base_console.c中的变量cons_com_port来控制。这个串口可以不同于控制台的串口,实际上最好是这样。
-p:激活剖析功能。操作系统的内核必须是编译成为支持剖析功能的。
-k:打开killswitch支持,这允许向第二个串口上传送字符以中止内核的运行。
-f:当使用一个串行的控制台时,使其工作在115200的速率而不是缺省的9600。这是Utah的扩展而并不在BSD之中。
这些参数都是以BSD 为中心指定的,这是因为在犹它大学OSKIT的设计者通常都是用FreeBSD的引导程序引导内核。
此外,如果使用了NetBoot引导程序,在oskit_bootargv中还会有另一个参数:
-retaddr address
这个参数指定一个OSKit可以跳到并且将控制权还给NetBoot的物理内存地址。Libkern中的bas_console.c中的缺省的_exit函数在退出时使用这个值。
1.7 OSKit的执行环境
OSkit中的许多部件在核心和用户方式下都可以使用,这就需要对部件的执行环境作出定义,例如部件什么时候可以嵌套进入等。此外,OSKIT使用了许多其它操作系统的代码,例如设备驱动程序和网络协议栈,都是原封不动的从原有的核心如BSD和Linux中借用来的,OSKIT通过附加代码模拟原始执行环境使得这些执行模块比它们原始执行环境更简单,用户也不需要详细了解原执行环境的细节。下面对OSKIT的每种执行模块进行简单的介绍。
纯执行模块。这是OSKIT执行环境中最简单的模块。这些部件或函数中没有全局变量或静态变量,只使用和处理目标环境传递的数据。例如函数strlen,它只通过目标环境传递给它的字符串指针求出字符串的长度,并将其返回,它只接触参数数据域而不影响目标环境。当这些函数使用的数据集是分离的,它们可以安全地同时被调用,而不需要同步,反之则不行。例如对重叠的目标缓冲区并发使用memcpy调用是不安全的。
非纯执行模块。这些模块中使用了全局变量或有可能改变全局共享状态,例如liboskit_kern(核心支持库)中的许多函数建立和访问全局处理器寄存器和数据结构,因此它们是非纯执行模块。非纯执行模块有以下特点:
* 非纯函数和部件可能依赖于全局状态,如全局变量、静态变量、处理器中特殊寄存器等。
* 除非有明确的声明,非纯函数和部件是不可重入的,并且运用在多线程系统中也是不安全的。为了在一个多线程/多处理器环境中使用这些函数和部件,目标操作系统必须提供适当的同步代码。
阻塞模块。它扩展了非纯模块以支持非抢占的多线程,这些模块中有一类可重入的函数称为阻塞函数,在这模块中,除非明确声明为非阻塞函数,否则函数是阻塞的。为了在一个可抢占的、可中断的或者多处理器的环境中使用阻塞模块,必须在进入模块前加锁,在退出模块时将锁释放。
可中断阻塞模块。在它之中每个部件都是一个单线程的执行域,在一个给定的时刻,只有一个(虚拟的或者物理的)CPU可以执行部件中的代码。例如:在一个多处理器系统中,在进程级,任意时刻在一个部件集内只有一个CPU被允许执行;这能够通过在部件前后放置全局锁来实现。
此外,OSKit的执行环境还有以下特点:
在一段时间内,部件中可以存在多个活动进程,但在某时刻只有一个进程被执行。
目标操作系统给每个活动进程提供一个独立堆栈,这个堆栈在阻塞函数运行时被保留。只有在操作完成后,对部件的调用返回时才放弃该堆栈。
部件中的代码总是运行在两个级别中之一,进程级或中断级。有意思的是一些部件的函数和方法只能在进程级被调用,而另一些只能在中断级被调用,还有的能在任何级别被调用。调用的细节属于接口描述的一部分。
部件中无论进程级或中断级的操作都能被部件中的中断处理程序中断,除非代码调用osenv_intr_disable屏蔽了中断。
当部件在进程级运行时,OSKIT假定中断开放,部件在处理过程中可能临时屏蔽掉中断,但必须在返回到目标操作系统前重新激活。同样,当部件在中断级运行时,OSKIT假定中断被屏蔽,但是部件可以在目标操作系统允许其它中断级别的活动中断该部件时重新激活中断。
当目标操作系统在一个部件内中断一个进程级的活动时,在继续这个活动前,操作系统必须执行完这个中断级别的活动。同理,若一个中断级的活动被中断,那么最近的中断级别的活动必须在继续前一个中断级别的活动之前完成。
部件中运行在中断级别的代码不能调用目标操作系统提供的阻塞回调函数;只有非阻塞的回调函数能够在中断级别被调用。
发布人:Crystal 来自:蓝森林