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的更多相关文章

  1. node.js整理 03文件操作-遍历目录和文本编码

    遍历目录 递归算法 遍历目录时一般使用递归算法,否则就难以编写出简洁的代码. 递归算法与数学归纳法类似,通过不断缩小问题的规模来解决问题 function factorial(n) { if (n = ...

  2. POI读word doc 03 文件的两种方法

    Apache poi的hwpf模块是专门用来对word doc文件进行读写操作的.在hwpf里面我们使用HWPFDocument来表示一个word doc文档.在HWPFDocument里面有这么几个 ...

  3. 【Visual Lisp】驱动器、目录、文件和注册表

    ;;驱动器.目录.文件.和注册表;;★★★01.获取并创建驱动器盘符组成的表★★★(setq Drives (vlax-get-property (vlax-create-object "S ...

  4. python文件(概念、基本操作、常用操作、文本文件的编码方式)

    文件 目标 文件的概念 文件的基本操作 文件/文件夹的常用操作 文本文件的编码方式 01. 文件的概念 1.1 文件的概念和作用 计算机的 文件,就是存储在某种 长期储存设备 上的一段 数据 长期存储 ...

  5. python-面向对象-13_文件

    文件 目标 文件的概念 文件的基本操作 文件/文件夹的常用操作 文本文件的编码方式 01. 文件的概念 1.1 文件的概念和作用 计算机的 文件,就是存储在某种 长期储存设备 上的一段 数据 长期存储 ...

  6. C#开发BIMFACE系列6 服务端API之获取文件信息

    在<C#开发BIMFACE系列4 服务端API之源上传文件>.<C#开发BIMFACE系列5 服务端API之文件直传>两篇文章中详细介绍了如何将本地文件上传到BIMFACE服务 ...

  7. linux下文件权限更改(转载)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_33571752/article/d ...

  8. Linux基础 -03

    2.2.3 head-tail 命令 #------head #head pass #查看头部内容,默认前10行 #head -n5 pass #查看头部前5行,使用-n指定 #-------tail ...

  9. 平台之大势何人能挡? 带着你的Net飞奔吧!

    镇楼图: 跨平台系列: Linux基础 1.Linux基础学习 By dnt http://www.cnblogs.com/dunitian/p/4822807.html 环境配置 1.Hyper-v ...

随机推荐

  1. 使用iframe的好处与坏处详细比拼

    一.使用iframe的坏处 1.搜索引擎的蜘蛛不会识别在iframe中被调用的图片.文本.url等内容的,因为该内容不属于该页面,只是访问的时候被临时的调用,而且在SEO建议中也有提到:"f ...

  2. 《内蒙古自治区第十二届大学生程序设计竞赛试题_G: 最大收益》

    问题 G: 最大收益 内存限制:128 MB时间限制:1 S标准输入输出 题目类型:传统评测方式:文本比较上传者:外部导入 提交:87通过:23 返回比赛提交提交记录 题目描述 Elly的叔叔经营一家 ...

  3. 最小生成树 HihoCoder-1097、1098、1109(最小生成树算法)

    太久没写最小生成树了,快忘光了.这几天回顾了一下 最小生成树一·Prim算法 AC G++ 369ms 17MB #include "cstdio" using namespace ...

  4. 吴裕雄--天生自然python学习笔记:Python3 网络编程

    Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高级别的网络 ...

  5. CIA Hive Beacon Infrastructure复现1——使用Apache mod_rewrite实现http流量分发

    0x00 前言 2017年11月9日维基解密公布一个代号为Vault8的文档,包含服务器远程控制工具Hive的源代码和开发文档.开发文档中的框架图显示Hive支持流量分发功能,若流量有效,转发至Hon ...

  6. Ruby爬虫header发送cookie,nokogiri解析html数据

    之前用php写过一个爬虫,同样是获取局域网的网站数据,这次我使用相同的网络环境,更低的电脑配置,使用ruby来再次爬虫,惊人的发现ruby使用自带的类库net/http爬取速度要远远超过php的cur ...

  7. 云服务器 ECS--查找公网ip使用终端连接云服务

    前段时间购买了阿里云服务器,购买之后一直没用使用,今天来操作一波,可谓一波三折,只能说,不看他们的操作指南你可能连地方都找不到,所以,在这里,我想给初次购买阿里云服务的童鞋门,写写我是怎么使用阿里云服 ...

  8. babel-runtime 使用场景

    Babel 转译后的代码要实现源代码同样的功能需要借助一些帮助函数,例如,{ [name]: 'JavaScript' } 转译后的代码如下所示: 'use strict'; function _de ...

  9. Proto3:C++基本使用

    本教程提供protocol buffer在C++程序中的基础用法.通过创建一个简单的示例程序,向你展示如何: 在.proto中定义消息格式 使用protocol buffer编译器 使用C++ pro ...

  10. HTC新机A9足以取代iPhone 你相信吗?

    你相信吗?" title="HTC新机A9足以取代iPhone 你相信吗?"> 自信和嘴硬之间,往往就是成功与失败的分水岭.乔老爷子当年推出iPhone.iPad等 ...