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

實(shí)驗(yàn)環(huán)境介紹

  • 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系統(tǒng)支持在不同進(jìn)程中共享打開文件。

內(nèi)核為打開的文件維護(hù)的數(shù)據(jù)結(jié)構(gòu)
  • 每個(gè)進(jìn)程維護(hù)一張文件描述符表,表示這個(gè)進(jìn)程打開的文件。與每個(gè)文件關(guān)聯(lián)的是
    • 文件描述標(biāo)志(close_on_exec,后續(xù)講解)
      [圖片上傳失敗...(image-e7d824-1531646493558)]

    • 指向該文件表項(xiàng)的指針

  • 內(nèi)核為所有打開的文件分別維持一張文件表,每個(gè)文件表項(xiàng)包含:
    • 文件狀態(tài)標(biāo)志(讀、寫、追加、同步和非阻塞等,后續(xù)講解)
    • 當(dāng)前文件的偏移量
    • 指向該文件v節(jié)點(diǎn)表項(xiàng)的指針
  • 每個(gè)進(jìn)程打開一個(gè)文件都有一個(gè)v節(jié)點(diǎn)結(jié)構(gòu),v節(jié)點(diǎn)包含了文件類型和對(duì)此文件進(jìn)行各種操作函數(shù)的指針。v節(jié)點(diǎn)還包含了該文件的i節(jié)點(diǎn)。這些信息是在打開文件時(shí)從從磁盤上讀入內(nèi)存的,所以,文件的所有信息都是隨時(shí)可用的。比如:i節(jié)點(diǎn)包含了文件的所有者、文件長(zhǎng)度、指向文件實(shí)際數(shù)據(jù)塊在磁盤上所在位置的指針等(第四章詳細(xì)來研究典型的unix系統(tǒng)文件系統(tǒng)以及i節(jié)點(diǎn))

linux沒有使用v節(jié)點(diǎn),使用的是通用的i節(jié)點(diǎn)結(jié)構(gòu)。雖然兩者實(shí)現(xiàn)不同,但在概念上v節(jié)點(diǎn)和i節(jié)點(diǎn)是一樣的,都指向了文件系統(tǒng)系統(tǒng)特有的i節(jié)點(diǎn)結(jié)構(gòu)。上圖中就是一個(gè)例子。

  • 多進(jìn)程打開同一個(gè)文件,如下圖,兩個(gè)進(jìn)程打開同一個(gè)文件,兩個(gè)進(jìn)程有各自文件描述符表,分別指向各自的文件表項(xiàng),兩個(gè)文件表項(xiàng)的v節(jié)點(diǎn)指針會(huì)指向同一個(gè)v節(jié)點(diǎn)表項(xiàng)
    [圖片上傳失敗...(image-7d0415-1531646493559)]
  • 常見操作說明
    • 完成每個(gè)write(無設(shè)置):更新文件表項(xiàng)中的當(dāng)前文件偏移量,如果偏移量會(huì)比當(dāng)前文件長(zhǎng)度大,則將i節(jié)點(diǎn)表項(xiàng)中的當(dāng)前文件長(zhǎng)度設(shè)置為當(dāng)前文件偏移量
    • O_APPEND:設(shè)置文件表項(xiàng)中的文件狀態(tài)標(biāo)志,每次進(jìn)行write操作時(shí),文件表項(xiàng)中的當(dāng)前文件偏移量首先被設(shè)置為i節(jié)點(diǎn)表項(xiàng)中的文件長(zhǎng)度。這就使得每次寫入都是追加
    • lseek操作:只是修改文件表項(xiàng)中的當(dāng)前偏移量,而不進(jìn)行I/O操作

可能有多個(gè)文件描述符指向同一個(gè)文件表項(xiàng),討論dup函數(shù)的時(shí)候,我們?cè)賮碛懻摗4送鈌ork之后也會(huì)發(fā)生同樣的情況,此時(shí)父進(jìn)程和紫禁城各自的每一個(gè)文件描述符共享對(duì)應(yīng)的同一個(gè)文件表項(xiàng)(第八章來討論)。注意:文件描述符標(biāo)志和文件狀態(tài)標(biāo)志在作用范圍方面是有區(qū)別的,前者只用于一個(gè)進(jìn)程的一個(gè)文件描述符,而后者則應(yīng)用于指向該給定文件表項(xiàng)的任何進(jìn)程中的所有描述符。后面討論fcntl函數(shù)的時(shí)候再來討論如何獲取文件描述符標(biāo)志和文件狀態(tài)標(biāo)志。

