嵌入式Linux

2019-07-12 16:01发布

嵌入式 Linux

Linux 从1991年问世到现在,短短的十几年时间已经发展成为功能强大、设计完善的操作系统之一,不仅可以与各种传统的商业操作系统分庭抗争,在新兴的嵌入式操作系统领域内也获得了飞速发展。嵌入式 Linux (Embedded Linux )是指对标准 Linux 经过小型化裁剪处理之后,能够固化在容量只有几 K 或者几 M 字节的存储器芯片或者单片机中,适合于特定嵌入式应用场合的专用 Linux 操作系统。

2.1 优势

嵌入式 Linux 的开发和研究是操作系统领域中的一个热点,目前已经开发成功的嵌入式系统中,大约有一半使用的是 Linux。Linux 之所以能在嵌入式系统市场上取得如此辉煌的成果,与其自身的优良特性是分不开的。

广泛的硬件支持

Linux 能够支持 x86、ARM、MIPS、ALPHA、PowerPC 等多种体系结构,目前已经成功移植到数十种硬件平台,几乎能够运行在所有流行的 CPU 上。Linux 有着异常丰富的驱动程序资源,支持各种主流硬件设备和最新硬件技术,甚至可以在没有存储管理单元(MMU)的处理器上运行,这些都进一步促进了 Linux 在嵌入式系统中的应用。

内核高效稳定

Linux 内核的高效和稳定已经在各个领域内得到了大量事实的验证,Linux 的内核设计非常精巧,分成进程调度、内存管理、进程间通信、虚拟文件系统和网络接口五大部分,其独特的模块机制可以根据用户的需要,实时地将某些模块插入到内核或从内核中移走。这些特性使得 Linux 系统内核可以裁剪得非常小巧,很适合于嵌入式系统的需要。

开放源码,软件丰富

Linux 是开放源代码的自由操作系统,它为用户提供了最大限度的自由度,由于嵌入式系统千差万别,往往需要针对具体的应用进行修改和优化,因而获得源代码就变得至关重要了。Linux 的软件资源十分丰富,每一种通用程序在 Linux 上几乎都可以找到,并且数量还在不断增加。在 Linux 上开发嵌入式应用软件一般不用从头做起,而是可以选择一个类似的自由软件做为原型,在其上进行二次开发。

优秀的开发工具

开发嵌入式系统的关键是需要有一套完善的开发和调试工具。传统的嵌入式开发调试工具是在线仿真器(In-Circuit Emulator,ICE),它通过取代目标板的微处理器,给目标程序提供一个完整的仿真环境,从而使开发者能够非常清楚地了解到程序在目标板上的工作状态,便于监视和调试程序。在线仿真器的价格非常昂贵,而且只适合做非常底层的调试,如果使用的是嵌入式 Linux,一旦软硬件能够支持正常的串口功能时,即使不用在线仿真器也可以很好地进行开发和调试工作,从而节省了一笔不小的开发费用。嵌入式 Linux 为开发者提供了一套完整的工具链(Tool Chain),它利用 GNU 的 gcc 做编译器,用gdb、kgdb、xgdb 做调试工具,能够很方便地实现从操作系统到应用软件各个级别的调试。

完善的网络通信和文件管理机制

Linux 至诞生之日起就与 Internet 密不可分,支持所有标准的 Internet 网络协议,并且很容易移植到嵌入式系统当中。此外,Linux 还支持 ext2、fat16、fat32、romfs 等文件系统,这些都为开发嵌入式系统应用打下了很好的基础。

2.2 挑战

目前,嵌入式 Linux 系统的研发热潮正在蓬勃兴起,并且占据了很大的市场份额,除了一些传统的 Linux 公司(如RedHat、MontaVista 等)正在从事嵌入式 Linux 的开发和应用之外,IBM、Intel、Motorola 等著名企业也开始进行嵌入式 Linux 的研究。虽然前景一片灿烂,但就目前而言,嵌入式 Linux 的研究成果与市场的真正要求仍有一段差距,要开发出真正成熟的嵌入式 Linux 系统,还需要从以下几个方面做出努力。

