Linux内核实时性

2024-08-19

Linux内核实时性(精选7篇)

Linux内核实时性 第1篇

在许多实时系统中, 比如分布式控制系统, 网络的实时性起着非常重要的作用。为了保证实时网络的运行, 网络数据包的传输延迟时间应尽可能的小。在过去有大量的研究是关于用规定的硬件和软件来设计硬实时系统, 包括field bus和一些复杂的实时操作系统。但是现在, 工业以太网TCP/IP被广泛应用于实时系统间的通信。但它却有大量的延迟和内在的抖动。如果它被用于普通的操作系统, 如Linux这种广泛应用于嵌入式实时系统但没有实时操作设计的操作系统, 其中的不确定性也会随着增加。

通过对实时系统的观察, 实时操作系统庞大的功能中只有少数部分的功能是经常被使用的。把整个Linux转变成实时的操作系统是一项庞大的工作。因此我们建议只针对某些特定功能比如处理数据包, 改写Linux内核来满足实时的条件。通过研究Linux内核, 我们可以很容易地发现中断处理时间对于网络延迟性能起着至关重要的作用。本文针对处理网络中断, 提出一种简单有效的处理方法, 就是通过修改特定的中断来控制亟待处理的实时数据包。

Linux中的网络中断分为Top Half和Bottom Half。Top Half又由两部分组成:从网络设备复制数据包到DMA和分析其结构类型。Bottom Half传送数据包给TCP层。大部分的数据包都是在Bottom Half处理的。因此如果不能保证Bottom Half快速执行, 则Linux也就不能实现实时。但不论数据包的优先权有多高, Bottom Half都是按顺序进行处理的。本文提出对需要实时处理的数据包进行标记, 确保在Top Half就进行处理。标记实时数据包可以通过在数据包的某个区域内做标记, 然后修改内核来识别该标记。在Top Half处理实时数据包是通过从Bottom Half复制和传送与该实时过程相关的内核代码到Top Half来实现的。本文介绍了上述实现过程以及实验的结果。

(二) 研究现状

虽然TCP/IP是应用最广泛的协议, 但它的实时性却不好。因此很多研究人员努力寻找能通过改变TCP/IP中的某一层性能, 比如MAC层, 网络层, 传输层, 来减少不确定性的方法。MAC层的修改主要是去掉在数据包传送过程中由于发生冲撞而产生的延迟。BRAM, MBRAM就是这种方法。但这需要所有的网络节点都要参与这个新算法。ST-II和RSVP主要是解决网络层的延迟。ST-II是用于多媒体通信, 数据传输速率和延迟都能得到控制。对于实时通信提供了有效的机制, 但对其他协议又存在兼容问题。RSVP通过对每个网络节点分配带宽来控制延迟。这同样也要求所有参与的节点都要遵从RSVP算法。修改传输层也会遇到不确定的因素。RTP是最常见的算法之一。它能够控制延迟但由于它易丢失数据而不适合用于工业以太网。

另外一个研究方向就是实时操作系统。如果使用特殊设计的实时系统, 通信延迟引起的抖动就可以减少。但这样的系统一般很昂贵而且对Linux的兼容性不好。而对于Linux的实时性的实现, RT-Linux不失为一种选择。RT-Linux是在硬件与Linux内核之间插入RT内核, 并且把Linux内核当做一个RT任务。RT-Linux代码修改量小而且定义简单。尽管如此, 包含的代码仍然很多, 如果需要实时的功能还要重新设计设备驱动。

(三) 实时网络中断进程

1. 实时以太网结构和IP包

本文定义了一个特殊的以太网结构real-time frame来处理实时数据, 这些数据都是根据以太网标准定义的。RFC894定义以太网使用0x800, IEEE802.3使用0x0800。本文打算使用这个值来定义这个结构。我们用一些特殊值作为实时结构的标记, 实际的实时数据是在IP头传送。为了兼容性, 我们使用IP头中很少使用的部分:identification和DF flag。这两个部分起初是用于数据包分段的。IP_DF flag是用来设置禁止分段的。当IP_DF设置好, 则identification就没有作用了。该算法使用这两个字段传送实时数据包。设置IP_DF flag, 使用identification作为实时信息字段, 它可达到16bit。由于实时数据包需要及时处理, 因此可把它放在网络中断过程中的Top Half中处理。若在关键时刻, IP头部不能包含足够长的消息就很难进行处理。图1是上述介绍的实时框架。我们采用0xff00表示实时框架的type字段。

2. 实时框架/消息的处理算法

图2所示是在接收端处理实时数据包的算法。本文主要分析Bottom Half结构的头部。如果数据包类型是实时结构, 则迅速提取消息并进行处理。然后把它交给Top Half来处理。否则排队等待直到网络Bottom Half处理结束。

在发送端, socket一般不工作, 这是因为我们需要修改以太网的头部, 在type字段中加入0xff00, 这样就能使实时结构与一般的以太网结构区别开来。数据包结构的生成分为两步:分配结构缓冲区和将头部写进结构中。Linux使用sk_buff管理结构缓冲区。实际上结构缓冲区是通过使用alloc_skb () 来分配的。图3 (a) 为与sk_buff相连的的分配的结构缓冲区。结构缓冲区的写入可用skb_reserve () 和skb_push () 。skb_reserve () 修改data指针指向IP header如图3 (b) 所示。Ethernet header也使用skb_push () 函数进行写入, 如图3 (c) 所示。

为了能在网络设备中直接使用I/O, 我们需要逻辑设备接口。全局变量dev_base是net_device结构中的一张表, net_device结构是由已注册的逻辑网络设备分配得到的。图4表示了通过dev_base和内核模块调用net_device结构的过程。把它加入到内核中, 就可以调用像skb_push () , skb_reserve () , alloc_skb () 这样的内核函数, 还有内核数据结构, 如dev_base。通过这些内核函数该模块可以建立实时结构, 通过eth () 发送到接口, 而该接口是由net_device结构中的指针指向speedo_start_xmit函数实现的。

(四) 算法性能

为了评估上述算法的性能, 可以作为Linux内核 (2.4.20版本) 的外部模块运行该算法。发送模块根据参数, 发送端IP, 接收端IP, 接收端以太网地址, 要发送的结构的数量和实时结构在普通结构的比率等方面来构造实时结构。两种实时消息类型定义如下:VAR_UP和VAR_DOWN分别是增加的内核变量和减少的内核变量。通过由网络设备驱动预先注册好的程序把生成的实时结构加到DMA中。OUT指令发送到设备时, 该结构也就被发送了。图5为发送的全过程。数据包到达的中断使响应的Top Half从DMA复制其结构到系统缓冲中。若结构头部类型是0xff00的实时结构则被提取出来, 并立刻处理, 其他的结构则在系统队列中排队等候, 直到Bottom Half对他们进行处理。

在实际实验中, 我们采用两种带有Linux内核模块不同类型的平台, 一种是普通的Pentium PC, 另一种是基于ARM的嵌入式系统, 如图6所示。Pentium平台有800Mhz Pentium III的CPU和256Mb的죁内存댂。嵌入式平台有ARM 920T处理器, 128Mb内存, 带有以太网接口。将带有前面介绍的网络模块的Linux内核移植到这两种实验平台上。

我们已经测量了数据包处理的时间和实时结构中处理的成功率。每37μsec发送1K的数据包。图7 (a) 为处理时间随着实时结构比例的增长而减少。但是如果数据包中超过40%都是实时结构, 处理时间则会处于饱和不变状态。这是因为实验中使用的中断处理器过于简单以至于37μsec的间隔足够处理每个数据包。随着中断处理器的发展, 我们希望处理时间在40%之后能够继续得到提高。

