目录
概述
本章学习目标:
- 了解文件系统层次结构和文件I/O库之间关系、应用场合、性能比较。
- 掌握使用系统级I/O函数进行文件I/O、文件元数据读取的基本编程方法,能根据应用需求进行I/O库选择。
- 掌握内核文件I/O数据结构的用途与文件打开过程,理解文件描述符含义、文件共享原理,以及I/O重定向原理。
- 掌握文件组织和文件物理结构,能进行优劣对比分析,理解提高文件搜索效率的基本方法。
4.1 文件系统层次结构
4.1.1 文件系统层次结构简介
文件管理系统的引入
- 一般来说,处理的数据信息和结果存在文件中,文件数据保存在外存中,磁盘是最常见的外存,属于存储设备,用户不能随意读写磁盘。
- 磁盘两个特点:一个是磁盘属于外部设备,外设编程难度极大。另一个是内存与磁盘之间只能以数据块而非字节为单位来传递数据,数据块大小通常为磁盘块大小,通常为512B~4KB。
- 为实现对磁盘高效操作,操作系统通过文件系统模块来存储、定位、提取数据。为驾驭复杂性,将文件系统设计成多层结构,每层都利用较低层为较高层创建新的功能来为更高层服务。
文件管理系统层次结构
文件系统:
- I/O控制:为最底层,由设备驱动程序与中断处理程序组成,实现内存与磁盘之间的信息传输。设备驱动程序作为翻译器,将上层给出的命令翻译成底层硬件能执行的动作或指令实现数据读写。
- 基本文件系统:想合适的设备驱动程序发送对磁盘上的物理块进行读写的命令,磁盘块的地址可用一个四元组表示(驱动器号,柱面号,磁道号,扇区号)。
- 文件组织模块:涉及文件系统结构,文件格式和文件位置,将逻辑地址或逻辑块号转化为基本文件系统所用的物理地址(磁盘块号或称盘块号)。
- 逻辑文件系统:管理元数据(包括文件系统的结构数据,但不包括实际数据或文件内容)
文件系统接口(文件系统调用):
- 命令接口:终端命令操作文件,如:mkdir、cp、mv等
- 程序接口:支持应用程序操作文件,如创建文件的系统调用create,打开文件的系统调用open等
- 文件系统接口(调用):是用户或应用程序操作文件的方法和手段。
4.1.2 文件I/O库函数
标准I/O库
- 将一个打开的文件标准化为一个流,流是一个指向FILE类型结构的指针
- stdin、stdout和stderr分别对应标准输入、标准输出和标准错误输出
#include<stdio.h>
extern FILE *stdin; /*标准输入,对应描述符0*/
extern FILE *stdout; /*标准输出,对应描述符1*/
extern FILE *stuerr; /*标准错误输出,对应描述符2*/
RIO库
- Randy Bryant为弥补系统I/O在读文本行和处理不足值时存在的缺陷而设计的。
- RIO库在用户态设置缓冲区,自动处理read/write函数的不足值,支持以文本行为单位读取数据,给网络编程开发带来了极大的便利。
UNIX I/O、标准I/O和RIO之间的关系
4.2 系统I/O概念与文件操作编程
4.2.1 UNIX I/O
UNIX I/O(又称系统I/O)
- 一个UNIX文件就是一个包含m个字节的序列:B0,B1,B2,……Bm-1
- 所有的I/O设备,如网络、磁盘和终端都被模块化为文件
- 所有的输入输出都被当成相应文件的读写来执行
- 这种将设备优雅的映射为文件的方式,允许UNIX内核引出一个简单、低级的操作应用接口,c称为UNIX I/O
- 它以一致的方式来执行所有的输入输出,包括几个系统调用函数。
UNIX I/O的几个系统调用函数
文件打开函数(open)
- 应用程序调用open要求内核打开相应的文件,来宣告它想要访问一个I/O设备或文件。
- 内核返回文件描述符,一个小的非负整数,以后操作用描述符来标识该文件,内核记录有关这个打开文件的所有信息(文件属性、读写指针),应用程序只需记住这个描述符。
- 系统将自动打开键盘、正常输出窗口(屏幕)、错误输出窗口三个设备文件,返回前三个文件描述符0、1、2,分别称为标准输入、标准输出、标准错误输出。
改变当前文件位置函数(lseek)
- 每个打开文件都保持着一个读写位置,其值是从距离文件起始位置的字节偏移量。
- 新打开文件的读写位置在文件起始位置,值为0,随读写操作移动,或通过调用函数lseek移动到任何位置。
文件读函数(read)
- 从文件当前读写位置传递n个字节到内存,读写指针向后移动n个字节。
- 若读写指针移动到最后一个字节之后,则触发EOF条件,返回-1,用于判断到达文件尾。
文件写函数(write)
- 向文件写入数据,并移动读写指针。
文件关闭函数(close)
- 关闭文件,回收内核打开文件结构内容,将未写入文件数据flush到文件,将文件属性写回外存。
注意:
UNIX I/O对文本文件与二进制文件的读写没有任何区别,因为它在实际读写数据时,全然不管数据内容,每次内存和文件之间传送指定数量的字节。
4.2.2 文件打开和关闭函数
1、文件打开函数
函数原型
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int fd=open(char* filename, int flags, mode_t mode);
//成功返回文件描述符,失败返回-1
主要参数说明
(1)flags —— 进程如何访问这个文件
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWR:可读可写
如果打开方式包括写操作,flags参数还可以是:
- O_CREAT:如果文件不存在,创建一个文件
- O_TRUNC:如果文件存在,就截断它
- O_APPEND:以添加方式打开文件,每次写操作之前,设置文件读写指针到文件结尾处。
(2)mode
若打开文件已存在,一般设置为0。若不存在会创建一个新文件,需要通过mode指定对文件的操作权限。
新建文件权限受文件权限掩码umask影响
- umask是新建文件掩码,umask变量值中为1的位是不允许新创建文件拥有的权限位。
- 新建文件的实际访问权限位被设置为mode&~umask
- 很多Linux系统中,umask默认值为0022,表示同组用户和其他用户没有都没有写权限,这样可以保护用户创建的文件免遭他人有意无意的修改、删除
- 掩码可通过umask命令来查看,用-p选项的umask来修改,也可在下面程序中修改
#include<sys/types.h>
#include<sys/stat.h>
mode_t umask(mode_t mask);
2、文件关闭函数
#include<unistd.h>
int close(int fd);
//返回值,若成功,则为0;若出错,为-1
4.2.3 文件读写编程与读写性能改进方法
read函数与write函数原型
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t n);
//成功返回读出的字节数;若遇到EOF,则为0;若出错,为-1.
ssize_t write(int fd, const void *buf, size_t n);
//若成功,返回写入的字节数;若出错,返回-1
- read(fd,buf,n):从描述符为fd的当前读写位置拷贝最多n个字节数据到存储器位置buf,返回值-1表示一个错误,而返回值0表示EOF,否则,返回值表示的是实际传送的字节数量。
- write(fd,buf,n):从缓冲区buf拷贝至多n个字节数据到文件fd的当前读写位置,并移动文件指针到写入内容之后,如果读写指针在文件中间,写入内容将覆盖原有内容。
示例:文件读写性能
4.2.4 文件定位与文件内容随机读写
lseek函数原型
#include<unistd.h>
#include<sys/types.h>
off_set lseek(int fd, off_t offset, int whence);
//成功返回调整后的读写位置,失败返回-1
参数whence:可取下列值之一