第6章、文件IO编程.pdf
《第6章、文件IO编程.pdf》由会员分享,可在线阅读,更多相关《第6章、文件IO编程.pdf(60页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、 嵌入式学院华清远见旗下品牌:www.embedu.org 嵌入式学院华清远见旗下品牌:www.embedu.org 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 第 6 章 文件 I/O 编程 本章目标 在搭建起嵌入式开发环境之后,从本章开始,读者将真正开始学习嵌入式 Linux 的应用开发。由于嵌入式 Linux 是经Linux 裁减而来的,它的系统调用及用户编程接口 API与 Linux基本是一致的,因此,在以后的章节中,笔者将首先介绍 Linux中相关内容的基本编程开发,主要讲解与嵌入式 Linux 中一致
2、的部分,然后再将程序移植到嵌入式的开发板上运行。因此,没有开发板的读者也可以先在 Linux 上开发相关应用程序,这对以后进入嵌入式 Linux 的实际开发是十分有帮助的。本章主要讲解文件 I/O 相关开发,经过本章的学习,读者将会掌握以下内容。掌握 Linux 中系统调用的基本概念 掌握 Linux 中用户编程接口(API)及系统命令的相互关系 掌握文件描述符的概念 掌握 Linux 下文件相关的不带缓存 I/O 函数的使用 掌握 Linux 下设备文件读写方法 掌握 Linux 中对串口的操作 熟悉 Linux 中标准文件 I/O 函数的使用 嵌入式 Linux 应用程序开发标准教程第 6
3、 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 6.1 Linux 系统调用及用户编程接口(API)由于本章是讲解 Linux 编程开发的第 1 章,因此希望读者更加明确 Linux 系统调用和用户编程接口(API)的概念。在了解了这些之后,会对Linux 以及Linux 的应用编程有更深入的理解。6.1.1 系统调用 所谓系统调用是指操作系统提供给用户程序调用的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务。例如用户可以通过进程控制相关的系统调用来创建进程、实现进程调度、进程管理等。在这里,为什么用户程序不能直接访问系统内核提供
4、的服务呢?这是由于在Linux 中,为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。但是,在有些情况下,用户空间的进程需要获得一定的系统服务(调用内核空间程序),这时操作系统就必须利用系统提供给用户的“特殊接口”系统调用规定用户进程进入内核空间的具体位置。进行系统调用时,程序运行空间需要从用户空间进入内核空间,处理完后再返回用户空间。Linux 系统调用部分是非常精简的系统调用(只有
5、 250 个左右),它继承了 UNIX系统调用中最基本和最有用的部分。这些系统调用按照功能逻辑大致可分为进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、socket 控制、用户管理等几类。6.1.2 用户编程接口(API)前面讲到的系统调用并不是直接与程序员进行交互的,它仅仅是一个通过软中断机制向内核提交请求,以获取内核服务的接口。在实际使用中程序员调用的通常是用户编程接口API,也就是本书后面要讲到的 API 函数。但并不是所有的函数都一一对应一个系统调用,有时,一个 API 函数会需要几个系统调用来共同完成函数的功能,甚至还有一些 API 函数不需要调用相应的系统调用(因
6、此它所完成的不是内核提供的服务)。在 Linux 中,用户编程接口(API)遵循了在 UNIX 中最流行的应用编程界面标准POSIX 标准。POSIX 标准是由 IEEE 和 ISO/IEC 共同开发的标准系统。该标准基于当时现有的 UNIX 实践和经验,描述了操作系统的系统调用编程接口(实际上就是 API),用于保证应用程序可以在源代码一级上在多种操作系统上移植运行。这些系统调用编程接口主要是通过 C 库(libc)实现的。嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 6.1.3 系统命令 以上讲解了系统调用、用
7、户编程接口(API)的概念,分析了它们之间的相互关系,那么,读者在第 2 章中学到的那么多的 Shell 系统命令与它们之间又是怎样的关系呢?系统命令相对 API 更高了一层,它实际上是一个可执行程序,它的内部引用了用户编程接口(API)来实现相应的功能。它们之间的关系如图 6.1 所示。6.2 Linux 中文件及文件描述符概述 在 Linux 中对目录和设备的操作都等同于文件的操作,因此,大大简化了系统对不同设备的处理,提高了效率。Linux 中的文件主要分为 4 种:普通文件、目录文件、链接文件和设备文件。那么,内核如何区分和引用特定的文件呢?这里用到了一个重要的概念文件描述符。对于 L
8、inux 而言,所有对设备和文件的操作都是使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。通常,一个进程启动时,都会打开 3 个文件:标准输入、标准输出和标准出错处理。这 3 个文件分别对应文件描述符为 0、1 和 2(也就是宏替换 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO,鼓励读者使用这些宏替换)。基于文件描述符的 I/O 操作虽然不能移植到类 Linux 以
9、外的系统上去(如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()。这些函数的特点是不带缓存,直接对文件(包括设备)进行读写操作。这些函数虽然不是 ANS
10、I C 的组成部分,但是是 POSIX 的组成部分。6.3.1 基本文件操作(1)函数说明。open()函数用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。close()函数用于关闭一个被打开的文件。当一个进程终止时,所有被它打开的文图 6.1 系统调用、API系统命令之间的关系 嵌入式 Linux 应用程序开发标准教程第 6 章、文件I/O编程 嵌入式学院华清远见旗下品牌:www.embedu.org 件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。read()函数用于将从指定的文件描述符中读出的数据放到缓存区中,并返回实际读入的字节数。若返
11、回 0,则表示没有数据可读,即已达到文件尾。读操作从文件的当前指针位置开始。当从终端设备文件中读出数据时,通常一次最多读一行。write()函数用于向打开的文件写数据,写操作从文件的当前指针位置开始。对磁盘文件进行写操作,若磁盘已满或超出该文件的长度,则 write()函数返回失败。lseek()函数用于在指定的文件描述符中将文件指针定位到相应的位置。它只能用在可定位(可随机访问)文件操作中。管道、套接字和大部分字符设备文件是不可定位的,所以在这些文件的操作中无法使用 lseek()调用。(2)函数格式。open()函数的语法格式如表 6.1 所示。表 6.1 open()函数语法要点 所需头
12、文件#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
13、_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 表示设置文件所
14、有者的可读可写属性。八进制表示法中 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
15、(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 wri
16、te()函数语法要点 所需头文件#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 函数原型
17、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_WR
18、ONLY,这样就可以对不同的情况指定相应的处理方法。另外,这里对该文件的权限设置为0600。其源码如下所示:下面列出文件基本操作的实例,基本功能是从一个文件(源文件)中读取最后10KB 数据并到另一个文件(目标文件)。在实例中源文件是以只读方式打开,目标文件是以只写方式打开(可以是读写方式)。若目标文件不存在,可以创建并设置权限的初始值为 644,即文件所有者可读可写,文件所属组和其他用户只能读。读者需要留意的地方是改变每次读写的缓存大小(实例中为 1KB)会怎样影响运行效率。/*copy_file.c*/#include#include#include#include#include#inc
19、lude#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_rea
20、d_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
21、 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.
22、3.2 文件锁(1)fcntl()函数说明。前面的这 5 个基本函数实现了文件的打开、读写等基本操作,本小节将讨论的是,在文件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux 通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。文件锁包括建议性锁和强制性锁。建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统都不使用建议性锁。强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。在 Linux
23、 中,实现文件上锁的函数有 lockf()和 fcntl(),其中 lockf()用于对文件施加建议性锁,而 fcntl()不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl()还能对文件的某一记录上锁,也就是记录锁。记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。注意 fcntl()是一个非常通用的函数,它可以对已打开的文件描述符进行各种操作,不仅包括管理文件锁,还包括获得和设置文件描述符和文件描述符标志、文
24、件描述符的复制等很多功能。在本节中,主要介绍建立记录锁的方法。(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 标志
25、,若标志未设置,则文件经过 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,设置记录锁的具体状态 函数返回值
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 文件 IO 编程
限制150内