图7 (b) 为另一个实验结果, 即实时结构处理的成功率。因为我们不考虑上层溢出的情况, 所以如果接收到的结构超出了接收端所允许的范围, 则将它们丢弃。随着实时数据包百分比的增加, 处理的成功率也应该提高。若不采用实时结构, 发送10K和1K数据包的成功率则分别低达3.1%和32.1%。若采用前面介绍的算法, 发送1K数据包且其中70%为实时结构时, 成功率可达到100%。然后成功率会随着数据包数量的增加而减少。尽管如此, 即使是10K的数据包, 实时结构超过90%, 成功率也能达到90%左右, 而无实时结构的成功率只有3.1%。有趣的是含有100%的实时结构的性能却不如含有90%的实时结构。对于实时结构, 实际在Top Half上花费了大量时间, 而在此处中断是不起作用的。对于只含有90%的实时结构来说, 剩下的10%非实时结构不能立刻进行处理, 而要放到Bottom Half中, 但至少这些结构不会阻碍优先级更高的实时结构到达系统, 因为它们有一小段中断无效周期。对于100%的实时结构来说, 所有的结构到达系统后, 将在Top Half上花费大部分的时间, 而在这段时间内, 它们会通过使中断无效而阻碍其它的结构到达系统。

(五) 结束语

本文实现了可以及时处理网络中断的系统。我们已经观察到网络中断处理的主要延迟是由于Bottom Half的推迟而导致的。把关键部分从Bottom Half中移到Top Half中, 就可以实现更好的实时响应。本文提出的技术可以确保该实时响应只对内核中做很小的改动。该方法已经实现并进行了测试。实验结果表明同时发送10K数据包时, 该方法能够成功处理90%, 而没有实时处理能力的系统只能成功处理3.1%。

摘要:Linux并不太适合实时环境, 而且把它重新设计成实时系统是非常复杂和具有挑战性的任务。需要提出一种方法能把像Linux这样的大型系统转变成实时系统时而又做尽可能少的改变。经研究发现大多数的实时系统的实时性能只是针对于目标设备。文章研究的方法就是针对目标设备修改原系统。此方法更具有实用性, 因为它并不对整个系统做多余地的改变, 而且能满足大多数的实时系统的要求。以网络设备为例, 假设该设备不断地接收到实时数据包, 而且要立即处理, 减少中断时间。

关键词:Linux,实时,中断

参考文献

[1]Park, J.:A Study on RTP/RTCP based real-time protocol over Ethernet for distributed control system[J].Proceeding of16th DCCS2000, Sydney, Australia (2000) .

[2]Yodaiken, V.:New frontiers for embedded computing.[J].Proceeding of17th International Conference on VLSI Design, India (2004) .

浅谈Linux内核的发展历程 第2篇

Linux操作系统的出现和UNIX系统有着很大的关联。Unix操作系统是由Ken.Thompson和Dennis Ritchie于1969年夏在美国的贝尔实验室的DECPDP-7小型计算机上开发的一种分时的操作系统。当时Ken Thompson为了能让他的电脑上运行他所酷爱星际旅行游戏, 在1969年夏天乘他夫人回家乡加利福尼亚渡假期间, 在一个月内开发出了Unix操作系统的原型。当时使用的是BCPL语言, 后经Dennis Ritchie于1972年用移植性很强的C语言进行了改写, 当时在大专院校很受欢迎。正是UNIX的出现和发展, 促使了Linux的出现和发展。它诞生于1991年的10月5日由计算机业余爱好者芬兰人Linus Torvalds设计开发, Linux操作系统刚开始时并没有被称作Linux, Linus给他的操作系统取名为FREAX, 意思是比较怪诞的。后来, 他把新开发的系统上传到ftp.funet.fi服务器上得时候, 当时管理员不太喜欢这个系统的称呼, 后来感觉这是Linus Torvalds开发的系统, 于是取名与Linus同谐音的名字Linux, Linux这个名称从此就开始流传下来, 一直沿用至今。

二、Linux版本号的定义

过去的Linux内核版本的命名规则中, 我们来以2.6.30的版本号为例, 2代表的是主版本号, 6代表的是次版本号, 30代表的是有一些改动的修正版本号。主版本号和次版本号的变动表示着重要功能的变化。在版本号中, 假如第二位是一位偶数, 则说明这个版本是稳定的版本, 假如第二位是一个奇数, 这说明这个版本是一个不稳定的版本, 需要从中添加一些补丁, 来完善这个系统, 当一个版本达到稳定版本的要求。但是, 自从2.6的版本开始, 内核命名的规则发生了一些变化, 在次版本号后面的还添加了一位, 比如说2.6.30-rc6, 意思就是说这个版本就是2.6.30-rc6的测试版本。测试成功之后, 就发行2.6.30这个版本, 即为稳定版本。

三、Linux内核的组成

内存管理、进程管理、进程间通信、虚拟文件系统和网络接口这5部分是linux内核组成的基本部分。

(一) 内存管理

内存管理主要的是对系统的物理内存进行合理有效的管理, 同时, 内核进行进程调度时候, 对系统中的内存资源进行合理地分配。虚拟内存管理是Linux内存管理的一大优点, 一般运行的进程保存在内存当中, 而其他没有运行的进程则保存在外存中, 这样有利于节省内存的使用资源, 优化了系统的性能。当系统急需内存的时候, 内存管理会进行相应的调度, 对内存和外存磁盘中的程序块进行交换, 减轻对内存的需求压力。

(二) 进程管理

进程管理的主要的目的是要对系统的进程进行合理的调度, 即是对CPU的访问进行合理的控制, 利用调度算法的优先级对进程的活动进行合理有序的管理。Linux是一种多任务操作系统, 意思是一个CPU在宏观上能同时处理多个任务, 这个功能的实现是基于进程管理实现的。在Linux操作系统中, 当有多个任务的请求的时候, 系统会根据调度算法对进程进行分配相应大小的时间片, 当一个进程所分配的时间片执行完了之后, CPU会进行进程上下文的切换, 进而转向执行其他满足调度规则的进程, 由于系统处理的速度非常快。

所以, 在宏观上, 我们感觉系统是在同时处理多个任务。其实, 在微观上, 其实进程管理在一个时间点上, 只处理一个进程。正是由于Linux系统具有进程的管理功能, 大大提高了系统的整体性能。

(三) 进程间通信

进程间通信主要就是要进程与进程之间进行数据的共享和数据的交换, 进程间通信主要用于控制不同进程之间在用户空间的同步、数据共享和交换。由于不用的用户进程拥有不同的进程空间, 因此进程间的通信要借助于内核的中转来实现。一般情况下, 当一个进程等待硬件操作完成时, 会被挂起。当硬件操作完成, 进程被恢复执行, 而协调这个过程的就是进程间的通信机制。

(四) 虚拟文件系统

Linux内核中的虚拟文件系统用一个通用的文件模型表示了各种不同的文件系统, 这个文件模型屏蔽了很多具体文件系统的差异, 使Linux内核支持很多不同的文件系统, 这个文件系统可以分为逻辑文件系统和设备驱动程序:逻辑文件系统指Linux所支持的文件系统, 例如e xt2、e xt3和fat等;设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。

(五) 网络接口

网络接口提供了对各种网络标准的实现和各种网络硬件的支持。网络接口一般分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。网络设备驱动程序则主要负责与硬件设备进行通信, 每一种可能的网络硬件设备都有相应的设备驱动程序。

四、Linux内核发展历程

当Linux的第一个稳定的工作版本被推出的时候, 紧接着就是Linux0.10版本和0.11版本的推出, 并发布到网上, 免费供人们使用, 随着开发与维护人员的不断努力, Linux系统逐渐向可靠稳定的系统进行靠拢。在1994年3月的时候, Linux1.0的出现, 使得Linux的用户群体的数量越来越多, 同时Linux的开发与维护人员也正在逐渐壮大。

自从1994年3月第一个Linux1.0正式版本被发布以来, Linux内核的发展非常的迅猛, 每经过一段时间就有新的版本的出现。内核的体系机构在不断的完善, Linux在社会的方方面面中的应用在不断增多, 对硬件支持在不断的扩充, 对跨平台的移植性有了大大的提高。

