首页 理论教育深入剖析Linux内核及设备驱动

深入剖析Linux内核及设备驱动

【摘要】:图3-3Linux内核整体框架③bridges:桥梁层。Linux内核通过统一的接口操作设备,这样就屏蔽了各个设备的差异,从而降低了和硬件的耦合关系。Linux内核为了简化实现,将共享资源的进程作为线程。考虑到外部事件需要对执行流程进行转换,另外不同的应用执行流程之间需要切换,这就要在Linux内核的逻辑层抽象出调度器这一逻辑功能。

通过分析图3-3,可以了解Linux内核是如何一层一层的将设备功能转换为用户功能的。按照这种层次的角度可以将Linux内核分为五层,分别如下:

①user space interfacea:该层是Linux内核直接面向用户层的接口,实现用户层需要的各种功能。该层在各个Linux内核版本中尽量保持一致。该层可以屏蔽各个版本内核在功能实现的细节差别,从而为应用层提供统一接口,降低应用层和内核之间的耦合度。

②virtual subsystems:该层为虚拟子系统层。所谓虚拟实际是一种高级抽象,是将下层(逻辑层)不同的具体实现中体现的数据和操作再做进一步抽象,单独形成的一层。该层主要目的就是为了形成实现无关的一层,使得整个系统不依赖于具体的逻辑实现。使上层系统从复杂多变的实现中解放出来。不仅提高了系统实现的灵活度,也降低系统耦合度。

978-7-111-49426-3-Chapter03-3.jpg

图3-3 Linux内核整体框架

③bridges:桥梁层。Linux内核中各个功能模块之间不是独立的,会有交叉功能的需要,比如文件需要映射到虚拟地址空间,网络文件系统的实现,这些功能都是需要跨越不同功能的实体。当跨越不同功能实体时,需要在二者之间建立关联,也就是桥梁。另外某些功能本身就需要有很多的实体,比如在处理器之上的执行实体进程(process),而功能内部的多个实体之间也需要相互沟通,这样就需要桥梁层的实现来解决这个问题。总之当需要沟通或者转换时就需要在bridges层实现。

④logical:该层负责功能的逻辑实现。某一个具体的功能可以有很多种实现,而这些具体的实现都是在logical层。比如Linux的文件系统有ext2和ext3不同的实现逻辑,而ext2和ext3都是在logical层中。可以说该层是Linux内核中最多样的一层,但是这些多样实现通过设计良好的接口去封装,使功能之间并没有耦合,因此没有增加系统的复杂度

⑤hardware interfaces:该层是Linux内核的最底层,其直接面向具体的硬件设备,是将硬件的具体功能提供给Linux内核的接口层。该接口层将设备的功能分类,并抽象出统一接口给Linux内核使用。Linux内核通过统一的接口操作设备,这样就屏蔽了各个设备的差异,从而降低了和硬件的耦合关系。

Linux内核各层体现不同程度的抽象,越往上层抽象程度越高,覆盖面越大,也越接近于应用的需求;越往底层其体现的特殊性就越多,覆盖面越小,越接近于具体的设备。有了这些层次的概念,对了解Linux内核是很有帮助的,在具体的实现中需要明确模块是在哪一层,具体管理的抽象实体是什么,掌握了这些,基本的框架就能理解了。

现在可以通过具体功能的例子,看看Linux内核是如何通过各层的抽象,使用设备实现用户功能的。

1.程序执行(processing)功能

processing功能对应的硬件实体是CPU。CPU是执行指令的单元,它可以接收外部事件(通过中断Interrupt)。指令执行需要操作数,相同操作指令结果的多样性通过操作数的变化来实现。操作数可以从memory中读取,这就可以通过使用相同的指令而从不同的memory地址取操作数,实现操作结果的变化,从而增加了系统的灵活性。另外执行逻辑的变化可以通过跳转指令完成,同样通过从memory读取指令地址,可以实现多变的执行逻辑。这样不同的操作数和跳转地址组合成一块memory,这些不同的memory(称作栈)就可以产生多变的系统执行流程和结果。CPU通过栈指针寄存器可以指向不同的memory的位置。为了减少寄存器,软件对该寄存器操作流程进行特殊规定,这样通过硬件和软件结合,使得CPU可以在不同的指令执行流程和操作数之间切换。从CPU的角度来看,它就是按照指令的要求,在不同的执行流程中进行切换并执行指令;从操作系统的角度考虑,这些不同的软件执行流程就是线程(thread)。Linux内核为了简化实现,将共享资源的进程作为线程。图3-4展示了Linux内核中进程间(不同的执行流程)的切换,就是通过内核栈的改变来实现的,具体是通过switch_to函数实现。

