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

實驗環(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)支持在不同進程中共享打開文件。

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

    • 指向該文件表項的指針

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

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

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

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

原子操作

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

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

  • 調(diào)用pread相當于調(diào)用lseek后調(diào)用read,但是有區(qū)別:
    • 調(diào)用pread時,無法中斷其定位和讀操作
    • 不更新當前文件偏移量
  • 調(diào)用pwrite相當于調(diào)用lseek后調(diào)用write,但是也有類似的區(qū)別
  • 測試代碼如下:
#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)]

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

但是注意: 這兩個描述符不共享文件描述符標志(close-on-exec標志)。close-on-exec標志(FD_CLOEXEC;對于重復(fù)描述符,請參閱fcntl(2)。

  • dup3:這個函數(shù)與dup2的類似,不同點在于fd2的文件描述符標志會可以被強制設(shè)置O_CLOEXEC;再者如果fd2等于fd,會返回失敗,errno=EINVAL
O_CLOEXEC模式和FD_CLOEXEC選項
  • 調(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選項,效果和使用O_CLOEXEC選項open函數(shù)相同,但分別調(diào)用open、fcnt兩個函數(shù),不是原子操作,多線程環(huán)境中存在競態(tài)條件,故用open函數(shù)O_CLOEXEC選項代替之。
  • 調(diào)用open函數(shù)O_CLOEXEC模式打開的文件描述符,或是使用fcntl設(shè)置FD_CLOEXEC選項,這二者得到(處理)的描述符在通過fork調(diào)用產(chǎn)生的子進程中均不被關(guān)閉。
  • 調(diào)用dup族類函數(shù)得到的新文件描述符將清除O_CLOEXEC模式。
O_CLOEXEC模式和FD_CLOEXEC選項的代碼測試(包括fork、單進程、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單進程設(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

此時,sleep進程沒有占用O_CLOEXEC.tmp文件

/* 1.2單進程無設(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

此時,sleep進程占用O_CLOEXEC.tmp文件,說明sleep進程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 2.1單進程設(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

此時,sleep進程沒有占用O_CLOEXEC.tmp文件

/* 2.2單進程無設(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
此時,sleep進程占用O_CLOEXEC.tmp文件,說明sleep進程沒有關(guān)閉O_CLOEXEC.tmp文件
/* 3.1多進程設(shè)置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會讓子進程繼承父進程的文件描述符標志(寫實復(fù)制)
/* 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函數(shù)

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

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

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

  • 函數(shù)詳解:

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

fcntl函數(shù)

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

功能作用
  • 復(fù)制一個已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
  • 獲取、設(shè)置文件描述符標志(cmd=F_GETFD或F_SETFD)
  • 獲取、設(shè)置文件狀態(tài)標志(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文件描述符標志
  • F_DUPFD_CLOEXEC:復(fù)制文件描述符,并且可以設(shè)置FD_CLOEXEC, 測試如下:
#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:獲取對應(yīng)fd的文件描述符標志(目前就FD_CLOEXEC一個標志)
  • F_SETFD:為對應(yīng)的fd設(shè)置文件描述符標志
  • F_SETFL:獲取文件狀態(tài)標志
    [圖片上傳失敗...(image-6fb50d-1531646493559)]

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

  • F_SETFL:設(shè)置文件狀態(tài)標志
  • F_GETOWN:獲取當前接受SIGIO和SIGURG(TCP帶外數(shù)據(jù)、緊急模式,也可以用select來處理)信號的進程id或進程組(第14張再來討論異步I/O信號)
  • F_SETOWN:設(shè)置用于接受SIGIO和SIGURG信號的進程id或者進程組id,arg為負數(shù)時,則表示arg絕對值的一個進程組id
總結(jié)簡單測試
  • 代碼如下:
#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,同步寫
  • 當給文件描述符設(shè)置這個標志后,每次write都要等待,直至數(shù)據(jù)已寫到磁盤上再返回。在UNIX系統(tǒng)中,通常write只是將數(shù)據(jù)排入隊列,實際寫磁盤可能在之后。使用這個O_SYNC后,這樣一來,當write返回時就知道數(shù)據(jù)已確實寫到了磁盤上,一面在系統(tǒng)異常時產(chǎn)生數(shù)據(jù)丟失

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

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

  • 我的實驗環(huá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)測試結(jié)果:
    | 操作 | 用戶CPU(s) | 系統(tǒng)CPU(s)| 時鐘時間(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操作命令,但是每個設(shè)備驅(qū)動程序可以定義他自己專屬的ioctl命令
    [圖片上傳失敗...(image-15fe1b-1531646493559)]

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

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

/dev/fd

  • 打開/dev/fd/n等效于復(fù)制文件描述符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的實現(xiàn)是使用了符號鏈接,對其進行creat可能會導(dǎo)致底層文件斷裂

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375