在1996年的6月的时候, Linux2.0内核的发布, 该操作系统已经可以支持多处理器, 该操作系统已经开始进入实际的应用领域当中, 而且大约有350万人在使用Linux操作系统, 在当时, 这个数量还在不断的增加。在1998年的时候, 是Linux发展最快的一年, 各大公司从以前对Linux的不太搭理的态度, 到开始对Linux进行投资, 特别是IMB对Linux的支持, 使得Linux成为当时服务器操作系统的重要的一员。由于当时Linux在全球的风靡, 中科院和新华科技发展了红旗Linux, 使得国内许多的用户开始认识并使用Linux操作系统。

参考文献

[1]左万历, 周长林.计算机操作系统教程 (第二版) .高等教育出版社.

[2]滕至阳.现代操作系统教程.北京:高等教育出版社, 2000.

[3]陈莉君, 张琼声, 张宏伟译.深入理解Linux内核.DANIEL P.BOVET&MAR CO CESATI.

基于Linux内核源码的解析方法 第3篇

1 Linux内核系统

当前计算机技术已经广泛的应用在生活和工作中,从计算机系统本身进行分析,其中的操作系统是对计算机系统中的硬件、软件等进行组织管理的系统,为计算机系统的使用者提供相应的计算机服务,以及访问接口[1]。操作系统是计算机系统的最基础的软件系统,也是计算机系统的支持系统,作为计算机的操作系统Linux系统和核心为Linux内核。

Linux内核是计算机从磁盘加载到计算机内存中的一个程序,其在计算机系统运行的过程中,处于运行的状态,而且其运行直到计算机系统关闭。Linux内核中的大部分都是在C语言的基础上完成的,但是也有部分是使用汇编软件编写的。Linux内核在Linux系统中有非常大的作用,地位也非常高,在整个Linux系统中,包括了4个子系统,分别为用户进程、系统调用接口、Linux内核以及硬件,Linux内核占主导地位。在计算机系统运行的过程中,Linux系统的应用,帮助计算机系统的运行解决安全性问题以及运行效率的问题,Linux内核在Linux系统中,是硬件与用户之间交流、沟通的关键性部分[2]。

随着信息技术的发展,在大型软件代码分析的过程中,静态代码分析需要对各个组成模块、文件、函数之间的关系进行分析,传统的分析只是从代码层分析各个函数、变量之间的关系,对于Linux内核源码而言,编译选项为软件提供了配置的可能性,但是针对Linux内核源码这种大型的软件系统或者过于庞大的系统而言,进行静态代码分析,缺少自上而下的分析线索。

针对Linux系统进行分析,编译选项与源文件之间的关系分析,采用传统的静态代码分析,也存在一定不足的地方,为了有效地分析Linux内核源码编译选项、文件、函数之间的依赖关系,需要通过功能描述和物理源文件之间存在的关系,再有效地分析这些函数、变量之间的关系。为了将这些问题解决,需要在Linux内核源码的基础上,产生一种编译选项到文件、函数关系分析的方法,这种方法也就是在Linux内核源码的基础上增加Makefile生成文件和Kconfig配置文件的分析。

2 依赖关系

在Linux内核源码中存在着一些的依赖关系,这些关系从数学的角度进行分析如下:

假设在Linux内核源码存在一个关系模式为R (U),其中U{A1,A2,A3,……,An)则是这个关系的全集,X、Y则是U的子集,令t以及u为关系模式R的任意两个组成元素,如果出现t和u在X的投影中出现t[X]=u[X],则可以得出t[]Y=u[Y]。也就是t[X]=u[X]≥t[]Y=u[Y],此时则说明X决定Y,或者是Y受到X的牵制,又或者Y依赖X,为此将其记作X→Y。

在计算机系统的操作(Linux系统)系统中Linux内核源码中,不是所有的源代码与编译选项存在关系,针对Linux系统功能进行分析,Linux内核源码中的与编译选项有关的源代码大多是具有可定制性功能的代码[3]。其结构如图1所示。

从图1可以知道,Linux内核源码中存在的依赖关系,可以分为3种层次。第一层次是以配置文件(Kconfig)定义的编译选项以及编译菜单;第二层是每一个编译选项相对应的文件;第三层是最地层文件相对应的函数。在这3个层次中,每一次都存在一定的依赖关系,例如函数关系,代入上面的关系式,也就是R (U),其中U{A1,A2,A3,……,An}为函数的全集,X、Y则是U中任意两个子集,如果在函数调用的过程中,令t以及u为关系模式R的任意两个组成元素,在一定的条件下t可以满足t[X]=u[X],并得出t[]Y=u[Y],则说明在这个函数之间,在t环境下,存在依赖的关系。将这个关系应用在Linux内核源码中,可以解释为在一定的条件下,函数X可以用于函数Y的调用。在Linux内核源码的编译选项、文件函数之间的关系分析中,也存在这样的依赖关系。

在Linux内核源码编译选项中,每一层存在的依赖关系并不是这一层中存在的所有子集的关系,这种依赖关系是相对应的。在Linux内核源码编译选项中存在的依赖关系,内部依赖是文件之间的相互引用,而且这种引用表现为Linux内核源码中大量跨文件函数的调用。通过对Linux内核源码的编译选项、文件、函数之间的依赖关系进行分析,Linux内核源码内部存在复杂的关系,这种关系可以称之为上层与下层之间的依赖关系。Linux内核源码中编译选项、编译菜单之间的关系,由配置文件、生成文件进行定义[4]。

3 配置文件、生成文件的编译选项依赖关系

在Linux内核源码中Makefile生成文件针对编译选项与文件之间的依赖关系进行定义和描述,Makefile生成文件对编译选项与文件之间的依赖关系进行的定义和描述,其实是Linux内核源码与编译选项依赖关系的细化。在Linux内核源码中Makefile生成文件组织如图2所示。

为了更好地认识Linux内核源码与编译选项、文件、函数之间存在的依赖关系,对编译选项之间的依赖关系、编译选项与文件之间的依赖关系、文件与文件之间的依赖关系等进行分析[5]。

在编译选项之间存在的依赖关系主要为编译选项名字、类型、选项与菜单之间的关系。在Linux内核源码中Kconfig配置文件的形式如图3所示。

其中在Linux内核源码中Kconfig配置文件的语言对于现编译选项类型的解释说明为:

从以上Linux内核源码中配置文件的语言说明进行分析,可以知道编译选项中编译类型中的关键词为bool、tristate、string、hex、int。而“def-bool”、“def-tristate”为default与bool、trisate的缩写。

在Linux内核源码中编译选项与菜单之间的依赖关系如图4所示。

在实际工作中Linux内核源码配置文件编译的以Linux内核源码/Kconfig为起点进行工作,其他编译选项之间的依赖关系的分析也是以上方法进行。Linux内核源码中编译选项、文件、函数之间的依赖关系分析,是为了方便计算机系统的操作系统以及其他系统的运行可以有保障,在运行中出现故障,可以查找故障发生处等。

随着计算机技术等信息技术的发展,计算机技术运行系统、操作系统等的性能和结构会进一步地深度优化,准确地掌握这些操作系统中编译选项内部存在的依赖关系,可以提高计算机系统操作系统的工作效率等。

4 结语

Linux系统是计算机系统的基础支持软件系统,在Linux系统中内核源码是核心,而Linux内核源码从源码到使用,需要经历配置、编译、安装3个环节。在Linux内核源码的编译选项中,关系复杂,存在一定的依赖关系,针对基于Linux内核源码的编译选项、文件、函数之间的依赖关系进行了解释分析,通过分析,证明Linux系统在计算机系统中的地位,以及重要性,在信息技术不断进步的过程中,Linux系统、计算机系统等会得到进一步的优化改善,将运行效率提升。

摘要:对Linux内核源码的编译选项、文件、函数之间依赖关系以及相关的内容进行分析研究。

关键词:Linux内核源码,编译选项,函数,依赖关系

参考文献

[1]李鹏飞.Linux内核编译机制分析以及优化研究[D].西安电子科技大学,2014.