978-7-111-49426-3-Chapter03-4.jpg

图3-4 Linux进程切换实现逻辑

有些执行流程的切换是由CPU自动完成的,比如中断、异常,这些都是系统为了满足外部事件,以及安全性、容错性的需求。有了中断之后系统就能对外部事件进行管理。由于不同的CPU中断处理存在差异,这就需要软件上抽象出中断管理的接口。图3-3中硬件接口层中的interrupt核心就是实现该功能的,具体的CPU只要实现相应的接口就可以在Linux内核中实现对中断的管理。

考虑到外部事件需要对执行流程进行转换,另外不同的应用执行流程之间需要切换,这就要在Linux内核的逻辑层抽象出调度器这一逻辑功能。该功能负责在不同线程之间选择合适的线程来让CPU执行(即切换到相应的执行流程中),以完成对应的任务。注意调度器实际是个选择器,其按照一定的算法选择合适的任务,具体的切换执行在Linux内核中是由switch_to函数实现的。Linux作为多用户系统,调度器的算法和性能十分重要,因为其影响到所有用户的感受。

应用层需要Linux内核能够执行应用程序,而在应用程序中不仅包含指令执行流程还包含程序操作的资源,比如打开的文件,地址空间中的内容以及对文件的映射等。这些需要Linux内核将它们作为一个整体进行抽象,这就形成了进程的概念,这在Linux内核中就是虚拟子系统层中的task。同样在应用接口层也需要相应的接口对task进行操作,比如创建、设置不同的执行指令等。

大型的任务需要分多个线程或者进程来完成,这样可以降低系统复杂度,这就提出了多个线程或进程之间沟通并同步的需求,这部分就是bridges层的功能。

2.内存管理(memory)功能

内存管理主要是对RAM的管理,是Linux内核中很大的一部分功能。应用层的程序需要任何时刻都能以应用程序自己设定的地址访问数据,并且应用程序应该只能知道自己的地址。这样从应用程序自身地址的角度考虑,不同的应用程序必然会使用相同的地址做不同的事情。而物理内存作为唯一资源就无法实现同一地址存放不同的内容。这就产生了矛盾,也就需要应用程序的地址和物理内存的地址不能是相同的概念,这之间的鸿沟需要通过一种转换进行解决,这样就产生了MMU。由于应用程序的地址空间不直接对应于物理内存的地址,所以在Linux内核中形成虚拟地址(virtual address)。Linux内核需要对所有应用层任务的虚拟地址空间进行管理,这部分就形成了virtual子系统层中的virtual memory。当然对虚拟地址空间的管理不只是管理地址,同时还要管理相应空间中存放数据及数据的属性,不同性质的数据在地址空间中不应该有交叉和重叠。Linux虚拟地址组织结构如图3-5所示。注意虚拟地址空间是和task相关联的,并且其中的数据是有分别的,每块虚拟空间中存放的数据都有不同的属性和操作方式。

978-7-111-49426-3-Chapter03-5.jpg

图3-5 Linux虚拟地址组织结构

物理内存是一个被动器件,只是接受地址和数据进行读写的操作,具体数据的属性并不关心。物理内存究竟如何管理是个需要仔细考虑的问题。管理总是要考虑粒度的,如果过大就会有粒度内部浪费的问题,如果过小就会消耗更多的资源来进行管理。权衡下来,硬件的体系结构通常将最小的内存管理单元定义成页(page),把页大小(即粒度)的设置留给系统软件完成。可以由系统软件根据管理的物理地址空间以及内存的大小来进行设置。ARM就可以设置4KB或者64KB不同的页大小。(www.chuimin.cn)

当然仅是页管理不能满足系统所有模块对内存使用的需求,这就需要在页的管理之上将数据进行结构的和细粒度的管理,这在Linux内核逻辑层中形成了slab内存管理,为内核其他模块提供管理内存的结构。slab分为kmem_cache和kmalloc两种形式,kmem_cache提供结构化的内存管理,kmalloc则提供细粒度的内存管理。

虚拟地址到物理地址的变换,硬件上由MMU来实现,其中也会涉及管理页的大小,ARM下MMU的实现逻辑如图3-6所示。其中各级页表也都是存放在内存中,一级页表的地址存放在系统寄存器中,对进程的切换时同样需要切换页表。

978-7-111-49426-3-Chapter03-6.jpg

图3-6 ARM MMU实现逻辑