提高系统实时性

Linux 虽然已经被成功地应用到了 PDA、移动电话、车载电视、机顶盒、网络微波炉等各种嵌入式设备上,但在医疗、航空、交通、工业控制等对实时性要求非常严格的场合中还无法直接应用,原因在于现有的 Linux 是一个通用的操作系统,虽然它也采用了许多技术来加快系统的运行和响应速度,并且符合 POSIX 1003.1b 标准,但从本质上来说并不是一个嵌入式实时操作系统。 Linux 的内核调度策略基本上是沿用 UNIX 系统的,将它直接应用于嵌入式实时环境会有许多缺陷,如在运行内核线程时中断被关闭,分时调度策略存在时间上的不确定性,以及缺乏高精度的计时器等等。正因如此,利用 Linux 作为底层操作系统,在其上进行实时化改造,从而构建出一个具有实时处理能力的嵌入式系统,是现在日益流行的解决方案。

改善内核结构

Linux 内核采用的是整体式结构(Monolithic),整个内核是一个单独的、非常大的程序,这样虽然能够使系统的各个部分直接沟通,有效地缩短任务之间的切换时间,提高系统响应速度,但与嵌入式系统存储容量小、资源有限的特点不相符合。嵌入式系统经常采用的是另一种称为微内核(Microkernel)的体系结构,即内核本身只提供一些最基本的操作系统功能,如任务调度、内存管理、中断处理等,而类似于文件系统和网络协议等附加功能则运行在用户空间中,并且可以根据实际需要进行取舍。Microkernel 的执行效率虽然比不上 Monolithic,但却大大减小了内核的体积,便于维护和移植,更能满足嵌入式系统的要求。可以考虑将 Linux 内核部分改造成 Microkernel,使 Linux 在具有很高性能的同时,又能满足嵌入式系统体积小的要求。

完善集成开发平台

引入嵌入式 Linux 系统集成开发平台,是嵌入式 Linux 进一步发展和应用的内在要求。传统上的嵌入式系统都是面向具体应用场合的,软件和硬件之间必须紧密配合,但随着嵌入式系统规模的不断扩大和应用领域的不断扩展,嵌入式操作系统的出现就成了一种必然,因为只有这样才能促成嵌入式系统朝层次化和模块化的方向发展。很显然,嵌入式集成开发平台也是符合上述发展趋势的,一个优秀的嵌入式集成开发环境能够提供比较完备的仿真功能,可以实现嵌入式应用软件和嵌入式硬件的同步开发,从而摆脱了"嵌入式应用软件的开发依赖于嵌入式硬件的开发,并且以嵌入式硬件的开发为前提"的不利局面。一个完整的嵌入式集成开发平台通常包括编译器、连接器、调试器、跟踪器、优化器和集成用户界面,目前 Linux 在基于图形界面的特定系统定制平台的研究上,与 Windows CE 等商业嵌入式操作系统相比还有很大差距,整体集成开发环境有待提高和完善。

三、关键技术

嵌入式系统是一种根据特定用途所专门开发的系统,它只完成预期要完成的功能,因此其开发过程和开发环境同传统的软件开发相比有着显著的不同。

3.1 开发流程

在嵌入式系统的应用开发中,整个系统的开发过程如图2所示:


图2 嵌入式系统的开发流程

嵌入式系统发展到今天,对应于各种微处理器的硬件平台一般都是通用的、固定的、成熟的,这就大大减少了由硬件系统引入错误的机会。此外,由于嵌入式操作系统屏蔽了底层硬件的复杂性,使得开发者通过操作系统提供的API函数就可以完成大部分工作,因此大大简化了开发过程,提高了系统的稳定性。嵌入式系统的开发者现在已经从反复进行硬件平台设计的过程中解脱出来,从而可以将主要精力放在满足特定的需求上。