[2]朱宇.基于嵌入式Linux操作系统的实时性研究[D].长安大学,2012.

[3]江梦涛,潘朋飞,宋杨,荆琦.Linux内核中编译选项、文件以及函数之间依赖关系的解析方法[J].计算机科学,2014,(S1):445—450+454.

[4]李超.基于Xen的Linux内核源码调试器设计[D].上海交通大学,2013.

Linux内核实时性 第4篇

关键词:文件系统,监控,虚拟文件层,中间层

1引言

随着信息技术和计算机技术的飞速发展, 计算机已广泛应用于科研, 政府等重要机构。同时, 与之带来的是数据安全隐患, 同时也给企业内部的信息安全带来巨大的威胁。因此, 国内外目前通过采取对文件系统的实时监控, 从而降低信息安全的隐患。而现有的基于Linux平台采用的文件系统监控技术多是挂钩系统实现, 但是这种方式会存在一些缺陷, 效率不够高, 且监控信息冗余较多。针对当前存在的缺陷, 本文将在Linux内核中的虚拟文件层和下层文件系统之间, 设计一个中间层。它不仅能监控所有文件的操作, 而且可以根据用户的要求过滤所需要的文件和数据信息, 并在过滤操作完成之后, 转发给下层文件系统操作。相比挂钩系统调用, 该方法效率更高, 监控信息也更加的全面。

2虚拟文件层

Linux之所以能够成功, 是因为它能够识别不同操作系统的不同类型的文件系统。我们可以将存放在Linux内核目录下的不同操作系统的不同文件类型的磁盘分区, 并把针对不同文件系统的操作整合到同一个框架中, 从而提供统一的文件操作接口, 即虚拟文件层。虚拟文件层是一个抽象的内核软件层, 可处理Linux内核文件系统能够识别的所有系统调用, 并且能为与之相应的不同的文件系统格式提供一个通用的接口。由于虚拟文件层主要由一组抽象的文件操作组成, 因此该层可以向用户隐藏针对不同文件系统的各种实现细节, 同时能够为用户提供一个文件系统操作界面, 以及统一的文件操作接口。尽管虚拟文件层隐藏了相关的文件系统实现细节, 然后它依然能够实现所有针对不同文件系统的相关操作, 用户可以通过相关的系统调用完成对文件相应的操作, 如read () 、write () 、close () , 然后再将这些系统调用转发到虚拟文件层中。之后系统调用的完成则不是由虚拟文件层进行操作, 而是需要继续将系统调用转发, 并由下层的文件系统来实现。

3中间层

若要对基于Linux内核的文件系统进行监控, 那么挂钩相关函数是必不可少的, 也就是对文件系统或者内核代码进行相应的修改。然而我们知道, 对文件系统或者内核的任何操作都是几乎不可能实现的。首先, 我们是很难掌握到关于内核和文件系统的代码;其次, 因为任何原因而可能出现的误操作都会严重破坏系统;因此, 通过挂钩函数实现文件系统的监控必然是不可行的。

相比之下, 虚拟文件层是文件系统一个抽象的内核软件层, 通过提供统一的接口来实现转发操作, 并屏蔽不同的文件系统所带来的实现细节问题。但考虑到虚拟文件层只负责文件系统调用的转发操作, 无法实时的对文件系统进行监控, 而我们又不能修改文件系统或者内核代码;基于上述原因, 我们可以在虚拟文件层和具体文件系统层之间, 自行设计并添加一个中间层, 整体功能与虚拟文件层相近, 新添加的中间层不仅可以实现系统调用的转发操作, 被上层文件操作调用的同时进行调用下层的文件操作, 还可以完成对文件系统的监控。该中间层通过向文件系统增加相应的特性, 从而达到不需修改任何内核或文件系统的代码, 便可实现对文件系统的监控。图1显示了增加中间层之后的文件系统架构。

中间层是利用vnode堆叠技术设计的。vnode是UNIX操作系统中虚拟文件系统表示一个文件或者目录的对象, 任何操作系统能够识别的针对不同文件类型的操作均可通过vnode提供的接口进行访问。vnode接口抽象了Linux内核操作文件系统的具体细节, 并形成虚拟文件层, 之后由虚拟文件层负责转发对文件系统的一系列操作, 从而达到隐藏对下层文件系统的具体操作细节的目的。

为了实现对文件系统的监控功能, 我们可以利用vnode的堆叠技术原理, 在不改变内核和文件系统源代码的基础上, 对vnode接口进行分层处理, 实现对文件操作的两次堆叠。存在两个虚拟层, 必然也就在内核和文件系统之间有两个vnode接口, 其中上层接口是虚拟文件层的抽象层, 而下层接口则是需添加监控功能的抽象层。中间层的设计技术将在以前的vnode之下, 实现一个新的vnode接口。中间层实现的vnode接口通过vnode接口接收上层传递过来的系统调用, 对包含的参数进行分析, 然后将具体文件操作转发至下层具体文件系统, 从而进行数据访问和文件操作。

4结语

相比较于目前国内外针对Linux文件系统监控的研究现状, 本文以vnode堆叠技术为基础, 在虚拟文件层和下层具体系统之间设计出更为高效的中间层, 以达到监控文件系统的目的。虚拟文件层通过提供统一的接口, 隐藏了下层具体文件系统的操作信息。

参考文献

[1]Power R.2002 CSI/FBI Computer Crime and Security Survey[Z].San Francisco:American Computer Security Institute, 2002.

Linux内核实时性 第5篇

Linux是最著名的类UNIX开源操作系统,它诞生于1991年,由芬兰赫尔辛基大学的学生Linus Torvalds开发出最早的版本,并发布在互联网上。Linux诞生之初就加入了GNU计划,并遵循GPL许可,由全世界的程序员不断的开发和改进,如今,Linux已日臻完善,成为世界上主流的服务器操作系统之一,它以良好的结构和高度的稳定性,得到了广泛的应用。

2. Linux内核功能

Linux是一个真正的多用户多任务操作系统,并具有良好的兼容性、稳定性和强大的可移植性,这些功能都源于它的内核。目前非常流行的手机Android系统,就是建立在Linux内核之上。Linux内核是连接系统软、硬件的一个支撑平台。

Linux内核是系统的核心,管理着整个计算机系统的软、硬件资源,控制整个计算机的运行。Linux内核由各功能模块构成,包括:进程管理(Process Management)、内存管理(Memory Management)、模块管理(Module Management)、网络管理(Network Management)、中断管理(Interrupt Management)、虚拟文件系统接口(VFS Layer)、文件系统(File System)、设备驱动程序(Device Driver)、进程间通信(Inter-process Communication)、系统启动(System Init)等模块。Linux内核直接影响操作系统的性能,在新版本的内核发布后,及时升级系统内核,有利于提升系统的整体性能。

3. Linux的版本

Linux的版本有狭义和广义之分,狭义的版本专指Linux的内核版本号;广义的版本是指以内核版本为基础,包含各公司开发的应用程序和系统设定与管理工具的完整操作系统。如著名的Red Hat、Debian、Turbolinux、Ubuntu、红旗、中标麒麟等等。

不同的发行版,内核来源都是相同的,Linux的内核版本号早期采用带两位小数的数字表示,0.01是第一个版本,之后出现0.02一直到1.0版本。从1.0版本开始,采用A.B.C三段数字来表示,“A”代表主版本号,“B”代表次版本号(偶数代表正式版,奇数代表测试版),“C”代表末版本号。从2011年3.0版本之后,虽然采用的仍是A.B.C格式,但是所代表的含义有所变化,“B”没有奇偶数的区别,而是随着新版本的发布而增加,“C”代表一些Bug修复、安全更新、添加新特性和驱动的次数。

Linux的内核可以通过www.kernel.org官网下载,截止2015年9月26日,最新的内核稳定版为4.2.1,最新支持嵌入式系统的LTS版(长期支持版)为4.1.8。新的内核加入一些新的功能和特性,包括“实时内核补丁”特性,该特性可实时修补内核,而无需重启系统,提高了系统稳定性和工作效率,同时,改进系统图形支持,改良了CPU风扇控制,改进储存系统,集成了更多的硬件驱动。

