1.基于操作系统的程序设计

1.1 多用户环境下的程序设计

   多用户环境下的程序设计是在操作系统的管理下进行的,即用户程序的执行,对系统设备、文件等各种资源的访问使用是在操作系统的控制下进行的。

1.2 系统编程的思想

   系统编程的思想就是要求程序员要站在操作系统的角度看问题,充分考虑到系统为用户提供了哪些服务和资源并正确地利用。

   系统编程的思想是对一个合格程序员的基本要求。



1.基于操作系统的程序设计

   系统编程的思想就是要程序员采用核心技术来解决实际问题,真正做到对系统资源的有效利用。

   在系统资源有限,特别是嵌入式系统设计中,系统编程的思想就更为重要。



2.Linux/UNIX系统调用

2.1系统调用与库函数

2.2系统支持

   文件系统;标准输入/输出;进程ID;出错处理。

出错处理:出错时系统调用或函数返回一个值(负数),并对全局变量errno进行设置,标准C提供了获取错误信息的方法。

#include<string.h>

char *strerror(int errno);


#include<stdio.h>

void perror(const char *msg);



2.Linux/UNIX系统调用

2.1系统调用与库函数

2.2系统支持

   文件系统;标准输入/输出;进程ID;出错处理。

出错处理:出错时系统调用或函数返回一个值(负数),并对全局变量errno进行设置,标准C提供了获取错误信息的方法。

#include<string.h>

char *strerror(int errno);


#include<stdio.h>

void perror(const char *msg);



2.Linux/UNIX系统调用

#include <string.h>

#include <stdio.h>

#include <errno.h>

int main(int argc,char *argv[])

{ char *str1;

str1=strerror(EACCES);

fprintf(stdout,"errno is %d:%s\n",EACCES,str1);

errno=ENOENT;

perror(argv[0]);

errno=EFAULT;

perror(argv[0]);

errno=EINVAL;

perror(argv[0]);

}



3  linux下程序开发流程

编写源代码(vi,emacs gedi)

编译源程序(gcc)

运行程序

调试程序(gdb)

交叉编译(嵌入式系统)

编写Makefile(vi,autotools)



3.1  gcc

   Linux系统下的gcc(GNU C Compiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作之一。gcc可以在多种硬体平台上编译出可执行程序,其执行效率与一般的编译器相比平均效率要高20%~30%。

   gcc编译器能将C、C++语言源程序、汇编程序、JAVA以及其他一些类型的语言程序编译、链接成可执行文件。在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。



   使用gcc 编译程序时, 编译过程可以被细分为四个阶段:

预处理(Pre-Processing):分析处理源代码中的各种宏指令如:#include #if;

编译(Compiling):检查代码的规范性,是否有语法错误,确认无误后把代码翻译为汇编语言代码;

汇编(Assembling):把汇编代码转换为二进制目标代码

链接(Linking):把二进制代码以及库文件连接起来成为可执行文件。



gcc 通过后缀来区别输入文件的类别:

.c 为后缀的文件: C 语言源代码文件

.a 为后缀的文件: 是由目标文件构成的库文件

.C ,.cc 或.cxx 为后缀的文件: 是C++ 源代码文件

.h 为后缀的文件: 头文件

.i 为后缀的文件: 是已经预处理过的C源代码文件

.ii 为后缀的文件: 是已经预处理过的C++ 源代码文件

.o 为后缀的文件: 是编译后的目标文件

.s 为后缀的文件: 是汇编语言源代码文件

.S 为后缀的文件: 是经过预编译的汇编语言源代码文件。

3.1  gcc



gcc 最基本的用法是∶

gcc [options] [filenames]

gcc 编译器的编译选项大约有100 多个,这里只介绍其中最基本、最常用的参数。

-c    编译为目标文件,不连接库

-S    编译为汇编代码

-E    预处理.预处理之后的代码将送往标准输出

-Wwarn  设置警告,可以设置的警告开关很多,通常用-Wall开启所有的警告;-w :不生成任何警告信息


3.1  gcc


-O level :设置优化级别,level可以是0,1,2,3或者s,默认为-O0,即不进行优化处理,没有数字默认是1,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是编译、连接的速度就相应地要慢一些。


例:

gcc optimize.c -o optimize

time ./optimize

gcc –O optimize.c -o optimize

time ./optimize

对比两次执行的输出结果

3.1  gcc



#include <stdio.h>

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;

}

一个例子:optimize.c



-Idirname  

   把dirname加到头文件的搜索路径中,而且gcc会在搜索标准头文件之前先搜索dirname.

C 程序中的头文件包含两种情况∶

#include <A.h>

#include “B.h”

对于<> ,预处理程序cpp 在系统预设的头文件目录( 如/usr/include) 中搜寻相应的文件;而对于””,cpp 在当前目录中搜寻头文件。这个选项的作用是告诉cpp ,如果在当前目录中没有找到需要的文件,就到指定的dirname 目录中去寻找。