嵌入式系统通常是一个资源受限的系统,因此直接在嵌入式系统的硬件平台上编写软件比较困难,有时候甚至是不可能的。目前一般采用的解决办法是首先在通用计算机上编写程序,然后通过交叉编译生成目标平台上可以运行的二进制代码格式,最后再下载到目标平台上的特定位置上运行。

需要交叉开发环境(Cross Development Environment)的支持是嵌入式应用软件开发时的一个显著特点,交叉开发环境是指编译、链接和调试嵌入式应用软件的环境,它与运行嵌入式应用软件的环境有所不同,通常采用宿主机/目标机模式,如图3所示。


图3 交叉开发环境

宿主机(Host)是一台通用计算机(如 PC 机或者工作站),它通过串口或者以太网接口与目标机通信。宿主机的软硬件资源比较丰富,不但包括功能强大的操作系统(如 Windows 和 Linux),而且还有各种各样优秀的开发工具(如 WindRiver 的 Tornado、Microsoft 的 Embedded Visual C++ 等),能够大大提高嵌入式应用软件的开发速度和效率。

目标机(Target)一般在嵌入式应用软件开发期间使用,用来区别与嵌入式系统通信的宿主机,它可以是嵌入式应用软件的实际运行环境,也可以是能够替代实际运行环境的仿真系统,但软硬件资源通常都比较有限。 嵌入式系统的交叉开发环境一般包括交叉编译器、交叉调试器和系统仿真器,其中交叉编译器用于在宿主机上生成能在目标机上运行的代码,而交叉调试器和系统仿真器则用于在宿主机与目标机间完成嵌入式软件的调试。在采用宿主机/目标机模式开发嵌入式应用软件时,首先利用宿主机上丰富的资源和良好的开发环境开发和仿真调试目标机上的软件,然后通过串口或者以网络将交叉编译生成的目标代码传输并装载到目标机上,并在监控程序或者操作系统的支持下利用交叉调试器进行分析和调试,最后目标机在特定环境下脱离宿主机单独运行。

建立交叉开发环境是进行嵌入式软件开发的第一步,目前常用的交叉开发环境主要有开放和商业两种类型。开放的交叉开发环境的典型代表是GNU工具链、目前已经能够支持x86、ARM、MIPS、PowerPC等多种处理器。商业的交叉开发环境则主要有 Metrowerks CodeWarrior、ARM Software Development Toolkit、SDS Cross compiler、WindRiver Tornado、Microsoft Embedded Visual C++ 等。

3.2 交叉编译和链接

在完成嵌入式软件的编码之后,需要进行编译和链接以生成可执行代码,由于开发过程大多是在使用 Intel 公司 x86 系列 CPU 的通用计算机上进行的,而目标环境的处理器芯片却大多为 ARM、MIPS、PowerPC、DragonBall 等系列的微处理器,这就要求在建立好的交叉开发环境中进行交叉编译和链接。

交叉编译器和交叉链接器是能够在宿主机上运行,并且能够生成在目标机上直接运行的二进制代码的编译器和链接器。例如在基于 ARM 体系结构的 gcc 交叉开发环境中,arm- Linux -gcc 是交叉编译器,arm- Linux -ld 是交叉链接器。通常情况下,并不是每一种体系结构的嵌入式微处理器都只对应于一种交叉编译器和交叉链接器,比如对于 M68K 体系结构的 gcc 交叉开发环境而言,就对应于多种不同的编译器和链接器。如果使用的是 COFF 格式的可执行文件,那么在编译 Linux 内核时需要使用 m68k-coff-gcc 和 m68k-coff-ld,而在编译应用程序时则需要使用 m68k-coff-pic-gcc 和 m68k-coff-pic-ld。

