目录
50.简述进行嵌入式Linux C语言简单应用程序开发的流程
GCC是GNU项目的编译器套件,能够编译用C、C++和Objective C编写的程序。
gcc[options][filenames]
该命令行使GCC在给定文件(filenames)上执行编译选项(options)指定的操作。
一个基本实例:
- //test.c
- #include
- void main()
- {
- printf(”Hello world!\n”);
- }
(1)使用gcc编译器编译:gcc -c test.c
(2)用户将会在同一目录下得到一个名为a.out的文件,然后在命令提示符下执行 $./a 就可以显示结果Hello world! 要注意的是a.out是默认生成的目标文件名,如果在同一个目录下,编译另外一个源程序且没有指明生成的目标文件名的话,原先的a.out文件会被覆盖。我们可以使用-o选项指定生成的目标文件名,如:gcc -o test -c test.c 这样在同一目录下会生成名为test.o的目标文件,然后执行$./test即可,会得到同样的输出Hello World!。
当使用GNU中编译语言如gcc、GNU C++编程开发应用时,绝大多数情况要使用make管理项目。要使用make进行项目管理必须编写Makefile文件。
Makefile文件是一个文本形式的数据库文件,其中包含的规则指明make编译哪些文件以及怎样编译这些文件。
一条规则包含3方面内容:make要创建的目标文件(target)、编译目标文件所需的依赖文件列表(dependencies),通过依赖文件创建目标文件所需要执行的命令组(commands)。
首先,包含多个源文件的项目在编译时都有长而复杂的命令行,使用make可以通过把这些命令行保存在makefile文件中而简化这个工作;
其次,使用make可以减少重新编译所需要的时间,因为它可以识别出那些被修改的文件,并且只编译这些文件;
最后,make在一个数据中维护了当前项目中各文件的相互关系,从而可以在编译前检查是否可以找到所有需要的文件。
当登录linux系统,并打开终端。根据登入的身份,进入终端后会看到如下提示符:
[root@tty/]# 或pear@ubuntu:~$
其中,root和pear表示登录用户 。其中,root表示登录用户是超级用户,pear表示表示登录用户是普通用户。Tty和 ubuntu均表示网络中主机名 ,/和 ~ 均表示登录用户的当前目录 (当登录用户,登录主机名以及进入目录不同时,相应的项也会改变),#表示登录用户是超级用户root ,如果是一般用户则为 $ 。
- target:dependency file1 dependency file2[…]
-
- commandl
-
- command2
-
- […]
注意:每一个命令行的首字符必须是Tab制表符,仅使用8个空格不够。除非特别指定,否则make的工作目录就是当前目录。
- printer:printer.o shape.o op_lib.o
- gcc -o printer printer.o shape.o op_lib.o
- printer.o:printer.c printer.h shape.h op_lib.h
- gcc -c printer.c
- shape.o:shape.c shape.h
- gcc -c shape.c
- op_lib.o:op-lib.c op_lib.h
- gcc -c op_lib.c
- clean:
- rm printer *.o
第一行中的三个依赖文件并不存在,如果是在shell上用命令行编译则会出错并退出,但在make管理项目中,在执行gcc时会先检查依赖文件是否存在,若不存在,则会先执行别的规则以生成缺少的依赖文件,最后生成相关的目标文件。如果依赖文件已经存在,则并不急于执行后面的命令重新得到它们,而是比较这些依赖文件以及与其对应的源文件的生成时间,如果有一个或多个源文件比相应依赖文件新,则重新编译这些文件以反映相关源文件的变化,否则,使用旧的依赖文件生成目标文件。
由于伪目标没有依赖文件,它不会自动执行。原因是:make在执行到伪目标时,make先检查它的依赖文件是否存在,由于伪目标没有依赖文件,make就认为该目标是最新版本,不需要重新创建。要想启动伪目标必须使用如下命令:
make[virtual target]
编写Makefile时也可以使用变量,所谓变量就是用指定文本串在Makefile中定义的一个名字,Makefile中变量一般用大写,并用等号给它赋值。引用时只需用括号将变量名括起来并在括号前加上$符号。如:
VARNAME=some-text
当编写大型应用程序的Makefile时,其中涉及的依赖文件和规则繁多,如果使用变量表示某些依赖文件的路径,则会大大简化Makefile。一般在Makefile文件开始就定义文件中所需的所有变量,这样使Makefile文件清晰且便于修改。如(#在Makefile文件中表示后面的内容是注释):
make管理项目也允许在Makefile中使用特殊变量,即make变量。make变量包括环境变量、自动变量和预定义变量。
环境变量就是系统环境变量,make命令执行时会读取系统环境变量并创建与其同名的变量,但是如果Makefile有同名变量,则用户定义变量会覆盖系统的环境变量值。
普通PC的引导装载程序组成:BIOS&BootLoader。BIOS负责完成硬件检测和资源分配。BIOS完成任务后会将控制权交给系统的BootLoader;BootLoader将操作系统内核从硬盘上读入RAM中,然后跳转到内核的入口点,从而启动操作系统。
嵌入式系统由于硬件资源的限制,通常没有像BIOS那样的固件程序(注:有的嵌入式CPU也会内嵌一段短小的启动程序)。整个系统的加载启动全部由BootLoader来处理。
BootLoader就是运行操作系统内核之前所运行的一段小程序,将系统的软硬件设置为一个合适的环境,以便为最终调用操作系统内核做好准备。
(1)对系统的硬件设备进行初始化;
(2)建立内存空间的映射图;
(3)将系统的软硬件设置为一个合适的环境,以便为最终调用操作系统内核做好准备。
大多数BootLoader都包含两种不同的操作模式:启动加载模式+下载模式
(1)启动加载(bootLoading)模式:也称为“自主”(autonomous)模式,也即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。这种模式是BootLoader的正常工作模式。在嵌入式产品发布的时侯,BootLoader必须工作在这种模式下。
(2)下载(down loading)模式:该模式下,目标机上的BootLoader将通过串口连接或网络连接等通信手段从主机(host)下载文件。工作于这种模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口。
主机和目标机之间一般通过串口建立连接,BootLoader软件在执行时通常会通过串口来进行输入输出。传输协议通常是xmodem/ymoderm/zmodem协议中的一种。但是,串口传输的速度是有限的。因此,可以通过以太网连接并借助TFTP协议来下载文件。
由于BootLoader的实现依赖于CPU的体系结构,因此大多数BootLoader都分为stage1和stage2两大部分。
依赖于CPU体系结构的代码通常都放在stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的,一般不会超过256Byte;由于是在内核启动前运行的第一个程序,因此它是在flash上运行的;有的文献把这一阶段称为“Flashloader”阶段。
stage2的代码量大,有上千行,通常用C语言来实现,多数时候需要嵌入汇编语言,可以实现复杂的功能,代码具有更好的可读性和可移植性;在SDRAM等随机存储器中运行;这一阶段是真正的“BootLoader”阶段。
在ARM体系中通常有3种方式控制程序的执行流程:
(1)在正常程序执行过程中,每执行一条ARM指令,程序计数器(PC)的值加4个字节;每执行一条Thumb指令,程序计数器寄存器(PC)的值加2个字节。整个过程按序执行。
(2)通过跳转指令,程序可以跳到指定的地址标号处执行,或者跳到指定的子程序执行。
(3)当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。在进入异常中断处理程序时,要保存被中断的程序的执行现场,在从异常中断处理程序退出时,要恢复被中断的程序的执行现场。
进入嵌入式系统开发时,由于受到硬件资源和环境等因素的影响,必须对Linux内核进行裁减,以适应硬件的需求或者某些环境特殊的实际要求。
Linux内核的配置系统由以下3部分组成:
(1)Makefile:Linux内核源代码中的Makefile定义Linux内核的编译规则;
(2)配置文件(config.in):给用户提供配置选择的功能;
(3)配置工具:包括配置命令解释器和配置用户界面。这些配置工具都是使用脚本语言,如Tcl/TK、Perl编写。
内核独立于普通应用程序,它一般处于系统态,拥有受保护的空间和访问硬件设备的所有权限。这种系统态和被保护起来的内存空间统称为内核空间。
应用程序在用户空间执行,它们只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,不能直接访问硬件,还有其他一些使用限制。
(1)内核编程时不能访问C库;
(2)内核编程时必须使用 GNU C;
(3)内核编程时缺乏像用户空间那样的内存保护机制;
(4)内核编程时浮点数很难使用;
(5)内核只有一个很小的定长堆栈;
(6)内核支持异步中断、抢占和SMP (Symmetric Multi-Processor, 对称式多处理器),因此须注意同步和并发;
(7)要考虑程序可移植性的重要性。
VFS由一组标准的、抽象的文件操作构成,以系统调用的形式提供于用户程序,如read()、write()、lseek()等。这样,用户程序就可以把所有的文件都看作是一致的、抽象的“VFS文件”,例如,在Linux操作系统中,可以按DOS格式的磁盘或分区(即文件系统)“安装”到系统中,然后用户程序就可以按完全相同的方式访问这些文件,就好像它们也是Ext2格式的文件一样。
嵌入式系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。
比如,基于ARM7TDMI内核的CPU在加电或复位时,通常都从地址0x00000000取它的第一条指令。而基于这种CPU构建的嵌入式系统,通常都有某种类型的固态存储设备被映射到这个预先安排的地址上。从而可以使系统在加电后,CPU首先执行BootLoader程序。
BootLoader、内核启动参数以及其它的系统映像在固态存储设备上的分配结构如图所示:
BootLoader启动流程示意图:
以 “Hello World!”程序为例。简述进行嵌入式Linux C语言简单应用程序开发的流程。(编写源程序、编译、调试,并在嵌入式设备上运行)
首先打开编辑器VIM。在Linux PC上打开一个终端窗口,输入命令vi helloworld.c,打开并建立源文件,输入源代码。
【对于Makefile文件的编辑依然可以使用vim。在helloworld.c所在的目录中建立Makefile文件】
(1)在Helloworld.c所在的目录中建立Makefile文件;
(2)Makefile文件保存好后,接下来就可以在helloworld.c文件目录下执行make命令进行编译了。编译后会产生ARM-Linux下的可执行文件helloworld,可以将此文件下载到目标板进行调试运行。
【由于嵌入式系统资源有限性,一般不能直接在目标系统进行调试, 通常采用gdb+gdbserver的方式进行调试,采用远程调试(remote)的方法。】
(1)建立安装gdb组件
(2)调试运行
(3)调试命令(注意命令列表中括号里的内容为命令简写方式)
Linux文件主要分为4种:普通文件、目录文件、链接文件和设备文件。函数:open close read write 。
UNIT11-嵌入式Linux应用程序设计:本章介绍了嵌入式Linux应用程序的一般开发方法。通过实例分析了简单应用、文件I/O操作和网络通信应用程序的设计的方法;并详细讲解了一些常用的API。