购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.2.1 Linux的组成

从用户的角度来说,Linux包括Linux内核和周围应用两部分,普通用户主要和周围应用打交道,而开发者,特别是内核或驱动的开发者则主要和Linux内核打交道。但是,一个正常工作的Linux系统,其实际组成更加复杂。从功能的角度,可以将Linux系统划分为三大部分,分别是引导程序、内核和root文件系统,如图1-1所示。

图1-1 Linux系统组成图

1.引导程序

引导程序负责内核启动之前的准备工作。CPU上电后,会以固定的动作来执行上电后的第一个程序。不同的CPU会有不同的固定动作。例如x86系列的CPU会将硬盘的第一个扇区(512个字节)读入内存来执行;ARM系列CPU则会根据配置,选择将NAND Flash的前4K数据复制到CPU的RAM中执行,或者直接在NOR Flash的0地址开始执行。

由上可知,CPU执行的第一个程序的大小是严格受限的,内核的体积远超其限制。因此,内核无法作为CPU上电执行的第一个程序;此外,计算机是支持安装多个操作系统的,如Windows和Linux,或是多个Linux发行版等。因此,操作系统启动之前,要给用户提供选择菜单,指定所要启动的操作系统。

基于以上因素,内核启动之前,需要先运行一个引导程序。这个程序很小,它可以初始化各种硬件资源,为后续加载大体积的内核准备物质条件;同时还提供交互界面,如选择启动Linux还是启动Windows、设置Linux内核启动参数等;最后将内核加载到内存,将控制权交给内核后,内核的启动就开始了。总的来说,引导程序的主要工作就是:硬件初始化、启动配置和加载内核等。

不同的CPU体系架构对应不同的Linux引导程序,例如ARM架构下通常使用uboot作为引导程序,而x86架构下则通常使用GRUB(GRand Unified Bootloader)作为引导程序,之前的版本是GRUB1,现在升级到GRUB2。不管是哪个具体的引导程序,目前只需要记住以下两点即可:引导程序是Linux系统的组成部分之一;引导程序的运行在Linux内核运行之前。

有关引导程序GRUB的作用,在GRUB的官网上引用了一个GRUB狂热粉丝Gordon Matzigkeit的一段话。他说:“有些人在谈论他们的计算机时喜欢同时承认操作系统和内核,所以他们可能会说他们使用GNU/Linux或GNU/Hurd。而其他人似乎认为内核是系统最重要的部分,所以他们喜欢把自己的GNU操作系统称为‘Linux系统’。我个人认为这严重的不公平,因为引导加载程序是最重要的软件。我以前把上面的系统称为“Lilo”或“GRUB”系统。不幸的是,没人明白我在说什么;现在我只是用GNU这个词作为GRUB的假名。所以,如果你曾经听到人们谈论他们所谓的GNU系统,记住他们实际上是在向最好的引导加载程序致敬…GRUB!”

GRUB官网在下面给出了他们的看法“我们,GRUB的维护者,并不(通常)鼓励Gordon的狂热程度,但它有助于记住引导加载程序应该得到认可。我们希望您像我们编写者一样喜欢使用GNU GRUB。”

2.Linux内核

Linux内核是Linux系统的核心,如图1-2所示,Linux内核在整个软件层次体系中是最贴近硬件的一层,它向下实现硬件的驱动和管理,向上将硬件抽象成操作系统中的资源供上层应用使用。

图1-2 Linux内核系统架构图

如图1-2所示,Linux内核主要由6大功能模块组成,分别是进程管理、进程间通信、内存管理、文件系统、设备驱动和网络,依次说明如下。

(1)Linux内核的组成部分

1)进程管理。进程管理模块将程序的每次运行抽象成一个进程。CPU的执行则划分成时间片,进程管理模块会根据相应的策略,将时间片分配给符合条件的进程,这个分配的过程就称为进程调度。进程得到时间片后,将进行进程的上下文切换,开始新的执行,一旦该时间片耗尽,再次进行进程的上下文切换,当前进程被挂起等待下次被调度。因为时间片都是毫秒级别,即使是在单核CPU上,也能够使得多个程序的执行呈现并行的效果。为了缩短高优先级的任务响应时间,Linux内核还支持抢占式内核的特性,它可以使得高优先级的进程不必等待正在运行的低优先级进程执行完时间片后释放CPU,而是直接剥夺低优先级进程的CPU使用权,立即执行。

