文件共享

UNIX支持在不同进程间共享打开文件,本文主要对以下几个方面简单介绍:
  • 原子操作;
  • 再次介绍dup与dup2;
  • fcntl函数;
  • ioctl函数;
  • /dev/fd;

1.介绍一下内核用于所有I/O的数据结构
内核使用了三种数据结构,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
(a) 文件描述符标志。
(b) 指向一个文件表项的指针。


(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
(a) 文件状态标志(读、写、增写、同步、非阻塞等)。
(b) 当前文件位移量。
(c) 指向该文件v节点表项的指针。


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

图1显示了进程的三张表之间的关系。该进程有两个不同的打开文件——一个文件打开为标准输入(文件描述符0) ,另一个打开为标准输出(文件描述符为1) 。
文件共享

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


如果两个独立进程各自打开了同一文件,则有图2中所示的安排:
文件共享

图2:两个独立进程各自打开同一个文件

我们假定第一个进程使该文件在文件描述符 3上打开,而另一个进程则使此文件在文件描述符 4上打开。 打开此文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个 v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前位移量。

2.原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。
单处理器中,在多进程(线程)访问资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。

3.dup与dup2
#include <unistd.h>
int dup(int filedes);
int dup2(int filedes, int filedes2);
两函数的返回:若成功为新的文件描述符,若出错为- 1

由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并不完全等同于close加上fcntl。它们之间的区别是:
(1) dup2是一个原子操作,而close及fcntl则包括两个函数调用。有可能在close和fcntl之间插入执行信号捕获函数,它可能修改文件描述符。
(2) 在dup2和fcntl之间有某些不同的errno。


4.fcntl函数
fcntl函数可以改变已经打开文件的性质。
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int filedes, int cmd ,…/*int arg*/ );
返回:若成功则依赖于cmd(见下),若出错为-1
在《unix环境高级编程》中提到:一般第三个参数是一个整数,与上面所示函数原型中的注释部分相对应。但是在书中提到在说明记录锁时,第三个参数则是指向一个结构的指针。
fcntl函数有五种功能:
  • 复制一个现存的描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  • 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
  • 获得/设置异步I/O有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW)

  • F_DUPFD 复制文件描述符filedes, 新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第三个参数值(取为整型值)中各值的最小值。新描述符与 filedes 共享同一文件表项 。但是,新描述符有它自己的一套文件描述符标志,其 FD_CLOEXEC文件描述符标志则被清除。
  • F_GETFD   对应于filedes 的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC。
  • F_SETFD   对于filedes 设置文件描述符标志。新标志值按第三个参数(取为整型值)设置。
  • 应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0 (系统默认,在exec时不关闭)或1 (在exec时关闭)。
  • F_GETFL   对应于filedes 的文件状态标志作为函数值返回。在说明open函数时,已说明了文件状态标志,如图3所示:
文件共享
图3:对于fcntl的文件状态标志

不幸的是,三个存取方式标志( O_RDONLY, O_WRONLY,以及O_RDWR )并不各占1位。(正如前述,这三种标志的值各是 0、1和2,由于历史原因。这三种值互斥—一个文件只能有这三种值之一。)因此首先必须用屏蔽字O_ACCMODE取得存取方式位,然后将结果与这三种值相比较。[list]
  • F_SETFL   将文件状态标志设置为第三个参数的值(取为整型值)。 可以更改的几个标志是:O_APPEND,O_NONBLO CK,O_SYNC和O_ASYNC。
  • F_GETOWN   取当前接收SIGIO和SIGURG信号的进程I D或进程组ID。
  • F_SETOWN   设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg指定一个进程ID,负的arg表示等于arg绝对值的一个进程组 D。
  • [/list]fcntl的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD,F_GETFD, F_GETFL以及F_GETOWN。第一个返回新的文件描述符,第二个返回相应标志,最后一个返回一个正的进程I D或负的进程组ID。
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <fcntl.h>
    int main(int argc, char *argv[])
    {
       int accmode, val;
       if(argc !=2){
         printf("usage:  pro_2 <descriptor#>");
         exit(1);
        }
       if((val=fcntl(atoi(argv[1]),F_GETFL,0))<0)
        {
            printf("fcntl error for fd %d",atoi(argv[1]));
            exit(1);
       }
        accmode=val&O_ACCMODE;
        if(accmode==O_RDONLY) printf("read only");
        else if(accmode==O_WRONLY) printf("write only");
        else if(accmode==O_RDWR) printf("read write");
        else{
            printf("unknown access mode");
            exit(1);
         }
          if(val & O_APPEND) printf(", append");
          if(val & O_NONBLOCK) printf(", nonblocking");
       #if !defined(_POSIX_SOURCE)&&defined(O_SYNC)
           if(val&O_SYNC) printf(", synchronous writes");
       #endif
          putchar('\n');
          exit(0);
    
    }

    /home/l/g/tomotoboy/io >gcc pro_2.c -o pro_2
    /home/l/g/tomotoboy/io >pro_2 0 </dev/tty
    read only
    /home/l/g/tomotoboy/io >pro_2 2 2>temp.foo
    write only
    /home/l/g/tomotoboy/io >pro_2 2 2>>temp.foo
    write only, append


    一个对于一个文件描述符设置一个或多个文件状态标志的函数
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <fcntl.h>
    void set_fl(int fd, int flags) /* flags are file status flags to turn on*/
    {
    	int val;
    	if((val = fcntl(fd, F_GETFL ,0))<0){
    		printf("fcntl FGETFL error");
    		exit(1);
                    }
    	val |=flags; /* turn on flags */
     	if(fcntl(fd, F_SETFL ,val)<0){
    		printf("fcntl F_SETFL error");
    		exit(1);
    	}
    }

    如果将中间的一条语句改为:
    val &= ˜flags;        /*turn flags off*/

    就构成了另一个函数,我们称其为 clr_ fl,并将在后面某个例子中用到它。此语句使当前文件状态标志值val与flags的反码逻辑与运算。如果在程序pro_3的开始处,加上下面一行以调用set_fl,则打开了同步写标志。
    set_fl(STDOUT_FILENO, O_SYNC);

    这就造成每次write都要等待,直至数据已写到磁盘上再返回。

    5.ioctl函数


    6./dev/fd
    比较新的系统都提供名为/dev/fd的目录,其目录项是名为 0、1、2等的文件。打开文件/dev/fd /n等效于复制描述符n (假定描述符n是打开的)。




    参考质料
    《UNIX环境高级编程》
    百度百科-原子操作
    Linux原子操作

    你可能感兴趣的:(数据结构,C++,c,F#,C#)