一文知晓嵌入式Linux


在这里插入图片描述

嵌入式Linux是什么

嵌入式Linux跟桌面Linux一样,是一个操作系统。从单片机走过来的童鞋往往习惯于直接控制寄存器,事必躬亲,从零开始实现想要的功能。而在嵌入式Linux的世界里,我们首先要抛弃这个思想,应把它作为最后没办法的办法。

就像我们想要在windows系统中编写一个程序,首先想到的不是操作CPU芯片的寄存器,而是学习Windows API一样。我们在嵌入式linux编程时,首先想到的应该是使用现成的驱动或软件或Linux API。没有的话看看能不能修改一下现成的资源为己所用。还是不行的话才考虑自己从头开始写。

我们常说的嵌入式linux系统,其实与电脑端运行的linux系统本质上是一样的,都是使用的linux内核,相同的文件系统目录结构。区别在于嵌入式linux系统多少经过裁剪的,可能在操作时你会发现,有些命令不支持,或者有些命令的个别参数不支持!还有就是内核的功能也有裁剪。

对于不同的行业,根据需求对软硬件进行裁剪选配,这也是嵌入式linux系统广泛应用的特点之一,具体特点包括:
C语言即可入门
使用命令行
强大的网络
远程运维
成本低

在学习嵌入式Linux之前,肯定要有C语言基础。汇编基础有没有无所谓(就那么几条汇编指令,用到了一看就会)。

C语言要学到什么程度呢?越熟当然越好,不熟的话也要具备基本技能。比如写一个数组排序、输入数字求和什么的。

学C语言唯一的方法是多写程序多练习,编译出错没关系,自己去解决;执行出错没关系,自己去分析。以前我是用VC来练习C语言的,经常去尝试着写一些C语言竞赛的题目。它们是纯C、纯数学、纯逻辑的题目,不涉及界面这些东西,很适合煅炼你的编程能力。

整体大概是如何的流程

学习基本的裸机编程

对于学硬件的人而言,必须先对硬件的基本使用方法有感性的认识,更必须深刻认识该硬件的控制方式,如果一开始就学linux系统、学移植那么只会马上就陷入一个很深的漩涡。

我在刚刚开始学ARM的时候是选择ARM7(主意是当时ARM9还很贵),学ARM7的时候还是保持着学51单片机的思维,使用ADS去编程,第一个实验就是控制led。学过一段时间ARM的人都会笑这样很笨,实际上也不是,我倒是觉得有这个过程会好很多,因为无论做多复杂的系统最终都会落实到这些最底层的硬件控制,因此对这些硬件的控制有了感性的认识就好很多了。

学习裸机的编程的同时要好好理解这个硬件的构架、控制原理,这些我称他为理解硬件。所谓的理解硬件就是说,理解这个硬件是怎么组织这么多资源的,这些资源又是怎么由cpu、由编程进行控制的。

比如说,s3c2410中有AD转换器,有GPIO(通用IO口),还有nandflash控制器,这些东西都有一些寄存器来控制,这些寄存器都有一个地址,那么这些地址是什么意思?又怎么通过寄存器来控制这些外围设备的运转?还有,norflash内部的每一个单元在这个芯片的内存中都有一个相应的地址单元,那么这些地址与刚刚说的寄存器地址又有什么关系?

他们是一样的吗?而与norflash相对应的nandflash内部的储存单元并不是线性排放的,那么s3c2410怎么将nandflash的地址映射在内存空间上进行使用?或者简单地说应该怎么用nandflash?再有,使用ADS进对ARM9行编程时都需要使用到一个初始化的汇编文件,这个文件究竟有什么用?他里面的代码是什么意思?不要这个可以吗?

诸如此类都是对硬件的理解,理解了这些东西就对硬件有很深的理解了,这对以后更深一步的学习将有很大的帮助,如果跳过这一步,我相信越往后学越会觉得迷茫,越觉得这写东西深不可测。因为,你的根基没打好。

不过先声明一下,本人并没有使用ADS对ARM9进行编程,我是学完ARM7后直接就使用ARM9学linux系统的,因此涉及使用ADS对ARM9进行编程的问题我很难回答_,自己去研究研究吧。

使用linux系统进行一些基本的实验

在买一套板子的时候一般会提供一些linux的试验例程,好好做一段时间这个吧,这个过程也是很有意义的,也是为进一步的学习积累感性认识,你能想象一个从没有使用过linux系统的人能学好linux的编程吗?好好按照手册上的例程做一做里面的实验,虽然有点娃娃学走路,有点弱智,但是我想很多高手都会经历这个过程。

研究完整的linux系统的的运行过程

所谓完整的linux系统包括哪些部分呢?
三部分:bootloader、linux kernel(linux内核)、rootfile(根文件系统)。

