西西河

主题:【原创】基于Linux内核的开放源代码操作系统的组成:第一篇 -- 请尽量

共:💬75 🌺106
全看分页树展 · 主题
家园 【原创】基于Linux内核的开放源代码操作系统的组成:第一篇

标题有点绕口,说明了这样一个操作系统必须是使用Linux作内核的,并且使用的软件都是发布在某种开放源代码许可下的,比如GPL,BSD,MIT等等。通常,流行的Linux distros都符合这样的要求。

第一篇:内核与系统初始化

如果以硬件为圆心,一个完整的操作系统应该看作包括以下几个同心圆:最里面的当然是Linux内核(以下都简称内核)以系统调用的形式为外面的各个层次提供对硬件的存取。现在有两个版本的内核可以选择:2.4和2.6。2.4版是上一个“稳定版本”,大概在四年前发布的。经过这四年的维护,应该十分成熟和稳定了。另外,还有一些新功能和新设备驱动程序也已经从2.6版向后移植(back ported)到了2.4版。特别在意稳定性的用户,如果不必支持最新的硬件设备,一般会继续使用2.4版的内核,直到2.6版的稳定性得到考证。事实上,2.6版已经很稳定了,只是由于Linus迟迟不kick off下一个开发版(2.7),而是继续向2.6版增加新功能,有些人对2.6版的稳定性还是心存疑虑。

2.6版的内核是在2003年发布的。相比于2.4版,2.6版的scalability更好了,POSIX多线程(pthreads)API的实现更加完整了,对hot pluggable和hot swappable设备的支持也更完善了,还增加了in-kernel cryptography(用于更好地实现IPSec、加密文件系统等等)。作为当前的“稳定版本”,新的设备驱动程序也往往针对2.6版本开发,然后再考虑移植到2.4版本上。

其实,对于某些特殊用途,还有2.2版本的内核可以考虑,其优点是对硬件要求低。随着功能的增加,Linux的身段日渐丰满。虽然,对于嵌入式应用,经过特殊剪裁后,2.4和2.6版的内核还是可以运行在相当低的硬件配置上的,但对于普通用户来说,以前一直为人们所津津乐道的4M+386已经很难再满足哪怕是最普通的应用了。不仅内核的体积在增长,用户态程序(user space applications)也不甘示弱,在增加新的功能的同时,带来更高的系统要求。

由于要支持多达十几种处理器类型、十几种文件系统,和数不清的硬件设备,Linux的内核编译配置越来越成为一项让人望而生畏的工作。我正在使用的2.6.9版的编译配置有900多个选项,从处理器体系结构到网卡到SCSI接口卡到USB mouse,应有尽有。正确地选择与硬件相配合的编译配置不仅要花一些功夫,还得对硬件有相当的了解。否则,轻则影响性能,重则某些部件不能正常工作,甚至系统无法启动。针对这个问题,Linux distros都会根据不同的用途,为其用户提供多个经过精心测试、调整的内核。通常情况下,这些缺省的内核通常都很好用,最大可能地支持所有的硬件设备,性能也不错。但是如果有特别需求,比方说特殊的硬件、或是对性能或安全性有特别要求,那么通常需要重新配置内核,甚至打补丁,然后编译。在这些情况下,缺省内核可以用来bootstrapping,也就是让系统进入到可以使用的状态。编译后的Linux内核以一个二进制映像文件(image file)的形式存在,在计算机启动的时候,Linux对系统的控制以这个映像文件被调入内存开始。

从理论上说,Linux是一个monolithic的内核,既所有的内核功能都放在一起,并在处理器的特权(privilege)级别上运行。优点是内核各个部分之间没有communication overhead,效率高,但缺点是大家都在一条船上,任何一个部分出问题都会导致整个内核的崩溃。与之相对的是所谓的微内核(micro kernel),只有最基本的功能运行在处理器的特权级别上,其他各个部分,包括文件系统,设备驱动程序等等都是相互独立的,并运行在处理器的非特权级别上。这样,任何一个部分的问题不会影响其他部分和全局,大大提高了系统的稳定性。由于各个部分是独立的,系统的模块性也提高了,各个功能模块可以根据需要单独启动、停止。但微内核结构的最大问题是效率不高,各个部分之间的communication overhead要比monolithic的内核高。在实际实现中,Linux内核的某些部分,比如设备驱动程序、文件系统等,可以编译成多个模块(module),然后根据实际需要动态地调入内存,与已在内存中的其他部分动态链接。