2)进程间通信(Inter-Process Communication,IPC)。Linux的设计哲学(源于UNIX哲学)中有一条是:一个程序只做好一件事,多个程序共同协作完成复杂的任务。进程间的相互协作离不开进程间通信机制。Linux内核支持多种进程间通信机制,如信号(Signal)、消息队列(Message Queue)、共享内存(Share Memory)、管道(Pipe)、信号量(Semaphore)和套接字(Socket)等。

3)内存管理。内存管理模块实现了对内存资源的管理和控制。在内存管理单元(Memory Management Unit,MMU)硬件的支持下,内存管理模块实现了进程的虚拟地址到物理地址的映射,即虚拟内存的功能。这样,每个进程的寻址范围可以超出物理地址范围,不再受物理内存空间的限制。而且每个程序都可以从相同的地址开始编址,相互独立互不影响,这样就构成了一个统一的程序虚拟地址空间,可以大幅简化编译器的开发。Linux还基于MMU实现了分页机制,分页简化了物理内存的管理。同时,程序运行时,根据程序的局部性原理,并不需要一次性将它所需的全部内存分配好,只需要将当前程序所在的分页加载进去即可,因此,该机制可以使得有限的物理内存能够运行大型的程序。此外,利用分页机制,还可以很方便地实现交换分区,将暂时用不到的内存分页存储到磁盘上,待到需要时再从磁盘加载,以性能换空间,有利于进一步提升物理内存的利用率。

4)文件系统。文件系统模块实现了两个方面的功能:第一个方面是虚拟文件系统(Virtual File System,VFS),VFS向上提供统一的文件操作接口,向下将Linux系统中的操作对象抽象成文件,除了存储在硬盘上的普通文件外,像目录、符号链接、管道、套接字以及各类设备等都被抽象成文件,皆可通过VFS所提供的接口统一操作;第二个方面是实现传统意义上的文件系统,此处的文件系统是指文件在存储设备的组织方式和数据结构,如Windows中的NTFS。硬盘第一次使用前,所做的格式化操作,就是在硬盘/分区上创建文件系统。Linux内核实现的标准文件系统是Ext2、Ext3和Ext4。

5)设备驱动。设备管理模块将计算机硬件及其外设分成三大类:字符设备、块设备以及网络设备。其中,字符设备通常指能够提供连续的数据流,支持以字节为单位按序读取,不支持随机读取的设备,典型的字符设备如键盘和串口等;块设备则是指支持寻址,以块为单位读取数据的设备,典型的块设备如硬盘等;网络设备则是指网卡等设备。不管是哪种设备,向上都是抽象成文件,以文件的方式进行操作。每种设备都有对应的驱动程序,因此,设备管理模块还提供了统一的框架,以供这些驱动如同积木一样插入到该框架中,然后提供统一的接口供上层使用。

6)网络。网络模块主要是实现了对网络硬件的支持,即各种网卡驱动;其次还实现了各类网络协议,典型的如TCP/IP等;此外,网络模块还实现了网络包的处理机制,如Netfilter通过hook捕获网络包等。

(2)Linux内核的接口:系统调用

以上6个模块只是Linux内核的主要组成部分,除此之外还有很多其他的功能模块,如安全模块等。这些模块之间互相作用,并以系统调用的方式对外提供接口,如图1-2所示。系统调用和C语言的函数调用不太一样,C语言中函数调用只需要指明函数名,传入参数即可。而系统调用则需要用汇编实现,每个系统调用对应一个唯一的编号,调用时要将编号和参数填入指定的寄存器,然后使用INT 80来产生软中断。内核响应80中断,读取寄存器中的值,就知道当前调用的是哪个系统调用,参数是什么,然后执行对应的操作,并将执行结果填入指定的寄存器,中断返回。在上层调用80中断前,程序是在CPU的低权限级别(如x86系列的Ring3级别)执行,此时称为用户态。而当内核陷入中断处理时,是在CPU的高权限(如x86系列的Ring0级别)级别执行,此时称为内核态。

(3)Linux C标准库:glibc

综上所述,上层应用直接使用系统调用会非常麻烦。为此,glibc将Linux内核的系统调用封装成了C语言直接可以调用的函数,供上层应用调用,这样大幅简化了上层应用的开发工作。

除了封装系统调用外,glibc作为Linux下的C标准库,向上层应用提供三大类接口:第一类是符合POSIX规范的接口;第二类是符合C语言标准的接口;第三类是Linux操作系统的专有接口。这3类接口有的是基于系统调用来实现的,有的则是直接用C语言编程来实现的,但向上提供的都是C语言函数接口。因此,上层应用只要是使用C/C++语言来编写程序,就可以直接调用这些接口,非常方便。而其他语言所编写的程序,如Java程序,它的最底层的执行也是基于glibc的,例如Java中JVM就是使用C++语言开发的。因此,一般情况下,在周围应用和Linux内核之间,还隔着一个glibc库。

