描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302501671丛书名: 21世纪高等学校嵌入式系统专业规划教材
本书的主要内容包含:嵌入式系统基础、基于ARM9处理器的硬件平台、Linux编程基础、嵌入式开发环境和系统移植、Linux驱动程序设计 、Qt/E应用程序设计、嵌入式数据库应用程序。并附Linux常用命令简介。
本书将温度采集系统项目分解成若干个子项目,分别放到各部分的实验之中,只要将这些实验综合在一起就可能形成一个系统。
本书适合高等院校计算机、电子、通信等专业嵌入式方向的教材,也可作为嵌入式领域科研人员的技术参考书。
目录
第1章嵌入式系统基础
1.1嵌入式系统的定义
1.2嵌入式系统的发展历程
1.2.1嵌入式系统的由来
1.2.2嵌入式系统发展的四个阶段
1.2.3嵌入式系统的发展趋势
1.3嵌入式系统的特点
1.4嵌入式系统的结构
1.5嵌入式处理器
1.5.1嵌入式处理器的特点
1.5.2嵌入式处理器的分类
1.5.3典型的嵌入式处理器
1.6嵌入式操作系统
1.7实例: 网络温度采集系统
1.8练习题
第2章基于ARM9处理器的硬件平台
2.1ARM处理器简介
2.1.1ARM处理器核的体系结构
2.1.2ARM微处理器核
2.1.3ARM编程模型
2.1.4ARM指令集
2.2S3C2410X控制器简介
2.2.1S3C2410X内部结构
2.2.2存储控制器
2.2.3NAND Flash控制器
2.2.4时钟和电源管理
2.2.5GPIO端口
2.2.6ADC和触摸屏接口
2.2.7PWM定时器
2.2.8通用异步收发器
2.2.9中断控制器
2.3S3C2410X外围硬件电路
2.3.1电源电路
2.3.2复位电路
2.3.3NAND Flash接口电路
2.3.4SDRAM接口电路
2.3.5UART串口电路
2.4练习题
第3章Linux系统编程基础
3.1GCC编译器
3.1.1GCC概述
3.1.2GCC编译过程
3.1.3GCC选项
3.2GDB调试器
3.2.1GDB的基本使用方法
3.2.2GDB基本命令
3.2.3GDB典型实例
3.3Make工具的使用
3.3.1Makefile的基础知识
3.3.2Makefile的应用
3.3.3自动生成Makefile
3.4Linux应用程序设计
3.4.1文件操作编程
3.4.2时间编程
3.4.3多线程编程
3.5练习题
第4章嵌入式交叉编译环境及系统裁剪
4.1嵌入式交叉编译环境构建
4.1.1嵌入式常用调试方法
4.1.2交叉编译环境构建
4.1.3串口通信软件配置
4.1.4Flash程序烧写
4.2Bootloader程序
4.2.1初识Bootloader程序
4.2.2常用的Linux Bootloader
4.2.3vivi的裁剪和编译
4.3Linux操作系统的裁剪和编译
4.3.1内核的裁剪和编译
4.3.2根文件系统的构建
4.4练习题
第5章Linux驱动程序设计
5.1Linux驱动程序概述
5.1.1设备驱动原理
5.1.2设备分类
5.1.3设备文件接口
5.1.4驱动程序的加载方法
5.1.5设备驱动的重要数据结构
5.1.6驱动程序常用函数介绍
5.2虚拟字符设备Demo驱动程序设计
5.2.1Demo字符设备
5.2.2Demo驱动程序设计
5.2.3Demo测试程序设计
5.3A/D驱动程序设计
5.3.1ADC工作原理
5.3.2A/D驱动程序设计
5.3.3温度采集应用程序设计
5.4练习题
第6章Qtopia Core应用程序设计
6.1嵌入式GUI概述
6.2Qtopia Core简介
6.2.1Qt简介
6.2.2Qt的体系结构
6.3Qtopia Core开发环境的构建
6.4Qtopia Core程序开发基础
6.4.1Qt中的主要类
6.4.2信号和槽
6.5Qtopia Core程序的结构与实例
6.6Qtopia Core交叉编译
6.7练习题
第7章嵌入式数据库
7.1嵌入式数据库概述
7.1.1为什么需要嵌入式数据库
7.1.2什么是嵌入式数据库
7.1.3常用的嵌入式数据库
7.2SQLite数据库
7.2.1SQLite安装
7.2.2SQLite命令
7.2.3SQLite数据类型
7.2.4SQLite的API函数
7.3基于Qtopia Core和SQLite的图书管理系统
7.4练习题
附录ALinux常用命令的使用
A.1基本命令
A.1.1管理文件和目录命令
A.1.2进程、关机和线上查询命令
A.1.3其他常用命令
A.2网络命令
A.3服务器配置
A.3.1FTP服务器
A.3.2Telnet服务器
A.3.3NFS服务器
附录Bvi基本操作
B.1vi简介
B.2vi基本操作
B.3基本命令
参考文献
近年来,随着嵌入式系统产品的迅猛发展,社会对嵌入式技术人才的需求也越来越多,学习嵌入式技术的人员数量也在迅速增加。由于嵌入式系统的多样性,增加了嵌入式系统学习和开发的难度。为了让初学者能较为全面地学习嵌入式系统的开发过程,为将来从事嵌入式领域的工作奠定基础,我们编写了本教材。全书共分7章,第1章讲述嵌入式系统基础知识、嵌入式处理器和嵌入式操作系统等,便于读者对嵌入式系统有初步认识。第2章讲述ARM系列处理器、S3C2410X控制器内部结构及外围电路等,让读者对嵌入式硬件平台有一个全面的认识。第3章讲述GCC编译工具的使用,以及Linux系统文件操作、时间获取和多线程编程等内容,为以后的嵌入式软件开发打基础。第4章讲述交叉编译环境的构建,以及Linux系统软件的裁剪和编译等。第5章讲述驱动程序基础,以及Linux系统字符设备驱动程序的设计,重点讲解了A/D驱动程序设计。第6章讲述Qtopia Core嵌入式图形界面应用程序设计。第7章讲述嵌入式数据库程序设计,并通过实例讲解了数据库在图形界面中的应用。书后附有Linux常用命令和vi的使用。本书由朱华生、吕莉、熊志文和徐晨光共同编著。其中,朱华生编写了第1章,吕莉编写了第2、3、4章,熊志文编写了第6、7章,徐晨光编写了第5章、附录A和B,全书由朱华生统稿。在本书的编写过程中,得到了清华大学出版社和南昌工程学院的大力支持和帮助,在此表示衷心感谢。鉴于作者水平有限,对于教材的内容及文字的不妥之处,望读者批评指正。编者希望在汲取大家意见和建议的基础上,不断修改和完善书中的有关内容,力争下一次改版后的内容更加充实正确。任何批评和建议请发到[email protected],以便共同提高。
编者2018年3月
GCC(GNU C Compiler)是GUN项目的C编译器套件,也是GNU软件家族中具有代表性的产品之一。GCC目前支持的体系结构有四十余种,如x86、ARM、PowerPC等系列处理器; 能运行在不同的操作系统上,如Linux、Solaris、Windows CE等操作系统; 可完成C、C 、Objective C等源文件向运行在特定CPU硬件上的目标代码的转换。GCC的执行效率与一般的编译器相比平均效率要高20%~30%。GCC是Linux平台下常用的编译器之一,它也是Linux平台编译器事实上的标准。同时,在使用Linux操作系统的嵌入式开发领域,GCC也是使用普遍的编译器之一。GCC编译器与GUN Binutils工具包是紧密集成的,如果没有Binutils工具,GCC也不能正常工作。Binutils是一系列开发工具,包括连接器、汇编器和其他用于目标文件和档案的工具。Binutils工具集里主要包含以下一系列程序: addr2line、ar、as、c 、gprof、ld、nm、objcopy、objdump、ranlib、readelf、size、strings和strip,它包含的库文件有: libiberty.a、libbfd.a、libbfd.so、libopcodes.a和libopcodes.so。在Linux操作系统中,文件的后缀名不代表文件的类型,但为了提高工作效率,通常会给每种文件定义一个后缀名。GCC支持的文件类型比较多,具体如表3.1所示。
表3.1GCC支持的文件类型
后缀说明后缀说明
.cC源程序.ii经过预处理的C 程序.a由目标文件构成的档案文件(库文件).mObjective C源程序.C.ccC 源程序.o编译后的目标程序.h头文件.s汇编语言源程序.i经过预处理的C程序.S经过预编译的汇编程序
3.1.2GCC编译过程下面通过一个常用的例子来说明GCC的编译过程。利用文本编辑器创建hello.c文件,程序内容如下。
#include
void main()
{
char msg[80]=”Hello,world!”;
printf( “%s\n”,msg);
}
编写完后,执行以下编译指令。
#gcc hello.c
因为编译时没有加任何选项,所以会默认生成一个名为a.out的可执行文件。执行该文件的命令及结果如下。
#./a.out
Hello,world!
使用GCC由C语言源代码程序生成可执行文件要经历4个过程,如图3.1所示。
图3.1GCC编译过程
1. 预编译预编译(preprocessing)的主要功能是读取源程序,并对头文件(include)、预编译语句(如define等)和一些特殊符号进行分析和处理。如把头文件复制到源文件中,并将输出的内容送到系统的标准输出。源代码中的预编译指示以“#”为前缀。通过在GCC后加上E选项完成对代码的预编译。命令如下。
# gcc E hello.c
执行命令时,控制台上会有数千行的输出,其中大多数来自stdio.h头文件,也有部分是声明。预编译主要完成以下3个具体任务。(1) 把include中的头文件复制到要编译的源文件中。(2) 用实际值替代define文本。(3) 在调用宏的地方进行宏替换。下面通过实例test.c来理解预编译完成的工作。test.c的代码如下。
#define number (1 2*3)
int main()
{
int n;
n=number 3;
return 0;
}
对test.c文件进行预编译,输入以下命令。
#gcc E test.c
执行命令后会显示如下内容。
# 1 “test.c”
# 1 “”
# 1 “”
# 1 “test.c”
main()
{
int n;
n=(1 2*3) 3;
return 0;
}
如果要将预编译结果保存在test.i文件中,可以输入以下命令。
#gcc E test.c o test.i
2. 编译编译(compilation)的主要功能包括两部分,部分是检查代码的语法,如果出现语法错误,则给出错误提示代码,并结束编译; 只有在代码无语法错误的情况下,才能进入第二部分。第二部分是将预编译后的文件转换成汇编语言,并自动生成后缀为.s的文件。编译的命令如下。
#gcc S test.c
执行命令后会生成一个名为test.s的汇编程序,文件内容如下。
.file “test.c”
.text
.align 2
.globl main
.type main,@function
main:
pushl%ebp
movl%esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl$10,-4(%ebp)
movl$0, %eax
leave
ret
.Lfe1:
.sizemain,.Lfe1main
.ident”GCC: (GNU) 3.2 20020903 (Red Hat Linux 8.0 3.27)”
3. 汇编汇编(assembly)的主要功能是将汇编语言代码变成目标代码(机器代码)。汇编只是将汇编语言代码转换成目标代码,但不进行连接,目标代码不能在CPU上运行。汇编使用选项为c,它会自动生成一个后缀名为.o的目标程序。汇编的命令如下。
#gcc c test.c
执行命令后会生成一个名为test.o的目标文件,目标文件是一个二进制文件,所以不能用文本编辑器来查看它的内容。4. 连接连接(linking)的主要功能是连接目标代码,并生成可执行文件。连接的命令如下。
#gcc o test test.o
也可以执行以下命令。
#gcc o test test.c
执行命令后会生成一个名为test的可执行文件。通过执行./test命令,就可以运行指定的程序。命令中的“./”是指在当前目录下执行程序。3.1.3GCC选项GCC编译器提供了较多的选项。选项必须以“”开始,常用的选项如表3.2所示。
表3.2GCC的常用选项
选项说明
c编译生成目标文件,后缀为.oE 只进行预编译,不做其他处理g在执行程序中包括标准调试信息I DirName将DirName加入到头文件的搜索目录列表中L DirName将DirName加入到库文件的搜索目录列表中,在默认情况下gcc只链接共享库l FOO链接名为libFOO的函数库O整个源代码会在编译、连接过程中进行优化处理,可执行文件的执行效率可以提高,但是编译、连接的速度就相应的要慢些O2比O有更好的优化能力,但编译连接速度就更慢o FileName指定输出文件名,如果没有指定,默认文件名是a.outpipe在编译过程的不同阶段间使用管道S只编译不汇编,生成汇编代码static链接静态库wall指定产生全部的警告信息
1. 输出文件选项如果不使用任何选项进行编译,生成的可执行文件都是a.out。如果要指定输出的文件名,可以使用选项o。例如将源文件hello.c编译成可执行文件hello。命令格式如下。
#gcc o hello hello.c
2. 链接库文件选项Linux操作系统下的库文件包括两种格式,一种是动态链接库,另一种是静态链接库。动态链接库的后缀为.so,静态链接库的后缀为.a。动态链接库是在程序运行过程中进行动态加载,静态链接库是在编译过程中完成静态加载。使用GCC编译时,编译器会自动调用C标准库文件,但当要使用到标准库以外的库文件时,一定要使用选项l来指定具体库的文件名,否则会报编译错误,如报undefined reference to ‘xxxx’错误。Linux操作系统下的库文件都是以lib三个字母开头的,因此在使用l选项指定链接的库文件名时可以省去l、i、b三个字母。例如,有一个多线程程序pthread.c,需要用到libpthread.a或libpthread.so库文件(文件保存在/usr/lib目录)。编译生成一个名为pthread的可执行程序的命令格式如下。
#gcc pthread.c lpthread o pthread
GCC在默认情况下,优先使用动态链接库,当需要强制使用静态链接库时,需要加上static选项。使用静态链接库,编译生成一个名为pthreads的可执行程序的命令格式如下。
#gcc pthread.c static lpthread o pthreads
可以使用ls l命令查看文件的大小,会发现pthreads比pthread文件大很多。
rwxrxrx 1 root root 12014 12月 28 22:22 pthread
rwxrxrx 1 root root 589856 12月 28 22:22 pthreads
3. 指定头文件目录选项编译时,编译器会自动到默认目录(一般为/usr/include)寻找头文件,但当文件中的头文件不在默认目录时,就需要使用I选项来指定头文件所在的目录(或称文件所在的路径)。如果不指定头文件所在的目录,编译时会报xxx.h: No such file or directory错误。假设someapp.c程序中有一个自定义的头文件放置在/usr/local/include/someapp目录下,则命令格式如下。
#gcc I /usr/local/include/someapp o someapp someapp.c
4. 指定库文件目录选项编译时,编译器会自动到默认目录(一般为/usr/lib)寻找库文件,但当编译时所用的库文件不在默认目录时,就需要使用L选项来指定库文件所在的目录。如果不指定库文件所在的目录,编译时会报cannot find lxxx错误。假设程序my.c需要使用libnew.so库文件,且该库文件保存在/home/someuser/lib目录,则命令格式如下。
#gcc my.c L/home/someuser/lib lnew o my
5. 警告选项在编译过程中,编译器的警告信息对于程序员来说是非常重要的,GCC包含完整的警告提示功能,以便确定代码是否正确,尽可能实现可移植性。GCC的编译器警告选项如表3.3所示。
表3.3GCC的警告选项
类型说明
Wall启用所有警告信息Werror在发生警告时取消编译操作,即将警告看作是错误w禁用所有警告信息
下面通过一实例来了解如何在编译时产生警告信息。example.c的代码如下。
#include
int main ()
{
int x,y;
for(x=1;x<=5;x )
printf(“x=%d\n”,x);
}
使用以下命令进行编译。
#gcc example.c
编译过程没有任何提示信息,生成一个a.out可执行文件。如果加入Wall选项进行编译,命令如下。
# gcc Wall example.c o example
编译过程将会出现下面的警告信息。
example.c: In function ‘main’:
example.c:4: warning: unused variable ‘y’
example.c:7: warning: control reaches end of nonvoid function
第1条警告信息的意思是: 在main函数有警告信息。第2条警告信息的意思是: 指出变量y在程序中未使用。第3条警告信息的意思是: main函数的返回类型是int,但在程序中没有return语句。GCC给出的警告从严格意义上不算错误,但是可能会成为错误的栖息之地。所以在嵌入式软件开发时,需要重视警告信息,好根据警告信息对源程序进行修改,直至编译时没有任何警告信息。Werror选项会要求GCC将所有的警告信息当成错误进行处理,需要将所有的警告信息都修改后才能生成可执行文件。命令如下。
#gcc Wall Werror example.c o example
当需要忽略警告信息时,可以使用w选项,命令如下。
#gcc w example.c o example
6. 调试选项代码通过了编译并不代表能正常工作。可以通过调试器检查代码,以便更好地找到程序中的问题。Linux下主要采用的是GDB调试器。在使用GDB之前,在执行程序中要包括标准调试信息,加入的方法是采用调试选项g。具体的命令如下。
#gcc g c hello.c
#gcc g o hello hello.o
7. 优化选项优化选项的作用在于缩减代码规模和提高代码执行效率,常用的选项有以下几个。(1) O、O1: 整个源代码会在编译、连接过程中进行优化处理,可执行文件的执行效率可以提高,但是编译、连接的速度会相应慢些。对于复杂函数,优化编译会占用较多的时间和相当大的内存。在O1下,编译会尽量减少代码的体积和代码的运行时间,但是并不执行会花费大量时间的优化操作。(2) O2: 除了不涉及空间和速度交换的优化选项,执行几乎所有的优化工作。比O有更好的优化效果,但编译连接速度更慢。O2将会花费更多的编译时间同时也会生成性能更好的代码。但并不执行循环展开和函数“内联”优化操作。(3) O3: 在O2的基础上加入函数内联、循环展开和其他一些与处理器特性相关的优化工作。下面通过optimize.c程序观察一下优化前后的效果。
#include
int main(void)
{
double counter;
double result;
double temp;
for (counter=0;counter<2000.0*2000.0*2000.0/20.0 2020; counter = (5-1)/4)
{
temp = counter / 1979;
result = counter;
}
printf(“Result is %lf\\n”, result);
return 0;
}
不加优化选项进行编译,程序执行耗时如下。
# gcc optimize.c o optimize
# time ./optimize
Result is 400002019.000000\n
real0m4.203s
user 0m4.190s
sys0m0.020s
增加优化选项进行编译,程序执行耗时如下。
# gcc O1 optimize.c o optimize1
# time ./optimize1
Result is 400002019.000000\n
real0m1.064s
user 0m1.060s
sys0m0.010s
3.2GDB调试器应用程序的调试是开发过程中必不可少的环节之一。Linux下的GNU的调试器称为GDB(GUN Debugger),该软件早由Richard Stallman编写。GDB是一个用来调试C和C 语言程序的调试器,它能使开发者在程序运行时观察程序的内部结构和内存的使用情况。GDB主要可以完成下面4个方面的功能。(1) 启动程序,按照程序员自定义的要求运行程序。(2) 单步执行、设置断点,可以让被调试的程序在所指定的断点处停住。(3) 监视程序中变量的值。(4) 动态地改变程序的执行环境。3.2.1GDB的基本使用方法下面通过一个例子test.c介绍GDB的基本使用方法,test.c文件的代码如下。
#include
int sum(int n);
main()
{
int s=0;
int i,n;
for(i=0;i<=50;i )
{
s=i s;
}
s=s sum(20);
printf(“the result is %d\n”,s);
}
int sum(int n)
{
int total=0;
int i;
for(i=0;i<=n;i )
total=total i;
return (total);
}
使用GDB调试器,必须在编译时加入调试选项g,命令如下。
#gcc g test.c o test
#gdb testGNU gdb Red Hat Linux(5.3post0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB.Type “show warranty” for details.
This GDB was configured as “i386redhatlinuxgnu”…
(gdb) l1#include
2int sum(int n);
3main()
4{
5int s=0;
6int i,n;
7for(i=0;i<=50;i )
8{
9s=i s;
10}
(gdb) l
11s=s sum(20);
12printf(“the result is %d\n”,s);
13}
14int sum(int n)
15{
16int total=0;
17int i;
18for(i=0;i<=n;i )
19total=total i;
20return (total);
(gdb) l
21}
(gdb) break 7 Breakpoint 1 at 0x804833f: file test.c,line 7.
(gdb) break sum Breakpoint 2 at 0x804838a: file test.c,line 16.
(gdb) info break Num TypeDisp Enb AddressWhat
1 breakpointkeep y 0x0804833f in main at test.c:7
2 breakpointkeep y 0x0804838a in main at test.c:16
(gdb) r Staring program: /lvli/test
Breakpoint 1, main () at test.c:7
7for(i=0;i<=50;i )
(gdb) n 9s=i s;
(gdb) n
7for(i=0;i<=50;i )
(gdb) print s $1 = 0
(gdb) c Continuing.
Breakpoin 2 ,sum(n=20) at test.c:16
16int total=0;
(gdb) c
Continuing.
the result is 1485
Program exited with code 024.
(gdb) q
3.2.2GDB基本命令GDB命令很多,可以通过help来帮助,方法是: 在启动GDB后,输入help命令。
(gdb)help
List of classes of commands:
aliases Aliases of other commands
breakpoints Making program stop at certain points
data Examining data
files Specifying and examining files
internals Maintenance commands
obscure Obscure features
running Running the program
stack Examining the stack
status Status inquiries
support Support facilities
tracepoints Tracing of program execution without stopping the program
userdefined Userdefined commands
Type “help” followed by a class name for a list of commands in that class.
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
因为GDB命令有很多,所以将它们分成许多种类。help命令只列出了GDB的命令种类,如果要查看某一种类下的具体命令,可以在help命令后加类名,具体格式如下。
help
例如想了解running类下的具体命令,可以输入以下命令。
help running
常用的GDB命令如表3.4所示。
表3.4GDB常用命令描述
命令描述
backtrace显示程序中的当前位置和表示如何到达当前位置的栈跟踪break设置断点cd改变当前工作目录clear清除停止处的断点continue从断点处开始继续执行delete删除一个断点或监测点display程序停止时显示变量或表达式file装入要调试的可执行文件info查看程序的各种信息kill终止正在调试的程序list列出源文件内容续表
命令描述
make使用户不退出GDB就可以重新产生可执行文件next执行一行代码,从而执行其整体的一个函数print显示变量或表达式的值pwd显示当前工作目录quit退出GDBrun执行当前被调试的程序set给变量赋值shell不退出GDB就执行UNIX shell命令step执行一行代码且进入函数内部watch设置监视点,使用户能监视一个变量或表达式的值而不管它何时变化
3.2.3GDB典型实例下面的程序中植入了错误,通过这个存在错误的程序掌握如何利用GDB进行程序调试。有一个bug.c程序,它的功能是将输入的字符串逆序显示在屏幕上,源代码如下。
#include
#include
int main(void)
{
int i,len;
char str[]=”hello”;
char *rev_string;
len=strlen(str);
rev_string=(char *)malloc(len 1);
printf(“%s\n”,str);
for(i=0;irev_string[len-i]=str[i];
rev_string[len 1]=’\0′;
printf(“the reverse string is%s\n”,rev_string);
}
程序的编译和运行结果如下。
# gcc o bug bug.c
# ./bug
hello
the reverse string is
以上运行的结果是错误的,正确结果如下。
hello
the reverse string is olleh
这时,可以使用GDB调试器来查看问题在哪儿。具体步骤是: 编译时加上g调试选项,然后再对可执行程序进行调试,命令如下。
# gcc g o bug bug.c
# gdb bug
执行命令后,进入调试环境,显示如下。
(gdb) l1#include
2#include
3int main(void)
4{
5 int i,len;
6 char str[]=”hello”;
7 char *rev_string;
8 len=strlen(str);
9 rev_string=(char *)malloc(len 1);
10printf(“%s\n”,str);
(gdb) l
11for(i=0;i12 rev_string[len-i]=str[i];
13rev_string[len 1]=’\0′;
14printf(“the reverse string is%s\n”,rev_string);
15}
(gdb) break 8 Breakpoint 1 at 0x80483b2: file example.c, line 8.
(gdb) r Starting program: /lvli/program/bugg/bug
Breakpoint 1, main () at bug.c:8
8 len=strlen(str);
(gdb) n 9 rev_string=(char *)malloc(len 1);
(gdb) print len $1 = 5
(gdb) n 10printf(“%s\n”,str);
(gdb) n
hello
11for(i=0;i(gdb) n
12 rev_string[len-i]=str[i];
(gdb) n
11for(i=0;i(gdb) n
12 rev_string[len-i]=str[i];
(gdb) n
11for(i=0;i(gdb) print rev_string[5] $2 = 104 ‘h’
(gdb) print rev_string[4] $3 = 101 ‘e’
(gdb) n
12 rev_string[len-i]=str[i];
(gdb) n
11for(i=0;i(gdb) print rev_string[3]
$4 = 108 ‘l’
(gdb) n
12 rev_string[len-i]=str[i];
(gdb) n
11for(i=0;i(gdb) print rev_string[2]
$5 = 108 ‘l’
(gdb) n
12 rev_string[len-i]=str[i];
(gdb) n
11for(i=0;i(gdb) print rev_string[1]
$6 = 111 ‘o’
(gdb) n
13rev_string[len 1]=’\0′;
(gdb) n
14printf(“the reverse string is%s\n”,rev_string);
(gdb) print rev_string[0] $7 = 0 ‘\0’
(gdb) c Continuing.
the reverse string is
通过以上调试过程可见,错误的根源在于没有给rev_string[0]赋值,所以rev_string[0]为’\0’,导致字符串输出为空。可以将rev_string[len-i]改成rev_string[len-1-i],这样结果就是期待的结果。3.3Make工具的使用在大型软件项目的开发过程中,通常有成百上千个源文件,如Linux内核源文件。如果每次都通过手工输入GCC命令进行编译,非常不方便,所以引入了Make工具来解决这个问题。Make工具可以将大型的开发项目分解成为多个更易于管理的模块,简洁明了地理顺各个源文件之间纷繁复杂的相互依赖关系,后自动完成编译工作。Make工具主要的作用是通过Makefile文件来描述源程序之间的相互关系,并自动完成维护编译工作。Makefile文件需要严格按照语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并定义源文件之间的依赖关系等。 3.3.1Makefile的基础知识1. Makefile文件
Makefile是描述文件依赖关系的说明,它由若干个规则组成,每个规则的格式如下。目标: 依赖关系
命令
其中: 目标是指make终需要创建的东西。另外,目标也可以是一个make执行的动作名称,如目标clean,可以称这样的目标为“伪目标”。依赖关系是指编译目标体要依赖的一个或多个文件列表。命令是指为了从指定的依赖体创建出目标体所需执行的命令。一个规则可以有多个命令行,每一条命令占一行。注意: 每一个命令的个字符必须是制表符Tab,如果使用空格会导致错误,make会在执行过程中显示Missing Separator(缺少分隔符)并停止。在3.1节中创建了一个名为hello.c的文件,并使用命令gcc o hello hello.c生成了一个可执行文件hello。如果要利用make工具生成可执行程序,则首先要在hello.c所在的目录下编写一个Makefile文件,文件内容如下。
all: hello.o
gcc hello.o o hello
hello.o:hello.c
gcc c hello.c o hello.o
clean:
rm *.o hello
以上共有3个规则,个规则是生成hello可执行程序,第二个规则是生成hello.o目标文件,第三个规则是删除hello和后缀为.o的所有文件。上面例子中的编译器是GCC,而在嵌入式项目开发中经常要使用交叉编译器,如本书采用的交叉编译器是armlinuxgcc。如果要交叉编译hello.c程序,就要将Makefile文件中所有gcc替换成armlinuxgcc,如果一个一个修改会非常麻烦,所以在Makefile中引进变量来解决。使用变量将上面的Makefile文件改写为如下形式。
CC=gcc
OBJECT=hello.o
all:$(OBJECT)
$(CC) $(OBJECT) o hello
$(OBJECT):hello.c
$(CC) c hello.c o $(OBJECT)
clean:
rm *.o hello
在文件中,CC和OBJECT是定义的两个变量,它们的值分别是gcc和hello.o。变量的引用方法是: 把变量用括号括起来,并在前面加上“$”。例如引用变量CC,就可以写成$(CC)。变量一般在Makefile文件的头部进行定义,按照惯例,变量名一般使用大写字母。变量的内容可以是命令、文件、目录、变量、文件列表、参数列表、常量、目标名等。如果要对上面的hello.c进行交叉编译,可以将Makefile文件改写为如下形式。
CROSS=armlinux
CC=$(CROSS)gcc
OBJECT=hello.o
all:$(OBJECT)
$(CC) $(OBJECT) o hello
$(OBJECT):hello.c
$(CC) c hello.c o $(OBJECT)
clean:
rm *.o hello
2. Make工具的使用Makefile文件编写完成以后,需要通过make工具来执行,命令格式如下。
#make [target]
参数target是指要处理的目标名。make命令会自动查找当前目录下的Makefile或makefile文件,如果文件存在就执行,否则报错。如果make命令后面没有任何参数,则表示处理Makefile文件中的个目标。例如,如果使用前面编写好的Makefile文件,执行make命令或make all命令,都表示执行all目标,即生成hello文件; 执行make clean命令,表示执行clean目标,即删除hello和后缀为.o的所有文件。GUN Make工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索Makefile文件,也可以通过f参数指定描述文件。如果编写的Makefile文件名为zhs,则可以通过make f zhs命令来执行。Make工具的选项很多,读者可以到make工具参考书上查找。3.3.2Makefile的应用3.3.1节只介绍了Makefile的简单编写和使用方法,本节通过实例来详细讲解Makefile的应用。1. 所有文件均在一个目录下的Makefile的编写现有7个文件分别是m.c、m.h、study.c、listen.c、visit.c、play.c、watch.c。m.c文件的内容如下。
#include
main()
{
int i;
printf(“please input the value of i from 1 to5:\n”);
scanf(“%d”,&i);
if(i==1)
visit();
else if(i==2)
study();
else if(i==3)
play();
else if(i==4)
watch();
else if(i==5)
listen();
else
printf(“nothing to do\n”);
printf(“This is a woderful day\n”);
}
study.c文件的内容如下。
void study()
{
printf(“study embedded system today\n”);
}
listen.c文件的内容如下。
#include
void listen()
{
printf(“listen english today\n”);
}
play.c文件的内容如下。
#include
void play()
{
printf(“play football today\n”);
}
visit.c文件的内容如下。
#include
void visit()
{
printf(“visit friend today\n”);
}
watch.c文件的内容如下。
#include
void watch()
{
printf(“watch TV today\n”);
}
m.h文件的内容如下。
void visit();
void listen();
void watch();
void study();
void play();
从上面的代码可以看出这些文件之间的相互依赖关系,如图3.2所示。
图3.2文件之间的依赖关系
现在利用这7个程序生成一个名为m的可执行程序,Makefile文件可编写如下。
CC=gcc
TARGET=All
OBJECTS= m.o visit.o listen.o watch.o study.o play.o
$(TARGET):$(OBJECTS)
$(CC) $(OBJECTS) o m
m.o:m.c m.h
$(CC) c m.c o m.o
visit.o:visit.c
$(CC) c visit.c o visit.o
listen.o:listen.c
$(CC) c listen.c o listen.o
watch.o:watch.c
$(CC) c watch.c o watch.o
study.o:study.c
$(CC) c study.c o study.o
play.o:play.c
$(CC) c play.c o play.o
clean:
rm *.o
这个Makefile文件可以通过预定义变量来简化。常见预定义变量如表3.5所示。
表3.5Makefile预定义变量
变量说明
$@规则的目标所对应的文件名$*不包含扩展名的目标文件名称$ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件$%如果目标是归档成员,则该变量表示目标的归档成员名称$
现用$@、$CC=gcc
TARGET=All
OBJECTS= m.o visit.o listen.o watch.o study.o play.o
$(TARGET):$(OBJECTS)
$(CC) $^ o m
m.o:m.c m.h
$(CC) c $< o $@
visit.o:visit.c
$(CC) c $< o $@
listen.o:listen.c
$(CC) c $< o $@
watch.o:watch.c
$(CC) c $< o $@
study.o:study.c
$(CC) c $< o $@
play.o:play.c
$(CC) c $< o $@
clean:
rm *.o
从修改后的Makefile文件可以看出,各个文件的编译命令几乎没有区别,所以进一步用%和*两个通配符来简化。
CC=gcc
TARGET=All
OBJECTS= m.o visit.o listen.o watch.o study.o play.o
$(TARGET):$(OBJECTS)
$(CC) $^ o m
*.o:*.c
$(CC) c $< o $@
clean:
rm *.o
2. 编写文件在不同目录下的Makefile假设程序的目录结构为: 源文件、可执行文件和Makefile在src目录中,头文件在include目录中,obj存放.o文件。就需要指定文件和头文件路径。仍以上面的程序为例。Makefile文件如下。
CC=gcc
SRC_DIR=./
OBJ_DIR=../obj/
INC_DIR=../include/
TARGET=all
$(TARGET):$(OBJ_DIR)m.o $(OBJ_DIR)visit.o $(OBJ_DIR)listen.o $(OBJ_DIR)watch.o\ $(OBJ_DIR)study.o $(OBJ_DIR)play.o
$(CC) $^ o $(SRC_DIR)m
$(OBJ_DIR)m.o:$(SRC_DIR)m.c $(INC_DIR)m.h
$(CC) I$(INC_DIR) c o $@ $<
$(OBJ_DIR)visit.o:$(SRC_DIR)visit.c
$(CC) c $< o @
$(OBJ_DIR)listen.o:$(SRC_DIR)listen.c
$(CC) c $< o @
$(OBJ_DIR)watch.o:$(SRC_DIR)watch.c
$(CC) c $< o @
$(OBJ_DIR)study.o:$(SRC_DIR)study.c
$(CC) c $< o @
$(OBJ_DIR)play.o:$(SRC_DIR)play.c
$(CC) c $< o @
clean:
rm $(OBJ_DIR)*.o
3.3.3自动生成Makefile编写Makefile确实不是一件轻松的事,尤其对于一个较大的项目而言更是如此。本节要讲的autoTools系列工具正是为此而设的,它只需用户输入简单的目标文件、依赖文件、文件目录等就可以轻松地生成Makefile。另外,这些工具还可以完成系统配置信息的收集,方便地处理各种移植性的问题。autoTools是系列工具,它包含了aclocal、autoscan、autoconf、autoheader和automake工具,使用autoTools主要就是利用各个工具的脚本文件来生成后的Makefile文件。其总体流程如图3.3所示。
图3.3自动生成Makefile的流程图
以3.1.2节中的hello.c为例介绍自动生成Makefile的过程。1. autoscan
# ls
hello.c
# autoscan
# ls
autoscan.log configure.scan hello.c
2. 创建configure.in文件configure.in是autoconf的脚本配置文件,是在configure.scan基础上修改的。修改如下。
# vi configure.scan
#*Autoconf*//以“#”号开始的行为注释
AC_PREREQ(2.59) //本文件要求的autoconf版本
AC_INIT(hello,1.0) // AC_INIT宏用来定义软件的名称和版本等信息
AM_INIT_AUTOMAKE(hello,1.0)//是automake所的宏,软件名称和版本号
AC_CONFIG_SRCDIR([hello.c]) //用来侦测所指定的源码文件是否存在
AC_CONFIG_HEADER([config.h])//用于生成config.h文件,以便autoheader 使用
AC_PROG_CC
AC_CONFIG_FILES([Makefile])//用于生成相应的Makefile 文件
AC_OUTPUT
后用命令mv configure.scan configure.in将configure.scan改成configure.in。3. 运行aclocal生成aclocal.m4文件
# aclocal
# ls
aclocal.m4 autoscan.log configure.in hello.c
4. 运行autoconf,生成configure可执行文件
# autoconf
# ls
aclocal.m4 autom4te.cache autoscan.log configure configure.in hello.c
5. 使用autoheader生成config.h.in
# autoheader
6. 创建Makefile.am文件automake用的脚本配置文件是Makefile.am,需要先创建相应的文件。
# vi Makefile.am
内容为: AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c
接下来用automake生成Makefile.in,使用选项addingmissing可以让automake自动添加一些必需的脚本文件,命令如下。
# automake addmissing
configure.in: installing ‘./installsh’
configure.in: installing ‘./missing’
Makefile.am: installing ‘depcomp
# ls
aclocal.m4 autoscan.log configure depcomp installsh Makefile.in
autom4te.cache config.h.in configure.in hello.c Makefile.am missing
7. configure通过运行自动配置设置文件configure,Makefile.in变成了终的Makefile。
# ./configure
checking for a BSDcompatible install… /usr/bin/install c
checking whether build environment is sane… yes
checking for gawk… gawk
checking whether make sets $(MAKE)… yes
checking for gcc… gcc
checking for C compiler default output… a.out
checking whether the C compiler works… yes
checking whether we are cross compiling… no
checking for suffix of executables…
checking for suffix of object files… o
checking whether we are using the GNU C compiler… yes
checking whether gcc acceptsg… yes
checking for gcc option to accept ANSI C… none needed
checking for style of include used by make… GNU
checking dependency style of gcc… gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
8. 执行make命令,生成可执行文件hello
# make
cd . && /bin/sh ./config.status config.h
config.status: creating config.h
config.status: config.h is unchanged
make allam
make[1]: Entering directory ‘/lvli/12’
source=’hello.c’ object=’hello.o’ libtool=no \
depfile=’.deps/hello.Po’ tmpdepfile=’.deps/hello.TPo’ \
depmode=gcc3 /bin/sh ./depcomp \
gcc DHAVE_CONFIG_H I. I. I.g O2 c ‘testf ‘hello.c’ ‖ echo ‘./”hello.c
gcc g O2o hello hello.o
cd . && /bin/sh ./config.status config.h
config.status: creating config.h
config.status: config.h is unchanged
make[1]: Leaving directory ‘/lvli/12
9. 运行hello
# ./hello
hello,world!
3.4Linux应用程序设计虽然Linux操作系统下的C语言编程与Windows操作系统下的C语言编程方法基本相同,但是也有细微的差别。下面通过文件操作、时间获取和创建线程等任务来了解Linux应用程序设计。3.4.1文件操作编程在Linux操作系统下,实现文件操作可以采用两种方法,一种是通过Linux系统调用来实现,另一种是通过C语言库函数调用来实现。前者依赖于Linux操作系统,后者独立于具体操作系统,即在任何操作系统下,使用C语言库函数操作文件的方法都相同。Linux的系统调用在5.1.3节介绍,本节只介绍利用C语言库函数来操作文件。下面介绍一些常用的文件操作函数,这些函数的说明包含在stdio.h头文件中。1. 打开和关闭文件函数打开文件可通过fopen函数来完成,关闭文件可通过fclose函数来完成,格式如下。
FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);
其中: 参数filename表示需要打开的文件名(包括路径,默认为当前路径)。mode为文件打开模式,常见模式如表3.6所示。若成功打开文件,fopen函数返回值是文件指针,若文件打开失败则返回NULL,并把错误代码存在errno 中。
表3.6常见模式
模式含义
r, rb只读方式打开文件,该文件必须存在r , rb 读写方式打开文件,若文件不存在则自动创建w, wb只写方式打开文件,若文件不存在则自动创建w , wb 读写方式打开文件,若文件不存在则自动创建a, ab追加方式打开文件,若文件不存在则自动创建a ,ab 读和追加方式打开文件,若文件不存在则自动创建
模式中的b用于区分文本文件和二进制文件。在Windows操作系统下有区分,但在Linux下不需要区分。2. 读取文件数据函数读取文件数据可通过fread、fgetc和fgets等函数实现,格式如下。
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
int fgetc(FILE * stream);
char *fgets(char *s,int size,FILE *stream);
fread函数的功能是从stream指向的文件中,读取长度为n*size个字节的字符串,并将读取的数据保存到ptr缓存中,返回值是实际读出数据的字节数。fgetc函数的功能是从stream指向的文件中读取一个字符,若读到文件尾,则返回EOF。fgets函数的功能是从stream指向的文件中读取一串字符,并存到s缓存中,直到出现换行字符、文件尾或已读了size-1个字符时结束,后会加上NULL作为字符串结束符。3. 向文件写数据函数向文件写数据可通过fwrite、fputc和fputs等函数实现,格式如下。
size_t fwrite(const void *ptr,size_t size,size_t n,FILE *stream);
int fputc(int c,FILE *stream);
int fputs(const char *s,FILE *stream);
fwrite函数的功能是将ptr缓存中的数据写到stream指向的文件中,写入长度为n*size个字节,返回值是实际写入的字节数。fputc函数的功能是向stream指向的文件中写入一个字符。fputs函数的功能是将s缓存中的字符串写入到stream指向的文件中。下面通过一个实际应用加深对文件操作的理解。【程序3.1】文件复制程序file_copy.c。
#include
#include
#define BUFFER_SIZE 1024
int main(int argc, char ** argv )
{
FILE *fileFrom,*fileTo;
char buffer[BUFFER_SIZE]={0};
int length=0;
/*检查输入命令格式是否正确*/
if(argc!=3)
{printf(“Usage: %s fileFrom fileTo\n”, argv[0]);
exit(0);
}
/*打开源文件*/
fileFrom = fopen(argv[1],”rb “);
if(fileFrom==NULL)
{
printf(” Open File %s Failed\n”, argv[1]);
exit(0);
}
/*打开或创建目标文件*/
fileTo = fopen(argv[2],”wb “);
if(fileTo==NULL)
{
printf(” Open File %s Failed\n”, argv[2]);
exit(0);
}
/*复制文件内容*/
while ((length =fread(buffer,1,BUFFER_SIZE,fileFrom))>0 )
{
fwrite(buffer,1,length,fileTo);
}
/*关闭文件*/
fclose(fileFrom);
fclose(fileTo);
return 0;
}
编译源程序并生成可执行程序file_copy,然后执行file_copy程序将hello.c复制成zhs.c,则编译和运行命令如下。
#gcc file_copy.c o file_copy
#./file_copy hello.c zhs.c
3.4.2时间编程在编程中经常要使用到时间,如获取系统时间、计算事件耗时等。下面介绍一些常用的时间函数,这些函数的说明包含在time.h头文件中。1. time函数函数格式: time_ttime(time_t*tloc); 函数功能: 获取日历时间,即从1970年1月1日0点到现在所经历的秒数,结果保存在tloc中。如果操作成功,则返回值为经历的秒数; 若操作失败,则返回值为((time_t)-1),错误原因存于errno中。2. gmtime函数函数格式: struct tm*gmtime(const time_t*timep);函数功能: 将日历时间转化为格林威治标准时间,并将数据保存在tm结构中。tm结构的定义如下。
struct tm
{
int tm_sec;//秒
int tm_min; //分
int tm_hour; //时
int tm_mday; //日
int tm_mon; //月
int tm_year; //年
int tm_wday; //本周第几日
int tm_yday; //本月第几日
int tm_isdst; //日光节约时间
};
3. gettimeofday函数函数格式: int gettimeofday(struct timeval *tv,struct timezone *tz); 函数功能: 获取从今日凌晨到现在的时间差,并存放在tv中,然后将当地时区的信息存放到tz中。两个结构的定义如下。
strut timeval {
long tv_sec;/* 秒数 */
long tv_usec; /* 微秒数 */
};
struct timezone{
int tz_minuteswest; /*和GMT时间差*/
int tz_dsttime;
};
4. sleep和usleep函数函数格式: unsigned int sleep(unsigned int sec);
void usleep(unsigned long usec);
函数功能: sleep函数的功能是使程序睡眠sec秒,usleep函数的功能是使程序睡眠usec微秒。下面通过一个实际应用加深对获取时间方法的理解。【程序3.2】算法分析程序test_time.c。
#include
#include
#include
#include
/* 算法 */
void function()
{
unsigned int i,j;
double y;
for(i=0;i<100;i )
for(j=0;j<100;j )
{usleep(10);y ;}
}
main()
{
struct timeval tpstart,tpend;
float timeuse;
gettimeofday(&tpstart,NULL);// 获取开始运行时间
function();
gettimeofday(&tpend,NULL); // 获取运行结束时间
/* 计算算法执行时间 */
timeuse=1000000*(tpend.tv_sectpstart.tv_sec) tpend.tv_usectpstart.tv_usec;
timeuse/=1000000;
printf(“Used Time:%f sec.\n”,timeuse);
exit(0);
}
程序编译及运行结果如下。
#gcc test_time.cotest_time
#./test_time
Used Time : 39.432861 sec.
3.4.3多线程编程采用多线程可以提高程序的运行效率,目前绝大多数嵌入式操作系统和中间件都支持多线程。Linux操作系统的多线程遵循POSIX线程接口,称为pthread。在Linux操作系统进行多线程编程时,需要使用到pthread.h头文件和libpthread.a库文件。下面介绍几个常用的线程函数。1. pthread_create函数函数格式: int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*start_rtn) (void ), void *arg); 函数功能: 创建一个新的线程。参数tid为线程id; attr为线程属性,通常设置为NULL; start_rtn是线程要执行的函数; arg是执行函数start_rtn的参数。当创建线程成功时,函数返回值为0; 若返回值为EAGAIN,则表示系统限制创建新的线程,例如线程数目过多; 若返回值为EINVAL,则表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。2. pthread_exit函数函数格式: int pthread_exit(void *rval_ptr); 函数功能: 退出当前线程,返回值保存在rval_ptr中。3. pthread_join函数函数格式: int pthread_join(pthread_t tid, void **rval_ptr);函数功能: 阻塞调用线程,直到指定的线程终止。参数tid是指定的线程,rval_ptr是线程退出的返回值。下面通过一个实际应用加深对多线程编程的理解。【程序3.3】创建线程程序pthread_create.c。
#include
#include
#include
/*子线程执行的函数*/
void *thread(void *str)
{
int i;
for (i = 0; i < 6; i)
{
sleep(2);
printf( “This in the thread : %d\n” , i );
}
return NULL;
}
int main()
{
pthread_t pth;
int i;
int ret;
ret=pthread_create(&pth, NULL, thread, (void *)(i));//创建线程
printf(“Test start\n”);
for (i = 0; i < 6; i)
{
sleep(1);
printf( “This in the main : %d\n” , i );
}
pthread_join(pth, NULL); //等待线程结束
return 0;
}
程序编译及运行结果如下。
# gcc pthread_create.c lpthread o pthread_create
# ./pthread_create
Test start
This in the main : 0//主线程上的输出
This in the thread : 0 //子线程上的输出
This in the main : 1//主线程上的输出
This in the main : 2//主线程上的输出
This in the thread : 1//子线程上的输出
This in the main : 3//主线程上的输出
This in the main : 4//主线程上的输出
This in the thread : 2//子线程上的输出
This in the main : 5//主线程上的输出
This in the thread : 3//子线程上的输出
This in the thread : 4//子线程上的输出
This in the thread : 5//子线程上的输出
3.5练习题1. 选择题(1) GCC软件是()。A. 调试器B. 编译器C. 文本编辑器D. 连接器(2) GDB软件是()。A. 调试器B. 编译器 C. 文本编辑器 D. 连接器(3) 如果生成通用计算机上(系统是Linux操作系统)能够执行的程序,则使用的C编译是()。A. TCB. VC C. GCC D. armlinuxgcc(4) GCC用于指定头文件目录的选项是()。A. oB. L C. g D. I(5) make有许多预定义变量,表示“目标完整名称”的是()。A. $@ B. $^ C. $< D. $>2. 填空题(1) Linux下,动态链接库文件是以结尾的,静态链接库文件是以结尾的。动态链接库是在动态加载的,静态链接库是在静态加载的。(2) GCC指定库文件目录选项的字母是。指定头文件目录选项的字母是。指定输出文件名选项的字母是。(3) 为了方便文件的编辑,在编辑Makefile时,可以使用变量。引用变量时,只需在变量前面加上符。(4) Makefile文件预定定义变量有很多,列举3个预定定义变量: 、和。(5) Makefile文件预定定义变量“$@”表示,“$^”表示,“$#include
main()
{
int i;
printf(“please input the value of i from 1 to5:\n”);
scanf(“%d”,&i);
if(i==1)
visit();
if(i==2)
study();
}
visit.h文件
void visit();
study.h文件
void study();
visit.c文件
#include “visit.h”
void visit()
{
printf(“visit friend today\n”);
}
study.c文件
#include “study.h”
void study()
{
printf(“study embedded system today\n”);
}
① 如果上述文件在同一目录,请编写Makefile文件,用于生成可执行程序zhs。② 如果按照下面的目录结构存放文件,请改写Makefile文件。bin: 存放生成的可执行文件; obj: 存放.o文件; include: 存放visit.h、study.h; src: 存放main.c、visit.c、study.c和Makefile。③ 如果按照下面的目录结构存放文件,请改写Makefile文件。bin: 存放生成的可执行文件; obj: 存放.o文件; include: 存放visit.h和study.h; src: 存放main.c和 Makefile; src1: 存放visit.c; src2: 存放study.c。(2) 按照要求完成以下操作。① 用vi编辑test.c文件,其内容如下。
#include
int main()
{
int s=0,i;
for(i=1;i<=15;i )
{
s=s i;
printf(“the value of s is %d \n”,s);
}
return 0;
}
② 使用gcc o test.o test.c编译,生成test.o。③ 使用gcc g o test1.o test.c编译,生成test1.o。④ 比较test.o和test1.o文件的大小,思考为什么?(3) 使用GDB调试上面的程序。① 带调试参数g进行编译。
#gcc g test.c o test
② 启动GDB调试,开始调试。
#gdb Gtest
③ 使用gdb的命令进行调试。(4) 编写一个程序,将系统时间以yearmonthday hour: minute: second格式显示在屏幕上,并将它保存在time.txt文件中。
评论
还没有评论。