那么这3部分是怎么相互协作来构成这个系统的呢?各自有什么用呢?三者有什么联系?怎么联系?系统的执行流程又是怎么样的呢?搞清楚这个问题你对整个系统的运行就很清楚了,对于下一步制作这个linux系统就打下了另一个重要的根基。介绍这方面的资料网上可以挖掘到几吨,自己好好研究吧。

开始做系统移植

上面说到完整的linux有3部分,而且你也知道了他们之间的关系和作用,那么现在你要做的便是自己动手学会制作这些东西。

当然我不可能叫你编写这些代码,这不实现。事实上这个3者都能在网下载到相应的源代码,但是这个源代码不可能下载编译后就能在你的系统上运行,需要很多的修改,直到他能运行在你的板子上,这个修改的过程就叫移植。在进行移植的过程中你要学的东西很多,要懂的相关知识也很多,等你完成了这个过程你会发现你已经算是一个初出茅庐的高手了。

在这个过程中如果你很有研究精神的话你必然会想到看源代码。很多书介绍你怎么阅读linux源代码,我不提倡无目的地去看linux源代码,用许三多的话说,这没有意义。等你在做移植的时候你觉得你必须去看源代码时再去找基本好书看看,这里我推荐一本好书倪继利的《linux内核的分析与编程》,这是一本针对linux-2.6.11内核的书,说得很深,建议先提高自己的C语言编程水平再去看。

至于每个部分的移植网上也可以找到好多吨的资料,自己研究研究吧,不过要提醒的是,很多介绍自己经验的东西都或多或少有所保留,你按照他说的去做总有一些问题,但是他不会告诉你怎么解决,这时就要靠自己,如果自己都靠不住就找学长一起研究研究吧,不能保证能解决你的问题,因为他未必遇到过你的问题,不过我相信能给你一点建议,也许有助你解决问题。

这一步的最终目的是,从源代码的官方主页上(都是外国的,悲哀)下载标准的源代码包,然后进行修改,最终运行在板子上。

盗用阿基米德的一句话:“给我一根网线,我能将linux搞定”。

研究linux驱动程序的编写

移植系统并不是最终的目的,最终的目的是开发产品,做项目,这些都要进行驱动程序的开发。

Linux的驱动程序可以说是五花八门,linux2.4和linux2.6的编写有相当大的区别,就是同为linux2.6但是不同版本间的驱动程序也有区别,因此编写linux的驱动程序变都不是那么容易的事情,对于最新版本的驱动程序的编写甚至还没有足够的参考资料。那么我的建议就是使用、移植一个不算很新的版本内核,这样到时学驱动的编程就有足够的资料了。

这部分的推荐书籍可以参考另一篇文章《推荐几本学习嵌入式linux的书籍》。

研究应用程序的编写

做作品做项目除了编写驱动程序,最后还要编写应用程序。现在的趋势是图形应用程序的开发,而图形应用程序中用得最多的还是qt/e函数库。我一直就使用这个函数库来开发自己的应用程序,不过我希望你能使用国产的MiniGUI函数库。盗用周杰伦的广告词就是“支持国产,支持MiniGUI”。MiniGUI的编程比较相似Windows下的VC编程,比较容易上手,效果应该说是相当不错的,我曾使用过来开发ARM7的程序。不过MiniGUI最大的不好就是没有像qtopia这样的图形操作平台,这大大限制了他的推广,我曾经幻想过与北京飞漫公司(就是MiniGUI的版权拥有者)合作使用MiniGUI函数库开发像qtopia这样的图形操作平台,不过由于水平有限这只能是幻想了,呵呵。

完成这一步你基本就学完了嵌入式linux的全部内容了。

还有一个小小的经验想和大家分享。我在学习嵌入式linux的过程中很少问人,客观原因是身边的老师、同学师兄都没有这方面的高手,主观原因是我不喜欢问人,喜欢自己研究解决问题。这样做有个好处,就是可以提高自己解决问题的能力,因为做这些东西总有很多问题你难以理解,别人也没有这方面的经验,也不是所有问题都有人给你答案,这时必须要自己解决问题,这样,个人的解决问题能力就显得非常关键了。因此我的建议就是一般的问题到网上搜索一下,确实找不到答案了就问问高手,还是不行了就自己去研究,不要一味去等别人帮你解决问题。

需要知道哪些基础知识

嵌入式Linux系统的构成
硬件
内核
应用程序(形成根文件系统)

构建嵌入式Linux系统的主要任务
内核部分
应用程序部分

