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 使用说明的更多相关文章

  1. 第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 ...

  2. APUE第4章 文件和目录

    4.2 文件函数 #include <sys/stat.h> int stat(const char *restrict pathname, struct stat *restrict b ...

  3. apue 第4章 文件和目录

    获取文件属性 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(c ...

  4. 第七篇:使用 fcntl 函数 获取,设置文件的状态标志

    前言 当打开一个文件的时候,我们需要指定打开文件的模式( 只读,只写等 ).那么在程序中如何获取,修改这个文件的状态标志呢? 本文将告诉你如何用 fcntl函数 获取指定文件的状态标志. 解决思路 1 ...

  5. UNIX环境编程学习笔记(5)——文件I/O之fcntl函数访问已打开文件的性质

    lienhua342014-08-29 fcntl 函数可以改变已打开的文件的性质. #include <fcntl.h> int fcntl(int filedes, int cmd, ...

  6. apue学习笔记(第三章 文件I/O)

    本章开始讨论UNIX系统,先说明可用的文件I/O函数---打开文件.读写文件等 UNIX系统中的大多数文件I/O只需用到5个函数:open.read.write.lseek以及close open函数 ...

  7. apue学习笔记(第四章 文件和目录)

    本章将描述文件系统的其他特性和文件的性质. 函数stat.fstat.fstatat和lstat #include <sys/stat.h> int stat(const char *re ...

  8. 《UNIX环境高级编程》(APUE) 笔记第四章 - 文件和目录

    4 - 文件和目录 1. 函数 stat.fstat.fstatat 和 lstat #inlcude <sys/stat.h> int stat(const char *restrict ...

  9. 《UNIX环境高级编程》(APUE) 笔记第三章 - 文件I/O

    3 - 文件I/O Github 地址 1. 文件描述符 对于内核而言,所有打开的文件都通过 文件描述符 (file descriptor) 引用.当打开一个现有文件或创建一个新文件时,内核向进程返回 ...

随机推荐

  1. Mysql定时备份[Windows]

    基于mysql5.6.39版本 一.备份脚本 1.windows环境创建批处理文件 @echo off rem ******MySQL backup start****** set mysqlHome ...

  2. python 画图像训练结果的loss图

    得到每个epoch的loss和predict精度后,就可以愉快地画图直观地看出训练结果和收敛性了. # coding:utf-8 import matplotlib.pyplot as plt dat ...

  3. c#学习笔记-string stringBuilder

    string aTest = "abc";//分配固定的内存大小 aTest += "ddd"; //销毁原先的数据再来分配,消耗大 StringBuilder ...

  4. Linux I2C核心、总线和设备驱动

    目录 更新记录 一.Linux I2C 体系结构 1.1 Linux I2C 体系结构的组成部分 1.2 内核源码文件 1.3 重要的数据结构 二.Linux I2C 核心 2.1 流程 2.2 主要 ...

  5. Javascript简单教程汇总

    什么是函数 一段定义好的代码,并可以反复使用的代码块 函数的作用 提升代码的可复用性,将一段代码进行预定义,需要使用的时候才触发 代码块 形成了一个相对独立的作用域 语法: function  函数名 ...

  6. 使用jMeter构造大量并发的随机HTTP请求

    在前一篇文章使用jMeter构造大量并发HTTP请求进行微服务性能测试里,我介绍了如何用jMeter构造并发HTTP请求.但是通过文中介绍的方式构造的并发请求,其请求参数都是硬编码的'Wang'. 有 ...

  7. 【Java并发】锁机制

    一.重入锁 二.读写锁 三.悲观锁.乐观锁 3.1 悲观锁 3.2 乐观锁 3.3 CAS操作方式 3.4 CAS算法理解 3.5 CAS(乐观锁算法) 3.6 CAS缺点 四.原子类 4.1 概述 ...

  8. springcloud——学习

    构建分布式系统 配置管理 服务发现 断路器.路由.微代理.事件总线 全局锁.决策竞选. springCloud是一个微服务框架, 提供多种功能. 底层封装的HttpClinet springCloud ...

  9. 设置IDEA中各种线条颜色

    在IDEA之中, 如同TearLine, 行号, 搜索结果高亮, Caret Line, 方法分隔线颜色的设置的操作方法如下: 操作路径: 1.Ctrl+Alt+S, 打开设置:   settings ...

  10. vue.js 父子组件间 props 数据同步处理

    常见的场景: 在一个vue组件A中,使用另外一个组件B.A将自己的数据通过B组件的Props属性(propX)传递到B组件实例内部,B组件内部会修改该Props属性(propX)的值,此时在A组件内部 ...