03.文件I/O
UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek和close。
本章所说明的函数称为不带缓冲的I/O。不带缓冲指的是每个read和write都调用内核中的一个系统调用(即在内核中执行),这些不带缓冲的的I/O函数不是IOS C的组成部分。
只要涉及在多个进程间共享资源,原子操作的概念就变得非常重要。
1、文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。
按照惯例,UNIX系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错输出相关联。默认情况下,文件描述符0,1,2与终端关联。在shell中,可以使用重定向来改变文件描述符0,1,2的关联文件,如:
#define BUFFERSIZE 100
int main()
{
char buf[BUFFERSIZE];
ssize_t n = read(0,buf,BUFFERSIZE);
write(1,buf,n);
exit(0);
}
输出结果为:
$ cat file.in
Hello world!
$ cat file.out
$ ./test < file.in > file.out
$ cat file.out
Hello world!
shell打开file.in并返回一个文件描述符fd,用dup或dup2或fcntl函数复制文件描述符0,文件描述符fd和0共享file.in的文件表项。同理,打开file.out并返回一个文件描述符fd,用dup或dup2或fcntl函数复制文件描述符1,文件描述符fd和1共享file.out的文件表项。
2、open函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname是要打开或创建文件的名字。
oflag参数用来说明此函数的多个选项,用下列一个或多个常量进行“或”运算构成oflag参数:
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
在这三个常量中必须指定一个且只能指定一个,下列常量是可选的:
O_APPEND 每次写时都追加到文件的尾端
O_CREATE 若此文件不存在,则创建它。使用此选项,需要第三个参数mode,用其指定该新文件的访问权限位
O_EXCL 如果同时指定了O_CREATE,而文件已经存在,则出错。可以用来测试一个文件是否存在。如果不存在,则创建此文件,这使测试和创建两者成为一个原子操作
O_TRUNC 如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0
O_NOCTTY 如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置为非阻塞模式
O_DSYNC 使每次write等待物理I/O操作完成,但如果写操作并不影响读取刚写入的数据,则不等待文件属性被更新
O_RSYNC 使每一个以文件描述符为参数的read操作等待,直至任何对文件同一部分进行的未决写操作完成
O_SYNC 使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O
O_DSYNC和O_SYNC的区别:
仅当文件属性需要更新以反映文件数据变化(如,更新文件大小以反映文件中包含了更多的数据)时,O_DSYNC才会影响文件属性。而设置O_SYNC,数据和属性总是同步更新。
3、create函数
int creat(const char *pathname, mode_t mode);
此函数等效于
open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);
creat的不足之处是它以只写方式打开所创建的文件。
4、close函数
int close(int fd);
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
当一个进程终止时,内湖会自动关闭它所有打开的文件。
5、lseek函数
系统默认情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
off_t lseek(int fd, off_t offset, int whence);
若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节;
若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可为正或负;
若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可为正或负。
如果文件描述符引用的是一个管道、FIFO或网络套接字,则返回-1,并将errno设置为ESPIPE。
lseek仅将当前的文件偏移量记录在内核中,它并不引起内核I/O操作。然后,该偏移量用于下一个读或写操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的写将加长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被读为0。
文件中的空洞并不要求在磁盘上占用存储区。
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) int main()
{
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int fd; if((fd = creat("file.hole",FILE_MODE)) < 0) {
return -1;
} if(write(fd,buf1,10) != 10) {
return -1;
} if(lseek(fd,16384,SEEK_SET) == -1) {
return -1;
} if(write(fd,buf2,10) != 10) {
return -1;
} _exit(0);
}
6、read函数
ssize_t read(int fd, void *buf, size_t count);
读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。
7、文件共享
UNIX系统支持在不同进程间共享打开的文件。
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个描述符关联的是:
(a)文件描述符标志(close_on_exec);
(b)指向一个文件表项的指针;
(2)内核为所有打开文件维持一张文件表,每个文件表项包含:
(a)文件状态标志(读、写、同步、非阻塞等);
(b)当前文件偏移量;
(c)指向该文件v节点表项的指针;
(3)每个打开文件(或设备)都有一个v节点结构。V节点包含了文件类型和对此文件进行各种操作的函数的指针。V节点还包括了该文件的i节点。这些信息是在打开文件时从磁盘上读入内存的。例如,i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件实际数据块在磁盘上所在位置的指针等。
下图显示了一个进程的三张表之间的关系。
下图显示了两个独立进程各自打开同一个文件。
我们假定第一个进程在文件描述符3上打开该文件,而另一个进程则在文件描述符4上打开该文件。打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前偏移量。
下面对一些操作进一步说明:
(a)在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量;
(b)如果用Q_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这使得每次写的数据都添加到文件的当前尾端处。
(c)若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前偏移量被设置为i节点表项中的当前文件长度。
注意,文件描述符只用于一个进程的一个描述符,文件状态标志适用于指向该给定文件表项的任何进程中的所有描述符。
上面所述的一切对于多个进程读同一个文件都能正确工作。但是,当多个进程写同一个文件时,则可能产生意想不到的结果。为了说明如何避免这种情况,需要理解原子操作的概念。
8、dup和dup2函数
int dup(int oldfd);
int dup2(int oldfd, int newfd);
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新描述符的数值,如果filedes2已经打开,则先将其关闭。如若filedes等于filedes2,则dup2返回filedes2,而不关闭它。
这些函数返回的新文件描述符与参数filedes共享同一个文件表项,如图:
新描述符的执行时关闭(close_on_exec)标志总是由dup函数清除。
复制一个描述符的另一种方法是使用fcntl函数,实际上,调用
dup(filedes);
等效于
fcntl(filedes,F_DUPFD,0);
而调用
dup2(filedes,filedes2);
等效于
close(filedes2);
fcntl(filedes,F_DUPFD,filedes2);
在后一种操作中,dup2并不完全等同于fcntl,它们之间的区别是:
(1)dup2是一个原子操作;而close及fcntl则包括两个函数调用,有可能在close和fcntl之间插入执行信号捕获函数,它可能修改文件描述符;
(2)dup2和fcntl有某些不同的errno;
9、sync、fsync和fdatasync函数
传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列。而是等待其写满或者当内核需要重用缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达对首时,才进行实际的I/O操作。这种输出方式被称为延迟写。
当系统发生故障时,这种延迟可能造成文件更新内容的丢失。
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。
fdatasync函数类似于fsync,但它只影响文件的数据部分,而除数据外,fsync还会同步更新文件的属性。
10、fcntl函数
fcntl函数可以改变已打开的文件的性质:
int fcntl(int fd, int cmd, ... /* arg */ );
第三个参数可以是一个整数,但在使用记录锁时,它可以是指向一个结构的指针。
fcntl函数有5种功能:
(1)复制一个现有的描述符(cmd=F_DUPFD);
(2)获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD);
(3)获得/设置文件状态标志(cmd=F_GETFL或F_SETFL);
(4)获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN);
(5)获得/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW);
F_DUPFD 复制文件描述符filedes。新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第三个参数值中各值的最小值。新描述符与filedes共享同一个文件表项,但新描述符有它自己的文件描述符
F_GETFD 对应于filedes的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志F_CLOEXEC
F_SETFD 对于filedes设置文件描述符标志
F_GETFL 对应于filedes的文件状态标志作为函数值返回,在说明open函数时,已介绍了文件状态标志
F_SETFL 将文件状态标志设置为第三个参数的值
F_GETOWN 取当前接收SIGIO和SIGURG信号的的进程ID或进程组ID
F_SETOWN 设置接收SIGIO和SIGURG信号的进程ID或进程组ID
fcntl的返回值与命令有关,如果出错,所有命令都返回-1。
在修改文件描述符标志或文件状态标志时,先要取得现有的标志值,然后根据需要修改它。
11、/dev/fd
/dev/fd目录中,其目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制文件描述符n(假定描述符n是打开的)。
在下列函数调用中:
fd = open("/dev/fd/0",mode);
大多数系统忽略它指定的mode,而另外一些则要求mode必须是所涉及的文件(在这里是标准输入)原先打开时所使用mode的子集。因为上面打开是等效于:
fd = dup(0);
所以描述符0和fd共享同一个文件表项。例如,若描述符0先前被打开为只读,那么我们也只能对fd进行读操作。
fd = open("/dev/fd/0",O_RDWR);
即使调用成功,我们仍不能对fd进行写操作。
03.文件I/O的更多相关文章
- node.js整理 03文件操作-遍历目录和文本编码
遍历目录 递归算法 遍历目录时一般使用递归算法,否则就难以编写出简洁的代码. 递归算法与数学归纳法类似,通过不断缩小问题的规模来解决问题 function factorial(n) { if (n = ...
- POI读word doc 03 文件的两种方法
Apache poi的hwpf模块是专门用来对word doc文件进行读写操作的.在hwpf里面我们使用HWPFDocument来表示一个word doc文档.在HWPFDocument里面有这么几个 ...
- 【Visual Lisp】驱动器、目录、文件和注册表
;;驱动器.目录.文件.和注册表;;★★★01.获取并创建驱动器盘符组成的表★★★(setq Drives (vlax-get-property (vlax-create-object "S ...
- python文件(概念、基本操作、常用操作、文本文件的编码方式)
文件 目标 文件的概念 文件的基本操作 文件/文件夹的常用操作 文本文件的编码方式 01. 文件的概念 1.1 文件的概念和作用 计算机的 文件,就是存储在某种 长期储存设备 上的一段 数据 长期存储 ...
- python-面向对象-13_文件
文件 目标 文件的概念 文件的基本操作 文件/文件夹的常用操作 文本文件的编码方式 01. 文件的概念 1.1 文件的概念和作用 计算机的 文件,就是存储在某种 长期储存设备 上的一段 数据 长期存储 ...
- C#开发BIMFACE系列6 服务端API之获取文件信息
在<C#开发BIMFACE系列4 服务端API之源上传文件>.<C#开发BIMFACE系列5 服务端API之文件直传>两篇文章中详细介绍了如何将本地文件上传到BIMFACE服务 ...
- linux下文件权限更改(转载)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_33571752/article/d ...
- Linux基础 -03
2.2.3 head-tail 命令 #------head #head pass #查看头部内容,默认前10行 #head -n5 pass #查看头部前5行,使用-n指定 #-------tail ...
- 平台之大势何人能挡? 带着你的Net飞奔吧!
镇楼图: 跨平台系列: Linux基础 1.Linux基础学习 By dnt http://www.cnblogs.com/dunitian/p/4822807.html 环境配置 1.Hyper-v ...
随机推荐
- [LC] 380. Insert Delete GetRandom O(1)
Design a data structure that supports all following operations in average O(1) time. insert(val): In ...
- 详解JavaScript Document对象
转自:http://segmentfault.com/a/1190000000660947 在浏览器中,与用户进行数据交换都是通过客户端的javascript代码来实现的,而完成这些交互工作大多数是d ...
- leetcode 1.回文数-(easy)
2019.7.11leetcode刷题 难度 easy 题目名称 回文数 题目摘要 判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数. 思路 一些一定不为回文数的 ...
- 吴裕雄--天生自然HTML学习笔记:HTML 列表
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- ES7中的async和await
ES7中的async和await 在上一章中,使用Promise将原本的回调方式转换为链式操作,这就将一个个异步执行的操作串在一条同步线上了.下一次的操作必须等待当前操作的结束. 使用Promise的 ...
- 前端学习之路CSS基础学习一
CSS CSS定义如何显示HTML元素,当浏览器读到一个样式表,它就按照这个样式表来对文档进行格式化. CSS实例 每个CSS样式由两个组成部分:选择器和声明.声明又包括包括属性和属性值.每个声明之后 ...
- ubuntu16.04问题 · 最初的梦想
ubuntu 包管理器命令 1 $ sudo synaptic 安装主题 1 $ sudo apt-get install unity-tweak-tool 下载主题 https://www.sysg ...
- Vimium - 让你体验Geek般的浏览体验
相信很多电脑高手们都会寻找一一些快捷高效的操作方式,如经常利用键盘的快速操作,让你脱离鼠标,可以让你不用花太多精力地去移动细小的指针进行操作,使得工作的效率提高许多. 不过,实际上很多时候我们还是不得 ...
- 如何理解TCP的三次握手协议?
• TCP是一个面向链接的协议,任何一个面向连接的协议,我们都可以将其类比为我们最熟悉的打电话模型. 如何类比呢?我们可以从建立和销毁两个阶段分别来看这件事情. 建立连接阶段 首先,我们来看看TCP中 ...
- 从高知社区知乎变故事会,看论坛IP的夹缝生存
"海贼-王路飞"疯狂饰演多个角色答题的事件,最终以其被封杀为结果:知乎官方账号发表<知乎小管家工作笔记:我们封禁了几个伪造身份的帐号>,内容为公布了新一批 ...