例:gcc foo.c -I /home/include -o foo






-Ldirname :将dirname所指出的目录加入到库文件的目录列表中在默认状态下,连接程序ld 在系统的预设路径中(如/usr/lib) 寻找所需要的库文件,这个选项告诉连接程序,首先到-L 指定的目录中去寻找,然后再到系统预设路径中寻找。

函数库分为静态库和动态库。

静态库:链接时,静态库的文件代码会被拷贝到可执行文件中。

动态库:链接时,动态库的代码不会被加入可执行文件中,而是在程序被执行的时候加载。

例:gcc –static hello.c -o hello

动态通常用.so 为后缀,静态用.a 为后缀。例如:libhello.so libhello.a 。






–l name :在连接时,装载名字为“libname.a” 的函数库,该函数库位于系统预设的目录或者由-L 选项确定的目录下。

例如,-lm 表示连接名“libm.a” 的数学函数库。


例:gcc foo.c -L /home/lib -lfoo -o foo


-llibrary 在连接的时候搜索library库.库是一些archieve文件--其成员是目标文件.如果有文件引用library, library在命令行的位置应该在那个文件之后,因此,越底层的库越要放在后面.比如如果要连接pcap库,那么就需要使用-lpcap对源文件进行编译.







-g 产生调试信息. gdb能够使用这些调试信息。

-o outfile 指定输出文件的文件名,默认为a.out






Dname=definition: 在命令行上定义宏,有两种方式,-Dname或者-Dname=definition. 等效于在程序中使用#define MACRO

在命令行上设置宏定义的目的主要是为了在调试的时候设定一些开关,而在发布的时候再关闭或者打开这些开关即可,当然宏定义也用来对代码进行有选择地编译.另外也还有其他的一些作用.

-Uname 取消宏定义name,作用和上面的正好相反.





gdb调试器是一款GNU开发组织并发布的UNIX/Linux下的程序调试工具,能在程序运行时观察程序的内部结构和内存的使用情况。它具有以下一些功能:

·监视程序中变量的值;

·设置断点以使程序在指定的代码行上停止执行;

·逐行的执行代码。

以下是利用gdb进行调试的步骤:

1)必须使程序在编译时包含调试信息。即包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb利用这些信息使源代码和机器码相关联。

      在编译时:gcc  –g  file.c


3.2 gdb






命  令 命令缩写 描                         述  

file   装入欲调试的可执行文件  

kill k 终止正在调试的程序  

list l 列出产生执行文件的源代码部分  

next n 执行一行源代码但不进入函数内部  

step s 执行一行源代码并进入函数内部  

run r 执行当前被调试的程序  

quit q 终止gdb  

watch   监视一个变量的值而不管它何时被改变  

break b 在代码里设置断点,使程序执行到这里时被挂起  

display disp 跟踪查看变量  

info i 描述程序状态,info break查看断点  

print p 打印内部变量的值  

congtinue c 继续程序运行  

set var   设置变量的值  

backtrace bt 查看函数调用信息 (在访问函数时查看)  

frame f 查看栈帧  

start st 执行程序至main()函数第一条指令  

finish  结束函数  

gdb常用命令



启动gdb的方法有以下几种:

   1、gdb program

   2、gdb program core

      用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件(一般是段错误)。

  ulimit -c unlimited

  gdb program core.NNNN(core文件)

   3、gdb program PID

      如果程序是一个服务程序,那么可以指定这个服务程序运行时的进程ID。gdb会自动attach上去并调试。program应该在PATH环境变量中搜索得到。


例:设有程序greet.c编译(gcc  -g –o greet greet.c)后,运行gdb  greet ,出现提示符(gdb),此时可在提示符下输入gdb的命令,如:

(gdb)list     //列出源程序(10行)

(gdb)b 9  或break func    //在指定行号或函数名处设置断点

(gdb) info break   //查看断点

(gdb) bt               //查看堆栈

(gdb) watch var1 或disp var1

(gdb) r

(gdb) c

(gdb) p var2

(gdb)quit




嵌入式软件开发中往往是基于交叉编译环境的,开发是在宿主机中完成(例如x86架构的PC机),运行是在目标机(例如ARM架构的嵌入式产品)中。因此还需要交叉编译。

即:通过基于ARM架构的gcc编译器将源程序编译成嵌入式产品中的可执行程序。



3.3 交叉编译



Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量的编译工作。用户只需编写一次简单的编译语句就可以


Make的优点

“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。

3.4 Makefile



Make的工作原理

当输入make命令之后,会默认的在当前目录下寻找名为“Makefile”或“makefile”的文件。

寻找到文件之后,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在找了依赖关系之后,冒号后面的文件还是不存在,那么就什么也不做。  