尽管glibc是周围应用同内核之间的桥梁,但它也并不是不可替代的。安卓系统就没有使用glibc,而是开发了一个轻量级的C语言库Bionic来替换glibc,上层的应用调用Bionic所提供的接口来工作。但即便是这样,Bionic所提供的功能仍然和glibc类似,因此,不管是使用哪个C语言库,其功能总是类似的。

glibc遵循的是LGPL协议,LGPL比GPL宽松。如果上层应用直接调用glibc的接口,不修改glibc自身,则上层应用无须遵守LGPL协议,无须公开源码,这就满足了很大一部分希望闭源的开发者的需求。但有的情况下,一个应用需要同时开发内核驱动和上层应用,按照GPL协议,内核驱动必须遵守GPL,要开放源码。但是,内核驱动往往和硬件紧密相连,包含了硬件厂商的诸多技术细节,从厂商的角度肯定是不愿意公开的。

那怎么办呢?安卓走出了一条让硬件驱动规避GPL的道路。它实现了一个通用驱动模块,该模块只负责上层应用与硬件设备之间的命令和数据的传输,不管具体传输的内容,也不实现具体的业务逻辑,业务逻辑放到上层的用户态应用程序去做。这样就将原来在内核中实现的驱动,提升到用户态程序来完成。

由于通用驱动模块增加了新的系统调用,上层应用要基于glibc来使用这些新的系统调用,会比较麻烦;同时glibc自身也比较庞大,在移动设备上的性能和效率并不令人满意,需要修改做适配;再加上glibc的LGPL协议对于商用来说,也还是有诸多限制。基于这三点原因,安卓的开发者Google就直接摒弃了glibc,实现了一个新的C语言库Bionic,Bionic相对更轻量级,更适合在资源受限的设备上使用,并采用了限制更少的开源许可证的方法。

这样的话,安卓的上层应用可以基于Bionic来开发,硬件驱动则可以基于通用驱动模块和Bionic来开发。上层应用和硬件驱动都不需要遵守GPL和LGPL,不需要开放源码,从而成功地绕开了GPL和LGPL。

(4)CentOS 8中的Linux内核

下面以CentOS 8为例,查看实际发行版中的内核信息。

1)打印内核名称,命令如下。

2)打印内核release信息,命令如下。

上述输出结果中,4.18.0表示4.18内核系列的第0次修订版,其中4是主版本号,18是次版本号,次版本号为偶数表示稳定版,为奇数表示开发中的版本,主版本和次版本号合在一起,表示内核的系列,此处为4.18系列,0为修订次数;193表示4.18.0内核的第193次微调patch;el8是发行版标识,表示Red Hat Enterprise Linux 8;x86_64为CPU信息,x86架构下的64位CPU。

3)打印内核version,命令如下。

上述输出结果中,SMP是Symmetrical Multi-Processing的缩写,中文翻译是“对称处理”技术。PC上使用的多核处理器,或者服务器上的多CPU等,都属于SMP。这里打印SMP信息,说明Linux内核支持多处理器(核)技术;Fri May 8 10:59:10 UTC 2020是内核编译发布的时间,计时采用UTC时间,Fri表示星期五(Friday),May表示五月。

UTC的全称是Universal Time Coordinated,中文翻译是“世界统一时间”或“世界标准时间”,它和北京时间一样,是一种计时方法。

4)查看CentOS 8的内核文件,命令如下。

系统打印内核文件名,如下所示。

上述输出结果中,vmlinuz表示可引导的、压缩的Linux内核,注意最后一个字母是z,不是x。后面的4.18.0-193.el8.x86_64是内核的release信息,前面已经解释过,不再赘述。该文件头部自带解压工具,因此它可以实现内核自解压,如果在外部使用gunzip等工具是无法对其进行解压的。

5)查看内核文件大小,大约8.6MB左右,命令如下。

为了防止Linux内核体积因为功能的增加而快速增大,同时为了节约资源,Linux内核支持内核模块机制,可以将非必需的Linux内核功能制作成内核模块,这些内核模块是一个个的单独的文件,并不和Linux内核文件组合在一起,在Linux内核运行时,将内核模块文件动态加入Linux内核。

