Linux内核简介
这一章将带我们从Unix的历史视角来认识Linux内核与Linux操作系统的前世今生。今天Unix系统业已演化成一个具有相似应用程序编程接口(API),并且基于相似设计理念的操作系统家族。但它又是一个别具特色的操作系统,从萌芽至今已经有40余年的历史。若要了解Linux,我们必须首先认识Unix系统。
Unix的历史
Unix虽然已经使用了40年,但计算机科学家仍然认为它是现存操作系统中最强大和最优秀的系统。从1969诞生以来,由Dennis Ritchie和Ken Thompson的灵感火花点亮的这个Unix产物已经成为一种传奇,它历经的时间的考验依然声明不坠。
Unix是从贝尔实验室的一个失败的多用户操作系统Multics中涅槃而生。Multics项目被终止后,贝尔实验室计算机科学研究中心的人们发现自己处于一个没有交互式操作系统可用的境地。在这种情况下,1969年的夏天,贝尔实验室的程序员们设计了一个文件系统原型,而这个原型最终演化成了Unix。Thompson首先在一台无人问津的PDP-7型机上实现了这个全新的操作系统。1971年,Unix被移植到PDP-11型机上。1973年,整个Unix操作系统用C语言进行了重写,正是这个不太引人注目的操作,给后来的的Unix系统的广泛移植铺平了道路。第一个在贝尔实验室以外被广泛使用的Unix版本是第6版,称为V6。
许多其他公司也把Unix移植到新的机型上。伴随着这些移植,开发者们按照自己的方式不断增强系统的功能,并由此产生了若干变体。1977年,贝尔实验室综合各种变体推出了Unix System III;1983年AT&T推出了SystemV。
由于Unix系统设计简洁并且在发布时提供源代码,所以许多其他组织和团体都对它进行了进一步的开发。加州大学伯克利分校便是其中影响最大的一个。他们推出的变体叫Berkeley Software Distibutions(BSD)。伯克利的第一个Unix演化版是1977年推出的1BSD系统,它的实现基于贝尔实验室的Unix版本,不但在其中加入了许多修正补丁,而且还集成了不少额外的软件;1978年伯克利继续推出了2BSD系统,其中包含我们如今仍在使用的csh、vi等应用软件。而伯克利真正独立开发的Unix系统是于1979年推出的3BSD系统,该系统引入了一系列令人振奋的新特性,支持虚拟内存便是其一大亮点。在3BSD以后,伯克利又相继推出了4BSD系列,包括4.0BSD、4.1BSD、4.2BSD、4.3BSD等众多分支。这些Unix演化版实现了任务管理、换页机制、TCP/IP等新的特性。最终伯克利大学在1994年重写了虚拟内存子系统(VM),并推出了伯克利Unix系统的最终官方版,即我们熟知的4.4BSD。现在多亏了BSD的开放性许可,BSD的开发才得以由Darwin、FreeBSD、NetBSD和OpenBSD继续。
20世纪80和90年代,许多工作站和服务厂商推出了他们的Unix,这个Unix大部分是在AT&T或伯克利发行版的基础上加上一些满足他们特定体系结构需要的特性。这其中就包括Digital的Tru64、HP的HP-UX、IBM的AIX、Sequent的DYNIX/ptx、SGI的IRIX和Sun的Solaris和SunOS。
由于最初一流的设计和以后多年的创新与逐步提高,Unix系统称成为一个强大、健壮和稳定的操作系统。下面的几个特点是使Unix强大的根本原因。首先,Unix很简洁:不像其他动辄数千个系统调用并且设计目的不明确的系统,Unix仅仅提供几百个系统调用并且有一个非常明确的设计目的。第二,在Unix中,所有的东西都被当作文件对待。这种抽象使得对数据和对设备的操作是通过一套相同的系统调用接口来进行的:open()、read()、write()、lseek()和close()。第三,Unix的内核与相关的系统工具软件是用C语言编写而成的–正是这个特点使得Unix在各种硬件体系结构面前都具备惊人的移植能力,并且使得广大的开发人员很容易就能接受它。第四,Unix的进程创建非常迅速,并且有一个非常独特的fork()系统调用。最后,Unix提供了一套非常简单但又非常稳定的进程间通信元语,快速简洁的进程创建过程使Unix的程序把目标放在一次执行保质保量的完成一个任务上,而简单稳定的进程间通信机制又可以保证这些单一目的的简单程序可以方便地组合在一起,去解决现实中越来越复杂的任务。正是由于这种策略和机制分离的设计理念,确保了Unix系统具备清晰的层次化结构。
今天,Unix已经发展成为一个支持抢占式多任务、多线程、虚拟内存、换页、动态链接和TCP/Ip网络的现代化操作系统。Unix的不同变体被应用在大到数百个CPU的集群、小到嵌入式设备的各种系统中。尽管Unix已经不再被认为是一个实验室项目了,但它仍然伴随着操作系统设计技术的进步而继续成长,人们仍然可以把它作为一个通用的操作系统来使用。
Unix的成功归功于其简洁和一流的设计。它能拥有今天的能力和成就应该归功于Dennis Ritchie和Ken Thompson和其他早期设计人员的最初决策,同时也要归功于那些永不妥协于成见,从而赋予Unix无穷活力的设计选择。
追寻Linus足迹:Linux简洁
1991年,Linus Torvalds为当时新推出的、使用Intel 80386微处理器的计算机开发了一款全新的操作系统,Linux由此诞生。那时,作为芬兰赫尔辛基大学的一名学生的Linus,正为不能随心所欲的使用强大而自由的Unix系统而苦恼。对于Torvalds而言,使用当时流行的Microsoft的DOS系统,除了玩波斯王子游戏外,别无他用。Linus热衷于使用Minix,一种教学用的廉价Unix,但是,他不能轻易修改和发布该系统的源代码(由于Minix的许可证),也不能对Minix开发者所作的设计轻举妄动,这让他耿耿于怀并由此对作者的设计理念感到失望。
Linus像任何一名生机勃勃的大学生一样决心走出这种困境:开发自己的操作系统。他开始写了一个简单的终端仿真程序,用于连接到本校的大型Unix系统上。他的终端仿真程序经过一学年的研发,不断改进和完善。不久,Linus手上就有了虽不成熟但是五脏俱全的Unix。1991年底,他在Internet上发布了早期版本。
从此Linux便启航了,最初的Linux发布者很快赢得了众多用户。而实际上,它成功的重要因素是,Linux很快吸引了很多开发者、黑客对其代码进行修改和完善。由于其许可证条款的约定,Linux迅速成为多人的合作开发项目。
到现在,Linux早已羽翼丰满,它被广泛移植到Alpha、ARM、PowerPC、SPARC、x86-64等许多其他体系结构之上。如今Linux既然被安装在最轻小的消费电子设备上,比如手表,同时也在服务规模最庞大的服务数据中心上,如超级计算机集群。今天,Linux的商业前景也越来越被看好,不管是新成立的Linux专业公司RedHat还是闻名遐迩的计算机巨头IBM,都提供林林总总的解决方案,从嵌入式系统、桌面环境一直到服务器。
Linux是类Unix系统。但它不是Unix。需要说明的是,尽管Linux借鉴了Unix许多设计并且实现了Unix的API,但Linux没有像其他Unix变种那样直接使用Unix的源代码。必要的时候,它的实现可能和其他Unix的实现大相径庭,但它没有抛弃Unix的设计目标并且保证了应用程序编程接口的一致。
Linux是一个非商业化的产品,这是它最让人感兴趣的特征。实际上Linux是一个互联网上的协作开发项目。尽管Linus被认为是Linux之父,并且现在依然是一个内核维护者,但开发工作其实是由一个结构松散的工作组协力完成的。事实上,任何人都可以开发内核。和该系统的大部分一样,Linux内核也是自由(公开)软件。当然,也不是无限自由的。它使用GNU的GPL第2版作为限制条款。这样做的结果是,你就可以自由的获取内核的代码并随意修改它,但如果你希望发布你修改过的内核,你也得保证让得到你的内核的人同时享有你曾经享受过的权利,当然,包括所有的源代码。
Linux用途广泛,包含的东西也名目繁多。Linux系统的基础是内核、C库、工具集和系统的基本工具,如登录程序和Shell。Linux系统也支持现代的X Windows系统,这样就可以使用完整的图形用户桌面环境,如GNOME。可以在Linux上使用的商业和自由软件数以千计。
操作系统和内核简介
由于一些现代商业操作系统日趋庞杂及其设计上的缺陷,操作系统地精确定义并没有一个统一的标准,许多用户把他们在显示器屏幕上看到的东西理所当然的认为就是操作系统。通常,当然在本书上也这么认为,操作系统是指在整个系统中负责完成最基本功能和系统管理的那部分。这些部分应该包括内核、设备驱动程序、启动引导程序、命令行Shell或者其他种类的用户界面、基本的文件管理工具和系统工具。系统这个词其实还包含了操作系统和所有运行在它之上的应用程序。
用户界面是操作系统的外在表象,内核才是操作系统的内在核心。系统其他部分必须依靠内核这部分软件提供的服务,像管理硬件设备、分配系统资源等。内核有时候还被称为管理者或者是操作系统核心。通常一个内核由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序和网络、进程间通信等系统服务共同组成。对于提供保护机制的现代系统来说,内核独立于普通应用程序,它一般处于系统态,拥有受保护的内存空间和访问硬件设备的所有权限。这种系统态和被保护起来的内存空间,统称为内核空间。相对的,应用程序在用户空间执行。它们只能看到允许它们使用的部分系统资源,并且只使用某些特定的系统功能,不能直接访问硬件,也不能访问内核划给别人的内存范围,还有其他一些使用限制。当内核运行时,系统以内核态进入内核空间执行。而执行一个普通用户程序时,系统将用户态进入用户空间执行。
在系统中运行的应用程序通过系统调用来与内核通信。应用程序通常调用库函数(比如C库函数)再由库函数通过系统调用界面,让内核代其完成各种不同任务。一些库调用提供了系统调用不具备的许多功能,在那些较为复杂的函数中,调用内核的操作通常只是整个工作的一个步骤而已。举个例子,拿printf()函数来说,它提供了数据的缓存和格式化等操作,而调用write()函数将数据写到控制台只不过是其中的一个动作罢了。不过,也有一些库函数和系统调用即使一一对应的关系,比如,open()库函数除了调用open()系统调用之外,几乎什么也不做。还有一些C库函数,像strcpy(),根本就不需要直接调用系统级的操作。当一个应用程序执行一条系统调用,我们说内核正在代其执行。如果进一步理解,在这种情况下,应用程序被称为通过系统调用在内核空间运行,而内核空间被称为运行于进程上下文中。这种交互关系–应用程序通过系统调用界面陷入内核–是应用程序完成其工作的基本行为方式。
内核还要负责管理系统的硬件设备。现有的几乎所有的体系结构,包括全部的Linux支持的体系结构,都提供了中断机制。当硬件设备想要和系统通信的时候,它首先要发出一个异步的中断信号去打断处理器的执行,继而打断内核的执行。中断通常对应着一个中断号,内核通过这个中断号查询相应的中断服务程序,并调用这个程序响应和处理中断。举个例子,当你敲击键盘的时候,键盘控制器发送一个中断信号告知系统,键盘缓冲区有数据到来。内核注意到这个中断对应的中断号,调用相应的中断服务程序。该服务程序处理键盘数据然后通知键盘控制器可以继续输入数据。为了保证同步,内核可以停用中止–既可以停止所有的中断也可以有选择的某个中断号对应的中断。许多操作系统的中断服务程序,包括Linux的,都不在进程上下文中执行。它们在一个与所有进程都无关的、专门的中断上下文中运行。之所以存在这样一个专门的环境,就是为了保证中断服务程序能够在第一时间响应和处理中断请求,然后快速的退出。
这些上下文代表着内核活动的范围。实际上我们可以将每个处理器在任何指定的时间点上的活动必然概括为下列三者之一:
- 运行于用户空间,执行用户进程
- 运行于内核空间,处于进程上下文,代表某个特定的进程执行
- 运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断
以上所列几乎包括所有情况,即使边边角角的情况也不例外,例如,当CPU空闲时,内核就运行一个空进程,处于进程上下文,但运行于内核空间。
Linux内核和传统Unix内核的比较
由于所有的Unix内核都同宗同源。并且提供相同的API,现代的Unix内核存在许多设计上的相似之处。Unix内核几乎毫不例外的都是一个不可分割的静态执行库。也就是说,它们必须以巨大、单独的可执行块的形式在一个单独的地址空间中运行。Unix内核通常需要硬件系统提供页机制(MMU)以管理内存。这种页机制可以加强对内存空间的保护,并保证每个进程都可以运行于不同的虚地址空间上。初期的Linux系统也需要MMU支持,但有一些特殊版本并不依赖于此。这无疑是一个简洁的设计,因为它可以使Linux系统运行在没有MMU的小型嵌入式系统上。不过现实之中,即便很简单的嵌入式系统都开始具备内存管理单元这种高级功能了。我们会重点关注支持MMU的Linux系统。
单内核与微内核设计之比较
操作系统内核可以分为两大阵营:单内核和微内核(第三阵营是外内核,主要用在科研系统中)。
单内核是两大阵营中一种较为简单的设计,在1980年之前,所有的内核都设计成单内核。所谓单内核就是把它从整体上作为一个单独的大过程来实现,同时也运行在一个单独的地址空间上。因此,这样的内核通常以单个静态二进制文件的形式存放于磁盘中。所有的内核服务都在这样的一个大内核地址空间中运行。内核之间的通信是微不足道的,因为大家都运行在内核态,并身处同一地址空间:内核可以直接调用函数,这与用户空间应用程序没有什么区别。这种模式的支持者认为单模块具有简单和性能高的特点。大多数Unix都设计为单模块。
另一方面,微内核并不作为一个单独的大过程来实现,相反,微内核的功能被划分为多个独立的过程,每个过程叫做一个服务器。理想情况下,只有强烈请求特权服务的服务器才运行在特权模式下,其他服务器都运行在用户空间。不过,所有的服务器都保持独立并运行在各自的地址空间上。因此,就不可能像单内核模块那样直接调用函数,而是通过消息传递处理微内核通信:系统采用了进程间通信(IPC)机制,因此,各个服务器之间通过IPC机制互通消息,互换“服务”。服务器的各自独立有效避免了一个服务器的失效祸及另一个。同样,模块化的系统允许另一个服务器为了另一个服务器而换出。
因为IPC机制的开销多于函数调用,又因为会涉及内核空间与用户空间的上下文切换,因此,消息传递需要一定的周期,而单内核中简单的函数调用没有这些开销。结果,所有实际应用的基于微内核的系统都让大部分或全部服务器位于内核,这样,就可以直接调用函数,消除频繁的上下文切换。Windows NT内核(Windows XP、Windows Vista和Windows 7等基于此)和Mach(Mac OS X的组成部分)是微内核的典型实例。不管是Windows NT还是Mac OS X,都在其新近版本中不让任何微内核服务器运行在用户空间,这违背了微内核设计的初衷。
Linux是一个单内核,也就是说,Linux运行在单独的内核地址空间上。不过,Linux汲取了微内核的精华:其引以为豪的是模块化设计、抢占式内核、支持内核线程以及动态装载内核模块的能力。不仅如此,Linux还避其微内核设计上性能损失的缺陷,让所有事情都运作在内核态,直接调用函数,无需消息传递。至今,Linux是模块化的、多线程的以及内核本身可调度的操作系统,实用主义再次占了上风。
当Linus和其他内核开发者设计Linux内核时,他们并没有完全彻底的与Unix诀别。他们充分的认识到,不能忽视Unix的底蕴(特别是Unix的API)。而由于Linux没有基于某种特定的Unix,Linus和他的伙伴对每个特定的问题都可以选择已知最理想的解决方案–在有些时候,当然也可以创造一些新的方案。Linux内核与传统的Unix系统之间存在一些显著的差异。
- Linux支持动态加载内核模块。尽管Linux内核也是单内核,可是允许在需要的时候动态地卸载和加载部分内核代码。
- Linux支持对称多处理机制(SMP),尽管许多Unix的变体也支持SMP,但传统的Unix并不支持这种机制。
- Linux内核可以抢占(preemptive)。与传统的Unix变体不同,Linux内核具有允许在内核运行的任务优先执行的能力。在其他各种Unix产品中,只有Solaris和IRIX支持抢占,但是大多数Unix内核不支持抢占。
- Linux对线程的支持比较有意思:内核并不区分线程和其他的一般线程。对于内核来说,所有的进程都是一样的–只不过是其中的一些共享资源而已。
- Linux提供具有设备类的面向对象的设备模型、热插拔事件,以及用户空间的设备文件系统(sysfs)。
- Linux忽略了一些被认为是设计得很拙劣的Unix特性,像STREAMS,它还忽略了那些难以实现过时标准。
- Linux体现了自由这个词的精髓。现有的Linux特性集就是Linux公开开发模型自由发展的结果。如果一个特性没有任何价值或者创意很差,没有任何人会被迫去实现它。相反的,针对变革,Linux已经形成了一种值得称赞的态度:任何改变都必须要能通过简洁的设计以及正确可靠的实现来解决现实中确实存在的问题。于是,许多出现在某些Unix变种系统中,那些出于市场宣传目的或没有普遍意义的一些特性,如内核换页机制等都被毫不迟疑地摒弃了。
不管Linux和Unix有多大的不同,它身上都深深的打上了Unix烙印。
Linux内核版本
Linux内核有两种:稳定的和处于开发中的。稳定的内核具有工业级的强度,可以广泛的应用和部署。新推出的稳定内核大部分都支持修正了一些Bug或者加入了一些新的设备驱动程序。另一方面处于开发中的内核许多东西变化的很快。而且由于开发者不断实验新的解决方案,内核常常发生剧烈的变化。
Linux通过一个简单的命名机制来区分稳定的和处于开发中的内核。这种机制使用三个或者四个“.”分隔的数字来代表不同内核版本,第一个数字是主版本号,第二个数字是从版本号,第三个数字是修订版本号,第四个可选的数字为稳定版本号(stable version)。从副版本号可以反映出该内核是一个稳定版本还是一个处于开发中的版本:该数字如果是偶数那么内核版本就是稳定版本;如果是奇数,那么它就是开发版。
处于开发中的内核一般要经历几个阶段。最开始,内核开发者们开始实验新的特性,这时候出现错误和混乱是在所难免的。经过一段时间,系统渐渐成熟,最终会有一个特性审定的声明。这时候,Linus就不再接收新的特性了,而对已有特性所进行的后续工作会继续进行。当Linus认为这个新内核确实是趋于稳定之后,就开始审定代码。这以后就只允许再向其中加入修改bug的代码了。在经过一个短暂的准备期后,Linus会将这个内核作为一个新的稳定版推出。
在一个特定的系列下,Linus会定期发布新内核。每个新内核都是一个新的修订版本。比如2.6内核系列的第一个版本就是2.6.0,第二个版本是2.6.1.这些修订版包含了BUG修复、新的驱动和一些新特性。但是,像2.6.3和2.6.4修订版本之间的差异是很微小的。
这种开发方式一直持续到2004年,当时在受邀参加的Linux开发者峰会上,内核开发者们确定延长2.6内核系列,从而推迟进入到2.7的步伐。原因是2.6的版本的内核已经被广泛接受、其已经证明了稳定成熟,而那些还不成熟的新特性其实并非人们所需。如今看来2.6版本内核的稳定出色无疑证明了该方针是多么英明。
为了解决版本发布周期的变长的副作用,内核开发者们引入了上面提到的稳定版本号,这个稳定版本号(如2.6.32.8中的8)包含了关键性bug的修改,并且常会向前移植处于开发板内核(如2.6.33)的重要修改。依靠这种方式,以前版本保证了仍能将重点放在稳定性上。
小结
这是一本关于Linux内核的书:内核的目标,为达到目标所进行的设计以及设计的实现。这本书侧重实用,同时讲述工作原理时会结合理论联系实践。我的目标是让你从一个业内人士的视角来欣赏和理解Linux内核的设计和实现之美。力求以一种有趣的方式(伴随我个人在开发过程中收集的种种奇闻轶事和方法技巧)引导你走过跌跌撞撞的起步阶段。无论是你立志于开发内核代码。或者是进行驱动开发,甚至只是希望能更好的了解Linux操作系统,你都将从本书受益。
当你阅读本书时,我希望你有一台装有Linux的机器,我希望你能够看到内核代码。其实这就很理想了,因为这意味着你是一名Linux的使用者,并且早已开始拿起手术刀对着源代码进行探索了,只不过需要一份结构图以便对这个经脉有个总体把握罢了。相反你可能没有使用过Linux,只是在好奇心的驱使下希望了解一些内核设计的秘密而已。但是,如果你的目的只是撰写自己的代码,那么源代码的作用无可替代。而且,你不需要付出任何代价,尽管用吧。
好了,最重要的是,在其中寻找快乐吧。