嵌入式Linux的开发大致可分为三个层次:引导装载内核、构造文件系统和图形用户界面。作为操作系统重要组成部分的文件系统,决定了操作系统本身的信息和用户的数据在存储设备上的组织形式。对嵌入式文件系统的研究、设计和开发也逐渐成为嵌入式系 统研究领域的一个方向。

内核精简

在精简内核在编译内核之前,首先要明确需要哪些驱动和模块,然后只选择需要的驱动和模块,例如,如果系统不需要网络支持,则可以去掉网络模块。内核一般是以压缩方式存放的,在系统启动时会自行解压。

内核都是常驻内存的,当需要调用应用程序时,再把需要的程序从磁盘调入内存运行。

嵌入式系统的组成
嵌入式硬件(嵌入式处理器和嵌入式外围设备)
嵌入式操作系统
嵌入式应用软件

嵌入式处理器

嵌入式系统的核心是各种类型的嵌入式处理器,嵌入式处理器与通用处理器最大的不同点在于,嵌入式CPU大多工作在为特定用户群所专门设计的系统中,它将通用CPU中许多由板卡完成的任务集成到芯片内部,从而有利于嵌入式系统在设计时趋于小型化,同时还具有很高的效率和可靠性。

嵌入式处理器的体系结构经历了从CISC(复杂指令集)至RISC(精简指令集)和Compact RISC的转变,位数则由4位、8位、16位、32位逐步发展到64位。目前常用的嵌入式处理器可分为低端的嵌入式微控制器(Micro Controller Unit,MCU)、中高端的嵌入式微处理器(Embedded Micro Processor Unit,EMPU)、用于计算机通信领域的嵌入式DSP处理器(Embedded Digital Signal Processor,EDSP)和高度集成的嵌入式片上系统(System On Chip,SOC)。

目前几乎每个半导体制造商都生产嵌入式处理器,并且越来越多的公司开始拥有自主的处理器设计部门,据不完全统计,全世界嵌入式处理器已经超过1000多种,流行的体系结构有30多个系列。

嵌入式外围设备

在嵌入系统硬件系统中,除了中心控制部件(MCU、DSP、EMPU、SOC)以外,用于完成存储、通信、调试、显示等辅助功能的其他部件,事实上都可以算作嵌入式外围设备。目前常用的嵌入式外围设备按功能可以分为存储设备、通信设备和显示设备三类。

存储设备主要用于各类数据的存储,常用的有静态易失型存储器(RAM、SRAM)、动态存储器(DRAM)和非易失型存储器(ROM、EPROM、EEPROM、FLASH)三种,其中FLASH凭借其可擦写次数多、存储速度快、存储容量大、价格便宜等优点,在嵌入式领域内得到了广泛应用。

目前存在的绝大多数通信设备都可以直接在嵌入式系统中应用,包括RS-232接口(串行通信接口)、SPI(串行外围设备接口)、IrDA(红外线接口)、I2C(现场总线)、USB(通用串行总线接口)、Ethernet(以太网接口)等。

由于嵌入式应用场合的特殊性,通常使用的是阴极射线管(CRT)、液晶显示器(LCD)和触摸板(Touch Panel)等外围显示设备。

嵌入式操作系统

为了使嵌入式系统的开发更加方便和快捷,需要有专门负责管理存储器分配、中断处理、任务调度等功能的软件模块,这就是嵌入式操作系统。嵌入式操作系统是用来支持嵌入式应用的系统软件,是嵌入式系统极为重要的组成部分,通常包括与硬件相关的底层驱动程序、系统内核、设备驱动接口、通信协议、图形用户界面(GUI)等。

嵌入式操作系统具有通用操作系统的基本特点,如能够有效管理复杂的系统资源,能够对硬件进行抽象,能够提供库函数、驱动程序、开发工具集等。但与通用操作系统相比较,嵌入式操作系统在系统实时性、硬件依赖性、软件固化性以及应用专用性等方面,具有更加鲜明的特点。

嵌入式操作系统根据应用场合可以分为两大类:一类是面向消费电子产品的非实时系统,这类设备包括个人数字助理(PDA)、移动电话、机顶盒(STB)等;另一类则是面向控制、通信、医疗等领域的实时操作系统,如WindRiver公司的VxWorks、QNX系统软件公司的QNX等。

实时系统(Real Time System)是一种能够在指定或者确定时间内完成系统功能,并且对外部和内部事件在同步或者异步时间内能做出及时响应的系统。在实时系统中,操作的正确性不仅依赖于逻辑设计的正确程度,而且与这些操作进行的时间有关,也就是说,实时系统对逻辑和时序的要求非常严格,如果逻辑和时序控制出现偏差将会产生严重后果。

