logo资料库

linux操作系统论文.doc

第1页 / 共6页
第2页 / 共6页
第3页 / 共6页
第4页 / 共6页
第5页 / 共6页
第6页 / 共6页
资料共6页,全文预览结束
从九十年代初诞生至今,各种各样的 linux 发行版本让我们充分领略了它的魅力,也极大地享受到了它给学习工作带来 的方便。一个优秀的操作系统离不开一个优秀的内核。在世界各地的成千上万的自由软件爱好者的共同努力下,linux 的内核不断地完善,功能越来越丰富,因而粉丝团也正以指数级的速度增长,那么,现在就让我们来分析一下其“灵魂” ——内核。 一.内核体系结构 Linux 内核主要由 5 个模块构成,它们分别是:进程调度模块、内存管理模块、文件系统模块、进程 间通信模块和网络接口模块。 进程调度模块用来负责控制进程对 CPU 资源的使用。所采取的调度策略是各进程能够公平合理地访 问 CPU,同时保证内核能及时地执行硬件操作。内存管理模块用于确保所有进程能够安全地共享机器主内 存区,同时,内存管理模块还支持虚拟内存管理方式,使得 Linux 支持进程使用比实际内存空间更多大的 内存容量。并可以利用文件系统把暂时不用的内存数据块会被交换到外部存储设备上去,当需要时再交换 回来。文件系统模块用于支持对外部设备的驱动和存储。虚拟文件系统模块通过向所有的外部存储设备提 供一个通用的文件接口,隐藏了各种硬件设备的不同细节。从而提供并支持与其它操作系统兼容的多种文 件系统格式。进程间通信模块子系统用于支持多种进程间的信息交换方式。网络接口模块提供对多种网络 通信标准的访问并支持许多网络硬件。 结构如下图:
其中内核级中的几个方框,除了硬件控制方框以外,其它粗线方框分别对应内核源代码的目录组织结 构。 除了这些图中已经给出的依赖关系以外,所有这些模块还会依赖于内核中的通用资源。这些资源包括内核所 有子系统都会调用的内存分配和收回函数、打印警告或出错信息函数以及一些系统调试函数。 二.内核对进程的控制 程序是一个可执行的文件,而进程(process)是一个执行中的程序实例。在 Linux 操作系统上同时可 以执行多个进程。系统除了第一个进程是“手工”建立以外,其余的都是进程使用系统调用 fork 创建的新进程,被 创建的进程称为子进程(child process),创建者,则称为父进程(parent process)。内核程序使用进程标识号(process ID,pid)来标识每个进程。进程由可执行的指令代码、数据和堆栈区组成。进程中的代码和数据部分分别对应一个 执行文件中的代码段、数据段。每个进程只能执行自己的代码和访问自己的数据及堆栈区。进程之间相互之间的通 信需要通过系统调用了进行。对于只有一个 CPU 的系统,在某一时刻只能有一个进程正在运行。内核通过调度程 序分时调度各个进程运行。 Linux 系统中,一个进程可以在内核态(kernel mode)或用户态(user mode)下执行,因此,linux 内 核栈和用户栈是分开的。用户栈用于进程在用户态下临时保存调用函数的参数、局部变量等数据。内核栈 则含有内核程序执行函数调用时的信息。 内核程序是通过进程表对进程进行管理的,每个进程在进程表中占有一项。在 linux 系统中,进程表 项是一个 task 结构。 当一个进程在执行时,CPU 的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下 文。当内核需要切换(switch)至另一个进程时,它就需要保存当前进程的所有状态,也即保存当前进程
的上下文,以便在再次执行该进程时,能够恢复到切换时的状态执行下去。在发生中断时,内核就在被中 断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中断服务结 束时能恢复被中断进程的执行。 当进程正在被 CPU 执行时,被称为处于执行状态(running)。当进程正在等待系统中的资源而处于等 待状态时,则称其处于睡眠等待状态。在 linux 系统中,还分为可中断的和不可中断的等待状态。当系统 资源已经可用时,进程就被唤醒而进入准备运行状态,该状态称为就绪态。当进程已停止运行,但其父进 程还没有询问其状态时,则称该进程处于僵死状态。当进程被终止时,称其处于停止状态。 只有当进程从“内核运行态”转移到“睡眠状态”时,内核才会进行进程切换操作。在内核态下运行 的进程不能被其它进程抢占,而且一个进程不能改变另一个进程的状态。为了避免进程切换时造成内核数 据错误,内核在执行临界区代码时会禁止一切中断。 三.内核对内存的使用 linux 内核程序占据在物理内存的开始部分,接下来是用于供硬盘或软盘等块设备使用的高速缓冲区部分。当 一个进程需要读取块设备中的数据时,系统会首先将数据读到高速缓冲区中;当有数据需要写到块设备上去时,系 统也是先将数据放到高速缓冲区中,然后由块设备驱动程序写到设备上。最后部分是供所有程序可以随时申请使用 的主内存区部分。内核程序在使用主内存区时,也同样要首先向内核的内存管理模块提出申请,在申请成功后方能 使用。对于含有 RAM 虚拟盘的系统,主内存区头部还要划去一部分,共虚拟盘存放数据。 由于计算机系统中所含 的实际物理内存容量是有限制的。为了能有效地使用这些物理内存,Linux 采用了 Intel CPU 的内存分页管理机制, 使用虚拟线性地址与实际物理内存地址映射的方法让所有同时执行的程序共同使用有限的内存。内存分页管理的基 本原理是将整个主内存区域划分成 4096 字节为一页的内存页面。程序申请使用内存时,就以内存页为单位进行分 配。 为了使用实际物理内存,每个进程的线性地址通过二级内存页表动态地映射到主内存区域的不同内存 页上。因此每个进程最大可用的虚拟内存空间是 64MB。每个进程的逻辑地址通过加上任务号*64M,即可 转换为线性地址。不过在注释中,通常将进程中的地址简单地称为线性地址。 四.内核源代码结构 Linux 内核完整的源代码目录结构如下:
五.内核与用户程序的关系 在 Linux 系统中,内核为应用程序提供了两方面的接口。其一是系统调用接口,也即中断调用 int 0x80;另一方面 是通过内核库函数与内核进行信息交流。内核库函数是基本 C 函数库 libc 的组成部分。许多的系统调用是作为基本 C 语言函数库的一部分实现的。 系统调用主要是提供给系统软件直接使用或用于库函数的实现。而一般用户开发 的程序则是通过调用象 libc 等库中的函数来访问内核资源。通过调用这些库中的程序,应用程序代码能够完成各种 常用工作,例如,打开和关闭对文件或设备的访问、进行科学计算、出错处理以及访问组和用户标识号 ID 等系统 信息。 系统调用是内核与外界接口的最高层。在内核中,每个系统调用都有一个序列号(在 include/linux/unistd.h 头文件中定义),并常以宏的形式实现。应用程序不应该直接使用系统调用,因为这样的话,程序的移植性就不好 了。因此目前 Linux 标准库 LSB(Linux Standard Base)和许多其它标准都不允许应用程序直接访问系统调用宏。 系统调用的有关文档可参见 Linux 操作系统的在线手册的第 2 部分。 库函数一般包括 C 语言又没有提供的执行高 级功能的用户级函数,例如输入/输出和字符串处理函数。某些库函数只是系统调用的增强功能版。例如,标准 I/O 库函数 fopen 和 fclose 提供了与系统调用 open 和 close 类似的功能,但却是在更高的层次上。在这种情况下,系统 调用通常能提供比库函数略微好一些的性能,但是库函数却能提供更多的功能,而且更具检错能力。系统提供的库 函数有关文档可参见操作系统的在线手册第 3 部分。 六.内核的配置和编译 配置与编译内核用到的工具很多,下面是对几个关键工具的介绍: Make Make 是一种帮助大型软件工程的编译工作实现自动化的编程语言。正确地使用 Make 可以大大减少因编译程序 而花费的时间,因为它可以消除不必要的再编译。Make 的基本设计思想是如果目标文件是在最近一次对源文件的修 改之后编译的,它就是“新的”,不需要重新编译;如果最近一次对源文件的修改之后没有及时更新目标文件,那 么该目标文件就是“旧的”,需要重新编译。为了理解 Make 如何执行一个任务,需要了解一些术语: ◆目标 需要执行的一个任务。多数情况下它就是用户要生成的文件的名字,但是它也可以仅是个任务的名字。 ◆依赖关系 两个目标之间相互依存的关系。如果修改目标 B 会造成目标 A 的修改,那么就说目标 A 依赖于目标 B, B 是 A 的先决条件。
◆变量 一种存储临时信息的载体。Make 中使用的变量应该加上括号,例如$(TEMP)。 ◆命令 执行任务时使用的指令,可以是一条、多条,甚至没有。 ◆规则 一条完整的规则具有以下格式: 目标(target) : 先决条件(prerequisites) 规则(command) ...... 其中只有目标必须要有,其它成分可以没有。一条完整的规则描述了编译一个目标的方法和依赖关系,是 Makefile 中最重要的部分。 ◆Makefile 文件 描述如何生成一个或多个目标的文件。它列出目标依赖的各个文件,并提供正确编译这些目标所 需要的规则。 接下来以 2.4.23 的 kbuild 为例,简要介绍一下内核的构建过程。首先,完整的内核构建过程由以下五种 Makefile 封装。 1.根目录 Makefile 它是最重要的 Makefile,定义所有与体系结构无关的变量和目标。它读取.config 文件,并根据其信息最终生 成 vmlinux 和 modules。Make 通过向下递归调用子目录中的 Makefile 来编译这两个目标。 2.配置文件.config 执行“make ”会在根目录下生成该配置文件,其内容记录了具体的配置选择,也可以将旧内核的配置文件放 在这里。 3.arch/*/Makefile 这是与特定体系结构相关的 Makefile。它包含在根目录下的 Makefile 中,为 kbuild 提供体系结构的特定信息。 4.子目录 Makefiles 它们存在于每个子目录下,大约有几百个。它们接受来自上层 Make 传递下来的信息,并根据这些信息来构造 一个需要编译的文件列表,并交由 Rules.make 处理。 5.Rules.make 几乎每个子目录 Makefile 都包含该 Makefile。根据子目录 Makefiles 构建的文件列表,Make 使用 Rules.make 定义的通用规则来编译所有来自列表的源文件。 kbuild 的执行过程是:Make 从根目录 Makefile 开始执行,从中获得与体系结构无关的变量和依赖关系,并 同时从 arch/*/Makefile 中获得体系特定的变量等信息,这些信息扩展了根目录 Makefile 提供的变量。此时 kbuild 已经拥有构建内核需要的所有变量和目标。然后,Make 进入子目录,把部分变量传递给子目录 Makefile。子目录 Makefile 根据配置信息决定编译哪些源文件,从而构建出一个需要编译的文件列表。最后,Rules.make 根据其定 义的编译规则决定这些文件的编译方式。 需要注意的是,由于 Make 的向下递归特性和无序性,其执行过程并不完全遵守顺序逐行执行的规则,但无论 Make 的执行有多复杂,也只分为两个阶段。第一个阶段 Make 会读取所有变量和分析所有目标的依赖关系,并最终 建立一棵依赖关系树。同时,所有的立即型变量(通过“:=”赋值)在这个过程中被扩展,就像 C 变量一样。而 在这个阶段的最后,所有的延迟型变量才被扩展(通过“=”赋值)。这点需要格外注意。第二个阶段 Make 会根 据依赖关系树执行命令。 因此,一个目标和其先决条件的规则定义的顺序是无所谓的,很可能一个目标的先决条件的规则定义在百行以 后才出现。Make 会耐心读完所有的 Makefile 后分析得出依赖关系树。 GCC GCC 是 GNU 的免费编译程序,也是内核惟一指定使用的编译器。GCC 在执行一个完整的编译任务时会经过以下 步骤: ◆预处理 GCC 会调用 cpp 程序来分析各种宏指令,如#define、#if、#include 等。 ◆编译 这一阶段根据输入文件产生汇编语言指令。由于通常情况下是立即调用汇编程序 as,所以输出一般不保存 在文件中,可以使用-S 选项强制输出源程序的汇编版本。 ◆汇编 这一阶段将汇编语言源程序作为输入,生成.o 目标文件。 ◆链接 这是最后一个阶段。该阶段中,各个.o 模块被链接在一起构成可执行文件。 as 用户可以明确地要求使用 as 来直接处理汇编文件。as 产生的目标文件可以分为文本段(.text)、数据段(.data)
和未初始化数据段(.bss)。 ld 与 as 相似,用户可以明确地要求使用 ld 链接程序将几个模块组合成一个单独的可执行文件。其链接过程通常由一 个叫 ld 链接脚本的文件来描述。该脚本使用 Linker Command Language 编写。使用“ld --verose”命令可以看到 这个默认使用的 ld 链接脚本。 ar ar 是 GNU 的二进制文件处理程序,用于创建、修改及从归档文件中抽取文件。由它生成的.a 归档文件实际上是一 个包含许多可执行二进制代码子程序集合的库文件。 使用“make rpm”可以把内核源代码制作成 RPM 包。在此之前,kbuild 会执行“make spec”生成 rpmbuild 程序用到的 spec 文件,详见“man rpmbuild”。 RPMBuild 中间件 根目录 scripts 下的各种脚本和 C 源文件都可以称作中间件。它们并不是内核组件的一部分,只是在 kbuild 执行过程中的辅助程序。以 split-include 为例,讲述配置文件的运作机理。 .config 由关键字/值对组成,其内容类似于: CONFIG_MPENTIUMIII=y # CONFIG_MPENTIUM4 is not set CONFIG_REISERFS_FS=m 这些信息在执行“make ”时自动生成。同时 include/linux/autoconf.h 依照.config 的内容生成。它的格式 类似于: #define CONFIG_MPENTIUMIII 1 #undef CONFIG_MPENTIUM4 #undef CONFIG_REISERFS_FS #define CONFIG_REISERFS_FS_MODULE 1 对比一下不难发现,include/linux/autoconf.h 明确地洞悉了.config 的意图:哪些组件不编译,哪些需要编 译进内核,而哪些又要作为模块来编译?split-include 根据 include/linux/autoconf.h 在 include/config/下建 立相关的目录和.h 文件。每个.h 文件只包括 include/linux/autoconf.h 中的某一行,比如在配置内核选项时支持 NTFS 文件系统,并把它编译进内核,在.config 中就会生成“CONFIG_NTFS_FS=y”,相应地在 include/linux/autoconf.h 中会生成“#define CONFIG_NTFS_FS 1”一项。这样,所有与 NTFS 文件系统相关的 C 源文件都会包含 include/config/ntfs/fs.h 头文件。 如果以前编译过内核,并且没有使用过“make mrproper”,.config、include/linux/autoconf.h 和 include/linux/config/就不会被删除。这里涉及到新旧内核的配置问题。一个全新的内核代码是未经配置的。如 果只在原内核的功能基础上增加对 NTFS 的支持,那么从头开始配置无疑是浪费时间。可以继续使用原内核的.config 文件,而所有的配置信息不会有任何更改,并且可以直接在原配置的基础上增加新功能。 旧值保存在 include/config/下的.h 文件中,新值保存在新生成的 include/linux/autoconf.h 文件中。split-include 的 代码不仅描述了如何处理这五种情况,还描述了 include/config/下文件和子目录的生成过程。 总结:随着硬件更新速度的日益加快,linux 内核的升级也会加快,尤其是驱动程序,让我们加入到开发者的行列, 一起享受创造的乐趣,自由的空气。 参考文献:《linux 内核解析》
分享到:
收藏