内核模块的目录位于/usr/lib/modules/目录下,会根据每个release的名字创建一个子目录,如4.18.0-193.el8.x86_64,它保存了该release内核的所有内核模块文件。例如e1000.ko.xz是一个典型的内核模块压缩文件,它是虚拟机网卡驱动,其中ko是内核模块文件后缀,xz表示这是一个采用xz压缩格式的压缩文件。

此外在/boot目录下,还有一个用于rescue(救援)的内核vmlinuz-0-rescue-c728625b6fee4703af663d7a424019c9。该文件的内容和vmlinuz-4.18.0-193.el8.x86_64是一样的。

为什么要放置两个同样的内核文件呢?这是因为,后续使用CentOS 8的过程中,很有可能会重新配置内核,并重新编译内核,这样就会重新生成vmlinuz-4.18.0-193.el8.x86_64。但有的时候由于错误的内核配置会导致CentOS 8出问题,甚至启动不了,此时,可以在系统启动时,在GRUB菜单中选择使用vmlinuz-0-rescue-c728625b6fee4703af663d7a424019c9来作为CentOS 8的内核,这样就能回到原点,便于改正错误。

3.root文件系统

Linux将一切抽象成文件,因此,整个Linux系统就是由很多很多各种类型的文件组成的,包括普通文件、目录、符号链接、设备文件等。每个文件都有一个路径,在Linux中,所有路径都有一个共同的起点——root目录(根目录),在命令中用一个斜杠/来表示。使用ls命令可以查看/目录的所有内容,如下所示。

/ 目录下分布着多个子目录,如果使用ls查看这些子目录,又可以看到它们下面还有子目录或者其他文件。因此,“ root文件系统 ”就是指: / 下所有的文件和目录的集合

root文件系统包含哪些目录和文件呢?文件系统层次化标准(Filesystem Hierarchy Standard,FHS)做了明确的规定,大多数的Linux发行版都会遵守这个规定。FHS的最新标准是3.0,可以访问http://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html获得更多详细的信息。

按照FHS的规定,/boot路径下要放置引导程序和内核。同时,根据FHS对root文件系统的定义,root文件系统的内容必须足以引导、还原、恢复和/或修复系统。因此,从这个角度来说,root文件系统是包含引导程序和内核的。

但是,从功能的角度来说,引导程序用于内核加载前的准备和配置、内核实现了操作系统的核心功能,文件系统则主要面向周围应用。从实现的角度来说,通常情况下,/boot下的内容同 /目录下其他目录的内容是分别存储在不同的分区上的。

此外,root文件系统和NTFS、Ext3等文件系统,虽然都是文件系统,但它们的含义完全不同。前者是指文件的集合,后者则是指文件在存储设备上的组织方法及数据结构。

如图1-1所示,root文件系统从功能划分上可以分为程序运行环境、Shell以及周围应用这三大部分,具体说明如下。

(1)程序运行环境

程序运行环境是指运行该程序所需要的加载程序、动态链接库等。以ls程序为例,使用ldd查看ls的依赖库如下:

如上所示,ls命令除了自身程序外,还依赖很多的动态链接库,典型的如libc.so.6等,这些库都位于/lib64下,主要是由glibc库所提供的,此外,ls命令的运行还需要ld-linux这个动态加载器,它负责解析和加载ls命令所依赖的动态链接库。因此,上述动态链接库及相关配置文件等,就构成了ls命令的运行环境。

(2)Shell

Shell是包裹在Linux内核及运行环境之外的一层“壳”,如图1-3所示。Shell是用户同Linux系统交互的程序,用户要运行哪个程序,都是通过在Shell中输入命令来实现的。

(3)周围应用

周围应用是指Linux上的应用程序,如图1-3所示,按照功能可以分为图形系统、办公套件、开发工具、服务器、虚拟化、娱乐和其他等七大类,这些应用绝大多数是GNU项目,这也是为什么Linux严格意义上应该称为GNU/Linux的原因。Linux充分相信用户,它给用户最大的自由,它是一个可以高度定制的系统,用户可以对内核进行配置,只开启必要的内核功能,从而进一步精简内核的体积;用户也可以对周围应用进行定制,选择是否需要图形界面,选择要安装哪些应用程序。

图1-3 Linux程序层次图 R0pZc6znLRn0ZrN4Nb51g/vNIobrOGZjVJ0+Ba0CKDee6sbQ7FW1t+Ur1nc72kBI

点击中间区域
呼出菜单
上一章
目录
下一章
×