原子操作

原子操作指的是由多步組成的一個(gè)操作。防止多個(gè)操作之間被別的操作打斷而導(dǎo)致數(shù)據(jù)不同步,從而導(dǎo)致多個(gè)操作的結(jié)果沒有達(dá)到預(yù)期
多進(jìn)程讀寫以及pread和pwrite函數(shù)

single unix specification包括了xsi擴(kuò)展,該擴(kuò)展允許原子性地定位并執(zhí)行I/O

  • 調(diào)用pread相當(dāng)于調(diào)用lseek后調(diào)用read,但是有區(qū)別:
    • 調(diào)用pread時(shí),無法中斷其定位和讀操作
    • 不更新當(dāng)前文件偏移量
  • 調(diào)用pwrite相當(dāng)于調(diào)用lseek后調(diào)用write,但是也有類似的區(qū)別
  • 測(cè)試代碼如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#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函數(shù)

[圖片上傳失敗...(image-4fec68-1531646493559)]

功能:這兩個(gè)函數(shù)用于復(fù)制一個(gè)現(xiàn)有的文件描述符
  • dup:返回的新文件描述符一定是當(dāng)前可用文件描述符中的最小值
  • dup2:可以指定新描述符的值,注意:
    • 如果fd不是有效的文件描述符,那么調(diào)用失敗,并且fd2沒有關(guān)閉
    • 如果fd是一個(gè)有效的文件描述符,然后fd2和fd的值一樣,那就啥事也不做,直接返回fd2的值
  • 在成功地從其中一個(gè)系統(tǒng)調(diào)用返回之后,舊的和新的文件描述符可以互換使用。它們引用相同的打開文件描述(參見open(2)),從而共享文件偏移量和文件狀態(tài)標(biāo)志;例如,如果使用lseek(2)在描述符上使用lseek(2)來修改文件偏移量,那么另一個(gè)描述符的偏移量也會(huì)發(fā)生變化。