内核之外,是一个用户态的系统程序库,为用户程序提供系统调用接口和一些最常用的routines,例如产生新的进程、分配和操作内存、存取文件等等。由于Linux是一个UNIX-like的操作系统内核,与之配合这个系统程序库通常是一个符合Single Unix Specifications标准的C语言库。依照惯例,这个库叫做libc。如果熟悉C语言程序开发,会认出这其实也是C程序的运行库,也就是说,所有用C语言开发的程序都必须和这个库链接。

基本上,桌面系统和服务器都会选用GNU Libc作为系统程序库。象所有其他的GNU软件一样,GNU Libc的源代码也是免费公开发布的,但是使用LGPL(Lesser GPL)作为许可协议。使用LGPL允许close sourced, proprietary的软件与libc链接。最新的2.3版GNU Libc配合2.6版的Linux内核一起完成了新的pthreads实现。在组成上,GNU Libc一般是由数个库文件组成,如libc、libm、libcrypt、libpthread等等。

如果是嵌入式系统,GNU Libc的体积可能过于庞大。一种解决办法是对其进行剪裁,去掉那些用不到的部分。此外,还有diet libc,ulibc等几种专门针对嵌入式应用开发的系统程序库可供选择。基本上,这些轻量化的(light weight)的库要么省掉了一些非关键性的部分,要么在设计时优先考虑程序的动态和静态尺寸,或者兼而有之。

Linux缺省的可执行程序格式是ELF(Executable and Linking Format),支持共享库和动态链接(shared library and dynamic loading)。象libc这样被所有用户程序使用的程序库当然会被编译成动态库,以节省大量的内存空间。

在系统程序库的外面,世界开始变得精彩纷呈,各层之间的界限也开始模糊起来。

首先,用户最先注意到的是一个被称为boot loader的程序。在不同的体系结构下,这个程序所做的工作略有不同。在PC(x86)上,BIOS在完成加电自检(Power On Self Test,POST)后,从硬盘上的特定扇区(比如主引导扇区)找到boot loader的入口程序,并加载到内存的特定地址,然后从那个地址开始执行boot loader。boot loader的工作其实很简单,就是在硬盘上找到编译好的Linux内核映像,把内核加载到内存中,然后把计算机的控制权交给内核。严格说来,在boot loader运行的时候,根本不能指望libc。libc所在的硬盘、其上的文件系统等等在那个时候都还处于一片混沌之中,不能为boot loader所用,因为内核都还没有被调入内存呢。如果把内核开始接管系统那一刻作为“the Epoch”,那么boot loader就是在史前时代了。在黑暗的史前时代,boot loader能依靠的只有它自己和计算机的BIOS。但是在实际的实现中,boot loader的配置、安装(把boot loading代码放到引导扇区)、计算内核映像文件在硬盘上的位置等工作都是在系统正常启动后进行的,当然可以利用libc了。所以,从这点上来说,把boot loader放在libc外面也不完全错。

最著名的Linux系统boot loader应该是LILO了,其名字来源于LInux LOader。后起之秀Grub以其灵活性和方便得到了越来越多人的赏识。除了Linux,LILO和Grub还可以加载其他操作系统内核,比方说Windows。事实上,这也是大家在一个计算机上安装多个操作系统的通常做法。在Linux的早期,还有一个有趣的DOS程序叫Loadlin.exe,可以把Linux内核加载到内存中,取代正在运行的DOS。

Linux内核接管计算机系统后,要进行一系列的初始化工作,包括将检测硬件,对内存进行分页,建立进程表等基本数据结构等等。之后,内核会尝试挂上(mount)根文件系统(root file system,或者简单的表示为“/”)。因为这个原因,根文件系统所在的硬盘设备驱动程序和文件系统功能必须在最早的时间被调入内存,最简单的办法莫过于把它们直接编译到内核映像文件中。通常,根文件系统所硬盘分区的设备号是由boot loader传给内核的。LILO和Grub都支持在其配置中指定根文件系统所在硬盘分区设备名,并转换成内核所能识别的设备号。

