嵌入式Linux应用程序开发详解-第6 章 文件IO编程.pdf
华清远见嵌入式培训专家 http:/ 华清远见培训教材 “黑色经典”系列之嵌入式“黑色经典”系列之嵌入式 Linux 应用程序开发详解应用程序开发详解 第 6 章 文件 I/O 编程 本章目标 在搭建起嵌入式开发环境之后,从本章开始,读者将真正开始学习嵌入式 Linux 的应用开发。由于嵌入式 Linux 是经 Linux 裁减而来的,它的系统调用及用户编程接口 API 与 Linux基本是一致的,因此,在以后的章节中,笔者将首先介绍 Linux 中相关内容的基本编程开发,主要讲解与嵌入式 Linux 中一致的部分,然后再将程序移植到嵌入式的开发板上运行。因此,没有开发板的读者也可以先在 Linux 上开发相关应用程序,这对以后进入嵌入式 Linux 的实际开发是十分有帮助的。本章主要讲解文件 I/O 相关开发,经过本章的学习,读者将会掌握以下内容。掌握 Linux 中系统调用的基本概念 掌握 Linux 中用户编程接口(API)及系统命令的相互关系 掌握文件描述符的概念 掌握 Linux 下文件相关的不带缓存 I/O 函数的使用 掌握 Linux 下设备文件读写方法 掌握 Linux 中对串口的操作 熟悉 Linux 中标准文件 I/O 函数的使用 QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 6.1 Linux 系统调用及用户编程接口(API)由于本章是讲解Linux 编程开发的第1 章,因此希望读者更加明确Linux 系统调用和用户编程接口(API)的概念。在了解了这些之后,会对Linux 以及Linux 的应用编程有更深入地理解。6.1.1 系统调用 所谓系统调用是指操作系统提供给用户程序调用的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务。例如用户可以通过进程控制相关的系统调用来创建进程、实现进程调度、进程管理等。在这里,为什么用户程序不能直接访问系统内核提供的服务呢?这是由于在 Linux 中,为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。但是,在有些情况下,用户空间的进程需要获得一定的系统服务(调用内核空间程序),这时操作系统就必须利用系统提供给用户的“特殊接口”系统调用规定用户进程进入内核空间的具体位置。进行系统调用时,程序运行空间需要从用户空间进入内核空间,处理完后再返回到用户空间。Linux 系统调用部分是非常精简的系统调用(只有 250 个左右),它继承了 UNIX 系统调用中最基本和最有用的部分。这些系统调用按照功能逻辑大致可分为进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、socket 控制、用户管理等几类。6.1.2 用户编程接口(API)前面讲到的系统调用并不是直接与程序员进行交互的,它仅仅是一个通过软中断机制向内核提交请求,以获取内核服务的接口。在实际使用中程序员调用的通常是用户编程接口API,也就是本书后面要讲到的 API 函数。但并不是所有的函数都一一对应一个系统调用,有时,一个 API 函数会需要几个系统调用来共同完成函数的功能,甚至还有一些 API 函数不需要调用相应的系统调用(因此它所完成的不是内核提供的服务)。在Linux 中,用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准POSIX标准。POSIX 标准是由IEEE和ISO/IEC共同开发的标准系统。该标准基于当时现有的UNIX 实践和经验,描述了操作系统的系统调用编程接口(实际上就是 API),用于保证应用程序可以在源代码一级上在多种操作系统上移植运行。这些系统调用编程接口主要是通过C库(libc)实现的。6.1.3 系统命令 以上讲解了系统调用、用户编程接口(API)的概念,分析了它们之间的相互关系,那么,读者在第 2 章中学到的那么多的 Shell 系统命令与它们之间又是怎样的关系呢?系统命令相对 API 更高了一层,它实际上一个可执行程序,它的内部引用了用户编程接口(API)来实现相应的功能。它们之间的关系如下图 6.1 所示。QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 用户编程接口 API 系统命令 系统调用 内核空间 用户空间 图 6.1 系统调用、API及系统命令之间的关系 6.2 Linux 中文件及文件描述符概述 正如第 1 章中所述,在 Linux 中对目录和设备的操作都等同于文件的操作,因此,大大简化了系统对不同设备的处理,提高了效率。Linux 中的文件主要分为 4 种:普通文件、目录文件、链接文件和设备文件。那么,内核如何区分和引用特定的文件呢?这里用到的就是一个重要的概念文件描述符。对于 Linux 而言,所有对设备和文件的操作都使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。通常,一个进程启动时,都会打开 3 个文件:标准输入、标准输出和标准出错处理。这3 个文件分别对应文件描述符为 0、1 和 2(也就是宏替换 STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,鼓励读者使用这些宏替换)。基于文件描述符的 I/O 操作虽然不能移植到类 Linux 以外的系统上去(如 Windows),但它往往是实现某些 I/O 操作的惟一途径,如 Linux 中低级文件操作函数、多路 I/O、TCP/IP 套接字编程接口等。同时,它们也很好地兼容 POSIX 标准,因此,可以很方便地移植到任何 POSIX 平台上。基于文件描述符的 I/O 操作是 Linux 中最常用的操作之一,希望读者能够很好地掌握。6.3 不带缓存的文件 I/O 操作 本节主要介绍不带缓存的文件 I/O 操作,主要用到 5 个函数:open、read、write、lseek和 close。这里的不带缓存是指每一个函数都只调用系统中的一个函数。这些函数虽然不是QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 ANSI C 的组成部分,但是是 POSIX 的组成部分。6.3.1 open 和 close(1)open 和 close 函数说明 open 函数是用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。close 函数是用于关闭一个打开文件。当一个进程终止时,它所有已打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。(2)open 和 close 函数格式 open 函数的语法格式如表 6.1 所示。表 6.1 open 函数语法要点 所需头文件#include /提供类型 pid_t 的定义#include#include 续表 函数原型 int open(const char*pathname,flags,int perms)pathname 被打开的文件名(可包括路径名)O_RDONLY:只读方式打开文件 O_WRONLY:可写方式打开文件 O_RDWR:读写方式打开文件 O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限 O_EXCL:如果使用 O_CREAT 时文件存在,则可返回错误消息。这一参数可测试文件是否存在 O_NOCTTY:使用本参数时,如文件为终端,那么终端不可以作为调用 open()系统调用的那个进程的控制终端 O_TRUNC:如文件已经存在,并且以只读或只写成功打开,那么会先全部删除文件中原有数据 flag:文件打 开 的 方式 O+APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾 函数传入值 perms 被打开文件的存取权限,为 8 进制表示法 函数返回值 成功:返回文件描述符 失败:1 在 open 函数中,flag 参数可通过“|”组合构成,但前 3 个函数不能相互组合。perms 是文件的存取权限,采用 8 进制表示法,相关内容读者可参见第 2 章。close 函数的语法格式如下表 6.2 所示。表 6.2 close 函数语法要点 QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 所需头文件#include 函数原型 int close(int fd)函数输入值 fd:文件描述符 函数返回值 0:成功 1:出错 (3)open 和 close 函数使用实例 下面实例中的 open 函数带有 3 个 flag 参数:O_CREAT、O_TRUNC 和 O_WRONLY,这样就可以对不同的情况指定相应的处理方法。另外,这里对该文件的权限设置为 0600。其源码如下所示:/*open.c*/#include#include#include#include#include#include int main(void)int fd;/*调用 open 函数,以可读写的方式打开,注意选项可以用“|”符号连接*/if(fd=open(/tmp/hello.c,O_CREAT|O_TRUNC|O_WRONLY,0600)0)perror(open:);exit(1);else printf(Open file:hello.c%dn,fd);if(close(fd)0)perror(close:);exit(1);else printf(Close );exit(0);root(none)1#./open QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 Open file:hello.c 3 Close hello.c root(none)tmp#ls-l|grep hello.c-rw-1 root root 0 Dec 4 00:59 hello.c 经过交叉编译后,将文件下载到目标板,则该可执行文件运行后就能在目录/tmp 下新建一个 hello.c 的文件,其权限为 0600。注意 open 函数返回的文件描述符一定是最小的未用文件描述符。由于一个进程在启动时自动打开了0、1、2 三个文件描述符,因此,该文件运行结果中返回的文件描述符为 3。读者可以尝试在调用 open 函数之前,加依据 close(0),则此后在 open 函数时返回的文件描述符为 0(若关闭文件描述符 1,则在执行时会由于没有标准输出文件而无法输出)。6.3.2 read、write 和 lseek(1)read、write 和 lseek 函数作用 read 函数是用于将指定的文件描述符中读出数据。当从终端设备文件中读出数据时,通常一次最多读一行。write 函数是用于向打开的文件写数据,写操作从文件的当前位移量处开始。若磁盘已满或超出该文件的长度,则 write 函数返回失败。lseek 函数是用于在指定的文件描述符中将文件指针定位到相应的位置。(2)read 和 write 函数格式 read 函数的语法格式如下表 6.3 所示。表 6.3 read 函数语法要点 所需头文件#include 函数原型 ssize_t read(int fd,void*buf,size_t count)fd:文件描述符 buf:指定存储器读出数据的缓冲区 函数传入值 count:指定读出的字节数 函数返回值 成功:读到的字节数 0:已到达文件尾 1:出错 在读普通文件时,若读到要求的字节数之前已到达文件的尾部,则返回的字节数会小于希望读出的字节数。write 函数的语法格式如下表 6.4 所示。表 6.4 write 函数语法要点 所需头文件#include 函数原型 ssize_t write(int fd,void*buf,size_t count)QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 fd:文件描述符 buf:指定存储器写入数据的缓冲区 函数传入值 count:指定读出的字节数 函数返回值 成功:已写的字节数 1:出错 在写普通文件时,写操作从文件的当前位移处开始。lseek 函数的语法格式如下表 6.5 所示。表 6.5 lseek 函数语法要点 所需头文件#include#include 函数原型 off_t lseek(int fd,off_t offset,int whence)fd:文件描述符 函数传入值 offset:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移)续表 SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小 SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量 whence:当前位置的基点 SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小 函数返回值 成功:文件的当前位移 1:出错 (3)函数使用实例 该示例程序首先打开上一节中创建的文件,然后对此文件进行读写操作(记得要将文件打开属性改为可读写,将文件权限也做相应更改)。接着,写入“Hello!Im writing to this file!”,此时文件指针位于文件尾部。接着在使用 lseek 函数将文件指针移到文件开始处,并读出 10个字节并将其打印出来。程序源代码如下所示:/*write.c*/#include#include#include#include#include#include#include#define MAXSIZE QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 int main(void)int i,fd,size,len;char*buf=Hello!Im writing to this file!;char buf_r10;len=strlen(buf);/*首先调用 open 函数,并指定相应的权限*/if(fd=open(/tmp/hello.c,O_CREAT|O_TRUNC|O_RDWR,0666)0)perror(open:);exit(1);else printf(open file:hello.c%dn,fd);/*调用 write 函数,将 buf 中的内容写入到打开的文件中*/if(size=write(fd,buf,len)0)perror(write:);exit(1);else printf(Write:%sn,buf);/*调用 lsseek 函数将文件指针移到文件起始,并读出文件中的 10 个字节*/lseek(fd,0,SEEK_SET);if(size=read(fd,buf_r,10)0)perror(read:);exit(1);else printf(read form file:%sn,buf_r);if(close(fd)0)perror(close:);exit(1);else printf(Close );exit(0);root(none)1#./write open file:hello.c 3 QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 Write:Hello!Im writing to this file!read form file:Hello!Im Close hello.c root(none)1#cat/tmp/hello.c Hello!Im writing to this file!6.3.3 fcntl(1)fcntl 函数说明 前面的这 5 个基本函数实现了文件的打开、读写等基本操作,这一节将讨论的是,在文件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。在 Linux 中,实现文件上锁的函数有 lock 和 fcntl,其中 flock 用于对文件施加建议性锁,而 fcntl 不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl 还能对文件的某一记录进行上锁,也就是记录锁。记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。注意 fcntl 是一个非常通用的函数,它还可以改变文件进程各方面的属性,在本节中,主要介绍它建立记录锁的方法,关于它其他用户感兴趣的读者可以参看 fcntl 手册。(2)fcntl 函数格式 用于建立记录锁的 fcntl 函数格式如表 6.6 所示。表 6.6 fcntl 函数语法要点 所需头文件#include#include#include 函数原型 int fcnt1(int fd,int cmd,struct flock*lock)fd:文件描述符 F_DUPFD:复制文件描述符 F_GETFD:获得 fd 的 close-on-exec 标志,若标志未设置,则文件经过 exec函数之后仍保持打开状态 F_SETFD:设置 close-on-exec 标志,该标志以参数 arg 的 FD_CLOEXEC 位决定 F_GETFL:得到 open 设置的标志 函数传入值 cmd F_SETFL:改变 open 设置的标志 QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 F_GETFK:根据 lock 描述,决定是否上文件锁 F_SETFK:设置 lock 描述的文件锁 F_SETLKW:这是 F_SETLK 的阻塞版本(命令名中的 W 表示等待(wait)。如果存在其他锁,则调用进程睡眠;如果捕捉到信号则睡眠中断 F_GETOWN:检索将收到 SIGIO 和 SIGURG 信号的进程号或进程组号 F_SETOWN:设置进程号或进程组号 Lock:结构为 flock,设置记录锁的具体状态,后面会详细说明 函数返回值 成功:0 1:出错 这里,lock 的结构如下所示:Struct flock short l_type;off_t l_start;short l_whence;off_t l_len;pid_t l_pid;lock 结构中每个变量的取值含义如表 6.7 所示。表 6.7 lock 结构变量取值 F_RDLCK:读取锁(共享锁)F_WRLCK:写入锁(排斥锁)l_type F_UNLCK:解锁 l_stat 相对位移量(字节)SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小 SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量 l_whence:相对位移量的起点(同 lseek的 whence)。SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小 l_len 加锁区域的长度 小技巧 为加锁整个文件,通常的方法是将 l_start 说明为 0,l_whence 说明为 SEEK_SET,l_len 说明为 0。(3)fcntl 使用实例 下面首先给出了使用 fcntl 函数的文件记录锁函数。在该函数中,首先给 flock 结构体的对应位赋予相应的值。接着使用两次 fcntl 函数分别用于给相关文件上锁和判断文件是否可以上锁,这里用到的 cmd 值分别为 F_SETLK 和 F_GETLK。这个函数的源代码如下所示:QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材/*lock_set 函数*/void lock_set(int fd,int type)struct flock lock;lock.l_whence=SEEK_SET;/赋值 lock 结构体 lock.l_start=0;lock.l_len=0;while(1)lock.l_type=type;/*根据不同的 type 值给文件上锁或解锁*/if(fcntl(fd,F_SETLK,&lock)=0)if(lock.l_type=F_RDLCK)printf(read lock set by%dn,getpid();else if(lock.l_type=F_WRLCK)printf(write lock set by%dn,getpid();else if(lock.l_type=F_UNLCK)printf(release lock by%dn,getpid();return;/*判断文件是否可以上锁*/fcntl(fd,F_GETLK,&lock);/*判断文件不能上锁的原因*/if(lock.l_type!=F_UNLCK)/*/该文件已有写入锁*/if(lock.l_type=F_RDLCK)printf(read lock already set by%dn,lock.l_pid);/*该文件已有读取锁*/else if(lock.l_type=F_WRLCK)printf(write lock already set by%dn,lock.l_pid);getchar();下面的实例是测试文件的写入锁,这里首先创建了一个 hello 文件,之后对其上写入锁,最后释放写入锁。代码如下所示:/*fcntl_write.c 测试文件写入锁主函数部分*/#include#include QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材#include#include#include#include int main(void)int fd;/*首先打开文件*/fd=open(hello,O_RDWR|O_CREAT,0666);if(fd 0)perror(open);exit(1);/*给文件上写入锁*/lock_set(fd,F_WRLCK);getchar();/*给文件接锁*/lock_set(fd,F_UNLCK);getchar();close(fd);exit(0);为了能够使用多个终端,更好地显示写入锁的作用,本实例主要在 PC 机上测试,读者可将其交叉编译,下载到目标板上运行。下面是在 PC 机上的运行结果。为了使程序有较大的灵活性,笔者采用文件上锁后由用户键入一任意键使程序继续运行。建议读者开启两个终端,并且在两个终端上同时运行该程序,以达到多个进程操作一个文件的效果。在这里,笔者首先运行终端一,请读者注意终端二中的第一句。终端一:rootlocalhost file#./fcntl_write write lock set by 4994 release lock by 4994 终端二:rootlocalhost file#./fcntl_write write lock already set by 4994 QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 write lock set by 4997 release lock by 4997 由此可见,写入锁为互斥锁,一个时刻只能有一个写入锁存在。接下来的程序是测试文件的读取锁,原理同上面的程序一样。/*fcntl_read.c 测试文件读取锁主函数部分*/#include#include#include#include#include#include int main(void)int fd;fd=open(hello,O_RDWR|O_CREAT,0666);if(fd 0)perror(open);exit(1);/*给文件上读取锁*/lock_set(fd,F_RDLCK);getchar();/*给文件接锁*/lock_set(fd,F_UNLCK);getchar();close(fd);exit(0);同样开启两个终端,并首先启动终端一上的程序,其运行结果如下所示:终端一:rootlocalhost file#./fcntl2 read lock set by 5009 release lock by 5009 终端二:QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 rootlocalhost file#./fcntl2 read lock set by 5010 release lock by 5010 读者可以将此结果与写入锁的运行结果相比较,可以看出,读取锁为共享锁,当进程 5009已设定读取锁后,进程 5010 还可以设置读取锁。思考 如果在一个终端上运行设置读取锁,则在另一个终端上运行设置写入锁,会有什么结果呢?6.3.4 select(1)select 函数说明 前面的 fcntl 函数解决了文件的共享问题,接下来该处理 I/O 复用的情况了。总的来说,I/O 处理的模型有 5 种。阻塞 I/O 模型:在这种模型下,若所调用的 I/O 函数没有完成相关的功能就会使进程挂起,直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。非阻塞模型:在这种模型下,当请求的 I/O 操作不能完成时,则不让进程睡眠,而且返回一个错误。非阻塞 I/O 使用户可以调用不会永远阻塞的 I/O 操作,如 open、write和 read。如果该操作不能完成,则会立即出错返回,且表示该 I/O 如果该操作继续执行就会阻塞。I/O 多路转接模型:在这种模型下,如果请求的 I/O 操作阻塞,且它不是真正阻塞 I/O,而是让其中的一个函数等待,在这期间,I/O 还能进行其他操作。如本节要介绍的 select 函数和 poll 函数,就是属于这种模型。信号驱动 I/O 模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动 I/O。这是由内核通知用户何时可以启动一个 I/O 操作决定的。异步 I/O 模型:在这种模型下,当一个描述符已准备好,可以启动 I/O 时,进程会通知内核。现在,并不是所有的系统都支持这种模型。可以看到,select 的 I/O 多路转接模型是处理 I/O 复用的一个高效的方法。它可以具体设置每一个所关心的文件描述符的条件、希望等待的时间等,从 select 函数返回时,内核会通知用户已准备好的文件描述符的数量、已准备好的条件等。通过使用 select 返回值,就可以调用相应的 I/O 处理函数了。(2)select 函数格式 Select 函数的语法格式如表 6.8 所示。表 6.8 fcntl 函数语法要点 所需头文件#include#include QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材#include 函数原型 int select(int numfds,fd_set*readfds,fd_set*writefds,fd_set*exeptfds,struct timeval*timeout)numfds:需要检查的号码最高的文件描述符加 1 readfds:由 select()监视的读文件描述符集合 writefds:由 select()监视的写文件描述符集合 exeptfds:由 select()监视的异常处理文件描述符集合 NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止 具体值:struct timeval 类型的指针,若等待为 timeout 时间还没有文件描符准备好,就立即返回 函数传入值 timeout 0:从不等待,测试所有指定的描述符并立即返回 函数返回值 成功:准备好的文件描述符 1:出错 思考 请读者考虑一下如何确定最高的文件描述符?可以看到,select 函数根据希望进行的文件操作对文件描述符进行了分类处理,这里,对文件描述符的处理主要涉及到 4 个宏函数,如表 6.9 所示。表 6.9 select 文件描述符处理函数 FD_ZERO(fd_set*set)清除一个文件描述符集 FD_SET(int fd,fd_set*set)将一个文件描述符加入文件描述符集中 FD_CLR(int fd,fd_set*set)将一个文件描述符从文件描述符集中清除 FD_ISSET(int fd,fd_set*set)测试该集中的一个给定位是否有变化 一般来说,在使用 select 函数之前,首先使用 FD_ZERO 和 FD_SET 来初始化文件描述符集,在使用了 select 函数时,可循环使用 FD_ISSET 测试描述符集,在执行完对相关后文件描述符后,使用 FD_CLR 来清楚描述符集。另外,select 函数中的 timeout 是一个 struct timeval 类型的指针,该结构体如下所示:struct timeval long tv_sec;/*second*/long tv_unsec;/*and microseconds*/可以看到,这个时间结构体的精确度可以设置到微秒级,这对于大多数的应用而言已经足够了。(3)使用实例 由于 Select 函数多用于 I/O 操作可能会阻塞的情况下,而对于可能会有阻塞 I/O 的管道、QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 网络编程,本书到现在为止还没有涉及。因此,本例主要表现了如何使用 select 函数,而其中的 I/O 操作是不会阻塞的。本实例中主要实现将文件 hello1 里的内容读出,并将此内容每隔 10s 写入 hello2 中去。在这里建立了两个描述符集,其中一个描述符集 inset1 是用于读取文件内容,另一个描述符集 inset2 是用于写入文件的。两个文件描述符 fds0和 fds1分别指向这一文件描述符。在首先初始化完各文件描述符集之后,就开始了循环测试这两个文件描述符是否可读写,由于在这里没有阻塞,所以文件描述符处于准备就绪的状态。这时,就分别对文件描述符 fds0和fsd1进行读写操作。该程序的流程图如图 6.2 所示。/*select.c*/#include#include#include#include#include int main(void)int fds2;char buf7;int i,rc,maxfd;fd_set inset1,inset2;struct timeval tv;/*首先按一定的权限打开 hello1 文件*/if(fds0=open(hello1,O_RDWR|O_CREAT,0666)0)perror(open hello1);/*再按一定的权限打开 hello2 文件*/if(fds1=open(hello2,O_RDWR|O_CREAT,0666)fds1?fds0:fds1;/*初始化读集合 inset1,并在读集合中加入相应的描述集*/FD_ZERO(&inset1);FD_SET(fds0,&inset1);/*初始化写集合 inset2,并在写集合中加入相应的描述集*/FD_ZERO(&inset2);QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 FD_SET(fds1,&inset2);tv.tv_sec=2;tv.tv_usec=0;/*循环测试该文件描述符是否准备就绪,并调用 select 函数对相关文件描述符做对应操作*/while(FD_ISSET(fds0,&inset1)|FD_ISSET(fds1,&inset2)if(select(maxfd+1,&inset1,&inset2,NULL,&tv)0)bufrc=0;printf(read:%sn,buf);else perror(read);if(FD_ISSET(fds1,&inset2)rc=write(fds1,buf,7);if(rc0)bufrc=0;printf(rc=%d,write:%sn,rc,buf);else perror(write);sleep(10);exit(0);QQ:313638714http:/华清远见嵌入式培训专家 http:/ 华清远见培训教材 开始打开文件hello1和hello2并在hello1中写入“Hello!”并lseek分别对fds0、fds1调用FD_ZERO、FD_SET初始化调用FD_ISSET测试inset1和inset2是否有变化调用select读hello1写hello2结束暂停10秒 图 6.2 select 实例流程图 读者可以将以上程序交叉编译,并下载到开发板上运行。以下是运行结果:root(none)1#./select rc=7 read:Hello!rc=7,write:Hello!rc=7,write:Hello!rc=7,write:Hello!QQ:313638714http:/ 嵌入式 Linux 应用程序开发详解第 6 章、文件 IO 编程 华清远见培训教材 root(none)1#cat hello1 Hello!root(none)1#cat hello2 Hello!Hello!可以看到,使用 select 可以很好地实现 I/O 多路复用,在有阻塞的情况下更能够显示出它的作用。6.4 嵌入式 Linux 串口应用开发 6.4.1 串口概述 用户常见的数据通信的基本方式可分为并行通信与串行通信两种。并行通信是指利用多条数据传输线将一个资料的各位同时传送。它的特点是传输速度快,适用于短距离通信,但要求传输速度较高的应用场合。串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。串口是计算机一种常用的接口,常用的串口有 RS-232-C 接口。它是于 1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准,它的全称是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。该标准规定采用一个 DB25 芯引脚的连接器或 9 芯引脚的连接器,其中 25 芯引脚的连接器如图 6.3 所示。S3C2410X 内部具有 2 个独立的 UART 控制器,每个控制器都可以工作在 Interrupt(中断)模式或者 DMA(直接内存访问)模式。同时,每个 UART 均具有 16 字节的 FIFO(先入先出寄存器),支持的最高波特率可达到 230.4Kbps。UART 的操作主要可分为以下几个部分:资料发送、资料接收、产生中断、产生波特率、Loopback 模式、红外模式以及自动流控模式。串口参数的配置读者在配置超级终端和 minicom 时也已经接触到过,一般包括波特率、起始位数量、数据位数量、停止位数量和流控协议。在此,可以将其配置为波特率 115200、起始位 1b、数据位 8b、停止位 1b 和无流控协议。在 Linux 中,所有的设