unix环境高级编程笔记(3)-- 文件I/O(2)

时间:2021-10-17 15:14:42

本文讨论如何在多个进程间共享文件,以及所涉及的内核数据结构。然后会介绍dup,dup2,fcntl等函数的使用。

1 数据结构

内核使用三种数据结构表示打开的文件:

(1)每个进程在进程表中都有一个记录项,记录项中有一张打开文件文件描述符表,每项包括:

  a )文件描述符标志(close_on_exec)

  b)指向一个文件表的指针

(2)内核为所有打开的文件维护一张文件表,每个文件表项包括:

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

  b)  当前文件偏移量

  c)  指向v节点的指针

 (3) v节点   v节点中包含了文件类型和对文件各种操作的函数的指针,i节点,当前文件长度.

    i节点包括:文件所有者,文件长度,文件所在的设备,指向文件实际数据块所在位置的指针.

下图显示一个进程的三张表的关系,该进程打开了两个文件。

unix环境高级编程笔记(3)-- 文件I/O(2)

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

下图显示两个进程打开同一个文件 fork后:

unix环境高级编程笔记(3)-- 文件I/O(2)

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

2 原子操作

由多步组成的操作,如果操作原子的执行,要么执行完所有操作,要么一步也不执行,不会被中途打断。

2.1 添加至一个文件

如果两个进程A和B都对同一个文件进行添加操作,如果没有在调用open时使用O_APPEND选项,而使用lseek函数将文件偏移量定位到文件尾。由于定位(lseek)和写(write)不是原子操作,当内核在两个进程间切换时会导致A,B进程write在同一位置。解决这一问题的方法是在调用open时使用O_APPEND选项。

2.2 pread和pwrite

定位搜索(seek)和I/O可以原子性的执行。

#include<unistd.h>
ssize_t pread(int filedes,void *buf,size_t nbytes,off_t offset);
                                                                 返回值:读到的字节数,若到达文件尾则返回0,出错返回-1.
ssize_t pwrite(int filedes,const void *buf,size_t nbytes,off_t offset);
                                                                 返回值:若成功返回已写的字节数,出错返回-1。

调用pread相当于顺序的调用lseek和read,但又有所不同:

a)不能中断定位和读操作(原子的执行)

b)不更新文件指针。

pwrite同上。

2.3 创建一个文件

在调用open时使用O_CREAT和O_EXCL选项。会使测试文件是否存在和创建文件组成一个原子操作。

3 dup和dup2函数

dup和dup2用来复制一个现存的文件描述符。

#include <unistd.h>
int dup(int filedes);
int dup2(int filedes,int filedes2);
                                     返回值:若成功返回新的文件描述符,否则返回-1.  

dup返回的文件描述符一定时当前可以文件描述符中最小的一个。

dup2可以指定filedes2为返回的文件描述符,如果filedes2存在,则先将其关闭,如果filedes2 和 filedes相等,则返回filedes2 ,而不关闭它。

此时新文件描述符与之前的文件描述符共享一个文件表项。

unix环境高级编程笔记(3)-- 文件I/O(2)

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

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

dup(filedes) == fcntl(filedes,F_DUPFD,0)

 

dup2(filedes,filedes2);

等效与

close(filedes2);

fcntl(filedes,F_DUPFD,filedes2);

不过dup2是原子操作。

4 fcntl函数

fcntl可以改变已打开文件的性质。

#include <fcntl.h>
int fcntl(int filedes,int cmd,.../* int arg */);
                                         返回值:若成功依赖于cmd,出错返回-1。

在本节中第三个参数总是整数,而在记录锁中,第三个参数是一个指向结构的指针。

fcntl的五项功能:

  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 ,cmd = F_SETLK或F_SETLKW )

F_DUPFD:复制文件描述符filedes,返回新文件描述符。返回值为大于或等于第三个参数的值。新文件描述符的FD_CLOEXEC标志被关闭。

F_GETFD:获取filedes的文件描述符标志作为函数返回值,当前只定义了一个FD_CLOEXEC。

F_SETFD:对filedes设置文件描述符标志。现在一般把第三个参数设置为0(系统默认,在exec时不关闭)或 1(在exec时关闭)而不直接使用FD_CLOEXEC。

F_GETFL:获得文件状态标志。即调用open时的参数:

        O_RDONLY        只读打开

        O_WRONLY        只写打开

        O_RDWR         为读,写打开

        O_APPEND         每次写添加

        O_NONBLOCK        非阻塞模式

        O_SYNC           等待写完成(数据和属性)

        O_DSYNC         等待写完成(仅数据)

        O_RSYNC         同步读写

不过前三个访问标志并不各占一位,分别为0,1,2.而且这三个值互斥,必须先用屏蔽字O_ACCMODE取得访问模式位,然后将结果与这三个值中的任何一个比较。

val = fcntl(fd,O_GETFL,0);

switch(val & O_ACCMODE)
{

      case O_RDONLY:

      case O_WRONLY:

      case O_RDWR:

}

if(val & O_APPEND)

  printf("append\n");
....

F_SETFL:将文件状态标志设置为第三个参数,除了前三个,其他的都可以设置。

F_GETOWN:取得当前获得SIGIO和SIGURG的进程ID或进程组ID。

F_SETOWN:设置接受SIGIO和SIGURG的进程ID和进程组ID。正的arg为进程ID,负的arg的绝对值为进程组ID。

若fcntl函数出错返回-1,若执行成功:F_DUPFD,F_GETFD,F_GETFL,F_GETOWN,第一个返回新的文件描述符,二三返回相应的标志,最后一个返回进程或进程组ID。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <stdlib.h>
 5 
 6 int main(int argc,char *argv[])
 7 {
 8     int val;
 9     if((val = fcntl(atoi(argv[1]),F_GETFL,0)) < 0)
10         printf("fcntl error\n");
11     switch(val & O_ACCMODE)
12     {
13         case O_RDONLY:
14             printf("read only");
15             break;
16         case O_WRONLY:
17             printf("write only");
18             break;
19         case O_RDWR:
20             printf("read write");
21             break;
22         default:
23             printf("unknow access mode");
24     }
25     if(val & O_APPEND)
26         printf(",append");
27     if(val & O_NONBLOCK)
28         printf(",nonblock");
29     printf("\n");
30     return 0;
31 
32 }

编译执行:

 

root@debian:/program# ./3-4 0 < /dev/tty
read only
root@debian:/program# ./3-4 1 > tmp
root@debian:/program# cat tmp
write only
root@debian:/program# ./3-4 5 5<>tmp
read write
root@debian:/program# ./3-4 2 2>>tmp
write only,append
root@debian:/program#