读书笔记-APUE第三版-(3)Unbuffered I/O

来源:互联网 发布:复利农场源码 编辑:程序博客网 时间:2024/05/18 01:34

UNIX系统的大部分文件I/O操作都可以通过以下五个方法实现:open、read、write、lseek和close。这些方法被称之为Unbuffered的原因是它们直接执行系统调用。

文件描述符

文件描述符代表被打开的文件。进程的标准输出文件、标准输入文件和标准错误文件分别对应文件描述符0、1和2。

常用函数


I/O效率

使用read/write进行文件读写操作。

#include "apue.h"#define BUFFSIZE 4096intmain(void){    int n;    char buf[BUFFSIZE];    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)        if (write(STDOUT_FILENO, buf, n) != n)            err_sys("write error");        if (n < 0)            err_sys("read error");    exit(0);}

下图为使用不同的BUFFSIZE运行程序的结果

  1. 运行程序,标准输入重定向到文件,标准输出重定向到/dev/null。
  2. 系统环境:Linux,ext4文件系统,block大小为4096字节。
  3.  从结果看,文件系统支持预先读取,所以BUFFSIZE为32时,性能也不太差。
  4.   测试过程中,需要注意内核缓存,如果重复读取同一个文件,操作系统会缓存(incore)。

 

BUFFSIZE

User CPU

(seconds)

System CPU

(seconds)

Clock CPU

(seconds)

Number

Of loops

1

20.03

117.50

138.73

516,581,760

2

9.69

58.76

68.60

258,290,880

4

4.60

36.47

41.27

129,145,440

8

2.47

15.44

18.38

64,572,720

16

1.07

7.93

9.38

32,286,360

32

0.56

4.51

8.82

16,143,180

64

0.34

2.72

8.66

8,071,590

128

0.34

1.84

8.69

4,035,795

256

0.15

1.30

8.69

2,017,898

512

0.09

0.95

8.63

1,008,949

1,024

0.02

0.78

8.58

504,475

2,048

0.04

0.66

8.68

252,238

4,096

0.03

0.58

8.62

126,119

8,192

0.00

0.54

8.52

63,060

16,384

0.01

0.56

8.69

31,530

32,768

0.00

0.56

8.51

15,765

65,536

0.01

0.56

9.12

7,883

131,072

0.00

0.58

9.08

3,942

262,144

0.00

0.60

8.70

1,971

524,288

0.01

0.58

8.58

986

 

进程间文件共享

  1. process table entry:由每个进程管理,包含打开的文件描述符、文件描述符标记(如close-on-exec)和指向file table entry的指针。
  2. file table entry:由内核维护,包含文件打开状态标记(对应之前open函数中的oflag)、当前位置偏移量和指向v-node table entry的指针
  3.  v-node table entry:文件元数据和指向文件数据的指针。(其中Linux只有i-node,下一章中讲解具体结构)

正常情况,两个进程分别打开同一个文件,它们拥有不同的process table entry和file table entry,共享同一个v-node table entry,所以能够支持不同的打开状态标记,不同的位置偏移量等。(之前提到的lseek操作只改变file table entry中的偏移量,不进行实际I/O操作。)

如果使用dup方法,针对这个文件,不同的进程共享相同的file table entry,拥有不同的process table entry。这常见于fork父子进程场景(后续介绍)。

原子操作

Appending

如果使用lseek+write操作,可以实现在文件末尾处写入数据,潜在问题是这是两个系统调用,不是原子性的。正确方式是使用O_APPEND标记打开文件,再进行write操作,内核会保证每次write操作都是在文件末尾处写入数据。XSI还提供了两个方法,实现原子性的lseek和读写操作,其中offset参数代表位置偏移量。

#include<unistd.h>ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);    Returns: number of bytes read, 0 if end offile, -1 on errorssize_t pwrite(int fd, const void *buf, size_tnbytes, off_t offset);    Returns: number of bytes written if OK, -1 on error

创建文件

同样,如果先检查文件是否存在,再创建文件,这两个操作也不是原子性的。正确方式是使用O_CREATE和O_EXCL组合标记open文件。

其他函数

dup函数

#include<unistd.h>int dup(int fd);int dup2(int fd, int fd2);    Both return: new file descriptor if OK, -1 on error

使用dup(fd1)函数返回fd3后的文件共享结构图如下,fd3和fd1具有相同的文件状态标记和当前位置。dup2可以直接指定新的文件描述符为fd2,常用于实现输入输出重定向操作。如dup2(fd1,STDOUT_FILENO),先关闭当前标准输出,再将标准输出重定向到fd1。


sync、fsync和fdatasync函数

为了提高效率,写入操作不会立刻将数据写入到磁盘,只是写入到内核维护的cache中,不定期同步到硬盘,这称之为延缓写入(delayed write)。系统也会定期(一般每隔30s)调用sync函数。为了保持一致性,可以手动调用sync系列方法。注:Sync只是将内核缓冲中的数据送给磁盘写队列,不会等待写入操作完成。

fcntl函数

获取、更新已经打开文件的状态(包括process table entry和file table entry中的状态),比如文件描述符状态标记FD_CLOEXEC(调用exe后是否关闭文件),文件打开状态标记等。

0 0