嵌入式系统在链接过程中通常都要求使用较小的函数库,以便最后产生的可执行代码能够尽可能地小,因此实际运用时一般使用经过特殊处理的函数库。对于嵌入式 Linux 系统来讲,功能越来越强、体积越来越大的 C 语言函数库 glibc 和数学函数库 libm 已经很难满足实际的需要,因此需要采用它们的精化版本 uClibc、uClibm 和 newlib 等。

目前嵌入式的集成开发环境都支持交叉编译和交叉链接,如 WindRiver Tornado 和 GNU 工具链等,编写好的嵌入式软件经过交叉编译和交叉链接后通常会生成两种类型的可执行文件:用于调试的可执行文件和用于固化的可执行文件。

3.3 交叉调试

嵌入式软件经过编译和链接后即进入调试阶段,调试是软件开发过程中必不可少的一个环节,嵌入式软件开发过程中的交叉调试与通用软件开发过程中的调试方式有所差别。在通用软件开发中,调试器与被调试的程序往往运行在同一台计算机上,调试器是一个单独运行着的进程,它通过操作系统提供的调试接口来控制被调试的进程。而在嵌入式软件开发中,调试时采用的是在宿主机和目标机之间进行的交叉调试,调试器仍然运行在宿主机的通用操作系统之上,但被调试的进程却是运行在基于特定硬件平台的嵌入式操作系统中,调试器和被调试进程通过串口或者网络进行通信,调试器可以控制、访问被调试进程,读取被调试进程的当前状态,并能够改变被调试进程的运行状态。

交叉调试(Cross Debug)又常常被称为远程调试(Remote Debug),是一种允许调试器以某种方式控制目标机上被调试进程的运行方式,并具有查看和修改目标机上内存单元、寄存器以及被调试进程中变量值等各种调试功能的调试方式。一般而言,远程调试过程的结构如图4所示。


图4远程调试结构

嵌入式系统的交叉调试有多种方法,可以被细分成不同的层次,但一般都具有如下一些典型特点:
调试器和被调试进程运行在不同的机器上,调试器运行在PC或者工作站上(宿主机),而被调试的进程则运行在各种专业调试板上(目标机)。
调试器通过某种通信方式与被调试进程建立联系,如串口、并口、网络、DBM、JTAG 或者专用的通信方式。
在目标机上一般会具备某种形式的调试代理,它负责与调试器共同配合完成对目标机上运行着的进程的调试。这种调试代理可能是某些支持调试功能的硬件设备(如 DBI 2000),也可能是某些专门的调试软件(如 gdbserver)。
目标机可能是某种形式的系统仿真器,通过在宿主机上运行目标机的仿真软件,整个调试过程可以在一台计算机上运行。此时物理上虽然只有一台计算机,但逻辑上仍然存在着宿主机和目标机的区别。

在嵌入式软件开发过程中的调试方式有很多种,应根据实际的开发要求和条件进行选择。就调试方法而言,嵌入式系统的交叉调试可以分为硬件调试和软件调试两种,前者使用仿真调试器协助调试过程,而后者则使用软件调试器完成调试过程。

硬件调试

相对于软件调试而言,使用硬件调试器可以获得更强大的调试功能和更优秀的调试性能。硬件调试器的基本原理是通过仿真硬件的执行过程,让开发者在调试时可以随时了解到系统的当前执行情况。目前嵌入式系统开发中最常用到的硬件调试器是 ROM Monitor、ROM Emulator、In-Circuit Emulator 和 In-Circuit Debugger。

采用 ROM Monitor 方式进行交叉调试需要在宿主机上运行调试器,在目标机上运行 ROM s监视器(ROM Monitor)和被调试程序,宿主机通过调试器与目标机上的 ROM 监视器建立通信连接,它们之间的通信遵循远程调试协议。ROM 监视器可以是一段运行在目标机 ROM 上的可执行程序,也可以是一个专门的硬件调试设备,它负责监控目标机上被调试程序的运行情况,能够与宿主机端的调试器一同完成对应用程序的调试。在使用这种调试方式时,被调试程序首先通过 ROM 监视器下载到目标机,然后在 ROM 监视器的监控下完成调试,目前使用的绝大部分 ROM 监视器能够完成设置断点、单步执行、查看寄存器、修改内存空间等各项调试功能。