实时系统主要通过三个性能指标来衡量系统的实时性,即响应时间(Response Time)、生存时间(Survival Time)和吞吐量(Throughput):
响应时间是实时系统从识别出一个外部事件到做出响应的时间。生存时间是数据的有效等待时间,数据只有在这段时间内才是有效的。吞吐量是在给定的时间内系统能够处理的事件总数,吞吐量通常比平均响应时间的倒数要小一点。

嵌入式应用软件

嵌入式应用软件是针对特定应用领域,基于某一固定的硬件平台,用来达到用户预期目标的计算机软件,由于用户任务可能有时间和精度上的要求,因此有些嵌入式应用软件需要特定嵌入式操作系统的支持。嵌入式应用软件和普通应用软件有一定的区别,它不仅要求其准确性、安全性和稳定性等方面能够满足实际应用的需要,而且还要尽可能地进行优化,以减少对系统资源的消耗,降低硬件成本。

如何构建嵌入式Linux系统的设计步骤

嵌入式系统目前主要有:Windows CE、VxWorks、QNX等,它们都具较好的实时性、系统可靠性、任务处理随机性等优点。但是它们的价格普遍偏高,很多开发商承受不起。因而,Linux操作系统成为嵌入式操作系统的首选。

Linux操作系统成为嵌入式操作系统首选的原因

在精简内核在编译内核之前,首先要明确需要那些驱动和模块,然后只选择需要的驱动和模块,例如,如果系统不需要网络支持,则可以去掉网络模块 。内核一般是以压缩方式存放的,在系统启动时会自行解压。内核都是常驻内存的,当需要调用应用程序时,再把需要的程序从磁盘调入内存运行。

嵌入式工程师需要哪些工具

总会看到有人说Linux上的应用程序开发是高手才可以完成的,而且这种“迷信”在目前似乎还很普遍。

然而,情况并不是这样的,从程序库的支持方面,Linux平台为用户级应用程序的开发提供了很多功能强大且丰富的程序库,而且它们大部分是跨平台的(Boost、OpenGL、STL、Qt、Java等)和基于POSIX标准的(glibc等),同时Linux内核还为驱动程序的开发提供了功能完备的内核接口,从开发工具方面,Linux提供了功能强大的编译器GCC和调试器GDB,借助它们的帮助,我们可以很轻松的在Linu x上开发出可移植性的应用程序。

既然如此,“迷信”又源于何来呢?一方面由于详细介绍Linux各种开发的书籍较少,各种Linux应用在国内仍不普及,另一方面则是由于很多人在安装好一个Linux后,苦于找不到一个得心应手的IDE环境,从而感到不知所措,毕竟,我们很多人都习惯了写好程序后,按下F5,剩下的任务就让IDE全权代理了。其实想在Linux下如此这般当然也没问题。既然说到了IDE,就让我们从它开始吧,相信选择一个好的IDE环境是你整个学习过程的一个不错的开始。

工欲善其事 必先利其器——IDE篇

其实Linux下有许多功能强大的IDE环境,因为从某种意义上说,Linux是专为开发者准备的操作系统,这个东西当然少不了,在这里为读者介绍一些比较常用的IDE。

KDevelop
这是一个用Qt开发的IDE,其主要支持的语言是C / C++,

Eclipse
近年来,eclipse可以说发展极为迅速,它不仅是一个以java为主的开发平台,其功能强大的插件体系结构使得它可以被当作各种应用程序来使用。作为各种插件的载体,eclipse提供了完整的GUI接口,用户完全可以借助eclipse来只关心自己想做的工作。

山高月晓 水落石出——IDE后台的故事 GCC篇

前面我们简要介绍了一些IDE环境,其中所有C/C++相关程序的编译都是由GCC来完成的,而IDE只不过起到了一个收集编译信息和为我们的项目生成makefile等作用(后面我们会提到)。出于目前Linux开发的特点,C仍是系统开发的主流语言。所以,对GCC有一个全面的了解是很有必要的,一旦IDE不能满足你的需求,我们要有手工打造程序的能力,而且出于学习的目的,我们往往不需要IDE生成的那些复杂的文件,为一个Hello world生成2M多的文件显然是多余的。

GCC的全称是GNU Compiler Collection,从这个名字我们不难看出,GCC代表着一个编译器的集合,目前GCC可以支持C, C++, Objective-C, Objective-C++, Fortran, Java, and Ada等语言。但是出于一般性考虑,我们这里只讨论GCC中的C/C++部分。

目前GCC的最新发布版是4.0.0,但是这个版本由于使用了新技术和新的编码规范,很多旧的代码都需要修改才可以通过编译,所以并不推荐使用这个版本。而相对稳定的新版本目前是3.4.4,大家可以到GNU的主页上更新下载。那么究竟GCC强大在哪里,如何使用?下面我就通过几个简单而实际的例子带你看看GCC提供的强大功能。

