linux多进程下的文件共享(包括每个进程的文件表项的详细介绍)

zz/2024/7/23 5:31:41

1. 文件共享

  (1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

      (a) 文件描述符标识(close_on_exec)。

     (b)指向一个文件表项的指针。

  (2)内核为所有的打开文件维持一张文件表。每个文件表项包含:

      (a)文件状态标志(读、写、添加、同步和非阻塞等)。

      (b)当前文件偏移量。

      (c)指向该文件v节点的指针。

   (3)每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如,i节点包含了文件的所有者,文件长度,文件所在的设备,指向文件实际数据块在磁盘上所在位置的指针等等。

    图1显示了一个进程的三张表之间的关系。该进程有两个不同的打开文件:一个文件打开为标注输入(文件描述符为0),另一个打开为标准输出(文件描述符为1)。从Unix系统的早期版本中[Thompson 1978]以来,这三张表之间的基本关系一直保持至今。这种安排对于在不同进程之间共享文件的方式非常重要。

                                        图1 打开文件的内核数据结构

2. 原子操作

2.1 添写至一个文件

    考虑一个进程,它要将数据添加到一个文件尾端。早期的UNIX系统版本并不支持open的O_APPEND选项,所以程序被编写成下列形式:

1. if (lseek(fd, 0L, 2) < 0) /* position to EOF */

2.     err_sys("lseek error");

3. if (write(fd, buf, 100) != 100) /* and write */

4.     err_sys("write error");

    对单个进程而言,这段程序能正常工作,但若对多个进程同时使用这种方法将数据添加到同一文件,则会产生问题。(例如,若此程序由多个进程同时执行,各自将消息添加到一个日志文件中,就会产生这种情况。)

    假定有两个独立的进程A和B都对同一个文件进行操作,给个进程都已打开了该文件,但未使用O_APPEND标志。此时,各数据结构之间的关系如图2所示。每个进程都有自己的文件表项,但是共享一个v节点表项。假定进程A调用了lseek,它将进程A的该文件当前偏移量设置为1500字节(当前文件尾端处)。然后内核调度进程使进程B运行。进程B执行sleek,也将其对该文件的当前偏移量设置为1500字节(当前文件尾端处)。然后B调用write函数,它将B的该文件当前文件偏移量增值1600.引文该文件的长度已经增加了,所以内核对v节点中的当前文件长度更新为1600.然后,内核又进行进程切换使进程A恢复运行。当A调用write时,就从其当前文件偏移量(1500字节)处将数据写到文件中去。这样就代换了进程B刚写到该文件中的数据。

     问题出在逻辑操作“定位到文件尾端处,然后写”上,它使用了两个分开的函数调用。解决问题的方法是使这两个操作对于其他进程而言成为一个原子操作。任何一个需要多个函数调用的操作都不可能是原子操作,因为在两个函数调用之间,内核有可能会临时挂起该进程。

     UNIX系统提供了一种方法是这种操作成为原子操作,该方法是在打开文件时设置O_APPEND标志。正如前面所述,这就是内核每次对这种文件进行写之前,都将进程的当前偏移量设置到文件的尾端处,于是在每次写之前就不在需要调用sleek了。

2.2 pread和pwrite函数

     Single UNIX Specification包括了XSI扩展,该扩展允许原子性地定位搜索(seek)和执行I(/O。pread和pwrite就是这种扩展。

1. #include <unistd.h>

2. 

3. ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);

4.                                         返回值:读到的字节数,若已到文件结尾则返回0,若出现错误返回-1

5. ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);

6.                                         返回值:若成功则返回已写的字节数,若出错则返回-1

    调用pread相当于顺序调用lseek和read,但是pread又与这种顺序调用有下列重要区别:

· 调用pread时,无法中断其定位和读操作。

· 不更新文件指针。

    调用pwrite相当于顺序调用lseek和write,但也和它们有类似的区别。

2.3 创建一个文件

    在对open函数的O_CREAT和O_EXCL选项进行说明时,我们已经见到另一个有关原子操作的例子。当同时指定这两个选项,而该文件又已经存在时,open将失败。我们曾提及检查该文件是否存在以及创建该文件这两个操作是作为一个原子操作执行的。如果没有这样一个原子操作,那么可能会编写下面的程序段:

1. if ((fd = open(pathname, O_WRONLY)) < 0) {

2.     if (errno == ENOENT) {

3.          if ((fd = creat(pathname, mode)) < 0)

4.               err_sys("create error");

5.     } else {

6.          err_sys("open error");

7.     }

8. }

     如果在open和creat之间,另一个进城创建了该文件,那么就会引起问题。例如,若在这两个函数调用之间。另一个进程创建了该文件,并且写进了一些数据,然后,原先的进程执行这段程序中的creat,这时,刚由另一个进程写上去的数据就会被擦除掉。如若将这两者合并在一个原子操作中,这种问题就不会存在了。

     一般而言,原子操作(atomic operation)指的是由多步组成的操作,如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

3. dup和dup2函数

     下面两个函数都可用来复制一个现存的文件描述符:

1. #include <unistd.h>

2. 

3. int dup(int filedes);

4. int dup2(int filedes, int filedes2);

