欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    第6章、文件IO编程.pdf

    • 资源ID:70321653       资源大小:861.37KB        全文页数:60页
    • 资源格式: PDF        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    第6章、文件IO编程.pdf

    嵌入式学院华清远见旗下品牌:www.embedu.org 嵌入式学院华清远见旗下品牌:www.embedu.org 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 第 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 函数的使用 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 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)实现的。嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 6.1.3 系统命令 以上讲解了系统调用、用户编程接口(API)的概念,分析了它们之间的相互关系,那么,读者在第 2 章中学到的那么多的 Shell 系统命令与它们之间又是怎样的关系呢?系统命令相对 API 更高了一层,它实际上是一个可执行程序,它的内部引用了用户编程接口(API)来实现相应的功能。它们之间的关系如图 6.1 所示。6.2 Linux 中文件及文件描述符概述 在 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()。这些函数的特点是不带缓存,直接对文件(包括设备)进行读写操作。这些函数虽然不是 ANSI C 的组成部分,但是是 POSIX 的组成部分。6.3.1 基本文件操作(1)函数说明。open()函数用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。close()函数用于关闭一个被打开的文件。当一个进程终止时,所有被它打开的文图 6.1 系统调用、API系统命令之间的关系 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。read()函数用于将从指定的文件描述符中读出的数据放到缓存区中,并返回实际读入的字节数。若返回 0,则表示没有数据可读,即已达到文件尾。读操作从文件的当前指针位置开始。当从终端设备文件中读出数据时,通常一次最多读一行。write()函数用于向打开的文件写数据,写操作从文件的当前指针位置开始。对磁盘文件进行写操作,若磁盘已满或超出该文件的长度,则 write()函数返回失败。lseek()函数用于在指定的文件描述符中将文件指针定位到相应的位置。它只能用在可定位(可随机访问)文件操作中。管道、套接字和大部分字符设备文件是不可定位的,所以在这些文件的操作中无法使用 lseek()调用。(2)函数格式。open()函数的语法格式如表 6.1 所示。表 6.1 open()函数语法要点 所需头文件#include /*提供类型 pid_t 的定义*/#include#include 函数原型 int open(const char*pathname,int flags,int perms)pathname 被打开的文件名(可包括路径名)O_RDONLY:以只读方式打开文件 O_WRONLY:以只写方式打开文件 O_RDWR:以读写方式打开文件 O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限 O_EXCL:如果使用 O_CREAT 时文件存在,则可返回错误消息。这一参数可测试文件是否存在。此时 open 是原子操作,防止多个进程同时创建同一个文件 O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用 open()的那个进程的控制终端 O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为 0。flag:文件打开的方式 O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾 函数传入值 perms 被打开文件的存取权限 可以用一组宏定义:S_I(R/W/X)(USR/GRP/OTH)其中 R/W/X 分别表示读/写/执行权限 USR/GRP/OTH 分别表示文件所有者/文件所属组/其他用户 例如,S_IRUSR|S_IWUSR 表示设置文件所有者的可读可写属性。八进制表示法中 600 也表示同样的权限 函数返回值 成功:返回文件描述符 失败:1 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 在 open()函数中,flag 参数可通过“|”组合构成,但前 3 个标志常量(O_RDONLY、O_WRONLY 以及 O_RDWR)不能相互组合。perms 是文件的存取权限,既可以用宏定义表示法,也可以用八进制表示法。close()函数的语法格式表 6.2 所示。表 6.2 close()函数语法要点 所需头文件#include 函数原型 int close(int fd)函数输入值 fd:文件描述符 函数返回值 0:成功 1:出错 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)fd:文件描述符 buf:指定存储器写入数据的缓冲区 函数传入值 count:指定读出的字节数 函数返回值 成功:已写的字节数 1:出错 在写普通文件时,写操作从文件的当前指针位置开始。lseek()函数的语法格式如表 6.5 所示。表 6.5 lseek()函数语法要点 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 所需头文件#include#include 函数原型 off_t lseek(int fd,off_t offset,int whence)fd:文件描述符 函数传入值 offset:偏移量,每一读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小 SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量 whence:当前位置的基点 SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小 函数返回值 成功:文件的当前位移 1:出错 (3)函数使用实例。下面实例中的open()函数带有3个flag参数:O_CREAT、O_TRUNC和O_WRONLY,这样就可以对不同的情况指定相应的处理方法。另外,这里对该文件的权限设置为0600。其源码如下所示:下面列出文件基本操作的实例,基本功能是从一个文件(源文件)中读取最后10KB 数据并到另一个文件(目标文件)。在实例中源文件是以只读方式打开,目标文件是以只写方式打开(可以是读写方式)。若目标文件不存在,可以创建并设置权限的初始值为 644,即文件所有者可读可写,文件所属组和其他用户只能读。读者需要留意的地方是改变每次读写的缓存大小(实例中为 1KB)会怎样影响运行效率。/*copy_file.c*/#include#include#include#include#include#include#define BUFFER_SIZE 1024 /*每次读写缓存大小,影响运行效率*/#define SRC_FILE_NAME src_file /*源文件名*/#define DEST_FILE_NAME dest_file/*目标文件名文件名*/#define OFFSE 10240 /*复制的数据大小*/int main()int src_file,dest_file;嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org unsigned char buffBUFFER_SIZE;int real_read_len;/*以只读方式打开源文件*/src_file=open(SRC_FILE_NAME,O_RDONLY);/*以只写方式打开目标文件,若此文件不存在则创建该文件,访问权限值为 644*/dest_file=open(DEST_FILE_NAME,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);if(src_file 0|dest_file 0)write(dest_file,buff,real_read_len);close(dest_file);close(src_file);return 0;$./copy_file$ls-lh dest_file-rw-r-r-1 david root 10K 14:06 dest_file 注意 open()函数返回的文件描述符一定是最小的未用文件描述符。由于一个进程在启动时自动打开了 0、1、2 三个文件描述符,因此,该文件运行结果中返回的文件描述符为 3。读者可以尝试在调用 open()函数之前,加一句 close(0),则此后在调用 open()函数时返回的文件描述符为 0(若关闭文件描述符 1,则在程序执行时会由于没有标准输出文件而无法输出)。嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 6.3.2 文件锁(1)fcntl()函数说明。前面的这 5 个基本函数实现了文件的打开、读写等基本操作,本小节将讨论的是,在文件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。在 Linux 中,实现文件上锁的函数有 lockf()和 fcntl(),其中 lockf()用于对文件施加建议性锁,而 fcntl()不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl()还能对文件的某一记录上锁,也就是记录锁。记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。注意 fcntl()是一个非常通用的函数,它可以对已打开的文件描述符进行各种操作,不仅包括管理文件锁,还包括获得和设置文件描述符和文件描述符标志、文件描述符的复制等很多功能。在本节中,主要介绍建立记录锁的方法。(2)fcntl()函数格式。用于建立记录锁的 fcntl()函数格式如表 6.6 所示。表 6.6 fcntl()函数语法要点 所需头文件#include#include#include 函数原型 int fcnt1(int fd,int cmd,struct flock*lock)函数传入值 fd:文件描述符 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org F_DUPFD:复制文件描述符 F_GETFD:获得 fd 的 close-on-exec 标志,若标志未设置,则文件经过 exec()函数之后仍保持打开状态 F_SETFD:设置 close-on-exec 标志,该标志由参数 arg 的 FD_CLOEXEC位决定 F_GETFL:得到 open 设置的标志 F_SETFL:改变 open 设置的标志 F_GETLK:根据 lock 参数值,决定是否上文件锁 F_SETLK:设置 lock 参数值的文件锁 cmd F_SETLKW:这是 F_SETLK 的阻塞版本(命令名中的 W 表示等待(wait)。在无法获取锁时,会进入睡眠状态;如果可以获取锁或者捕捉到信号则会返回 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 设置为 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org SEEK_SET,l_len 设置为 0。(3)fcntl()使用实例 下面首先给出了使用 fcntl()函数的文件记录锁功能的代码实现。在该代码中,首先给 flock 结构体的对应位赋予相应的值。接着使用两次 fcntl()函数,分别用于判断文件是否可以上锁和给相关文件上锁,这里用到的 cmd 值分别为 F_GETLK 和 F_SETLK(或 F_SETLKW)。用 F_GETLK 命令判断是否可以进行 flock 结构所描述的锁操作:若可以进行,则 flock 结构的 l_type 会被设置为 F_UNLCK,其他域不变;若不可行,则 l_pid 被设置为拥有文件锁的进程号,其他域不变。用 F_SETLK 和 F_SETLKW 命令设置 flock 结构所描述的锁操作,后者是前者的阻塞版。文件记录锁功能的源代码如下所示:/*lock_set.c*/int lock_set(int fd,int type)struct flock old_lock,lock;lock.l_whence=SEEK_SET;lock.l_start=0;lock.l_len=0;lock.l_type=type;lock.l_pid=-1;/*判断文件是否可以上锁*/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);/*l_type 可能已被 F_GETLK 修改过*/嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org lock.l_type=type;/*根据不同的 type 值进行阻塞式上锁或解锁*/if(fcntl(fd,F_SETLKW,&lock)0)printf(Lock failed:type=%dn,lock.l_type);return 1;switch(lock.l_type)case F_RDLCK:printf(Read lock set by%dn,getpid();break;case F_WRLCK:printf(Write lock set by%dn,getpid();break;case F_UNLCK:printf(Release lock by%dn,getpid();return 1;break;default:break;/*end of switch */return 0;下面的实例是文件写入锁的测试用例,这里首先创建了一个 hello 文件,之后对其上写入锁,最后释放写入锁,代码如下所示:/*write_lock.c*/#include#include#include#include#include#include 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org#include lock_set.c int main(void)int fd;/*首先打开文件*/fd=open(hello,O_RDWR|O_CREAT,0644);if(fd 0)printf(Open file errorn);exit(1);/*给文件上写入锁*/lock_set(fd,F_WRLCK);getchar();/*给文件解锁*/lock_set(fd,F_UNLCK);getchar();close(fd);exit(0);为了能够使用多个终端,更好地显示写入锁的作用,本实例主要在 PC 机上测试,读者可将其交叉编译,下载到目标板上运行。下面是在 PC 机上的运行结果。为了使程序有较大的灵活性,笔者采用文件上锁后由用户键入一任意键使程序继续运行。建议读者开启两个终端,并且在两个终端上同时运行该程序,以达到多个进程操作一个文件的效果。在这里,笔者首先运行终端一,请读者注意终端二中的第一句。终端一:$./write_lock write lock set by 4994 release lock by 4994 终端二:$./write_lock write lock already set by 4994 write lock set by 4997 release lock by 4997 由此可见,写入锁为互斥锁,同一时刻只能有一个写入锁存在。接下来的程序是文件读取锁的测试用例,原理和上面的程序一样。/*fcntl_read.c*/#include 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org#include#include#include#include#include#include lock_set.c int main(void)int fd;fd=open(hello,O_RDWR|O_CREAT,0644);if(fd 0)printf(Open file errorn);exit(1);/*给文件上读取锁*/lock_set(fd,F_RDLCK);getchar();/*给文件解锁*/lock_set(fd,F_UNLCK);getchar();close(fd);exit(0);同样开启两个终端,并首先启动终端一上的程序,其运行结果如下所示:终端一:$./read_lock read lock set by 5009 release lock by 5009 终端二:$./read_lock read lock set by 5010 release lock by 5010 读者可以将此结果与写入锁的运行结果相比较,可以看出,读取锁为共享锁,当进程 5009 已设置读取锁后,进程 5010 仍然可以设置读取锁。思考 如果在一个终端上运行设置读取锁的程序,则在另一个终端上运行设置写入锁的程序,会有什么结果呢?嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 6.3.3 多路复用(1)函数说明。前面的 fcntl()函数解决了文件的共享问题,接下来该处理 I/O 复用的情况了。总的来说,I/O 处理的模型有 5 种。n 阻塞 I/O 模型:在这种模型下,若所调用的 I/O 函数没有完成相关的功能,则会使进程挂起,直到相关数据到达才会返回。对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。n 非阻塞模型:在这种模型下,当请求的 I/O 操作不能完成时,则不让进程睡眠,而且立即返回。非阻塞 I/O 使用户可以调用不会阻塞的 I/O 操作,如open()、write()和 read()。如果该操作不能完成,则会立即返回出错(例如:打不开文件)或者返回 0(例如:在缓冲区中没有数据可以读取或者没有空间可以写入数据)。n I/O 多路转接模型:在这种模型下,如果请求的 I/O 操作阻塞,且它不是真正阻塞 I/O,而是让其中的一个函数等待,在这期间,I/O 还能进行其他操作。本节要介绍的 select()和 poll 函数()就是属于这种模型。n 信号驱动 I/O 模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动 I/O。这是由内核通知用户何时可以启动一个I/O 操作决定的。n 异步 I/O 模型:在这种模型下,当一个描述符已准备好,可以启动 I/O 时,进程会通知内核。现在,并不是所有的系统都支持这种模型。可以看到,select()和 poll()的 I/O 多路转接模型是处理 I/O 复用的一个高效的方法。它可以具体设置程序中每一个所关心的文件描述符的条件、希望等待的时间等,从select()和 poll()函数返回时,内核会通知用户已准备好的文件描述符的数量、已准备好的条件等。通过使用 select()和 poll()函数的返回结果,就可以调用相应的 I/O 处理函数。(2)函数格式。select()函数的语法格式如表 6.8 所示。表 6.8 select()函数语法要点 所需头文件#include#include#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()监视的异常处理文件描述符集合 函数传入值 timeout NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 具体值:struct timeval 类型的指针,若等待了 timeout 时间还没有检测到任何文件描符准备好,就立即返回 0:从不等待,测试所有指定的描述符并立即返回 函数返回值 大于 0:成功,返回准备好的文件描述符的数目 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)如果文件描述符 fd 为 fd_set 集中的一个元素,则返回非零值,可以用于调用 select()之后测试文件描述符集中的文件描述符是否有变化 一般来说,在使用 select()函数之前,首先使用 FD_ZERO()和 FD_SET()来初始化文件描述符集,在使用了 select()函数时,可循环使用 FD_ISSET()来测试描述符集,在执行完对相关文件描述符的操作之后,使用 FD_CLR()来清除描述符集。另外,select()函数中的 timeout 是一个 struct timeval 类型的指针,该结构体如下所示:struct timeval long tv_sec;/*秒*/long tv_unsec;/*微秒*/可以看到,这个时间结构体的精确度可以设置到微秒级,这对于大多数的应用而言已经足够了。poll()函数的语法格式如表 6.10 所示。表 6.10 poll()函数语法要点 所需头文件#include#include 函数原型 int poll(struct pollfd*fds,int numfds,int timeout)嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org fds:struct pollfd 结构的指针,用于描述需要对哪些文件的哪种类型的操作进行监控。struct pollfd int fd;/*需要监听的文件描述符*/short events;/*需要监听的事件*/short revents;/*已发生的事件*/events 成员描述需要监听哪些类型的事件,可以用以下几种标志来描述。POLLIN:文件中有数据可读,下面实例中使用到了这个标志 POLLPRI::文件中有紧急数据可读 POLLOUT:可以向文件写入数据 POLLERR:文件中出现错误,只限于输出 POLLHUP:与文件的连接被断开了,只限于输出 POLLNVAL:文件描述符是不合法的,即它并没有指向一个成功打开的文件 numfds:需要监听的文件个数,即第一个参数所指向的数组中的元素数目 函数传入值 timeout:表示 poll 阻塞的超时时间(毫秒)。如果该值小于等于 0,则表示无限等待 函数返回值 成功:返回大于 0 的值,表示事件发生的 pollfd 结构的个数 0:超时;1:出错(3)使用实例。由于多路复用通常用于 I/O 操作可能会被阻塞的情况,而对于可能会有阻塞 I/O的管道、网络编程,本书到现在为止还没有涉及。这里通过手动创建(用 mknod 命令)两个管道文件,重点说明如何使用两个多路复用函数。在本实例中,分别用 select()函数和 poll()函数实现同一个功能,以下功能说明是以 select()函数为例描述的。本实例中主要实现通过调用 select()函数来监听 3 个终端的输入(分别重定向到两个管道文件的虚拟终端以及主程序所运行的虚拟终端),并分别进行相应的处理。在这里我们建立了一个 select()函数监视的读文件描述符集,其中包含 3 个文件描述符,分别为一个标准输入文件描述符和两个管道文件描述符。通过监视主程序的虚拟终端标准输入来实现程序的控制(例如:程序结束);以两个管道作为数据输入,主程序将从两个管道读取的输入字符串写入到标准输出文件(屏幕)。为了充分表现 select()调用的功能,在运行主程序的时候,需要打开 3 个虚拟终端:首先用 mknod 命令创建两个管道 in1 和 in2。接下来,在两个虚拟终端上分别运行catin1 和 catin2。同时在第三个虚拟终端上运行主程序。在程序运行之后,如果在两个管道终端上输入字符串,则可以观察到同样的内容将在主程序的虚拟终端上逐行显示。如果想结束主程序,只要在主程序的虚拟终端下输入以q或Q字符开头的字符串即可。如果三个文件一直在无输入状态中,则主程序一直处于阻塞状态。为了防止无限期的阻塞,在 select 程序中设置超时值(本实例中设置为 60s),当无输入状态持续到超时值时,主程序主动结束运行并退出。而 poll 程序中依然无限等待,当然 poll()函数也可以设置超时参数。该程序的流程图如图 6.2 所示。嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 获得三个文件描述符(两个管道和标准输入)准备工作:创建两个管道开始初始化读文件描述符集合Select:监视文件描述符集合的文件读文件描述符集合的重置Tmp_inset-Inset有效的文件描述符存在?Select的返回是否输入“q”?打印到屏幕上打印到屏幕上结束是标准输入管道2管道1是否超时/出错 图 6.2 select 实例流程图 使用 select()函数实现的代码如下所示:/*multiplex_select*/#include#include#include#include#include#include#define MAX_BUFFER_SIZE 1024 /*缓冲区大小*/#define IN_FILES 3 /*多路复用输入文件数目*/#define TIME_DELAY 60 /*超时值秒数*/#define MAX(a,b)(a b)?(a):(b)int main(void)int fdsIN_FILES;char bufMAX_BUFFER_SIZE;int i,res,real_read,maxfd;struct timeval tv;fd_set inset,tmp_inset;/*首先以只读非阻塞方式打开两个管道文件*/fds0=0;if(fds1=open(in1,O_RDONLY|O_NONBLOCK)0)嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org printf(Open in1 errorn);return 1;if(fds2=open(in2,O_RDONLY|O_NONBLOCK)0)printf(Open in2 errorn);return 1;/*取出两个文件描述符中的较大者*/maxfd=MAX(MAX(fds0,fds1),fds2);/*初始化读集合 inset,并在读集合中加入相应的描述集*/FD_ZERO(&inset);for(i=0;i IN_FILES;i+)FD_SET(fdsi,&inset);F

    注意事项

    本文(第6章、文件IO编程.pdf)为本站会员(asd****56)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开