通过Helloworld的编译熟悉GCC的基本使用方法

似乎为所有新语言提供一个Hello World样本程序已经成为了一种不成文的标准,人们通过它来认识语言的一些基本要素。在这里,我们使用一个Hello World来看看如何用GCC生成可执行文件。

把上面的文件存成helloworld.c,之后打开控制台,输入如下的命令
gcc helloworld.c –o helloworld

如果一切正常的话,你的控制台上应该没有任何输出。用ls查看你的工作目录,你会发现目录下多了一个名为helloworld的可执行文件,之后,执行
./hellworld就会看到这个程序的输出了。

很简单不是吗?但是学过计算机的朋友都应该知道,程序的编译过程要分为下图所示的过程而GCC的强大之处就在于它允许你在上面所示的任何一个过程中停下来查看中间结果,并对其加以控制。

预处理
首先是预处理过程,GCC的-E选项可以让GCC在预处理后停止编译,并向标准输出打印预处理过后的文件。下面的-o用于指定输出文件的文件名。
gcc –E hellowrold.c –o helloworld.cpp

下面是helloworld.cpp的一部分的内容,我们看到,文件已经包含了stdio.h中的内容。

如果我们想执行下一步的编译过程,可以继续使用GCC的-x 选项,该选项用于显示指定文件的后缀名(而不是让编译器根据后缀来自行判断)。我们比较常用的language type有如下几种,(如果读者想获得更为完整参数说名,请参考GCC manual):
l c c-header c-cpp-output
l c++ c+±header c+±cpp-output
l assembler assembler-with-cpp

另外,下表列出了常用的GCC后缀名

当然,你也可以省略掉language type的部分,这时候GCC会根据文件的后缀名自行判断,就像你没有使用该选项一样。

下面继续我们的编译过程

编译
如果我们想获得编译后的源文件可以使用-S选项,该选项让gcc只执行编译(生成汇编文件)而不进行汇编(生成目标文件),此时,我们可以用-o选项指定输出的汇编文件的名称。
gcc –S helloworld.cpp –o hellowrld.S

汇编
另外,我们还可以使用GCC的-c选项来编译和汇编源文件而不链接,此时-o指定的输出文件就是编译后的目标文件名:
gcc –x c++ -c helloworld.cpp –o helloworld.o

链接
最后,我们可以利用GCC来把我们刚才生成的.o文件链接成可执行程序
gcc helloworld.o –o helloworld

这一次,我们使用了-o选项指定了可执行文件名,也就是说,根据输入文件类型的不同,-o有着不同的含义。

函数库的链接和包含文件
对于我们编写的任和一个程序,没有库函数的支持是不可想象的,而当我们要使用的头文件和函数库不在GCC默认的搜索路径下的时候(例如OpenGL、Qt、KDE、Boost等),我们就需要手工来告诉GCC他们的位置。

先来看头文件路径的指定。我们可以利用-I来指定我们希望GCC去搜索的头文件目录,例如我们要使用X11的程序,我们就要使用下面的选项再来看库函数的设置:我们通过-L和-l两个命令行选项完成任务。其中-L用于告诉GCC在中去寻找函数库,而-l选项则告诉GCC使用用户指定的程序库。在Linux中,函数库的命名是遵循UNIX约定的,即lib{lib name},例如libsocket.so,所以当你需要告诉GCC使用这些库的时候,你就可以使用-lsocket选项。通常,这两个命令是结合在一起使用的,例如引用X11程序库的时候,我们可以这样:
–L/usr/X11R6/lib –lX11

另外,GCC在默认情况下使用共享库来链接程序,而当你想链接静态库的时候,一定要使用-static选项,例如-lncurses -static

在这一部分的最后,我们对编译时用到的GCC常用命令做一个简要的总结:
上面,我们提到了关于GCC编译的常用命令,这里另外补充一些帮助性的常用命令,他们可以让你对GCC的基本配置和使用作一个了解。

在这部分的最后,我们来谈一谈关于构建软件时链接参数的设定问题。在上面的第5部分我们已经提到了,函数库的使用是需要-L和-l一起配合来使用的,但实际上,往往一个像样的程序需要很多库的支持,例如,如果你需要编写一个GTK程序,我们需要下面的链接参数:

-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXi -lXext -lX11 –lm,看上去有些吓人,你可能会问,我如何知道需要这些呢,如果我想编写KDE的程序呢,还有OpenGL呢?其实,情况比你想象的要好很多,在/usr/bin目录下,有很多名为xxx-config的脚本,它们的作用就是向用户显示编译链接程序时使用的参数的。这些脚本可以接受一些参数,比较常用的有—libs用于列出链接特定程序时使用的程序库,另外–cflags用于生成头文件的包含目录,也就是上面我们提到的-I参数。于是,对于GTK程序,我们可以使用下面的命令来编译:
gcc gtksource.c gtk-config –libs --cflags