需要注意的是对CPU来说,只要有页表MMU,就能通过虚拟地址完成实际物理地址的具体操作,CPU本身并不需要对虚拟空间进行管理。虚拟空间管理则是完全针对虚拟地址进行的。由于虚拟地址和task是关联的,所以实际上,虚拟空间管理在Linux内核中就是对所有用户进程虚拟空间的管理。

3.存储(storage)管理

存储管理针对的硬件设备是外部磁盘,如IDE、SATA、SCSI等。随着嵌入式的发展,外部存储设备又增加了Nand、SD卡等。这些存储设备的特点是大多硬件要求以块为单位进行操作,这就已经定下了管理粒度即块。存储设备的这个特性自然的形成了block device和block driver作为硬件接口层。该硬件接口层只是完成以块为单元的数据操作。

数据在存储设备中如何组织分配等更细致的问题,就需要逻辑层处理。这部分处理细化出各种文件系统,当然某些数据库也是在逻辑层实现的。文件系统屏蔽了块设备的细节,而且将数据组织并抽象出文件的概念。为了更好地组织、管理和查找文件,可以将多个文件放入一个目录,这又形成了目录的概念。文件系统管理的是这些逻辑层面的概念,以及对应到实际设备块的映射逻辑。具体的块操作完全由设备接口层完成。

由于逻辑层有各种各样的文件系统,则需要在虚拟层再进行抽象,屏蔽各种具体实现的差异,从而形成虚拟文件系统模块。这样用户接口层就可以通过虚拟文件系统的统一接口对不同的文件系统进行操作。

又由于外部磁盘读写比较慢,而内存读写速度要快得多,这样在逻辑上做成页缓冲可以提高性能。在层次上页缓冲涉及虚拟文件系统和页管理,属于交叉功能,所以应该在bridges层实现。

4.网络(networking)管理

网络管理最底层是网络设备,相当于网络协议分层的物理层。其他层的网络协议是在物理层之上的,同样的网络设备上可以传输不同的上层网络协议封装的数据。所以在层次上,物理层之上的协议归入逻辑层,而物理层的设备归入设备接口层。

再向上同样需要抽象出对协议族的管理和对应用层的socket接口来简化用户的操作。

5.系统(system)管理

Linux内核中有各种各样的系统。为了提升系统在不同情况下的适应能力,通常各种系统都会有一些参数和状态信息提供给用户或者系统管理人员,进行查看、修改、调优。这就需要Linux内核设计统一的接口,可以让各个系统模块的开发者来添加相应的参数,同样需要为应用开放统一的操作接口和方式。随着Linux的发展,最终产生了proc和sysfs两个大的接口系统进行该类操作。该部分实现会散布在各个系统功能中,本身的层次概念可以参考图3-3,但不是必需的。

6.用户界面(user interface)管理

用户界面直接关系到用户体验的部分,设备也是最复杂多样的。比如键盘、鼠标、显示器、音频设备、摄像头等各种各样的设备,这些设备没有统一的数据格式和标准,如果一定要找这些设备的共同点就是数据有时效性。数据的意义和时间是关联的,任何数据会因为时间的改变而意义不同,用《信号与系统》中的名词就是时变系统。如何在系统中抽象并管理这些设备是比较复杂的问题。Linux内核大牛们用了一个很精巧的办法,就是首先使用高级别的抽象概念设备文件,该类设备都归结为字符设备(char device);其次将操作也抽象处理成为统一接口,而将具体设备的属性完全留给底层的设备驱动来进行管理。这样应用层程序就可以通过统一的字符设备(char device)文件进行操作。

下层的实现可以针对设备的特点加入更多的细节进行管理,这样Linux内核中就针对不同设备的特点进行归类,形成input、audio、frame buffer、video等各种各样的设备类型。这些设备类型的具体实现一般是归入逻辑层。

逻辑层之下就是具体设备的驱动,归入设备接口层。在逻辑层之上,可以将与设备传输的数据进行抽象或者格式化,形成系统统一的规范,比如input event处理等,将这部分作为虚拟层。

这样各个层次紧密结合,既可以适应广泛的设备,又可以对应用提供统一操作接口。当然对具体类型设备的操作要遵循相应类型设备的操作规范。

注意在图3-3中只有char device出现在用户空间接口层,这并不意味着块设备不可以直接对用户应用程序开放。只是这种开放通常只针对像fdisk这种格式化或者文件系统相关的工具。逻辑上块设备更应该是在文件系统之下,作为数据存储的基础。

对各个层进行深刻理解,有助于内核以及驱动开发人员更加明确地理解系统各个功能的实现机制和设计思路,通过理解每层的抽象概念可以更好地跳出细节理解系统。