3.4 Makefile



   缺省情况下,当make寻找makefile文件时,它试图搜寻具有如下的名字的文件,按顺序:‘GNUmakefile’、‘makefile’和‘Makefile’。

   通常情况下把makefile文件命名为‘makefile’或‘Makefile’。(推荐使用‘Makefile’,因为它基本出现在目录列表的前面,后面挨着其它重要的文件如‘README’等)。虽然首先搜寻‘GNUmakefile’,但并不推荐使用。除非makefile文件是特为GNU make编写的,在其它make版本上不能执行,才使用‘GNUmakefile’作为makefile的文件名。

    如果make不能发现具有上面所述名字的文件,它将不使用任何makefile文件。可以使用命令参数给定目标,make试图利用内建的隐含规则确定如何重建目标。

使用非标准名字makefile文件,-f name或--file=name指定makefile文件

3.4 Makefile



一个简单的Makefile文件包含一系列的“规则”,其样式如下:

目标(target)⋯: 依赖(prerequiries)⋯

<tab>命令(command)

目标(target)通常是要产生的文件的名称,目标的例子是可执行文件或OBJ文件。目标也可是一个执行的动作名称,诸

如‘clean’(详细内容请参阅假想目标一节)。

依赖是用来输入从而产生目标的文件,一个目标经常有几个依赖。

命令是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。注意:每个命令行前面必须是一个Tab字符,即命

令行第一个字符是Tab。这是不小心容易出错的地方。  

3.4 Makefile


通常,如果一个依赖发生变化,则需要规则调用命令对相应依赖和服务进行处理从而更新或创建目标。但是,指定命令更新目标的规则并不都需要依赖,例如,包含和目标‘clean’相联系的删除命令的规则就没有依赖。

规则一般是用于解释怎样和何时重建特定文件的,这些特定文件是这个详尽规则的目标。Make需首先调用命令对依赖进行处理,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作。

一个Makefile文件可以包含规则以外的其它文本,但一个简单的Makefile文件仅仅需要包含规则。  

3.4 Makefile



makefile文件包含5方面内容:具体规则、隐含规则、定义变量、指令和注释。

以‘#’开始的行是注释行。  

3.4 Makefile



edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o

cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

main.o : main.c defs.h

cc -c main.c

kbd.o : kbd.c defs.h command.h

cc -c kbd.c

command.o : command.c defs.h command.h

cc -c command.c

display.o : display.c defs.h buffer.h

cc -c display.c

insert.o : insert.c defs.h buffer.h

cc -c insert.c

search.o : search.c defs.h buffer.h

cc -c search.c

files.o : files.c defs.h buffer.h command.h

cc -c files.c

utils.o : utils.c defs.h

cc -c utils.c

clean :

rm edit main.o kbd.o command.o display.o \

insert.o search.o files.o utils.o

一个例子



使用Makefile文件创建可执行的称为‘edit’的文件,键入:make

使用Makefile文件从目录中删除可执行文件和目标,键入:make clean

在例子中,目标包括可执行文件‘edit’和OBJ文件‘main.o’及‘kdb.o’。依赖是C语言源文件和C语言头文件如‘main.c’和‘def.h’等。事实上,每一个OBJ文件即是目标也是依赖。所以命令行包括‘cc -c main.c’和‘cc -c kbd.c’。

当目标是一个文件时,如果它的任一个依赖发生变化,目标必须重新编译和连接。任何命令行的第一个字符必须是‘Tab’字符(一定要牢记:Make并不知道命令是如何工作的,它仅仅能提供保证目标需要更新时,按照制定的具体规则执行命令。)

目标‘clean’不是一个文件,仅仅是一个动作的名称。正常情况下,在规则中‘clean’这个动作并不执行,目标‘clean’也不需要任何依赖。一般情况下,除非特意告诉make执行‘clean’命令,否则‘clean’命令永远不会执行。象这些不需要依赖仅仅表达动作的目标称为假想目标

一个例子



在makefile文件中使用名为objects, OBJECTS, objs, OBJS, obj, 或 OBJ的变量代表所有OBJ文件已是约定。这里定义格式如下:

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o

然后,在每一个需要列举OBJ文件的地方,使用写为$(objects)形式的变量代替。下面是使用变量后的完整的makefile文件:

objects = main.o kbd.o command.o display.o \

insert.o search.o files.o utils.o

edit : $(objects)

cc -o edit $(objects)

cc -c main.c

……

clean :

rm  $(objects)


使用变量简化makefile文件


分析以下的makefile文件。

CC = gcc

OPTIONS = -x04 –o

OBJECTS = main.o input.o compute.o

SOURCES = main.c input.c compute.c

HEADERS = main.h input.c compute.h

power:main.c $(OBJECTS)

$(CC) $(OPTIONS) power $(OBJECTS) –lm

main.o:main.c $(HEADERS)

input.o:input.c input.h

compute.o:compute.c compute.h

all.tar:$(SOUCES) $(HEADERS) makefile

tar -cvf $(SOURCES) $(headers) makefile > all.tar

clean:

rm *.o