当然,为每一种程序写一个config显然不是一个好办法,目前新的开发包都使用pkg-config这个脚本来生成链接参数。你可以使用pkg-config –list-all查看pkg-config支持的所有链接参数

当你在上面这份列表中查到了自己想要程序包时,就可以使用下面的命令来编译程序了
gcc .suffix pkg-config --libs --cflags

让GCC帮助你更好的工作

上面我们简单介绍了GCC的常用命令行选项,其实GCC的功能比上面提到的那些要丰富得多,GCC对代码的警告、优化、调试等方面提供了丰富的支持,下面我们就从一些例子来看看GCC提供的这些功能。

对问题代码提出警告

GCC对程序代码提供了完整的检查功能,由于C/C++语言本身的特点,很多错误都是程序员无意间犯下的,例如使用了未定义的变量、在bool表达式中使用了=而不是==等等问题,利用GCC提供的代码检查功能,我们可以让编译器为我们找到这些问题,避免运行时发生灾难。

首先,我们来看一个“问题代码”

/* test_warning.c We use this file to check the warning facilities provided by GCC*/
#include
#include
void main() { /* main should return int*/
int a, b;
long long l = 2.2; /* long long type is GNU extension, not standard ANSI / ISO type*/
miss_decl(); /* We call an undeclared function*/
if (a = 0) /* May be we want == here instead of =/
printf(“a really equals to 0 ? / n”);
if (b != 0) /
We used uninitialized variables*/
/* %d and “We should put b here” don’t match*/
printf(“We make a mistake again!b = %d / n”, “We should put b here”);
};
void miss_decl() {
/* /* This type of annotation is prohibited*/
printf(“We should put the declaration before it’s been used!/ n”);
}

上面这些代码故意制造了很多编程中出现的常见问题,接下来,我们就用这段代码来检测一下GCC提供的各种常用的警告设施。

首先,我们不使用任何警告设施编译上面的程序
gcc test_warning.c –o test_warning

默认情况下,GCC会给出输出,其中GCC识别出了main函数不标准(warning)以及使用了未声明的函数(error)两个问题,但是其他的GCC并未察觉。

利用-pedantic找出不符合ANSI / ISO标准的代码
执行下面的命令:
gcc –pedantic test_warning.c –o test_warning

可以看到,这次GCC以警告的形式报告了代码中long long的使用,但是要说明的是我们并不能依赖这个选项来保证我们的代码完全符合ANSI / ISO标准,因为该选项只报告ANSI C要求编译器进行检察的内容。另外,你还可以使用-pedantic-errors让GCC把所有的警告都变成错误。

利用-Wformat检查printf中的参数不匹配问题
执行下面的命令:
gcc –Wformat test_warning.c –o test_warning

利用-WComment找出注释中的错误
执行下面的命令:
gcc –WComment test_warning.c –o test_warning

利用-Wparentheses查找bool表达式中的=错误
执行下面的命令:
gcc –Wparentheses test_warning.c –o test_warning

用-Wuninitialized查找未初始化变量的使用
执行下面的命令:
gcc –O –Wuninitialized test_warning.c –o test_warning
值得说明的是,在使用这个选项的时候,一定要配合上-O(后面我们会提到)选项

利用-Wimplicit-function-declaration / -Werror-implicit-function-declaration检查未声明函数的使用。

执行下面的命令:
gcc -Wimplicit-function-declaration test_warning.c –o test_warning

另外-Werror-implicit-function-declaration和-Wimplicit-function-declaration作用是类似的,只是如果你使用了未声明的函数,前者会把它认为是一个错误。

如果你只是想对你的代码进行全面的检查,你大可不必把上面的选项一并列出来,GCC提供了-Wall选项,含义就是列出所有代码中的警告
执行下面的命令:
gcc –Wall test_warning.c –o test_warning

如果你想走另一个极端,也就是不想让gcc输出任何警告,那么使用-w选项,该选项禁止所有的警告
执行下面的命令:
gcc –w test_warning.c –o test_warnin
<输出结果>

对于上面所有的选项,你都可以把它们和-Werror选项一起使用,这样就可以把所有的警告都变成错误。另外,如果你只是想对代码进行检查而并不执行编译的话,可使用-fsyntax-only选项,像下面的命令这样
gcc –fsyntax-only test_warning.c

基本上来说,我们常用的一些警告选项就是这些,而其中-Wall更是我们极为常用的功能。

优化选项

