第3章——《文件I/O》(2)

实验环境介绍

  • gcc:4.8.5
  • glibc:glibc-2.17-222.el7.x86_64
  • os:Centos7.4
  • kernel:3.10.0-693.21.1.el7.x86_64

文件共享

unix系统支持在不同进程中共享打开文件。

内核为打开的文件维护的数据结构
  • 每个进程维护一张文件描述符表,表示这个进程打开的文件。与每个文件关联的是
    • 文件描述标志(close_on_exec,后续讲解)
      [图片上传失败...(image-e7d824-1531646493558)]

    • 指向该文件表项的指针

  • 内核为所有打开的文件分别维持一张文件表,每个文件表项包含:
    • 文件状态标志(读、写、追加、同步和非阻塞等,后续讲解)
    • 当前文件的偏移量
    • 指向该文件v节点表项的指针
  • 每个进程打开一个文件都有一个v节点结构,v节点包含了文件类型和对此文件进行各种操作函数的指针。v节点还包含了该文件的i节点。这些信息是在打开文件时从从磁盘上读入内存的,所以,文件的所有信息都是随时可用的。比如:i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等(第四章详细来研究典型的unix系统文件系统以及i节点)

linux没有使用v节点,使用的是通用的i节点结构。虽然两者实现不同,但在概念上v节点和i节点是一样的,都指向了文件系统系统特有的i节点结构。上图中就是一个例子。

  • 多进程打开同一个文件,如下图,两个进程打开同一个文件,两个进程有各自文件描述符表,分别指向各自的文件表项,两个文件表项的v节点指针会指向同一个v节点表项
    [图片上传失败...(image-7d0415-1531646493559)]
  • 常见操作说明
    • 完成每个write(无设置):更新文件表项中的当前文件偏移量,如果偏移量会比当前文件长度大,则将i节点表项中的当前文件长度设置为当前文件偏移量
    • O_APPEND:设置文件表项中的文件状态标志,每次进行write操作时,文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这就使得每次写入都是追加
    • lseek操作:只是修改文件表项中的当前偏移量,而不进行I/O操作

可能有多个文件描述符指向同一个文件表项,讨论dup函数的时候,我们再来讨论。此外fork之后也会发生同样的情况,此时父进程和紫禁城各自的每一个文件描述符共享对应的同一个文件表项(第八章来讨论)。注意:文件描述符标志和文件状态标志在作用范围方面是有区别的,前者只用于一个进程的一个文件描述符,而后者则应用于指向该给定文件表项的任何进程中的所有描述符。后面讨论fcntl函数的时候再来讨论如何获取文件描述符标志和文件状态标志。

原子操作

原子操作指的是由多步组成的一个操作。防止多个操作之间被别的操作打断而导致数据不同步,从而导致多个操作的结果没有达到预期
多进程读写以及pread和pwrite函数

single unix specification包括了xsi扩展,该扩展允许原子性地定位并执行I/O

  • 调用pread相当于调用lseek后调用read,但是有区别:
    • 调用pread时,无法中断其定位和读操作
    • 不更新当前文件偏移量
  • 调用pwrite相当于调用lseek后调用write,但是也有类似的区别
  • 测试代码如下:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define bool unsigned char
#define true 1
#define false 0

#ifdef TEST
#define READ(fd, buf_p, count, offset) pread(fd, buf_p, count, offset) 
#else
#define READ(fd, buf_p, count, offset) read(fd, buf_p, count) 
#endif

#define READ_TIME 10

#define FILE_PATH "/home/manjingliu/apue/part_3/tmp"

bool
create_thread(void *(*fun)(void *_arg), void *arg);

void *
pread_test(void *arg);

int
main(int argc, char *argv[])
{
    system("touch /home/manjingliu/apue/part_3/tmp;echo 1234567890 > \
        /home/manjingliu/apue/part_3/tmp");

    int fd = open(FILE_PATH, O_RDONLY);
    if (fd < 0) {
        printf("opne %s error\n", FILE_PATH);
    }

    create_thread(pread_test, &fd);
    create_thread(pread_test, &fd);

    while (1)
        sleep(1);

    exit(EXIT_SUCCESS);
}