4. Linux内核的升级与编译

Linux新内核的每次发布,都伴随着一些关键或重大的改进和新特性,因此,及时升级系统内核也变得十分必要。

4.1 Linux内核的升级

本次内核升级过程在Red Hat Linux Enterprise 5系统下进行。

4.1.1 启动系统启动Red Hat Linux Enterprise 5,进入桌面环境,打开终端程序,在命令行输入:

[root@bogon~]#uname-r

2.6.18-8.el5xen

显示内核版本号为2.6.18版,为较早期的版本,有必要进行内核升级。

4.1.2 下载内核安装包文件

进入Linux内核官网(www.kernel.org),查找出最新的内核版本为4.2.1,由于本机版本较旧,为保证升级成功率,选择相近的3.8.7版进行升级,成功后,可再次升级到最新的4.2.1版本。输入wget命令,下载内核安装包文件:

4.1.3 解压安装包文件

将下载的文件解压到/usr/src/kernels目录下,如无此目录需先创建,并用命令tar解压:

[root@bogon~]#mkdir/usr/src/kernels

[root@bogon~]#tar–xvf linux-3.8.7.tar.bz2–C/usr/src/kernels

4.1.4 安装或升级ncurses动态库

要执行make menuconfig命令,使用菜单配置内核,必须先安装或升级ncurses动态库,ncurses是一个能提供基于文本终端的图形互动功能动态库。可以通过系统自动升级ncurses库,前提是系统已经安装了yum程序,也可以下载ncurses的rpm安装包来手动安装。

4.1.5 清理旧的内核升级文件

在升级新的内核文件之前,要删除之前编译所生成的文件、配置文件、备份文件等,用命令make mrporper实现。

[root@bogon~]#cd/usr/src/kernels/linux-3.8.7

[root@bogon~]#make mrporper

4.1.6 通过菜单方式配置内核

[root@bogon~]#cd/usr/src/kernels/linux-3.8.7

[root@bogon~]#make menuconfig

当前面安装完ncurses动态库后,输入上述命令,窗口会出现文本交互菜单,在一些基本选项里输入“Y”确认,“N”放弃,“M”编译等选项,进行编译前的设置工作。

4.2 内核的编译

编译前的准备工作完成之后,接下来进入内核编译步骤。

4.2.1 编译内核

进入/usr/src/kernels/linux-3.8.7目录,执行make bz Image命令进行内核编译。

[root@bogon~]#make bz Image

命令执行成功后,会在/usr/src/linux/arch/i386/boot目录下生成一个新内核的映像文件bz Image。

4.2.2 编译内核模块

[root@bogon~]#make modules

4.2.3 安装内核模块

[root@bogon~]#makemodules_install

该过程一般需要花费半小时以上的时间。

4.2.4 安装内核到系统

将编译好的新内核,安装到Linux系统中。

[root@bogon~]#make install

4.2.5 启动选项的设置

Linux内核升级后,并没有将旧内核删除,因此在重启系统时,在GRUB菜单中会出现新、旧两个内核选项,通常新内核启动项位于第一行,旧内核启动项位于第二行,系统默认启动项为旧内核。此时,就需要通过修改开机菜单配置文件“/etc/grub.conf”,将默认启动选项设置为新内核,系统才会在启动之后,自动启用新内核。用vi命令打开配置文件。

[root@bogon~]#vi/etc/grub.conf

找到第一行的“default=0”参数项,将参数设为1,表示启动选项默认到第二行上,即系统启动时会自动加载新内核。

4.2.6 验证新内核

重启系统之后,要看运行的是不是新内核,可以通过命令验证。

[root@bogon~]#uname–r

3.8.72-1.el6.i686

表示新内核已经安装完成,并开始工作。

5. 结束语

Windows系统的内核与系统是合为一体的,要想升级操作系统,很多情况下需要重装系统,对服务器的管理和维护都不利;而Linux的内核与系统应用是既独立,又紧密相关的,可以在不需要重装系统的情况下进行内核的升级,有利于服务器的管理和维护。

首先,Linux内核版本相对于发行版来说是独立的,是由Linux的创始人Linus的团队负责研发,并单独发布;而基于Linux内核之上的系统应用,是由各公司独立开发,内核的开发与外部系统应用并不同步。

其次,内核版本与发行版本又是紧密相关的,Linux系统的新功能与特性,都依赖于内核,只有及时将系统内核升级,才能运用这些新功能,而且,可以不需要重装系统进行升级,给服务器管理带来了便利。

Linux系统对计算机硬件配置的要求十分低,部署了Linux系统的服务器可长时间稳定的运行,而且,在Linux内核4.0以后的版本,添加了“实时内核补丁”的特性,可以在服务器不重启的状态下,将内核更新,使系统达到最佳运行状态,这也是其它服务器操作系统目前不具备的。因此,及时将Linux内核进行编译升级,更有利于Linux服务器的稳定和管理效率的提高。

参考文献

[1]曹江华.Linux常用命令手册[M].北京:电子工业出版社,2015.315-317.

[2]黄照鹤.Linux指令范例速查手册[M].北京:清华大学出版社,2014.432-434.

[3]赵炯.Linux内核完全剖析[M].北京:机械工业出版社,2006.405-407.

[4](美)索贝尔.Linux命令、编辑器与shell编程(第3版)[M].北京:清华大学出版社,2013.203-206.

Linux内核实时性 第6篇

Linux内核是采用deflate压缩规范进行压缩的, 这是一种无专利限制的非常好的通用压缩算法, 它采用lz77+Huffman编码的组合。对Linux内核解压的流程和defalte压缩规范做了介绍, 并对相应的数据结构以及实现解压的外围算法和代码作必要的分析。

2 内核解压缩流程

如图1所示。

boot/compressed/head.S调用boot/compressed/misc.c中的decompress_kernel () 函数。

对于小内核[zImage], decompress_kernel () 调用setup_normal_output_buffer () 设置解压缩输出的目标起始位置output_data=0x100000;对于大内核[bzImage], decompressed_kernel () 解压的目标代码分两块存放, 所以调用setup_output_buffer_if_we_run_high () 设置两个目标地址low_buffer_start=0x2000和high_buffer_start=max (&end+0x3000, 0x100000+low_buffer_size) 。低地址部分涵盖0x2000~0x90000, 大小是low_buffer_size;高地址是取&end+0x3000和0x100000+low_buffer_size中的较大值, 这样既能保证离自解压代码结束地址end后留有0x3000的堆空间 (HEAP_SIZE) , 又保证高解压数据部分距离0x100000有不小于low_buffer_size的距离, 这样就能使合并已解压的内核的时候不会被low_buffer的数据覆盖掉。此外, 因为在现阶段内核尚未正式运行, 因此解压过程中的内存分配是使用compressed/misc.c中的malloc () 实现的, malloc () 维护着堆的当前指针free_mem_ptr, free_mem_ptr的最初位置就是&end, 结束位置free_mem_end_ptr是两种情况之一, 小内核是0x90000, 大内核是&end+0x3000。malloc (size) 分配size字节的内存, 成功则free_mem_ptr后移size字节, 如果发现已经到了堆末尾 (free_mem_ptr>=free_mem_end_ptr时) 就报错。堆内存的回收函数free () 被定义为空操作。因为每次解压完一个压缩数据快block后, inflate () 函数都会调用gzip_release () 还原free_mem_ptr的值为初始值 (&end) 的, 而每次解压一个block前会先调用gzip_mark () 备份这个初始指针。