采用 ROM Emulator 方式进行交叉调试时需要使用 ROM 仿真器,它通常被插入到目标机上的 ROM 插槽中,专门用于仿真目标机上的 ROM 芯片。在使用这种调试方式时,被调试程序首先下载到 ROM 仿真器中,它等效于下载到目标机的 ROM 芯片上,然后在 ROM 仿真器中完成对目标程序的调试。ROM Emulator 调试方式通过使用一个 ROM 仿真器,虽然避免了每次修改程序后都必须重新烧写到目标机 ROM 中这一费时费力的操作,但由于 ROM 仿真器本身比较昂贵,功能相对来讲又比较单一,因此只适应于某些特定场合。

采用 In-Circuit Emulator(ICE)方式进行交叉调试时需要使用在线仿真器,它是仿照目标机上的 CPU 而专门设计的硬件,可以完全仿真处理器芯片的行为,并且提供了非常丰富的调试功能。在使用在线仿真器进行调试的过程中,可以按顺序单步执行,也可以倒退执行,还可以实时查看所有需要的数据,从而给调试过程带来了很多的便利。嵌入式系统应用的一个显著特点是与现实世界中的硬件直接相关,存在各种异变和事先未知的变化,从而给微处理器的指令执行带来各种不确定因素,这种不确定性在目前情况下只有通过在线仿真器才有可能发现,因此尽管在线仿真器的价格非常昂贵,但仍然得到了非常广泛的应用。

采用 In-Circuit Debugger(ICD)方式进行交叉调试时需要使用在线调试器。由于ICE的价格非常昂贵,并且每种 CPU 都需要一种与之对应的 ICE,使得开发成本非常高,一个比较好的解决办法是让 CPU 直接在其内部实现调试功能,并通过在开发板上引出的调试端口,发送调试命令和接收调试信息,完成调试过程。目前 Motorola 公司提供的开发板上使用的是 DBM 调试端口,而 ARM 公司提供的开发板上使用的则是 JTAG 调试端口,使用合适的软件工具与这些调试端口进行连接,可以获得与 ICE 类似的调试效果。

软件调试

软件调试通常要在不同的层次上进行,有时可能需要对嵌入式操作系统的内核进行调试,而有时可能仅仅只需要调试嵌入式应用程序就可以了。在嵌入式系统的整个开发过程中,不同层次上的软件调试需要使用不同的调试方法。

嵌入式操作系统的内核调试相对来讲比较困难,这是因为在内核中不便于增加一个调试器程序,而只能通过远程调试的方法,通过串口和操作系统内置的"调试桩"(debug stub)进行通信,共同完成调试过程。调试桩可以看成是一个调试服务器,它通过操作系统获得一些必要的调试信息,并且负责处理宿主机发送来的调试命令。具体到嵌入式 Linux 系统内核,调试时可以先在 Linux 内核中设置一个调试桩,用作调试过程中和宿主机之间的通信服务器,然后就可以在宿主机中通过调试器的串口与调试桩进行通信,并通过调试器控制目标机上 Linux 内核的运行。

嵌入式应用软件的调试可以使用本地调试和远程调试两种方法,相对于操作系统的调试而言,这两种方式都比较简单。如果采用的是本地调试,首先要将所需的调试器移植到目标系统中,然后就可以直接在目标机上运行调试器来调试应用程序了;如果采用的是远程调试,则需要移植一个调试服务器到目标系统中,并通过它与宿主机上的调试器共同完成应用程序的调试。在嵌入式 Linux 系统的开发中,远程调试时目标机上使用的调试服务器通常是 gdbserver,而宿主机上使用的调试器则是 gdb,两者相互配合共同完成调试过程。

3.4 系统测试