5.                      两函数的返回值:若成功则返回新的文件描述符,若出错则返回-1

      由dup返回的新文件符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新描述符的数值。如果filedes2已经打开,则先将其关闭。如若filedes等于filedes2,则dup2返回filedes2,而不关闭它。

      这些函数返回的新文件描述符与参数参数filesdes共享同一个文件表项。如图3所示。

                                   图3 执行dup之后的内核数据结构

    在图3中,我们假定进程执行了:

1. newfd = dup(1);

    当此函数开始执行时,假定下一个可用的文件描述是3(这是非常有可能的,因为0、1、2是由shell打开的)。因为两个描述符指向同一文件表项,所以它们共享同一个文件状态标志(读、写、添加等)以及同一文件当前偏移量。

    每个文件描述符都有它自己的一套文件描述符标志。新描述的执行时关闭(close-on-exec)标志总是由dup函数清除。

    复制一个描述符的另一种方法是使用fcntl函数,实际上,调用

1. dup(filedes);

等效于

1. dup2(filedes, F_DUPFD, 0);

而调用

1. dup2(filedes, filedes2);

等效于

1. close(filedes2);

2. fcntl(filedes, F_DUPFD, filedes2);

在后一种情况下,dup2并不完全等同于close()加上fcntl.它们之间的区别是:

· dup2是一个原子操作,而close及fcntl则包含两个函数调用。有可能在close和fcntl之间插入执行信号捕获函数,它可能修改文件描述符。

· dup2和fcntl有某些不同的errno。

 


http://www.ngui.cc/zz/2769453.html

相关文章

建议性锁和强制性锁的区别

建议性锁&#xff1a; 所谓建议性锁就是假定人们都会遵守某些规则去干一件事。例如&#xff0c;人与车看到红灯都会停&#xff0c;而看到绿灯才会继续走&#xff0c;我们可以称红绿等为建议锁。但这只是一种规则而已&#xff0c;你并不防止某些人强闯红灯。而强制性锁是你想闯红…

C/C++的类型安全

什么是类型安全&#xff1f; 类型安全很大程度上可以等价于内存安全&#xff0c;类型安全的代码不会试图访问自己没被授权的内存区域。“类型安全”常被用来形容编程语言&#xff0c;其根据在于该门编程语言是否提供保障类型安全的机制&#xff1b;有的时候也用“类型安全”形容…

嵌入式动态内存分配过程

参考&#xff1a;http://blog.chinaunix.net/space.php?uid20312618&doblog&cuid1815216 一、概述&#xff1a; 动态内存分配&#xff0c;特别是开发者经常接触的Malloc/Free接口的实现&#xff0c;对许多开发者来说&#xff0c;是一个永远的话题&#xff0c;而且有时…

阿里HR筛选简历

上周发了一个阿里内推的帖子&#xff0c;没想到短时间内就收到了成百上千封简历。 我仔仔细细地看了每一封简历&#xff0c;附带有Github地址的我也点进去仔细看了代码。 最终我留下了30%的简历&#xff0c;而且这30%中只有10%的本科生。 所有通过内推初步筛选的小伙伴会在8月3…

点到线段的最短距离——矢量法

最近在看recast&detour源码的时候有遇到许多数学上的算法问题&#xff0c;特此记录&#xff0c;以便以后查看。 矢量法推导&#xff1a; 求点P到线段AB的最短距离。分成以下三种情况(a),(b),(c)。 &#xff08;勘误&#xff1a;dPC 应该是在 ∠PAB和∠PBA都小于90的情况…

Restore IP Addresses---LeetCode

题目要求&#xff1a;输入一串字符串&#xff0c;输出可能组成的ip地址。 先申请题意&#xff0c;弄清需求&#xff1a; 会存在不是数字的字符串吗&#xff1f;都是数字 10.10.10.01 最后一个01&#xff0c;符合要求吗&#xff1f;不符合要求。 leetcode上的示例答案&#x…

Recast Detour 寻路引擎学习建议

1.初步了解Recast&Detour,完成工程的下载和生成运行 http://www.stevefsp.org/projects/rcndoc/prod/index.html 2.了解Recast-导航数据的创建 看源代码中的Sample_SoloMesh.handleBuild 函数 http://www.critterai.org/projects/nmgen_study/overview.html 此英文文档…

Failed to load Assets/Plugins/xxx.dll with error

遇到这个错误&#xff0c;首先检查看提示的路径下面是否真的有 xxx.dll 如果有&#xff0c;那就是这个dll所引用的其他的库你电脑上没有&#xff0c;本地电脑的环境问题&#xff0c;把相关的dll拷贝到系统目录或者特定的目录下就可。 如何查看你缺少什么库&#xff1a; 下载一…

std::lambda小记

目录 不同形式的语法说明 [ capture ] 例子 lambda是C11中才引入的新特性&#xff0c;能定义匿名对象&#xff0c;而不必定义独立的函数和函数对象。 在介绍函数对象的for_each例子中&#xff0c;如果不用创建函数对象&#xff0c;可以使用下面 std::for_each(dest.begin()…

std::bind小记

当std::bind函数&#xff08;是一个函数模版&#xff09;&#xff0c;用来绑定函数的某些参数并生成一个新的std::function对象。 如何来确定绑定的是函数的第几个参数&#xff0c;引入std::placeholders命名空间&#xff1a; _1,函数调用的第一个参数&#xff0c; _2第2个参…