这一部分的内容可以分成两部分,一部分是让编译器对代码进行分析后,进行的代码优化,另一部分是我们可以为编译器制定一些关于硬件的信息,让他生成对硬件结合的更好的代码,而我们之所以要用源代码来编译程序,很多情况下,是出于这方面的原因。

首先来看代码优化,从代码的整体优化上,GCC提供了下面的选项

-O –O1
这两个选项的含义是一样的,GCC将执行减少代码尺寸和执行时间的优化,对于那些会严重影响编译时间的优化选项,这个级别的优化并不会执行。

-O2
在这一级别GCC将会提供所有支持的优化,但这其中并不包括以空间换时间的优化手段,例如编译器不会使用循环展开和函数内联。和-O相比,该选项进一步加快了编译时间和生成代码的性能。

-O3
除了-O2提供的优化选项外,还指定了-finline-functions,-funswitch-loops和-fgcse-afer-reload选项,目的只有一个就是全力执行代码优化。

-Os
这个选项是专门用来优化代码尺寸的,-Os打开了所有-O2级别中不会显著增长代码尺寸的优化选项

-O0
该选项代表不执行优化

在这里要说明的是,尽管GCC提供了1~3和s这4个整体优化选项,但从实际的优化效果上来看,往往O3优化出来的程序的效率并不是最高的,而大部分情况下我们都在使用-O2,如果你希望获得最高的效率利益,那么不妨这4个选项都试试。另外,其实这些选项只不过是GCC提供的很多单方面优化的一个组合,如果你想了解更为具体的优化内容,可以去查看GCC手册,出于篇幅限制,这里不细谈了。最后要记住的一点是,如果你的程序是用于高精度数值计算的,那么记住不要使用上面任何的优化选项。

下面来看基于硬件优化,由于这部分和计算机硬件相关,这里仅用Intel的CPU做一些说明。

对于所有为Intel和AMD x86-64提供的优化选项都是用m开头的,下面写一些常用的选项:
-march
该选项用来指定CPU的类型,常用的有i386 / i486 / i586 / pentium-mmx / i686 / pentium2 / pentium3 / pentium-m / pentium4 / prescott / k6 / athlon / athlon-4 / k8等等,读者可以根据自己的情况进行指定。

-mfpmath
该选项用于指定浮点运算单元的类型。包括:387

使用标准的数学协处理器:sse

使用SSE指令集提供的标量浮点运算。在Pentium3 / Athlon-4以及更新的芯片上支持这个特性。另外,在pentium4以及AMD x86-64处理器上,SSE2还可以进行双精度浮点计算。

sse,387
混合使用387数学协处理器和SSE指令集,该选项可以充分的利用CPU的浮点寄存器和xmm寄存器,但是该选项还处在试验阶段。
-malign-double

该选项使得GCC把double / long double / long long类型的变量在4字节或2字节地址上对齐,在Pentium级的CPU上,这会使得代码的执行速度更快,当然带来的代价是需要更多的内存来执行程序。
-mmmx –msse –msse2 –msse3 –m3dnow

这些选项用来启动内置函数直接使用这些处理器扩展指令的功能。在编译3D或多媒体程序的时候,使用他们是非常有效的。

对调试的支持
当程序出错的时候,我们可以在Visual Studio中轻松的进行调试,而在Linux中,一旦出现Segmentation Fault,似乎我们除了用眼睛去看代码就没有更好的选择了,其实情况不然,用GCC向程序加入一些适当的调试信息,我们可以利用GDB去调试程序。在这里,我们介绍最为常用的-g和-ggdb选项。

先来看-g。该选项可以利用操作系统的“原生格式(native format)”生成调试信息。GDB可以直接利用这个信息。尽管我们可以把-O和-g放在一起使用,但是,这种做法是极为不推荐的。

如果你想用GDB来调试程序,那么你可以使用-ggdb来让GCC为GDB生成更为丰富的调试信息,但是,此时你就不能用其他的调试器来进行调试了。

最后要说明的是,上面这两个选项都可以接受一个输出调试信息的级别,默认的级别是2。如果你指定1级(-g1),那么GCC会生成最少的调试信息,这包括函数和全局变量的描述信息,但是对于局部变量和行号等信息,在这个级别是不会输出的。另外一个级别是3级(-g3),在这一级别上,GCC会为程序中的所有宏定义和符号生成调试信息。

希望童鞋们能过对想学习Linux开发中用到的一些基本的技术和知识有一个了解,并且能够自己动手开始做些试验性的工作,其实,这里还有很多问题没有谈到,例如利用GDB进行调试、利用make管理工程、利用autoconf为程序生成配置脚本、利用CVS管理程序源文件等等,这些问题有待在今后一起交流。