decompress_kernel () 调用linux/lib/inflate.c中定义的gunzip () , gunzip () 再调用inflate () 进行解压。压缩数据block (块) 为单位进行解码, inflate () 循环调用inflate_block () , 每次解压一个块。inflate_block () 先读取块头的几位, 第一位是结束块标志 (最后一个块) , 这个值会返回给调用者inflate () , 以决定是否结束循环;紧接着的两位是块类型, 块被分为3种类型, dynamic、stored和fixed, 分别表示为10、00、01, inflate_block () 根据不同的值分别调用inflate_dynamic () 、inflate_stored () 、inflate_fixed () 进行解码。stored块是没经过压缩的, 它的数据会原封不动地拷贝到输出端, 另两种类型的块的解压过程都会经过两个阶段:调用huft_build () 构造huffman解码表和调用inflate_codes () 进行解码, 这也是下面代码分析中要着重讨论的。

由于内核是压缩后与boot/compressed/head.S和boot/compressed/misc.c的目标代码连接的, 因此boot/compressed/misc.c中的decompress_kernl () 不可能调用到linux/lib/inflate.c中的gunzip () 及其他函数, 那这个函数怎么调用的呢?

在misc.c开始部分有#include"../../../../lib/inflate.c", 从而在预编译阶段, 就把linux/lib/inflate.c中的函数代码都包含进misc.c中了, 最后都编译在misc.o中, 因此解压阶段调用的是自己的gunzip () 。

3 deflate压缩规范

Linux内核解压缩的核心是inflate () , inflate是针对用deflate压缩规范的压缩数据进行解压的算法。因此, 要了解Linux内核的解压过程, 有必要了解deflate规范。deflate采用lz77压缩算法加上Huffman-Shannon-Fano[HSF]编码的组合。经典的lz77滑动窗口数据压缩算法使用一个32KB的滑动窗口window, 对于待压缩数据的下一个输入串, 寻找在滑动窗口中重复匹配的最长字串的起始位置和长度, 使用长度length和距离distance表示, 普通字符literal的值 (包括不可打印字符) 为0~255, 256用来表示block结束标记, 257~286表示长度码 (配合使用附加位) , 0~30表示距离码 (配合附加位) , 在此基础上对字符/长度和距离使用HSF编码。HSF编码是Huffman编码 (即最优可变长度前缀码) 的变种, 它除了Huffman编码的特点外, 码字是按二进制数值递增的。

inflate算法构建类似树的多级解码表, 每个子表使用编码中的连续几位形成的二进制为索引。首先读入初始长度 (比如最小码长) 的位串, 作为一级表的索引, 根据编码搜寻到的表项或者是叶子节点 (可以直接得出编码代表的值) , 或者指向下一级子表, 并指示接着要读入的编码位数 (作为下一级表的索引) , 最终达到解码的目的, 使字符/长度位串 (0~286) 和Huffman码一一对应, 根据Huffman码作为索引查询Huffman多级解码表最终可以确定原始值。inflate算法就是在lz77编码基础上对literal/length和distance再用Huffman编码。因此解码的过程必须是简而言之这样的过程:读入基本长度的位串 (比如Huffman编码的最小长度) , 以其值为索引检索literal/length的Huffman表, 得到1个huft结构指针t, 比较t->e的值, 有如下几种情况:

(1) =15表示本块block (inflate压缩数据由一个个数据块block组成) 结束;

(2) =16表示是0~256的字符值 (包括不可打印字符) , 直接输出到Window缓冲区;

(3) >16表示当前表示中间表, 以t->e-16的值为索引查询t->v.t指向的下一级huffman表;

(4) <16表示这个编码是代表最终值 (叶子节点) 的, 以t->v.n码得到长度的基本值, 再读入t->e个附加位, 两个值相加得到最终的lz77编码中的长度值。

distance的得到过程与上同。两个值length和distance都解出来后, 把window[ptr-distance]开始的length个字节拷贝到window[ptr]处, 并更新ptr指针 (后移length个位置) 。

注:这里的ptr指缓冲区 (或称为滑动窗口) 的当前位置。每次达到最大值 (Window填满) 后输出被解压的数据到目的位置output_data, 然后Window指针清0[缓冲数据仍保留直到被覆盖]。另外这里的huffman码使用的倒序编码, 即最小值的位LSB (least signified bit) 在最左边, 最大值的位MSB (most signified bit) , 详见代码分析。

比如对于最小长度是7位的9位编码1010110 01来说, 实际编码是这样的10 0110101, 先读入7位0110101为索引访问第一级huffman表, 得huft结构指针t, t->e应该=16+2, 表示还要读入2位索引, 于是再读入10, 以它的值2为索引检索t->v.t指向的二级表 (二级表一般有很多张, 1个一级表项就可以指向一个二级表) , 得到新的t, 这时t->e因该是小于16的, 指示number of extra bits (附加位数) , t->v.n指示基本值。

4 数据完整性校验

压缩解压过程必然伴随着数据完整性的验证, gunzip中使用了32位循环冗余校验外加数据长度的检查。在压缩数据的过程中, 压缩程序会不断地计算crc值, 直到压缩过程结束, 然后在最后一个压缩数据块之后写入32位长度的crc校验值和32位的数据原始长度值。在解压时, 解压程序也会计算crc值, 当解完最后一个数据块后gunzup会读入最后一个压缩数据块之后的原始crc值和原始长度值, 把解压生成的crc值和解压的数据长度分别与之对比, 都一致的话就表明解压成功了。以下是32位CRC校验使用的多项式:

5 HSF编码

5.1 概述

deflate压缩规范使用的是Huffman编码的一个变种, 全称是Huffman-Shannon-Fano编码, 它除了具有Huffman编码的优点外, 还有个特点就是随着码长由小到大, 编码的二进制数值也是从小到大递增的, 同一码长下数值呈连续分布。这个特点使得只要确定了编码的码长, 就可以算出编码。

5.2 生成HSF编码的例程

这是来自RFC1951文档的一段例程, 演示了deflate中使用的Huffman编码的生成方法。学过数据结构的都知道, 一般的Huffman编码是变长前缀码, 是通过构造一棵Huffman树生成的。这里的HSF编码增加了两个附加规则:

所有给定长度的编码都是按字典序连续, 和它们所代表的字符顺序相同。短编码在长编码前面。

HSF的编码方法码长和码值是顺次增加的, 最小最短的码排在最前, 0作为第一个码长的编码, 同样长度的码数值连续递增, 等该长度的编码数达到预定值后, 先递增编码 (加1) , 然后移动一位 (方向取决于编码顺序) , 作为下个编码长度的首个编码。按照这种方法就能生成变长前缀码。

再来看上述代码, 代码5.1根据bl_count[]数组中预先确定的各码长编码数把各个码长的最小编码保存在next_code[bits]数组中 (bits代表码长) 。代码5.2根据next_code[]数组生成广义字符0~max_code的编码。从代码可知, 编码总是从0开始。

比如码长分别为3、4、5, 其编码数bl_count[3]=3, bl_count[4]=2, bl_count[5]=4。那么next_code[3]=000, next_code[4]=0110, next_code[5]=10100, 顺次下来的编码清单是这样的:

最后生成的编码依次是:000、001、010、0110、0111、10000、10001、10010、10011。

5.3 HSF解码表

5.3.1 HSF解码表结构

和传统Huffman编码算法使用的二叉树不同, HSF解码使用的是多级表结构, 每级表使用HSF编码中的连续几位作为索引, 找到表项, 这是一个huft结构, 如果已经读入了编码的所有码长, 那么这个表项指示的将是解码的值, 否则该表项指示下一级子表以及要读入的后续码长 (作为子表的索引) , 直至读完一个编码的所有码长。表结构如图2所示。

L是解压程序设置的解码表码长, 在构建解码表时如果超出实际码长的范围会被调整为最小或最大码长, 当L=最大码长时, 就只存在一级表了。

5.3.2 表项huft结构定义

多级解码表的每一个表项都是一个huft结构。

e是一个多用途字段:

(1) >16表示当前表项有子表, 接着读入 (e-16) 位作为索引查询v.t指向的下一级表。

(2) =16表示已达叶节点, 码值就是v.n, 直接输出到Window缓冲区。

(3) =15表示block结束。

(4) <15表示已达叶节点, 但是码值不是简单值。需要通过v.n得到基本值, 再读入e个附加位, 两个值相加得到最终的解码值。