bool
create_thread(void *(*fun)(void *_arg), void *arg)
{
    pthread_t tid;
    int err = pthread_create(&tid, NULL, fun, arg);
    if (err) {
        printf("create thread error\n");
        return false;
    }
    printf("create thread %ld successfully\n", tid);

    if (pthread_detach(tid)) {
        printf("detach thread %ld error(%s)\n", tid, strerror(errno));
        return false;
    }
    printf("detach thread %ld successfully\n", tid);

    return true;
}

void *
pread_test(void *fd)
{
    printf("tid: %ld\n", pthread_self());

    char buf[10] = {0};
    int i = 0;
    while (i < READ_TIME) {
        if (READ(*((int *)fd), &buf[i], 1, i) <= 0)
            printf("tid: %ld read error(%s)\n", pthread_self(), strerror(errno));
        i++;
        sleep(1);
    }

    i = 0;
    while (i < READ_TIME)
        printf("%c\n", buf[i++]);
    
    printf("tid: %ld exit\n", pthread_self());
    return ((void *)0);
}

read版本
result:
[manjingliu@localhost part_3]$ gcc -Wall 3_11.c -o 3_11 -std=c99 -lpthread 
[manjingliu@localhost part_3]$ 
[manjingliu@localhost part_3]$ ./3_11
create thread 139985312143104 successfully
detach thread 139985312143104 successfully
create thread 139985303750400 successfully
detach thread 139985303750400 successfully
tid: 139985303750400
tid: 139985312143104
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985303750400 read error(Success)
tid: 139985312143104 read error(Success)
2
4
6
8
0





tid: 139985312143104 exit
1
3
5
7
9






tid: 139985303750400 exit

pread版本
result:
[manjingliu@localhost part_3]$ gcc -Wall 3_11.c -o 3_11 -std=c99 -lpthread -DTEST
3_11.c: In function ‘pread_test’:
3_11.c:79:3: warning: implicit declaration of function ‘pread’ [-Wimplicit-function-declaration]
  if (READ(*((int *)fd), &buf[i], 1, i) <= 0)
  ^
[manjingliu@localhost part_3]$ ./3_11
create thread 139777443800832 successfully
detach thread 139777443800832 successfully
tid: 139777443800832
create thread 139777435408128 successfully
detach thread 139777435408128 successfully
tid: 139777435408128
1
2
3
4
5
6
7
8
9
0
tid: 139777443800832 exit
1
2
3
4
5
6
7
8
9
0
tid: 139777435408128 exit

dup和dup2函数

[图片上传失败...(image-4fec68-1531646493559)]

功能:这两个函数用于复制一个现有的文件描述符
  • dup:返回的新文件描述符一定是当前可用文件描述符中的最小值
  • dup2:可以指定新描述符的值,注意:
    • 如果fd不是有效的文件描述符,那么调用失败,并且fd2没有关闭
    • 如果fd是一个有效的文件描述符,然后fd2和fd的值一样,那就啥事也不做,直接返回fd2的值
  • 在成功地从其中一个系统调用返回之后,旧的和新的文件描述符可以互换使用。它们引用相同的打开文件描述(参见open(2)),从而共享文件偏移量和文件状态标志;例如,如果使用lseek(2)在描述符上使用lseek(2)来修改文件偏移量,那么另一个描述符的偏移量也会发生变化。

但是注意: 这两个描述符不共享文件描述符标志(close-on-exec标志)。close-on-exec标志(FD_CLOEXEC;对于重复描述符,请参阅fcntl(2)。

  • dup3:这个函数与dup2的类似,不同点在于fd2的文件描述符标志会可以被强制设置O_CLOEXEC;再者如果fd2等于fd,会返回失败,errno=EINVAL
O_CLOEXEC模式和FD_CLOEXEC选项
  • 调用open函数O_CLOEXEC模式打开的文件描述符在执行exec调用新程序中关闭,且为原子操作。
  • 调用open函数不使用O_CLOEXEC模式打开的文件描述符,然后调用fcntl 函数设置FD_CLOEXEC选项,效果和使用O_CLOEXEC选项open函数相同,但分别调用open、fcnt两个函数,不是原子操作,多线程环境中存在竞态条件,故用open函数O_CLOEXEC选项代替之。
  • 调用open函数O_CLOEXEC模式打开的文件描述符,或是使用fcntl设置FD_CLOEXEC选项,这二者得到(处理)的描述符在通过fork调用产生的子进程中均不被关闭。
  • 调用dup族类函数得到的新文件描述符将清除O_CLOEXEC模式。
O_CLOEXEC模式和FD_CLOEXEC选项的代码测试(包括fork、单进程、dup的情景)
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILENAME "O_CLOEXEC.tmp"

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(EXIT_FAILURE);\
} while (0)