嵌入式系统的硬件一般采用专门的测试仪器进行测试,而软件则需要有相关的测试技术和测试工具的支持,并要采用特定的测试策略。测试技术指的是软件测试的专门途径,以及能够更加有效地运用这些途径的特定方法。在嵌入式软件测试中,常常要在基于目标机的测试和基于宿主机的测试之间做出折衷,基于目标机的测试需要消耗较多的时间和经费,而基于宿主机的测试虽然代价较小,但毕竟是在仿真环境中进行的,因此难以完全反映软件运行时的实际情况。这两种环境下的测试可以发现不同的软件缺陷,关键是要对目标机环境和宿主机环境下的测试内容进行合理取舍。

测试工具指的是那些能够用来辅助测试的工具,测试工具主要用来支持测试人员的测试工作,本身不能直接用来进行测试,测试工具一般都是通用工具,测试人员应该根据实际情况对它们进行适当的调整。嵌入式软件测试中经常用到测试工具主要有内存分析工具、性能分析工具、覆盖分析工具、缺陷跟踪工具等。

内存分析工具

嵌入式系统的内存资源通常是受限的,内存分析工具可以用来处理在进行动态内存分配时产生的缺陷。当动态分配的内存被错误地引用时,产生的错误通常难以再现,可出现的失效难以追踪,使用内存分析工具可以很好地检测出这类缺陷。目前常用的内存分析工具有软件和硬件两种,基于软件的内存分析工具可能会对代码的执行性能带来很大影响,从而影响系统的实时性;基于硬件的内存分析工具价格昂贵,并且只能在特定的环境中使用。

性能分析工具

嵌入式系统的性能通常是一个非常关键的因素,开发人员一般需要对系统的某些关键代码进行优化来改进性能,而首先遇到的问题自然就是确定需要对哪些代码进行优化。性能分析工具可以为开发人员提供有关的数据,说明执行时间是如何消耗的,是什么时候消耗的,以及每个进程所使用的时间。这些数据可以帮助确定哪些进程消耗了过多的执行时间,从而可以决定如何优化软件,以获得更好的时间性能。此外,性能分析工具还可以引导开发人员发现在系统调用中存在的错误以及程序结构上的缺陷。

覆盖分析工具

在进行白盒测试时,可以使用代码覆盖分析工具追踪哪些代码被执行过,分析过程一般通过插桩来完成,插桩可以是在测试环境中嵌入硬件,也可以是在可执行代码中加入软件,或者是两者的结合。开发人员通过对分析结果进行总结,可以确定哪些代码被执行过,哪些代码被遗漏了。目前常用的覆盖分析工具一般都会提供有关功能覆盖、分支覆盖、条件覆盖等信息。

四、小结

现今的嵌入式系统在网络化潮流的推动下,已经逐渐摆脱过去那种小巧而简单的模式,开始进入复杂度高、功能强大的阶段,吸引了许多程序设计人员和硬件开发人员的视线。本文讨论了嵌入式 Linux 系统的基本知识、开发流程、开发工具、调试工具、测试工具等,并指出了嵌入式系统的开发与一般通用计算机软件开发的不同点及应该注意的事项,这些都是今后在进行嵌入式 Linux 系统开发时必须具备的基础知识。

五、参考资源

http://www.embeddedtechnology.com是嵌入式系统技术的核心网站,包括许多最新的嵌入式领域的技术动态,以及大量的嵌入式产品、开发工具、产品提供商的介绍。
http://www.ddjembedded.com是嵌入式系统杂志《Dr. Dobb's Embedded Systems》的官方网站,包含大量与嵌入式系统相关的文章。
Karim Yaghmour,Building Embedded Linux Systems,USA:O'Reilly,2003
魏忠,蔡勇,雷红卫编著,嵌入式开发详解,北京:电子工业出版社,2003
李善平,刘文峰,王焕龙等编著, Linux 与嵌入式系统,北京:清华大学出版社,2003
探矽工作室著,嵌入式系统开发圣经,北京:中国铁道出版社,2003