b是本表项涵盖的码长, 也是解码过程中阶段性需要丢弃的位数 (解完一部分就丢掉) 。v是一个union类型, 当表项是非叶结点时, v.t指向下一级子表;否则, v.n指示了码值 (或者基本值) 。

5.3.3 解码表的内存分配与回收

解压阶段创建huffman解码表时使用malloc () 分配内存malloc () 维护一个堆指针free_mem_ptr, 指向下次分配内存开始的地址。堆位于内核上方的内存空间。

堆指针free_mem_ptr初始指向end, end是内核编译连接时连接程序生成的符号, 指向自解压内核的结束地址+1。

内存回收函数free () 是空操作, 实际的内存回收通过恢复堆指针实现的, 这发生于每次解码完一个块之后。

6 代码分析

(1) 解压缩主函数decompress_kernel ()

(2) 解压缩数据输出缓冲区的设置

1) 小内核的解压数据输出的目标地址指针output_data被设置为内存1MB处 (0x100000)

2) 大内核的输出缓冲区

如果是大内核, 解压数据有两个地方存放, 低部缓冲区low_buffer和高部缓冲区high_buffer。对于大内核, decompress_kernel () 调用setup_output_buffer_if_we_run_high () 函数设置moveparams结构参数, 其中包括了高、低两块缓冲区的起始地址和大小。

这个结构是head.S中调用decompress_kernel () 时压入堆栈中的, 函数返回后, 根据函数的返回值 (eax=high_loaded) , 不为0的话转去执行合并程序。合并程序根据这个堆栈中的缓冲区结构参数将两个缓冲区中的解压数据合并成完整的内核, 并放到内存地址从0x100000开始的地方。以下是setup_output_buffer_if_we_run_high () 函数分析:

3) 解压后设置两个解压缓冲区的大小

decompress_kernel () 调用gunzip () 解压内核成功后, 如果是大内核, 还要设置解压数据所在的两个缓冲区 (low_buffer和high_buffer) 的大小, 这是由下面的函数实现的:

(3) 读入压缩内核数据

1) 获取1字节get_byte ()

内核解压缩时是用get_byte () 读入字节数据。这是一个宏定义, 如下:

fill_inbuf () 的功能是初始化数据指针inbuf, 指向input_data, 大小是input_len。这里的input_data和input_len都是连接程序ID生成的符号, input_data指示压缩内核所在的内存地址, input_len是压缩内核的长度。首次运行get_byte () 时, inptr=insize=0, 所以调用fill_inbuf () 设置inbuf指针并返回压缩数据的第一个字节inbuf[0], 以后则返回后续字节inbuf[inptr++]。

以下是compressed/Makefile中连接脚本, 可以看一下input_data和input_len是如何产生的:

内核目标代码system首先被去掉elf文件头存为$$tmppiggy, 然后被压缩为$$tmppiggy.gz, 使用连接脚本$$tmppiggy.lnk连接为elf格式文件piggy.o, 该文件只包含一个数据段, 就是上述被压缩的内核$$tmppiggy.gz, 并引出符号input_data和input_len分别表示数据部分的地址和大小, ID程序把boot/compressed/head.o、boot/compressed/和自解压内核连接完成后。

注:inbuf[0]是压缩内核的第一个字节, inbuf[inptr++]指向后续字节 (inptr=1起) 。

2) 获取任意n位二进制 (n范围是1~32)

仅有读入1字节的宏get_byte () 还是不够的, 因为HSF编码是变长的, 有的码长不足1字节, 有的码长超过1字节, 因此编码是跨字节边界的, 需要有获取指定位长二进制的手段。

如何获取并操作跨字节边界的位串?通过定义NEEDBITS和DUMPBITS两个宏获取和丢弃指定数量的位, 实现对位操作的扩展。

b是解压程序中定义的32位变量, 作为读入位串的缓存, k指示其中的有效数据位数。NEEDBITS (n) 获取b中的n位, 它首先判断b中的数据位数k是否不足n, 当不足n时, 循环调用get_byte () 读取字节, 并调整k=k+8, 直至有效位数k>=n为止。此时b中的低n位 (0~n-1位) 就是解压程序所需的n位数据。DUMPBITS (n) 宏则通过右移位丢弃b中的低n位, 并调整k=k-n (表示有效位数减少n位) 。

通过以上的宏, 虽然每次还是读取8B, 但是一次可以对跨字节边界的n位进行操作了 (n=1~32) 。

(4) 输出解码数据

解压过程中, 使用32KB的滑动窗口作为输出的缓存。这是在boot/compressed/misc.c中定义的:

inflate.c中的解压函数在解码出一个字节数据后, 就拷贝到滑动窗口中 (slide[w++]=?) , 局部变量w保存窗口中的当前写入位置;而slide是定义为Window的宏。

当滑动窗口已满就要把其中的解压数据拷贝到输出缓冲区去, 这是通过如下代码实现的:

内核在输出解压数据的时候对滑动窗口中的解码数据进行crc校验值的计算。以下是输出代码清单:

在解压阶段, 程序维持着几个指针。首先是输入数据的指针inptr, 解码前通过inbuf[inptr++]读取数据并后移指针;其次是滑动窗口指针w, 解码后slide[w++]保存解码数据并后移指针w;最后是输出缓冲区指针, 小内核使用output_ptr, 通过out=&output_data[output_ptr];得到当前输出的起始地址, 输出后后移output_ptr指针, 大内核不使用output_ptr而直接使用并调整output_data指针。

(5) makecrc ()

该函数生成8位二进制1~256的32位crc码。crc_32_tab[i]中保存各字符的32位crc值。

参考文献

[1]Linux内核2.4.0源代码, 可以从www.kernel.org下载.

[2]RFC1951 (Request For Comment 1951) .

Linux内核实时性 第7篇

进程是程序执行的动态过程, 是系统分配资源的最小单位。而线程是更小的执行实体, 是进程的分支, 其本身不占用系统资源, 而是与其所属的进程共享系统的软硬件资源。创建和撤销另一个进程的工作可以由同一个线程完成, 多个线程可以同时进行。就绪、阻塞、运行, 这三种状态是线程的基本状态。

多线程技术旨在提高CPU的性能, 一个CPU同能能执行多个程序, 能分享同一个CPU的资源。但是多线程技术并不等于是多个CPU, 当两个线程同时需要同一个资源的时候, 只有一个能够得到, 另一个就要等待, 进而暂时停止, 直到资源限制方可继续。

使用多线程技术的原因有三, 其一, 与进程相比, 线程这种多任务操作方式, 能最大程度的节约系统的耗费, 其耗费大约是进程的1/30左右。其二, 线程之间方便通信。其三, 多线程作为一种多任务并发的工作机制, 能进一步提高应用程序响应, 改善程序结构, 提高多CPU效率。

2 Linux内核的技术特点

1) Linux采用分页式内存管理, 对于基本物理页面的管理采用Buddy算法, 对于任意长度的内核数据结构采用动态分配, 使用Slab算法;

2) Linux引入了虚拟文件系统层作为物理文件系统的接口, 支持文件延迟写、顺序访问、预读和内存影射文件, 使用buffer cache和page cache分别以盘块和页面为单位的两类磁盘访问数据;

3) Linux支持虚拟内存的使用, 共享和私有页面可以交换执行;

4) Linux在进程管理中内使用的核设计原则是不可重入式的。进程在内核态运行时, 仅在时间片用完、请求数据或等待I/O完成时触发调度, 需要的自旋锁和信号量很少。

3 Linux内核多线程的实现

3.1 Linux内核为多线程技术的实现提供的支持

多线程的实现需要操作系统的有效识别和支持, Linux2.5系列的内核针对多线程引起的调度问题专门进行了设计和优化。

首先必须对Linux内核进行正确的设置, 才能真正达到多线程处理的效果, 在配置时应将对称多处理器SMP勾选上, 并在BOOT中将通过引导选项acpismp=force来指定使用超线程。支持多线程的Linux对于信息的处理速度是实际物理处理器的一倍。

