第2章Linux系统应用程序开发38读性大大降低的糟糕情况,同时不要试图去将你的代码注释写得更好,而是应该将程序代码写得更好,不要花费大量的时间和精力去解释这些的代码。通常情况下,注释只说明代码的功能,而不会说明其实现的原理,因为基于一个好的软件开发流程而产生出来的软件代码,它的编制基础是详细设计和实现文档,软件代码仅仅是该文档的产出物。这也说明程序中的注释仅仅是完成了简单说明和介绍的功能。在实际应用过程中,应该避免把注释插到函数体内,应写在函数前面,以说明其功能。同样对于必须加以解释的代码或变量,注释也应写在它们的前面。●大括号的运用大括号(“{”和“}”)的处理在C程序书写风格中也是很重要的,与缩进格式不同,几乎没有任何理由可以说服程序员去选择一种风格而不是选择另外一种风格,我们建议采用统一的风格来处理大括号的排版,而不会在意函数体还是其他——开始和结束的括号都放在下一行的第一列。●函数的书写格式函数应该是短小精悍的,它的代码长度应该有限。也就是说,一个函数的昀大长度和函数的复杂程度以及缩进大小成反比。如果计划编写一个简单但长度相对较长的函数,并且已经对不同的情况做了很多细化的工作,那么编写一个稍长的函数也是可以的。但是,假如计划编写一个很复杂的函数,而且你已经估计到,其他人很难读懂这个函数,一般建议请重新考虑这个函数,并将它们分割成更小的函数。在进行函数设计时,还需要考虑的是,该函数困难要定义的局部变量的数量,理论上,这些变量不应该超过10个,否则就有可能出错。●其他方面的注意事项(1)如果参数太多,不能放在同一行,则在每行参数开头处对齐。(2)当一个表达式需要分成多行书写时,应该在操作符之前分割。(3)尽量不要让两个不同优先级的操作符出现在相同的对齐方式中,应该增加括号通过代码缩进表示嵌套关系。(4)不要在声明多个变量时跨行,每一行都应以一个新的声明开头。(5)当一个if-else语句中嵌套了另一个if-else语句时,应该用大括号把if-else语句括起来。(6)尽量避免在if条件中进行赋值运算。(7)如果没有声明,不要将BOOL值TRUE和FALSE对应与1和0进行编程,大多数编译器会将FALSE视为0,任何非0值都是TRUE。我们建议重新定义BOOL值并锁定0和1。(8)预防和避免非法指针的使用。2.3Linux系统程序应用开发实例分析本节内容包括较为丰富,考虑工程开发的实际需要,笔者在这里精心选编了若干比较有代表性的例子来加以介绍,包括一些常见的程序开发所必须的典型案例与技巧,包括系统文件系统应用编程、内存管理、多进程编程、进程通讯机制、串口通讯、网络编程、GUI编程初步等各个部分。所涵盖内容覆盖面较全,每一个实例都给出实际代码,并尽可能的给出真实的实验结果,考虑篇幅限制,每个实验都给出简要注释和分析,详尽分析留给读者。嵌入式Linux工程开发实践392.3.1文件系统应用编程Linux支持多种文件系统,如ext、ext2、minix、iso9660、msdos、fat、vfat、nfs等。在这些具体文件系统的上层,Linux提供了虚拟文件系统(VFS)来统一它们的行为,虚拟文件系统为不同的文件系统与内核的通信提供了一致的接口。下面对涉及到的文件系统编程关键函数给以分析:z关键函数分析1.在linux下,在对文件的任何操作之前,必须先用open()函数将文件打开,其原型为:intopen(constchar*filename,intflags[,mode_tmode])其中[]中的项为可选项,filename为指定的文件及其路径名,flags参数控制文件的打开方式,可以是O_RDONLY、O_RDWR、O_WRONLY等等,分别表示只读、读写、只写方式等,mode参数指定正在创建的文件的存储权限,可以是S_IWGRP、S_IRUSR、S_IWUSR等的或值。函数创建并返回一个由filename所指定的文件的心的描述符。错误时,返回一个-1,并给errno置相应的错误代码。2.对文件进行读操作可以用read()函数,其原型为:ssize_tread(intfd,void*buffer,size_tsize)该函数从文件描述符fd所指的文件中读取size个字节的字符,并把它放到buffer所指向的缓冲区中。函数正常返回实际所读取的字节数。返回0表明到达了文件的尾端,返回-1表明出错,并给errno置相应的错误代码。3.对文件进行写操作可以用write()函数,其原型为:ssize_twrite(intfd,constvoid*buffer,size_tsize)该函数把size个字节从buffer所指向的缓冲区中写入到用fd所指向的文件中。函数正常返回实际所写入的字节数。返回-1表明出错,并给errno置相应的错误代码。4.当对文件操作完毕后,要将文件关闭,否则会出现数据丢失的情况。关闭文件用close()函数,其原型为:intclose(intfd)。fd为已经打开的文件的描述符。正常操作的返回是0,在操作失败时返回-1,并给errno置相应的错误代码。5.linux系统中所有文件都有一个与之相对应的索引节点,其中包括了文件的相关信息。这些信息被保存在stat结构体中。可以使用stat()函数来查看stat结构体。该函数的原型为:intstat(constchar*pathname,stat*sbuf)参数sbuf是指向stat结构体的指针,函数正常返回值为0;调用失败时,返回-1,并将errno设置为相应值。6.在linux中,打开一个目录流,可以使用open()函数,其原型为:DIR*opendir(constchar*dirname)该函数返回一个指向DIR数据类型的指针。读取目录项,可以使用readdir()函数:structdirent*readdir(DIR*dirstream)函数返回包含文件信息的结构指针。7.关闭目录流用closedir()函数:intclosedir(DIR*dirstream).。z实例分析下面通过编写一个程序,来具体说明问题。在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello,world”,关闭该文件。再次打开该文件,读取其中的内容并输出在屏幕上。具体程序代码如下:#includesys/types.h#includesys/stat.h#includefcntl.h第2章Linux系统应用程序开发40#includestdio.h#defineLENGTH100main(){intfd,len;charstr[LENGTH];fd=open(hello.txt,O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);/*创建并打开文件*/if(fd){write(fd,Hello,world,strlen(Hello,world));/*写入Hello,world字符串*/close(fd);}fd=open(hello.txt,O_RDWR);len=read(fd,str,LENGTH);/*读取文件内容*/str[len]='\0';printf(%s\n,str);close(fd);}运行结果如图2.14所示:2.3.2内存分配管理内存管理系统是操作系统中昀为重要的部分,因为系统的物理内存总是少于系统所需要的内存数量。虚拟内存就是为了克服这个矛盾而采用的策略。系统的虚拟内存通过在各个进程之间共享内存而使系统看起来有多于实际内存的内存容量。虚拟内存可以提供以下的功能:z广阔的地址空间。系统的虚拟内存可以比系统的实际内存大很多倍。z进程的保护。系统中的每一个进程都有自己的虚拟地址空间。这些虚拟地址空间是完全分开的,这样一个进程的运行不会影响其他进程。并且,硬件上的虚拟内存机制是被保护的,内存不能被写入这样可以防止迷失的应用程序覆盖代码的数据。z内存映射。内存映射用来把文件映射到进程的地址空间。在内存映射中,文件的内容直接连接到进程的虚拟地址空间。z公平的物理内存分配。内存管理系统允许系统中每一个运行的进程都可以公平地得到系统的物理内存。z共享虚拟内存虽然虚拟内存允许进程拥有单独的虚拟地址空间,但有时可能会希望进程共享内存。下面分析进行内存分配和回收编程所涉及的一些概念和函数:1)动态内存分配是在运行程序过程中,由于外部程序要储存某些信息,需要决定如何使用内存空间的一种技术。至于动态分配内存块的多少,需要使用的内存空间的长短,这都图2.14运行结果嵌入式Linux工程开发实践41依赖于你所需要保存数据的大小。2)linux系统中,分配内存可用malloc()函数或realloc()函数。Realloc()函数的原型是:void*realloc(void*ptr,size_tnewsize)此函数将ptr指针所指向的对象原来所使用的内存单元的大小改变为newsize参数所指定的大小。返回得到的内存块的首地址。如果给ptr传递一个空指针(NULL),realloc的操作和malloc(newsize)相同。3)在程序退出之前必须将已经分配出去的内存空间释放,否则会出现内存“空洞”问题。释放内存可以使用free()函数。该函数的原型为:voidfree(void*ptr)该函数释放由ptr参数指向的内存单元,其中ptr指向内存单元的首地址。通过free函数释放的内存单元可以进行重新分配。下面通过一个程序来进一步说明内存分配问题:#includestdio.h#includestdlib.hintmain(intargc,char**argv){char*p;inti;p=malloc(30);strcpy(p,not30bytes);printf(p=%s\n,p);if(argc==2){if(strcmp(argv[1],-b)==0)p[42]='a';/*touchoutsidethebounds*/elseif(strcmp(argv[1],-f)==0){free(p);/*freememoryandthenuseit*/p[0]='b';}}/*free(p);*/return0;}相应代码实验结果如图2.15所示:可以看出这里实现了内存的动态分配。分配空间释放资源启动命令项图2.15运行结果第2章Linux系统应用程序开发422.3.3Linux进程管理编程:在Linux中,进程是正在执行的程序。它相当于windows环境内的任务这一概念。每个进程包括程序代码和数据。其中数据包含程序变量数据、外部数据和程序堆栈等。对于系统的命令解释程序shell为了执行一条命令,就要建立一个新的进程并运行它,例如:$catfile1该命令就会使shell专门建立一个进程来运行cat命令。再看一个复杂一些的命令:$ls|wc–l。这个命令就会使shell建立两个进程,以并发运行命令ls和wc,把目录列表命令ls的输出通过管道送至字计数命令wc。因为一个进程对应于一个程序的执行,所以绝对不要把进程与程序这两个概念相混淆。进程是动态的概念,而程序为静态的概念。实际上,多个进程可以并发执行同一个程序,对于公用的实用程序就常常是这样。例如,几个用户可以同时运行一个编辑程序,每个用户对此程序的执行均作为一个单独的进程。在UNIX/Linux中,一个进程又可以启动另一个进程,这就给UNIX的进程环境提供了一个象文件系统目录树那样的层次结构。进程树的顶端是一个控制进程,它是一个名为init的程序的执行,该进程是所有用户进程的祖先。进程是程序在一个包括指令段、用户数据和系统数据段的环境中运行一次的过程。Linux是一个多用户的操作系统,其一个重要特征就是并发性。一个程序可以有多个进程,每个进程又可以有若干个子进程。为了区分各个不同的进程,系统给每一个进程分配了一个PID。下面对涉及到的进程管理编程关键函数与概念给以分析:z关键函数分析1.进程模型是理解访问权限、开启文件与信号和作业控制之间的关系的关键;2.进程标