- 1 -
中国科技论文在线
嵌入式 Linux 应用程序健壮性研究#
孙新贺,王晓辉*
基金项目:基金:国家863 计划(重点)项目项目名称:煤矿井下采掘装备遥控关键技术项目编号:
2008AA062200
(中国矿业大学信电学院,江苏 徐州 221008)
摘要:嵌入式 Linux操作系统以其出色的稳定性、可靠性在嵌入式系统领域得到日益广泛的
应用,嵌入式 Linux 操作系统现场应用复杂,控制对象多样,为了保证良好的控制效果,对
嵌入式 Linux 操作系统的应用程序的健壮性提出了更高的要求。本文提出了几种提高嵌入式
Linux 操作系统健壮性的方案,并详细阐述了具体的实现方法。应用程序经优化后,可提高
健壮性和运行稳定性,保证控制可靠性。
关键词: 嵌入式 Linux;健壮性;GCC;守护进程
中图分类号:
Study on Robustness of Program in Embedded Linux
Sun Xinhe, Wang Xiaohui
(School of Information and Electrical Engineering,China University of Mining and
Technology,JiangSu Xuzhou221008)
Abstract: For its excellent stability and reliability, Embedded Linux operating system is widely used.
Complicated scene, Variety of control object. In order to ensure control effect, higher requirement for
the robustness of embedded Linux applications was put forward. This paper presents several programs
to improve the robustness of embedded Linux, and describes the realization method. Applications
optimized using this method, can improve the robustness and stability of operation, and ensure control
reliability.
Keywords: Embedded Linux;Robustness;GCC;Daemon;
0 引言
随着嵌入式系统的发展,Linux 作为开源、免费、高性能的操作系统,在嵌入式系统领
域得到越来越广泛的应用。Linux 作为一个成熟的操作系统,其稳定性、可靠性都能得到良
好的保证。
目前,运行于嵌入式 Linux 操作系统上的应用程序多种多样,这些应用程序在操作系统
提供的平台上运行,各种服务都是由它们来处理,所以整个嵌入式系统的健壮性在相当大程
度上由这些应用程序的健壮性来决定。
应用程序的健壮性,也称为鲁棒性,是在异常和危险情况下保证系统生存的关键特性。
例如:健壮性良好的应用程序在用户输入错误、磁盘故障、网络繁忙、进程停止响应甚至有
意攻击的情况下能保证应用程序不崩溃、保证控制对象的安全。
应用于工业现场并承担控制任务的嵌入式 Linux 系统,其控制程序的健壮性直接影响到
整个控制系统的控制效果以及现场安全。所以,提高嵌入式 Linux 应用程序的健壮性是工业
应用的必然要求。
本文从应用程序的代码书写、编译、运行等方面提出了几种提高应用程序健壮性的方案。
- 2 -
中国科技论文在线
1 建立守护进程
应用程序在运行过程中,会遇到各种异常情况,而作为应用程序本身,只能处理部分异
常,对于影响整个应用程序进程的异常,例如系统调用失败导致的段错误等,应用程序本身
无法处理。另外,应用程序在运行过程中,可能因用户操作或者操作系统繁忙等原因造成该
应用程序停止响应,此时,该应用程序的控制对象可能会因得不到控制量而出现异常,甚至
对于某些重要的控制对象,可能会因缺少控制量输入而发生严重的事故,所以,在一些实际
应用中,需要去获取应用程序进程的运行情况,并根据其运行情况对应用程序进程的运行进
行必要的干预。
守护进程是 Linux 下的一种特殊进程,它运行于后台,并且独立于控制终端,一般用于
周期性的执行某种任务或者等待处理某些发生的事件,它不仅可以通过启动脚本启动,也可
以通过作业规划进程 crond 启动,还可以由用户使用用户终端启动,故其具有很高的灵活性,
可以方便的用来对应用程序进程的运行情况进行跟踪,在必要的时候对应用程序进程进行干
预。
本文将此种守护进程归纳为三类。
一类守护进程:该类守护进程只负责监视应用程序是否在正常运行,不参入任何控制工
作,只专注于发现应用程序是否已经因故障或者错误而产生进程结束信号,若发现应用程序
进程出现异常,则守护进程会立即采取必要措施来保证应用程序的正常运行。此类守护进程
提供的保护功能不算完善,但其需要的系统开销非常小,资源占用很小。
二类守护进程:该类守护不仅负责监视应用程序的正常运行,同时还对应用程序输出的
控制量进行监视。当应用程序输出的控制量明显出现异常,则该类进程会终止应用程序的控
制量输出,同时,该进程将缺省控制量输出来保证控制对象的安全性,同时给出警告,以通
知工作人员尽快处理应用程序的错误。该类守护进程开销相对一类守护进程要大,但其安全
性得到较大提高。
三类守护进程:该类守护进程除了具备一类守护进程的功能外,同时还具备相对完备的
控制量输出,该类守护进程相当于应用程序的一个冗余,即当应用程序的控制量出现问题时,
该进程会直接代替应用程序进行控制,以保证控制对象的正常工作。严格的说,该类进程不
能完全称之为守护进程,因为该类守护进程功能相对强大,结构相对复杂,但是其保护功能
非常完善,可以极大的提升控制的可靠性。
上述是三类守护进程,各自具备不同的特点,根据现场的实际要求灵活选用,可以在很
大程度上提高应用程序的健壮性。
2 安全使用内存
对于任何一个应用程序,其内存使用的方式都对整个程序的运行效率、稳定性有重大的
影响。例如:对于资源紧张的嵌入式系统,如果编写的程序造成内存泄露,运行的初始阶段
可以正常运行,但是当因内存泄露的积累而造成内存资源耗尽时,该应用程序便会崩溃;如
果是一个资源相对丰富的嵌入式系统,引起内存泄露的应用程序可能可以稳定运行较长时间
后才出现资源耗尽情况,此种情况在测试应用程序时期难以发现,但对实际应用会有重大影
响。
一般来讲,Linux 的内存的分配方式有以下几种[1]:
1、从静态存储区域分配:该部分内存在程序编译的时候已经分配完成,在程序的整个
- 3 -
中国科技论文在线
运行期间都存在,一般用于全局变量。
2、从栈分配:一般来讲,函数内的局部变量的存储单元都可在栈上创建,函数执行完
成后,相应的存储单元会自动释放。栈内存分配功能内置于处理器的指令集中,效率很高,
但是分配的内存容量有限。
3、从堆分配:该种分配方式就是所谓的动态内存分配,使用该种分配方式是提高程序
效率的基础。该部分内存需要程序员使用 malloc 或者 new 来申请任意大小的内存,同时,
程序员必须负责在内存使用完成后用 free 或者 delete 来释放内存,使用非常灵活,但是很容
易出现问题,实际上,应用程序使用内存出现的问题几乎都出在该种分配方式上。
在使用动态内存的过程中,一定要先保证内存能够成功的分配,即在使用内存之前首先
要检查该内存指针是否为 NULL;同时,若该内存区域可以使用,则使用前一定要对该区域
内存进行初始化,因为内存的默认初值目前还没有统一的标准。
内存在正常使用中,一定要防止操作越过内存的边界,即所谓的内存溢出。内存溢出容
易使程序运行紊乱,并且可能直接导致应用程序崩溃。在内存使用完成后,必须使用相应的
语句来释放该部分内存,否则该部分内存即产生内存泄露。
在实际的代码书写过程中,可以通过查询分配内存语句和释放内存语句的个数来检查是
否存在内存泄露。若分配内存的操作次数和释放内存的操作次数相等,则内存一般不会产生
泄露,若不相等,则内存肯定存在泄露,必须查明原因,修改相应代码,否则该应用程序很
可能会出现问题。
目前,Linux 下提供了若干软件来帮助程序员解决内存分配的问题,例如 GNU 的 C 语
言库 glibc 本身就包含了三个简单的内存检查工具 mcheck(), MALLOC_CHECK, mtrace。同
时,Linux 下也提供了内存泄露的检测程序,例如 Leaky,使用这些程序可以帮助程序员分
析代码,从而更安全的使用内存,提高应用程序的健壮性。
3 灵活选用系统调用和库函数
系统调用是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现
的,再通过一定的方式把结果返回用户。系统调用是用户程序和内核交互的接口,运行于内
核态,没有全面的错误处理机制,所以直接使用系统调用是很危险的,必须充分保证安全性。
函数库是对系统调用或者其下层函数库的封装,函数库的引入是为了提供统一的系统调
用的接口,从而大大提高程序的可移植性,提高的系统调用的安全性。系统调用与函数库的
关系如图 1 所示。
图 1 系统调用与库函数
- 4 -
中国科技论文在线
系统调用通常提供一个接口,用户通过该接口直接访问内核;而库函数通常提供比较复
杂的功能,并且库函数多种多样,非常丰富。函数库中的函数,尤其与输入输出相关的函数,
则必须通过系统调用来完成。因此可以将函数库的函数当成应用程序设计人员与系统调用程
序之间的一个中间层,通过这个中间层,用户可以用一致的接口来安全的调用系统调用。
从应用程序的执行效率方面来说,系统调用的执行效率绝大多数要比库函数高,尤其是
处理输入输出的函数。因为使用系统调用省掉了函数库中间层,从而使程序的运行效率得到
提高。但是直接使用运行于内核态的系统调用,其安全性则有待考虑,所以,直接使用系统
调用则要在代码书写阶段多加注意,保证代码的正确性。
对于对运行效率要求较高的应用程序,直接使用系统调用会提高应用程序的运行效率,
但是一定要注意应用程序代码的正确性;而对于对运行效率要求不高的程序,则可直接使用
函数库。实际上,即使对一个应用程序而言,对实时性要求比较高的部分,可直接采用系统
调用实现,实时性要求相对不高的部分,可使用函数库实现。这样可以保证程序的运行效率,
同时也改善了因直接使用系统调用所带来的安全问题。
4 编译器代码优化
在嵌入式 Linux 操作系统中,最为常用的编译器为 GCC(GNU C Compiler)。任何一
个 Linux 下的应用程序都需要经过编译器的编译才能生成可执行文件,而 GCC 最为 Linux
下应用最为广泛的编译器,其对应用程序的健壮性会有很大的影响。
GCC 功能强大,并且内置了一些选项来帮助程序员提高程序的健壮性。例如:可以使
用-Wall 选项来使应用程序输出所有的警告信息,通过分析警告信息然后调整或者重写产生
警告信息的代码,可以使代码的运行更加稳定可靠;当 GCC 编译不符合 ANSI/ISO C 语言
标准的源代码时,加上了-pedantic 选项,则使用了扩展语法的地方将产生相应的警告信息;
如果同时使用-Werror 选项则会在所有产生警告的地方停止编译,迫使程序员对产生警告的
代码进行修改。
除上述简单的选项外,GCC 还提供了非常强大的代码优化功能,代码优化指的是编译
器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程
序的执行性能。
GCC 提供的代码优化功能通过编译选项-On(n 为代表优化级别的整数)来控制优化代
码的生成。对于不同版本的 GCC,n 的取值范围及其对应的优化效果并不完全相同,比较典
型的范围是从 0 到 2 或 0 到 3[2]。具体代码优化内容见表 1。
表1 GCC 代码优化表
优化级别 编译选项 优化内容
1 -O1
进行的优化类型取决于目标处理器,一般包括线程跳转和延迟退栈两种优
化,通过这两种优化,使目标应用程序达到体积减小、执行时间减小的效
果。
2 -O2 在完成所有-O1 级别的优化之后进行一些额外的调整工作,如处理器指令调度等。
3 -O3 在完成所有-O2 级别的优化之外,还包括循环展开和其他一些与处理器特性相关的优化工作。
表 1 中的 2 级代码优化是优化代码长度、编译时间和性能之间的比较理想的平衡点,推
荐大多数应用程序使用该选项进行编译。
通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。代码优化
- 5 -
中国科技论文在线
虽然能够给程序带来更好的执行性能,但在某些特定情况下,优化代码并不一定能带来更
好的运行性能。例如资源严重受限的情况,一些优化选项会增加可执行代码的体积,如果
程序在运行时能够申请到的内存资源非常紧张(如一些内存很小的嵌入式设备),则不能对代
码进行高级别优化,因为由这带来的负面影响可能会产生非常严重的后果;另外,在对程序
进行跟踪调试的时候,使用代码优化选项会使代码进行重组,从而增加跟踪和调试的难度,
所以在代码跟踪调试阶段不推荐使用代码优化功能。
5 结论
对于嵌入式 Linux 操作系统中的应用程序,使用以上所述的各种方法对其进行优化,则
应用程序的健壮性会得到明显提升,应用程序的运行稳定性和可靠性能得到较大改善。笔者
按照以上方案对原有项目软件进行优化,不仅提高了程序的响应速度,而且使程序对异常情
况的处理能力大大提高,可保证长时间高效率的稳定运行。
[参考文献] (References)
[1] 范永开 杨爱林.Linux 应用开发技术详解[M].北京:人民邮电出版社,2006,91-94
[2] Michael K. Application Development[M].北京:电子工业出版社,2005,26-30
[3] Peter +/GNOME 程序设计[M].北京:机械工业出版社,2001,21-239
[4] Claudia Salzberg Linux Kernel Primer[M].北京:机械工业出版社,2006,113-150
[5] Syd +程序设计[M].北京:清华大学出版社,2002,30-156
[6] 刘志勇.基于 PC/104 平台 Linux 内核启动过程分析[J].海洋技术,
[7] 许德新.嵌入式 Linux 在 PC/104 平台上的构建与实现[J].应用科技,