【IT168 技术文档】
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之前,可以做以下的一些事情忽略那些不需要的部分,使编译工作完成的更快一些:
修改<oskit/dev/linux_ethernet.h>,使其只包括需要的行。除了可以让编译速度更快以外,对于某些不兼容的硬件,也需要这样做,以防止死机。
修改<oskit/dev/linux_scsi.h>,使其只包括需要的驱动程序。
修改<oskit/fs/linux_filesystem.h>,使其只包含需要的文件系统。
修改源码根目录下的GNUmakefile,设定SUBDIRS,使其只有需要的目录。只有在对OSKit有了一些使用经验之后并且知道你需要哪些目录以后,才可以这样做。直接在GNUmakefile.in中指定subdirs也可以达到这样的目的,那样做的好处是防止以后再次进行配置时覆盖曾经做过的修改。
设定环境变量CFLAGS为-O1 -pipe。这会让编译器在编译时所做的优化工作比较少,并且在编译器不把临时文件写到内存虚拟的文件系统时提高编译的速度。
OSKIT的编译
目前,编译OSKit需要以下的工具:
1.GNU make
2.GNU CC (gcc) 2.95.2版。
3.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_作为前缀。)
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 [<boot-opts and env-vars> -]<args to main>
注意,如果没有"-"的话,所有的参数都将被传递给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中的变量conserials_com_port来控制。
-d:激活通过串口使用GDB,具体使用的串口由libkern中的base_console.c中的变量conserials_com_port来控制。这个串口可以不同于控制台的串口,实际上最好是这样。
-p:激活剖析功能。操作系统的内核必须是编译成为支持剖析功能的。
-k:打开killswitch支持,这允许向第二个串口上传送字符以中止内核的运行。
-f:当使用一个串行的控制台时,使其工作在115200的速率而不是缺省的9600。这是Utah的扩展而并不在BSD之中。
这些参数都是以BSD 为中心指定的,这是因为在犹它大学OSKIT的设计者通常都是用FreeBSD的引导程序引导内核。此外,如果使用了NetBoot引导程序,在oskit_bootargv中还会有另一个参数:
-retaddr address:这个参数指定一个OSKit可以跳到并且将控制权还给NetBoot的物理内存地址。Libkern中的baserials_console.c中的缺省的_exit函数在退出时使用这个值。
OSKit的执行环境
OSkit中的许多部件在核心和用户方式下都可以使用,这就需要对部件的执行环境作出定义,例如部件什么时候可以嵌套进入等。此外,OSKIT使用了许多其它操作系统的代码,例如设备驱动程序和网络协议栈,都是原封不动的从原有的核心如BSD和Linux中借用来的,OSKIT通过附加代码模拟原始执行环境使得这些执行模块比它们原始执行环境更简单,用户也不需要详细了解原执行环境的细节。下面对OSKIT的每种执行模块进行简单的介绍。
纯执行模块。这是OSKIT执行环境中最简单的模块。这些部件或函数中没有全局变量或静态变量,只使用和处理目标环境传递的数据。例如函数strlen,它只通过目标环境传递给它的字符串指针求出字符串的长度,并将其返回,它只接触参数数据域而不影响目标环境。当这些函数使用的数据集是分离的,它们可以安全地同时被调用,而不需要同步,反之则不行。例如对重叠的目标缓冲区并发使用memcpy调用是不安全的。
非纯执行模块。这些模块中使用了全局变量或有可能改变全局共享状态,例如liboskit_kern(核心支持库)中的许多函数建立和访问全局处理器寄存器和数据结构,因此它们是非纯执行模块。非纯执行模块有以下特点:
* 非纯函数和部件可能依赖于全局状态,如全局变量、静态变量、处理器中特殊寄存器等。
* 除非有明确的声明,非纯函数和部件是不可重入的,并且运用在多线程系统中也是不安全的。为了在一个多线程/多处理器环境中使用这些函数和部件,目标操作系统必须提供适当的同步代码。
阻塞模块。它扩展了非纯模块以支持非抢占的多线程,这些模块中有一类可重入的函数称为阻塞函数,在这模块中,除非明确声明为非阻塞函数,否则函数是阻塞的。为了在一个可抢占的、可中断的或者多处理器的环境中使用阻塞模块,必须在进入模块前加锁,在退出模块时将锁释放。
可中断阻塞模块。在它之中每个部件都是一个单线程的执行域,在一个给定的时刻,只有一个(虚拟的或者物理的)CPU可以执行部件中的代码。例如:在一个多处理器系统中,在进程级,任意时刻在一个部件集内只有一个CPU被允许执行;这能够通过在部件前后放置全局锁来实现。
此外,OSKit的执行环境还有以下特点:
在一段时间内,部件中可以存在多个活动进程,但在某时刻只有一个进程被执行。
目标操作系统给每个活动进程提供一个独立堆栈,这个堆栈在阻塞函数运行时被保留。只有在操作完成后,对部件的调用返回时才放弃该堆栈。
部件中的代码总是运行在两个级别中之一,进程级或中断级。有意思的是一些部件的函数和方法只能在进程级被调用,而另一些只能在中断级被调用,还有的能在任何级别被调用。调用的细节属于接口描述的一部分。
部件中无论进程级或中断级的操作都能被部件中的中断处理程序中断,除非代码调用osenv_intr_disable屏蔽了中断。
当部件在进程级运行时,OSKIT假定中断开放,部件在处理过程中可能临时屏蔽掉中断,但必须在返回到目标操作系统前重新激活。同样,当部件在中断级运行时,OSKIT假定中断被屏蔽,但是部件可以在目标操作系统允许其它中断级别的活动中断该部件时重新激活中断。
当目标操作系统在一个部件内中断一个进程级的活动时,在继续这个活动前,操作系统必须执行完这个中断级别的活动。同理,若一个中断级的活动被中断,那么最近的中断级别的活动必须在继续前一个中断级别的活动之前完成。
部件中运行在中断级别的代码不能调用目标操作系统提供的阻塞回调函数;只有非阻塞的回调函数能够在中断级别被调用。