嵌入式Linux进阶之路

Linux开发工程师

首先成长为一名Linux开发工程师,这是最基本的目标,他应该具备如下的素质:

(1)了解Linux基本概念和基本操作(历史,文化,起源)(命令行,配置,操作,开发模式)
(2)会使用Linux平台下的编辑器vi(默认安装,占内存小,方便向嵌入式移植)
(3)会使用Linux平台下的编译器GCC(相应的十几个命令行参数)
(4)会使用Linux平台下的管理工具Make(与Makefile相配合,尤其用于大项目中多平台移植的情况下)
(5)会使用Linux平台下的调试器GDB(可以了解底层硬件,及汇编指令)

嵌入式工程师

他更关心的是将来在哪一目标机上运行,不关心操作系统,进行的更多与编程相关的工作:
(1)合格的C程序的程序员(理解基本的控制结构:循环、分支、数组、指针)
(2)基本的编程思想(编程实践中总结算法)
(3)程序的编译和链接
(4)可执行文件内部(段、起始地址、如何加载执行)
(5)目标机处理器体系结构
(6)知道处理器的外设即System on Chip(内部存储结构及外设驱动)
(7)汇编语言及指令(主要是用在加载过程中)

系统工程师

系统工程师夹在驱动工程师及应用工程师这两个层次之间,他要对系统有一个整体的概念,系统主要是指操作系统,往往理论有余而实践不足:
(1)进程管理与调度(知道进程间的切换与调度,多任务)
(2)内存管理(建立MMU页表,知道从虚地址到物理地址的映射)
(3)驱动管理(管理设备,编写设备驱动,加载设备驱动)
(4)文件系统(制作文件系统,文件系统的读写,甚至驱动有时也被当成文件系统来管理)
(5)网络协议知识(协议实现作为上层应用与底层驱动之间的衔接)
(6)图形系统知识(支持多窗口及管理窗口,包括窗口切换,窗口移动和覆盖)

嵌入式Linux系统工程师

(1)嵌入式Linux基本概念
(2)交叉编译
(3)Linux内核开发和调试工具
(4)启动代码移植(修改Uboot,Vivi等)
(5)内核交叉编译(Linux不支持芯片时要芯片级的移植,支持芯片时要板级的移植)
(6)根文件系统制作
(7)设备驱动程序开发

下面针对一个简单的"hello world"程序来区分一下以上所讲的工程师之间的区别:

#include<stdio.h>
int main(void)
{
printf(“hello world!\n”);
}

初级程序员看到的是:
a、头文件
b、主函数main
c、子函数printf
d、函数调用
e、入口参数
f、参数返回

高级程序员看到的是:
a、头文件—预处理过程
b、主函数main—程序入口
c、子函数printf—标准库函数
d、函数调用—跳转指令
e、入口参数—栈空间的参数传递
f、函数返回—返回指令

嵌入式程序员看到的是:
a、头文件—预处理过程—宏展开/条件编译
b、主函数main—程序入口—启动代码
c、子函数printf—标准库函数—链接过程
d、函数调用—跳转指令—PC寄存器
e、入口参数—栈空间的参数传递—寻址方式
f、函数返回—返回指令—LR寄存器

嵌入式程序员还能看到的是:
可执行文件的内部组织结构
代码段
指令格式和类型
地址无关代码
数据段
RWdata读写数据段
ROdata只读数据段
BSS未初始化数据段

系统工程师看到的是:
子函数printf—标准库函数—链接过程
动态链接(Dynamic Linking)(需要系统的支持)
静态链接(Static Linking)(嵌入式系统常采用静态链接,以适应不同系统)
标准C库
系统调用(System Call)
软件中断(Software Interrupt)

Linux系统工程师看到的是:
可执行文件的加载过程(不用了解系统调用这一级)
Shell进程–fork系统调用—exec系统调用
进程状态(就绪、运行、阻塞)
进程调度和调度算法
进程的上下文切换

嵌入式Linux系统工程师看到的是:
用户空间和系统空间(分别运行用户模式和管理模式)
ARM—用户模式和管理模式(执行权限不同)
SWI—软中断指令(系统调用就是由用户空间向系统空间切换)
MMU—虚地址和实地址(不同进程如何实现自己独立的地址空间)
BUS—地址总线、数据总线和控制总线(程序如何获得数据,系统如何取指)
SOC—片上系统(整个嵌入式系统如何运行)

|整理文章为传播相关技术,版权原作者所有|
|如有侵权,请联系删除|
参考资料:付斌21ic电子网
嵌入式Linux的一切,看这一篇就够了!
https://mp.weixin.qq.com/s/PVAJVHrzHFRicBNYCzYwaQ

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值