int main()
{
    int fd, fd2, val;
    pid_t pid;

#ifdef _O_CLOEXEC
    if ((fd = open(FILENAME, O_RDWR | O_CREAT | O_CLOEXEC, 0600)) < 0) 
#else
    if ((fd = open(FILENAME, O_RDWR | O_CREAT, 0600)) < 0) 
#endif
        err_sys("open error");

#ifdef _DUP
    if ((fd2 = dup(fd)) < 0)
        err_sys("dup error");
    if (write(fd2, "123", 3) < 0)
        err_sys("write error");

    if ((val = fcntl(fd, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");
    else
        printf("O_CLOEXEC is %s set\n", (val & FD_CLOEXEC) ? "" : "not");

    if ((val = fcntl(fd2, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");
    else
        printf("O_CLOEXEC is %s set in dup\n", (val & FD_CLOEXEC) ? "" : "not");
#endif

#ifdef _FCNTL_CLOEXEC
    if ((val = fcntl(fd, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");

    val |= FD_CLOEXEC;
    if (fcntl(fd, F_SETFD, val) < 0)
        err_sys("fcntl( F_SETFD) error");
#endif

#ifndef _FORK 
    if (execl("/usr/bin/sleep", "sleep", "10000", (void*)0) < 0)
        err_sys("execl error");
#else
switch ((pid = fork())) {
        case -1:
            err_sys("fork error");
        case 0:
            sleep(10000);
            break;
        default:
            sleep(10000);
            break;
    }
#endif

    exit(EXIT_SUCCESS);
}
/* 1.1单进程设置O_CLOEXEC,execl执行命令 */
[manjingliu@localhost part_3]$ gcc -D_O_CLOEXEC -o cloexec 3_12.c 
[manjingliu@localhost part_3]$ ./cloexec 

[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+  63509  59279  0 11:47 pts/3    00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63509
COMMAND  PID      USER  FD  TYPE DEVICE  SIZE/OFF    NODE NAME
sleep  63509 manjingliu  cwd    DIR  253,0      117  1164545 /home/manjingliu/apue/part_3
sleep  63509 manjingliu  rtd    DIR  253,0      244      64 /
sleep  63509 manjingliu  txt    REG  253,0    33112 50381418 /usr/bin/sleep
sleep  63509 manjingliu  mem    REG  253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep  63509 manjingliu  mem    REG  253,0  2173512    42306 /usr/lib64/libc-2.17.so
sleep  63509 manjingliu  mem    REG  253,0    164240    32424 /usr/lib64/ld-2.17.so
sleep  63509 manjingliu    0u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63509 manjingliu    1u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63509 manjingliu    2u  CHR  136,3      0t0        6 /dev/pts/3

此时,sleep进程没有占用O_CLOEXEC.tmp文件

/* 1.2单进程无设置O_CLOEXEC,execl执行命令 */
[manjingliu@localhost part_3]$ gcc -o nocloexec 3_12.c 
[manjingliu@localhost part_3]$ ./nocloexec 

[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+  63565  59279  0 11:55 pts/3    00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63565
COMMAND  PID      USER  FD  TYPE DEVICE  SIZE/OFF    NODE NAME
sleep  63565 manjingliu  cwd    DIR  253,0      134  1164545 /home/manjingliu/apue/part_3
sleep  63565 manjingliu  rtd    DIR  253,0      244      64 /
sleep  63565 manjingliu  txt    REG  253,0    33112 50381418 /usr/bin/sleep
sleep  63565 manjingliu  mem    REG  253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep  63565 manjingliu  mem    REG  253,0  2173512    42306 /usr/lib64/libc-2.17.so
sleep  63565 manjingliu  mem    REG  253,0    164240    32424 /usr/lib64/ld-2.17.so
sleep  63565 manjingliu    0u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63565 manjingliu    1u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63565 manjingliu    2u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63565 manjingliu    3u  REG  253,0        0  1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp

此时,sleep进程占用O_CLOEXEC.tmp文件,说明sleep进程没有关闭O_CLOEXEC.tmp文件
/* 2.1单进程设置FD_CLOEXEC,execl执行命令 */
[manjingliu@localhost part_3]$ 
[manjingliu@localhost part_3]$ gcc -o fcntl_cloexec -D_FCNTL_CLOEXEC 3_12.c 
[manjingliu@localhost part_3]$ ./fcntl_cloexec

[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+  63622  59279  0 12:02 pts/3    00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63622
COMMAND  PID      USER  FD  TYPE DEVICE  SIZE/OFF    NODE NAME
sleep  63622 manjingliu  cwd    DIR  253,0      155  1164545 /home/manjingliu/apue/part_3
sleep  63622 manjingliu  rtd    DIR  253,0      244      64 /
sleep  63622 manjingliu  txt    REG  253,0    33112 50381418 /usr/bin/sleep
sleep  63622 manjingliu  mem    REG  253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep  63622 manjingliu  mem    REG  253,0  2173512    42306 /usr/lib64/libc-2.17.so
sleep  63622 manjingliu  mem    REG  253,0    164240    32424 /usr/lib64/ld-2.17.so
sleep  63622 manjingliu    0u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63622 manjingliu    1u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63622 manjingliu    2u  CHR  136,3      0t0        6 /dev/pts/3

此时,sleep进程没有占用O_CLOEXEC.tmp文件

/* 2.2单进程无设置FD_CLOEXEC,execl执行命令 */
[manjingliu@localhost root]$ ps -ef | grep -v grep | grep sleep
manjing+  63633  59279  2 12:05 pts/3    00:00:00 sleep 10000
[manjingliu@localhost root]$ lsof -p 63633
COMMAND  PID      USER  FD  TYPE DEVICE  SIZE/OFF    NODE NAME
sleep  63633 manjingliu  cwd    DIR  253,0      178  1164545 /home/manjingliu/apue/part_3
sleep  63633 manjingliu  rtd    DIR  253,0      244      64 /
sleep  63633 manjingliu  txt    REG  253,0    33112 50381418 /usr/bin/sleep
sleep  63633 manjingliu  mem    REG  253,0 106070960 16997546 /usr/lib/locale/locale-archive
sleep  63633 manjingliu  mem    REG  253,0  2173512    42306 /usr/lib64/libc-2.17.so
sleep  63633 manjingliu  mem    REG  253,0    164240    32424 /usr/lib64/ld-2.17.so
sleep  63633 manjingliu    0u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63633 manjingliu    1u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63633 manjingliu    2u  CHR  136,3      0t0        6 /dev/pts/3
sleep  63633 manjingliu    3u  REG  253,0        0  1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp
此时,sleep进程占用O_CLOEXEC.tmp文件,说明sleep进程没有关闭O_CLOEXEC.tmp文件
/* 3.1多进程设置O_CLOEXEC选项  */
[manjingliu@localhost part_3]$ gcc -o fork_cloexec -D_O_CLOEXEC -D_FORK 3_12.c 
[manjingliu@localhost part_3]$ ./fork_cloexec 

[manjingliu@localhost root]$ ps -ef | grep -v grep | grep fork
dbus        660      1  0 Jul10 ?        00:00:02 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
manjing+  63646  59279  0 12:09 pts/3    00:00:00 ./fork_cloexec
manjing+  63647  63646  0 12:09 pts/3    00:00:00 ./fork_cloexec

[manjingliu@localhost root]$ lsof -p 63646
COMMAND    PID      USER  FD  TYPE DEVICE SIZE/OFF    NODE NAME
fork_cloe 63646 manjingliu  cwd    DIR  253,0      198 1164545 /home/manjingliu/apue/part_3
fork_cloe 63646 manjingliu  rtd    DIR  253,0      244      64 /
fork_cloe 63646 manjingliu  txt    REG  253,0    8832 1164900 /home/manjingliu/apue/part_3/fork_cloexec
fork_cloe 63646 manjingliu  mem    REG  253,0  2173512  42306 /usr/lib64/libc-2.17.so
fork_cloe 63646 manjingliu  mem    REG  253,0  164240  32424 /usr/lib64/ld-2.17.so
fork_cloe 63646 manjingliu    0u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63646 manjingliu    1u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63646 manjingliu    2u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63646 manjingliu    3u  REG  253,0        0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp

[manjingliu@localhost root]$ lsof -p 63647
COMMAND    PID      USER  FD  TYPE DEVICE SIZE/OFF    NODE NAME
fork_cloe 63647 manjingliu  cwd    DIR  253,0      198 1164545 /home/manjingliu/apue/part_3
fork_cloe 63647 manjingliu  rtd    DIR  253,0      244      64 /
fork_cloe 63647 manjingliu  txt    REG  253,0    8832 1164900 /home/manjingliu/apue/part_3/fork_cloexec
fork_cloe 63647 manjingliu  mem    REG  253,0  2173512  42306 /usr/lib64/libc-2.17.so
fork_cloe 63647 manjingliu  mem    REG  253,0  164240  32424 /usr/lib64/ld-2.17.so
fork_cloe 63647 manjingliu    0u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63647 manjingliu    1u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63647 manjingliu    2u  CHR  136,3      0t0      6 /dev/pts/3
fork_cloe 63647 manjingliu    3u  REG  253,0        0 1164889 /home/manjingliu/apue/part_3/O_CLOEXEC.tmp

这说明fork会让子进程继承父进程的文件描述符标志(写实复制)
/* 4.1dup对O_CLOEXEC选项的影响  */
[manjingliu@localhost part_3]$ gcc -o dup_cloexec -D_O_CLOEXEC -D_DUP 3_12.c 
[manjingliu@localhost part_3]$ ./dup_cloexec 
O_CLOEXEC is  set
O_CLOEXEC is not set in dup

说明dup清除了新文件描述符的文件描述符标志O_CLOEXEC

sync、fsync和fdatasync函数

[图片上传失败...(image-c358c8-1531646493559)]

  • 传统unix系统实现,在内核中设有缓冲区告诉缓存或页告诉缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核现将数据复制到缓冲区中,然后排入队列,晚点再写入磁盘,这种方式叫做延迟写。

  • 如果这些缓冲区又需要存放别的数据时,内核会把这些缓冲区的数据写入磁盘。为了让文件系统中的数据和缓冲区中的数据保持一致。提供了上述的函数

  • 函数详解:

    • sync只是将所有缓冲区中的数据排入写入队列,然后就返回,并不代表实际写磁盘操作结束。一般有个update的系统守护进程周期性调用sync函数,注意:sync命令也只是调用这个函数
    • fsync:这个对指定的文件描述符起作用,并且等到写磁盘操作结束才结束,并且更新文件的属性,fsync可用于数据库这样的应用程序,需要及时写
    • fdatasync:类似于fsync,但是只是影响文件的数据部分,不影响文件的属性

fcntl函数

[图片上传失败...(image-ca8955-1531646493559)]

功能作用
  • 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
  • 获取、设置文件描述符标志(cmd=F_GETFD或F_SETFD)
  • 获取、设置文件状态标志(cmd=F_GETFL或F_SETFL)
  • 获取、设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  • 获取、设置记录锁(cmd=F_GETLK、F_SETT或F_SETLKW)

这次只先说明下除了记录锁相关的几种

cmd操作
  • F_DUPFD:复制文件描述符,清楚FD_CLOEXEC文件描述符标志
  • F_DUPFD_CLOEXEC:复制文件描述符,并且可以设置FD_CLOEXEC, 测试如下:
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILENAME "fcntl.tmp"

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(EXIT_FAILURE);\
} while (0)


int main()
{
    int fd, fd2, fd3, val;

    if ((fd = open(FILENAME, O_RDWR | O_CREAT | O_CLOEXEC, 0600)) < 0) 
        err_sys("open error");

    if ((fd2 = fcntl(fd, F_DUPFD)) < 0)
        err_sys("fcntl(F_GETFD) error");

    if ((fd3 = fcntl(fd, F_DUPFD_CLOEXEC, 1)) < 0)
        err_sys("fcntl(F_GETFD) error");

    if ((val = fcntl(fd, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");
    else
        printf("O_CLOEXEC is %s set in fd\n", (val & FD_CLOEXEC) ? "" : "not");

    if ((val = fcntl(fd2, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");
    else
        printf("O_CLOEXEC is %s set in F_DUPFD fd2\n", (val & FD_CLOEXEC) ? "" : "not");

    if ((val = fcntl(fd3, F_GETFD)) < 0)
        err_sys("fcntl(F_GETFD) error");
    else
        printf("O_CLOEXEC is %s set in F_DUPFD_CLOEXEC fd3\n", (val & FD_CLOEXEC) ? "" : "not");

    exit(EXIT_SUCCESS);
}

result:
[manjingliu@localhost part_3]$ ./3_14
O_CLOEXEC is  set in fd
O_CLOEXEC is not set in F_DUPFD fd2
O_CLOEXEC is  set in F_DUPFD_CLOEXEC fd3
  • F_GETFD:获取对应fd的文件描述符标志(目前就FD_CLOEXEC一个标志)
  • F_SETFD:为对应的fd设置文件描述符标志
  • F_SETFL:获取文件状态标志
    [图片上传失败...(image-6fb50d-1531646493559)]

注意:O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH这几个值并不是占一个bit位(ps: 在本机上没找O_EXEC、O_SEARCH这两个宏),可以用O_ACCCMODE获得某个文件的文件状态标志

  • F_SETFL:设置文件状态标志
  • F_GETOWN:获取当前接受SIGIO和SIGURG(TCP带外数据、紧急模式,也可以用select来处理)信号的进程id或进程组(第14张再来讨论异步I/O信号)
  • F_SETOWN:设置用于接受SIGIO和SIGURG信号的进程id或者进程组id,arg为负数时,则表示arg绝对值的一个进程组id
总结简单测试
  • 代码如下:
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define FILENAME "fcntl.tmp"

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(EXIT_FAILURE);\
} while (0)


int main(int argc, char *argv[])
{
    int val;

    if (argc != 2) 
        err_sys("Usage: a.out ");

    if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
        err_sys("fcntl error for fd %d", atoi(argv[1]));

    switch (val & O_ACCMODE) {
    case O_RDWR:
        printf("read write");
        break;

    case O_RDONLY:
        printf("read only");
        break;

    case O_WRONLY:
        printf("write only");
        break;

    default:
        printf("unknow access mode");
    }

    if (val & O_APPEND)
        printf(", append");
    if (val & O_NONBLOCK)
        printf(", nonblocking");
    if (val & O_SYNC)
        printf(", synchronous");

#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
    if (val & O_FSYNC)
        printf(", synchronous writes");
#endif

    putchar('\n');
    exit(EXIT_SUCCESS);
}

reuslt:
[manjingliu@localhost part_3]$ ./3_15 O_CLOEXEC.tmp 
read write, append
[manjingliu@localhost part_3]$ ./3_15 < /dev/tty
Usage: a.out 
errno:0 Success
[manjingliu@localhost part_3]$ ./3_15 0 < /dev/tty
read only
[manjingliu@localhost part_3]$ ./3_15 1  >  temp.foo
[manjingliu@localhost part_3]$ cat temp.foo 
write only
[manjingliu@localhost part_3]$ ./3_15 2 2>>  temp.foo
write only, append
[manjingliu@localhost part_3]$ ./3_15 5 5<>  temp.foo
read write
O_SYNC,同步写
  • 当给文件描述符设置这个标志后,每次write都要等待,直至数据已写到磁盘上再返回。在UNIX系统中,通常write只是将数据排入队列,实际写磁盘可能在之后。使用这个O_SYNC后,这样一来,当write返回时就知道数据已确实写到了磁盘上,一面在系统异常时产生数据丢失

  • 程序运行时,设置O_SYNC标志会增加系统时间和时钟时间。下图为apue中从一个磁盘文件中将492.6MB的数据复制到另外一个文件。然后对比设置了O_SYNC标志的程序。
    [图片上传失败...(image-2ca378-1531646493559)]

  • 在macOS X上的实验结果如下:
    [图片上传失败...(image-541483-1531646493559)]

  • 我的实验环境进行简单实验(XFS文件系统,32768bytes的缓冲区),代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BYTES (1 * 516581760L) 
#define BUFF_LEN 32768

#define ORG_FILE "oo.org"
#define FILE_NAME1 "oo.1"
#define FILE_NAME2 "oo.2"
#define FILE_NAME3 "oo.3"
#define FILE_NAME4 "oo.4"
#define FILE_NAME5 "oo.5"

#define err_sys(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
    exit(EXIT_FAILURE);\
} while (0)

#define err_dump(fmt, arg...) \
do { \
    printf(fmt, ##arg);\
    printf("\nerrno:%d %s\n", errno, strerror(errno));\
} while (0)

void
cal_func_time(void (*func)(void), int arg);

void
set_fl(int fd, int flags);

void 
normal_write(void);

void 
normal_o_sync_write(void);

void 
normal_fdatasync_write(void);

void 
normal_fsync_write(void);

void 
normal_o_sync_fsync_write(void);

int
main(int argc, char *argv[])
{
    cal_func_time(normal_write, 0);
    cal_func_time(normal_o_sync_write, 0);
    cal_func_time(normal_fdatasync_write, 0);
    cal_func_time(normal_fsync_write, 0);
    cal_func_time(normal_o_sync_fsync_write, 0);

    exit(EXIT_SUCCESS);
}

void
cal_func_time(void (*func)(void), int arg)
{
    int sc_clk_tck;
    sc_clk_tck = sysconf(_SC_CLK_TCK);
    struct tms begin_tms, end_tms;
    clock_t begin, end;
    begin = times(&begin_tms);

    func();
    end = times(&end_tms);
    printf("real time: %lf\n", (end - begin) / (double)sc_clk_tck);
    printf("user time: %lf\n",
            (end_tms.tms_utime - begin_tms.tms_utime) / (double)sc_clk_tck);
    printf("sys time: %lf\n",
            (end_tms.tms_stime - begin_tms.tms_stime) / (double)sc_clk_tck);
}

void
set_fl(int fd, int flags)
{
    int val;
    if ((val = fcntl(fd, F_GETFL, 0)) < 0)
        err_sys("fcntl F_GETFL error");

    val |= flags;

    if (fcntl(fd, F_SETFL, val) < 0)
        err_sys("fcntl F_SETFL error");
}

void 
normal_write(void)
{
    ssize_t n;
    char buf[BUFF_LEN];

    printf("+++++++++++write disk normally+++++++++++++\n");

    int open_fd = open(ORG_FILE, O_RDONLY);  
    if (open_fd < 0) {
        err_sys("open %s error", ORG_FILE);
    } 

    int open_fd1 = open(FILE_NAME1, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd1 < 0) {
        err_sys("open open %s error", FILE_NAME1);
    }

    long has_read = 0;
    while (1) {
        n = read(open_fd, buf, BUFF_LEN);
        if ( n < 0 ) {
            err_sys("read error");
            break;
        } else if (n == 0) {
            printf("has read over all: %ld\n", has_read);
            break;
        } 

        if (write(open_fd1, buf, n) != n) {
            printf("write error\n");
            break;
        } else 
            has_read += n;
    }
    close(open_fd);
    close(open_fd1);
}

void 
normal_o_sync_write(void)
{
    ssize_t n;
    char buf[BUFF_LEN];

    printf("+++++++++++normal_o_sync_write+++++++++++++\n");

    int open_fd = open(ORG_FILE, O_RDONLY);  
    if (open_fd < 0) {
        err_sys("open %s error", ORG_FILE);
    } 

    int open_fd1 = open(FILE_NAME2, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd1 < 0) {
        err_sys("open open %s error", FILE_NAME2);
    }
    
    set_fl(open_fd1, O_SYNC);

    long has_read = 0;
    while (1) {
        n = read(open_fd, buf, BUFF_LEN);
        if ( n < 0 ) {
            err_sys("read error");
            break;
        } else if (n == 0) {
            printf("has read over all: %ld\n", has_read);
            break;
        } 

        if (write(open_fd1, buf, n) != n) {
            printf("write error\n");
            break;
        } else 
            has_read += n;
    }
    close(open_fd);
    close(open_fd1);
}

void 
normal_fdatasync_write(void)
{
    ssize_t n;
    char buf[BUFF_LEN];

    printf("+++++++++++normal_fdatasync_write+++++++++++++\n");

    int open_fd = open(ORG_FILE, O_RDONLY);  
    if (open_fd < 0) {
        err_sys("open %s error", ORG_FILE);
    } 

    int open_fd1 = open(FILE_NAME3, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd1 < 0) {
        err_sys("open open %s error", FILE_NAME2);
    }
    
    long has_read = 0;
    while (1) {
        n = read(open_fd, buf, BUFF_LEN);
        if ( n < 0 ) {
            err_sys("read error");
            break;
        } else if (n == 0) {
            printf("has read over all: %ld\n", has_read);
            break;
        } 

        if (write(open_fd1, buf, n) != n) {
            printf("write error\n");
            break;
        } else {
            if (fdatasync(open_fd1))
                err_dump("fdatasync error");
            has_read += n;
        }
    }
    close(open_fd);
    close(open_fd1); 
}

void 
normal_fsync_write(void)
{
    ssize_t n;
    char buf[BUFF_LEN];

    printf("+++++++++++normal_fsync_write+++++++++++++\n");

    int open_fd = open(ORG_FILE, O_RDONLY);  
    if (open_fd < 0) {
        err_sys("open %s error", ORG_FILE);
    } 

    int open_fd1 = open(FILE_NAME4, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd1 < 0) {
        err_sys("open open %s error", FILE_NAME2);
    }
    

    long has_read = 0;
    while (1) {
        n = read(open_fd, buf, BUFF_LEN);
        if ( n < 0 ) {
            err_sys("read error");
            break;
        } else if (n == 0) {
            printf("has read over all: %ld\n", has_read);
            break;
        } 

        if (write(open_fd1, buf, n) != n) {
            printf("write error\n");
            break;
        } else {
            if (fsync(open_fd1))
                err_dump("fsync error");
            has_read += n;
        }
    }
    close(open_fd);
    close(open_fd1);    
}

void 
normal_o_sync_fsync_write(void)
{
    ssize_t n;
    char buf[BUFF_LEN];

    printf("+++++++++++normal_o_sync_fsync_write+++++++++++++\n");

    int open_fd = open(ORG_FILE, O_RDONLY);  
    if (open_fd < 0) {
        err_sys("open %s error", ORG_FILE);
    } 

    int open_fd1 = open(FILE_NAME5, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd1 < 0) {
        err_sys("open open %s error", FILE_NAME2);
    }
    
    set_fl(open_fd1, O_SYNC);

    long has_read = 0;
    while (1) {
        n = read(open_fd, buf, BUFF_LEN);
        if ( n < 0 ) {
            err_sys("read error");
            break;
        } else if (n == 0) {
            printf("has read over all: %ld\n", has_read);
            break;
        } 

        if (write(open_fd1, buf, n) != n) {
            printf("write error\n");
            break;
        } else {
            if (fsync(open_fd1))
                err_dump("fsync error");
            has_read += n;
        }
    }
    close(open_fd);
    close(open_fd1);    
}
  • Centos7.4上xfs文件系统测试结果:
    | 操作 | 用户CPU(s) | 系统CPU(s)| 时钟时间(s) |
    | :-------- | :--:| :--: |:--:|
    | 正常写到磁盘文件 | 0.000000 | 0.870000 | 8.940000 |
    | 设置O_SYNC后写到磁盘文件 | 0.020000 | 2.890000 | 16.170000 |
    | 写到磁盘后接着调用fdatasync | 0.110000 | 48.110000 | 117.990000 |
    | 写到磁盘后接着调用fsync | 0.080000 | 49.790000 | 116.660000 |
    | 设置O_SYNC后写到磁盘,接着调用fsync| 0.100000 | 51.290000 | 121.600000 |

ioctl函数

  • ioctl能够实现上述io函数都不能操作的其他io操作

  • 终端I/O大量使用ioctl来操作,不过已经有些函数开始替代ioctl的一些操作

  • 系统为不同的设备定义了通用的ioctl操作命令,但是每个设备驱动程序可以定义他自己专属的ioctl命令
    [图片上传失败...(image-15fe1b-1531646493559)]

  • 磁带操作可以进行文件结束操作、倒带、越过指定的记录,用本章的函数是难以表示这些操作的,但可以用ioctl来操作

  • 第18章来讨论使用ioctl获取、设置终端窗口的大小,19章来讨论ioctl访问伪终端的功能

/dev/fd

  • 打开/dev/fd/n等效于复制文件描述符n,如下
fd = open("/dev/fd/0", mode);
等价于
fd = dup(0);
0和fd共享一份文件表项
  • 如果描述符n之前被打开为只读,那么我们也只能对fd进行读操作,而且下列操作是成功的,但是还是不能进行写操作
fd = open("/dev/fd/0", O_RDWR);
  • 也可以对/dev/fd路径进行creat操作

在linux上这么做必须很小心,linux的实现是使用了符号链接,对其进行creat可能会导致底层文件断裂

你可能感兴趣的:(第3章——《文件I/O》(2))