但是注意: 這兩個(gè)描述符不共享文件描述符標(biāo)志(close-on-exec標(biāo)志)。close-on-exec標(biāo)志(FD_CLOEXEC;對(duì)于重復(fù)描述符,請(qǐng)參閱fcntl(2)。

  • dup3:這個(gè)函數(shù)與dup2的類似,不同點(diǎn)在于fd2的文件描述符標(biāo)志會(huì)可以被強(qiáng)制設(shè)置O_CLOEXEC;再者如果fd2等于fd,會(huì)返回失敗,errno=EINVAL
O_CLOEXEC模式和FD_CLOEXEC選項(xiàng)
  • 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符在執(zhí)行exec調(diào)用新程序中關(guān)閉,且為原子操作。
  • 調(diào)用open函數(shù)不使用O_CLOEXEC模式打開的文件描述符,然后調(diào)用fcntl 函數(shù)設(shè)置FD_CLOEXEC選項(xiàng),效果和使用O_CLOEXEC選項(xiàng)open函數(shù)相同,但分別調(diào)用open、fcnt兩個(gè)函數(shù),不是原子操作,多線程環(huán)境中存在競(jìng)態(tài)條件,故用open函數(shù)O_CLOEXEC選項(xiàng)代替之。
  • 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符,或是使用fcntl設(shè)置FD_CLOEXEC選項(xiàng),這二者得到(處理)的描述符在通過fork調(diào)用產(chǎn)生的子進(jìn)程中均不被關(guān)閉。
  • 調(diào)用dup族類函數(shù)得到的新文件描述符將清除O_CLOEXEC模式。
O_CLOEXEC模式和FD_CLOEXEC選項(xiàng)的代碼測(cè)試(包括fork、單進(jìn)程、dup的情景)
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#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單進(jìn)程設(shè)置O_CLOEXEC,execl執(zhí)行命令 */
[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

此時(shí),sleep進(jìn)程沒有占用O_CLOEXEC.tmp文件

/* 1.2單進(jìn)程無設(shè)置O_CLOEXEC,execl執(zhí)行命令 */
[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

此時(shí),sleep進(jìn)程占用O_CLOEXEC.tmp文件,說明sleep進(jìn)程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 2.1單進(jìn)程設(shè)置FD_CLOEXEC,execl執(zhí)行命令 */
[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

此時(shí),sleep進(jìn)程沒有占用O_CLOEXEC.tmp文件

/* 2.2單進(jìn)程無設(shè)置FD_CLOEXEC,execl執(zhí)行命令 */
[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
此時(shí),sleep進(jìn)程占用O_CLOEXEC.tmp文件,說明sleep進(jìn)程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 3.1多進(jìn)程設(shè)置O_CLOEXEC選項(xiàng)  */
[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會(huì)讓子進(jìn)程繼承父進(jìn)程的文件描述符標(biāo)志(寫實(shí)復(fù)制)
/* 4.1dup對(duì)O_CLOEXEC選項(xiàng)的影響  */
[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清除了新文件描述符的文件描述符標(biāo)志O_CLOEXEC

sync、fsync和fdatasync函數(shù)

[圖片上傳失敗...(image-c358c8-1531646493559)]

  • 傳統(tǒng)unix系統(tǒng)實(shí)現(xiàn),在內(nèi)核中設(shè)有緩沖區(qū)告訴緩存或頁(yè)告訴緩存,大多數(shù)磁盤I/O都通過緩沖區(qū)進(jìn)行。當(dāng)我們向文件寫入數(shù)據(jù)時(shí),內(nèi)核現(xiàn)將數(shù)據(jù)復(fù)制到緩沖區(qū)中,然后排入隊(duì)列,晚點(diǎn)再寫入磁盤,這種方式叫做延遲寫。

  • 如果這些緩沖區(qū)又需要存放別的數(shù)據(jù)時(shí),內(nèi)核會(huì)把這些緩沖區(qū)的數(shù)據(jù)寫入磁盤。為了讓文件系統(tǒng)中的數(shù)據(jù)和緩沖區(qū)中的數(shù)據(jù)保持一致。提供了上述的函數(shù)

  • 函數(shù)詳解:

    • sync只是將所有緩沖區(qū)中的數(shù)據(jù)排入寫入隊(duì)列,然后就返回,并不代表實(shí)際寫磁盤操作結(jié)束。一般有個(gè)update的系統(tǒng)守護(hù)進(jìn)程周期性調(diào)用sync函數(shù),注意:sync命令也只是調(diào)用這個(gè)函數(shù)
    • fsync:這個(gè)對(duì)指定的文件描述符起作用,并且等到寫磁盤操作結(jié)束才結(jié)束,并且更新文件的屬性,fsync可用于數(shù)據(jù)庫(kù)這樣的應(yīng)用程序,需要及時(shí)寫
    • fdatasync:類似于fsync,但是只是影響文件的數(shù)據(jù)部分,不影響文件的屬性

fcntl函數(shù)

[圖片上傳失敗...(image-ca8955-1531646493559)]

功能作用
  • 復(fù)制一個(gè)已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
  • 獲取、設(shè)置文件描述符標(biāo)志(cmd=F_GETFD或F_SETFD)
  • 獲取、設(shè)置文件狀態(tài)標(biāo)志(cmd=F_GETFL或F_SETFL)
  • 獲取、設(shè)置異步I/O所有權(quán)(cmd=F_GETOWN或F_SETOWN)
  • 獲取、設(shè)置記錄鎖(cmd=F_GETLK、F_SETT或F_SETLKW)

這次只先說明下除了記錄鎖相關(guān)的幾種

cmd操作
  • F_DUPFD:復(fù)制文件描述符,清楚FD_CLOEXEC文件描述符標(biāo)志
  • F_DUPFD_CLOEXEC:復(fù)制文件描述符,并且可以設(shè)置FD_CLOEXEC, 測(cè)試如下:
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#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:獲取對(duì)應(yīng)fd的文件描述符標(biāo)志(目前就FD_CLOEXEC一個(gè)標(biāo)志)
  • F_SETFD:為對(duì)應(yīng)的fd設(shè)置文件描述符標(biāo)志
  • F_SETFL:獲取文件狀態(tài)標(biāo)志
    [圖片上傳失敗...(image-6fb50d-1531646493559)]

注意:O_RDONLY、O_WRONLY、O_RDWR、O_EXEC、O_SEARCH這幾個(gè)值并不是占一個(gè)bit位(ps: 在本機(jī)上沒找O_EXEC、O_SEARCH這兩個(gè)宏),可以用O_ACCCMODE獲得某個(gè)文件的文件狀態(tài)標(biāo)志

  • F_SETFL:設(shè)置文件狀態(tài)標(biāo)志
  • F_GETOWN:獲取當(dāng)前接受SIGIO和SIGURG(TCP帶外數(shù)據(jù)、緊急模式,也可以用select來處理)信號(hào)的進(jìn)程id或進(jìn)程組(第14張?jiān)賮碛懻摦惒絀/O信號(hào))
  • F_SETOWN:設(shè)置用于接受SIGIO和SIGURG信號(hào)的進(jìn)程id或者進(jìn)程組id,arg為負(fù)數(shù)時(shí),則表示arg絕對(duì)值的一個(gè)進(jìn)程組id
總結(jié)簡(jiǎn)單測(cè)試
  • 代碼如下:
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#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 <description#>");

    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 <description#>
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,同步寫
  • 當(dāng)給文件描述符設(shè)置這個(gè)標(biāo)志后,每次write都要等待,直至數(shù)據(jù)已寫到磁盤上再返回。在UNIX系統(tǒng)中,通常write只是將數(shù)據(jù)排入隊(duì)列,實(shí)際寫磁盤可能在之后。使用這個(gè)O_SYNC后,這樣一來,當(dāng)write返回時(shí)就知道數(shù)據(jù)已確實(shí)寫到了磁盤上,一面在系統(tǒng)異常時(shí)產(chǎn)生數(shù)據(jù)丟失

  • 程序運(yùn)行時(shí),設(shè)置O_SYNC標(biāo)志會(huì)增加系統(tǒng)時(shí)間和時(shí)鐘時(shí)間。下圖為apue中從一個(gè)磁盤文件中將492.6MB的數(shù)據(jù)復(fù)制到另外一個(gè)文件。然后對(duì)比設(shè)置了O_SYNC標(biāo)志的程序。
    [圖片上傳失敗...(image-2ca378-1531646493559)]

  • 在macOS X上的實(shí)驗(yàn)結(jié)果如下:
    [圖片上傳失敗...(image-541483-1531646493559)]

  • 我的實(shí)驗(yàn)環(huán)境進(jìn)行簡(jiǎn)單實(shí)驗(yàn)(XFS文件系統(tǒng),32768bytes的緩沖區(qū)),代碼如下:

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/times.h>

#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文件系統(tǒng)測(cè)試結(jié)果:
    | 操作 | 用戶CPU(s) | 系統(tǒng)CPU(s)| 時(shí)鐘時(shí)間(s) |
    | :-------- | :--:| :--: |:--:|
    | 正常寫到磁盤文件 | 0.000000 | 0.870000 | 8.940000 |
    | 設(shè)置O_SYNC后寫到磁盤文件 | 0.020000 | 2.890000 | 16.170000 |
    | 寫到磁盤后接著調(diào)用fdatasync | 0.110000 | 48.110000 | 117.990000 |
    | 寫到磁盤后接著調(diào)用fsync | 0.080000 | 49.790000 | 116.660000 |
    | 設(shè)置O_SYNC后寫到磁盤,接著調(diào)用fsync| 0.100000 | 51.290000 | 121.600000 |

ioctl函數(shù)

  • ioctl能夠?qū)崿F(xiàn)上述io函數(shù)都不能操作的其他io操作

  • 終端I/O大量使用ioctl來操作,不過已經(jīng)有些函數(shù)開始替代ioctl的一些操作

  • 系統(tǒng)為不同的設(shè)備定義了通用的ioctl操作命令,但是每個(gè)設(shè)備驅(qū)動(dòng)程序可以定義他自己專屬的ioctl命令
    [圖片上傳失敗...(image-15fe1b-1531646493559)]

  • 磁帶操作可以進(jìn)行文件結(jié)束操作、倒帶、越過指定的記錄,用本章的函數(shù)是難以表示這些操作的,但可以用ioctl來操作

  • 第18章來討論使用ioctl獲取、設(shè)置終端窗口的大小,19章來討論ioctl訪問偽終端的功能

/dev/fd

  • 打開/dev/fd/n等效于復(fù)制文件描述符n,如下
fd = open("/dev/fd/0", mode);
等價(jià)于
fd = dup(0);
0和fd共享一份文件表項(xiàng)
  • 如果描述符n之前被打開為只讀,那么我們也只能對(duì)fd進(jìn)行讀操作,而且下列操作是成功的,但是還是不能進(jìn)行寫操作
fd = open("/dev/fd/0", O_RDWR);
  • 也可以對(duì)/dev/fd路徑進(jìn)行creat操作

在linux上這么做必須很小心,linux的實(shí)現(xiàn)是使用了符號(hào)鏈接,對(duì)其進(jìn)行creat可能會(huì)導(dǎo)致底層文件斷裂

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容