Linux内核对多线程处理器提供的支持如下:

启动时Linux将自动检测是否是多线程处理器, 如是, 启动逻辑处理器。

可重用旋转等待 (spin-wait) 的优化和同步变量的对齐。当使用多线程系统的时候, 为了取得良好的性能, 首先是对spin-wait loops的方式进行编程, 第二个是同步变量的对齐。

进程用户栈和内核栈的切换。进程因为中断或者系统调用而陷入内核态时, 进程所使用的堆栈也要从用户栈转到内核栈。进程陷入内核态后, 先把用户态堆栈的地址保存在内核栈之中, 然后设置堆栈指针寄存器的内容为内核栈的地址, 这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态时, 在内核态的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

3.2 调度器功能的优化

Linux2.6版本从调度系统的应用上, 其设计理念是满足实时性的要求和多处理机并行执行。因而新调度系统兼具有2.4版本交互式作业优先、高CPU使用率、轻载条件下调度/唤醒的高性能、基于优先级调度、公平共享、SMP高效亲和、实时调度和cpu绑定等调度手段等优势, 并具有其自身的特点:如可扩展性高、更好的实时性能、O1调度算法、子进程在父进程之前运行、新的SMP亲和方案等等。在诸多方面进行了进一步的改善。

3.3 Linux中的线程实现机制

Linux内核仍然使用进程模式, 真正线程的实现是在用户空间中, Linux内核只是为线程的创建提供了一个进程的模型。Linux为用户线程提供了可用的线程库, 本文以Linux Threads线程库为例进行阐述。

Linux内核中的进程控制块是task_struct数据结构, 它记录着进程所占用的系统资源。

在Linux Treads线程库中, Linux定义了struct_pthread_descr_struc用来对线程的数据结构进行描述, 并定义了一个全局数组变量, 名为_pthread_handles, 用来对进程所属线程进行描述和引用。此外, 还定义了两个全局系统线程, 名为_pthread_initial_thread和Pthread_manager_thread, 位于_pthread_handles的前两位。

创建线程是, 虽然线程库产生的仍然是独立的进程, 但是因为是通过克隆系统调用clone () , 而不是fork () 产生的, 因此产生的线程具有轻负载的特点。

用_pthread_main_thread表征_pthread_manager_thread的父线程, 所有克隆的线程, 预期父进程共享资源。struct_pthread_descr_struct属于双环链表结构, _pthread_manager_thread所在的链表仅包括它一个元素, 实际上, _pthread_manager_thread是一个特殊线程, errno、p_pid、p_priority三个域在Linux Threads中被使用。进程中用户线程均在_pthread_main_thread中被连接了起来。

经过一系列pthread_create () 之后形成的_pthread_handles数组将如下所示。创建的线程先在_pthread_handles中出现, 然后连入以_pthread_main_thread为首指针的链表中 (如图所示) 。

4 Linux多线程互斥问题

各线程之间可以共享系统的软硬件资源, 但有些资源一次只能为一个线程使用, 例如打印机、变量等, 这类资源称为临界资源。对于这些资源的调用, 被安排在某一代码段中, 此代码段可能被反复执行, 若有两个或两个以上这样的代码段要竞争共享资源, 此代码段即为临界区。线程之间的互斥可以保证资源的正确和完整性。线程之间通过互斥关系相互竞争, 但这与其本身无关。

Linux解决内核态中互斥问题, 有四种机制:屏蔽中断、原子操作、信号量和自旋锁。原子操作和中断屏蔽是硬件在处理互斥问题中最常用的做法。原子操作和等待队列可以看做是信号量互斥解法的基础层面, 此外, 信号量的实现又是以自旋锁作为基础。

4.1 屏蔽中断

cpu在必要的时候可以进行屏蔽中断。通过中断处理程序完成的称谓软中断。中断属于异步事件, 发生的先后与正在运行中的进程无关。中断的事件长短很重要, 过长的中断很容易造成数据的丢失。

因此, 在中断屏蔽后, 要求当前的内核应尽快执行完临界区代码。但如果开中断, 又容易造成互斥, Linux在解决这项矛盾时, 采用的是分段处理的方法。将关键性操作放在前段完成, 后段可以稍后执行, 或可以把多次后段执行部分中的相关部分合并执行。

对当前正在运行的进程进行中断, 不是随机发生的, 要满足严格的规则:

1) 正在内核态中被执行的进程代码是不能被中断的, 也就是只有在用户态中被执行的进程代码才能被中断;

2) 对于当前进程引起的异常处理代码, 不能影响中断处理程序的执行;

3) 中断处理程序可以中断任何一个进程, 也可以中断异常处理代码的执行。同时也可以若干中断处理程序交错执行。

4.2 原子操作

原子操作是一条汇编语言指令, 其大部分均属于芯片级。是指那些在执行的过程中不会被其他代码中断的操作。

Linux内核实现原子操作的函数很多, 分为正对Bit变量和整数变量两类, 不论是哪类, 在被内核代码调用后, 均不会被中断处理代码中断。这些函数的操作形式均与所属Cpu构架相关。原子操作包括读、写、加、减的功能等一系列操作。

4.3 信号量

Linux为了保护临界区, 设置了若干信号量, 只有得到信息的进程才能执行代码, 使用临界资源, 否则将进入等待队列。

信号量的结构被定义在semaphore.h中

Struct semaphore{atomic_t Count;

Int sleepers;

Wait_queue_head_t wait;}

核内的信息量有down () 和up () , 分别表示申请资源和释放资源。

1) count计数器。用以记录资源的个数。当为正数时, 表示有可用资源, 初始值为1;当为0或负数时, 表示无可用资源, 并有进程处于等待状态。当资源被使用时, count-1;当释放资源后, count+1;

2) Wakeup () 正在等待资源的进程处于休眠状态, 当资源被释放, Count为1时, linux内核会选在一个进程进行唤醒并使用此资源;

3) Wait_queue是Linux内核的一个基本功能单位。当没有可用资源, 进城被挂起, 并进入到队列中。

信号量的操作过程:Linux把count的初始值设为1, Linux选择一个进程使用此资源, count作减一操作count=0, 表示没有资源可用, 此时所有请求使用资源的进程进入到等待队列, 进入到休眠状态, 当有资源被释放时, count做加一的操作count=1, wakeup函数将进行唤醒操作, 被唤醒的进程使用资源。

4.4 自旋锁

自旋锁 (spin lock, 当临界代码较短时, 使用自旋锁是非常方便的, 因为它可以节省上下文切换的事件。当加锁时, 若已处于锁定状态, 应用一个死循环测试锁的状态, 直到成功取得锁。

自旋锁有两个基本组成部分:spin_lock_string, 用于对锁进行减1操作, 并循环检查锁值, 直到大于0;spin_unlock_string, 用于对锁负值为1。当有进程在一个cpu上运行时, 自旋锁被锁定。spin_lock的值减1, 当另一个cpu要运行此进程时, 会发现锁已经被锁上, 只有当锁解开, 值大于0时, 才能被另一个cpu使用。

特别需注意的是, 自旋锁非常容易出现死锁的情况。如果发生, 整个系统就会被挂起。

因此锁定的事件要尽可能短;不要在锁定时, 调用如访问用户内存、内核态分配内核、使用信号量等易引起休眠的操作。

参考文献

[1]陈李君.Linux操作系统内核分析[M].北京:人民邮电出版社, 2000.

[2]Linux Torva lds Linux Kernel Source Code2.4.2[Z].

本文来自 99学术网(www.99xueshu.com),转载请保留网址和出处

【Linux内核实时性】相关文章:

Linux内核驱动09-14

系统内核05-17

安全内核06-23

操作系统内核05-31

语文教学内核09-20

微内核操作系统07-14

证券内核制度发行研究论文04-15

证券内核制度发行研究论文提纲09-17

数据实时性09-22

实时识别05-19

上一篇:安全改进下一篇:顾客关系质量