根文件系统挂上以后,内核需要找到一个被称作init的程序,并产生系统中的第一个用户进程来执行这个程序。由于该进程是所有其他用户进程的老祖宗,其进程号(process ID,简写为pid)自然地就是1。和根文件系统所在硬盘分区设备名一样,这个init程序的路径也是由boot loader传给内核的。除了可以在配置文件中指定外,LILO和Grub还支持在系统启动时接受用户的命令行输入。也就是说,具体执行什么样的程序是可以在每次系统启动时根据需要改变的。某些情况下,当系统无法正常启动时,系统管理员可以利用这个功能运行某些修复工具。

自init运行的那一刻,内核彻底退到幕后,操作系统开始由用户态程序控制。由于这个原因,系统程序库libc需要放在根文件系统中,根据惯例(或者标准),是在“/lib”子目录下。正常情况下,init是机器启动后第一个用到libc的程序。

传统上,AT&T SysV和BSD各有自己的init步骤。在众多的Linux distros中,有些跟随SysV,有些遵循BSD的传统。但是,两种流派的init最后所完成的工作都包括:

* 挂上所有的文件系统,例如“/usr”、“/home”、“/tmp”、“/proc”等等。在根文件系统的“/etc”子目录下,有一个叫fstab的文件里列出了需要在系统启动时挂上的文件系统。

* 初始化各个子系统,例如键盘、鼠标、TCP/IP网络、SCSI,等等。如果一个子系统的相应内核代码(如设备驱动程序)没有编译进内核映像文件里的,那么这是个机会把相应的模块调入内存。当然,如果在机器启动后并不立即需要该子系统,相应模块可以等到需要的时候再加载。

* 启动各种用户态的daemon,例如web server、email server、数据库服务器等等。具体每个机器的用途不一样,需要安装和启动的daemon程序也各不相同。需要注意的是,即便是当作桌面系统的机器,通常也会在后台运行一些daemon程序,例如syslog、sshd等等,即是遵循UNIX的传统,也是为了方便。

* 为命令行登录生成终端(tty)和控制台(console)设备,并运行登录程序。那个著名的命令行界面的“Login:”提示符就是在这个时候出现在屏幕上的。输入正确的用户名和密码后,登录程序运行一个称作shell(能翻译成“壳”么?)的命令行解释程序,成为用户与机器之间的主要接口。大体上说,shell的工作就是接受用户输入的命令行,启动一个新的进程,执行相应的程序,并将程序的输出放到用户所要求的去处。

* 如果机器是作服务器用的,系统的启动过程一般就到此为止了。如果是用做桌面系统的,那么,这个时候就需要启动图形界面了。所有的Linux distros都选择了X作为图形界面,这也是UNIX的传统。在基于Linux的操作系统上,图形卡(video card)的驱动程序是运行在用户态的,虽然内核为所谓的direct rendering提供了必要的支持。这和Windows不同。在Mac OS X上,图形卡的驱动程序也是作为设备驱动程序在系统启动的第一时间加载到内存与内核链接的。

* 图形界面初始化完成后,图形界面的登录程序会开始运行,为用户在图形界面下提供登录和认证(authentication)服务。

注:本来只打算写一篇简短的介绍文章,粗略地描述一下一个基于Linux内核的开放源代码操作系统的各个组成部分。但是在动笔后,发现三言两语根本无法说清楚,干脆扩展为一个系列,并把起头当作了第一篇。

后面计划有:

第二篇:命令行界面(shell、文件系统、常用命令)

第三篇:图形界面(X、Gnome、浏览器、Email、图形编辑工具、Music player、DVD player、相片管理)

第四篇:服务器(web服务器、电子邮件服务器、文件服务器)

第五篇:软件开发(源程序编辑器、IDE、编译器、捕虫器)

第六篇:软件安装和系统维护

见解有限,难免有疏漏和错误,还请不吝指正。

元宝推荐:铁手,闲看蚂蚁上树,Highway,

本帖一共被 2 帖 引用 (帖内工具实现)
全看分页树展 · 主题


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河