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

實(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

本章描述的函數(shù)被稱為不帶緩沖的I/O(將與第5章的標(biāo)準(zhǔn)I/O函數(shù)對(duì)照),不帶緩沖的I/O函數(shù)不是ISO C的組成部分。是POSIX.1和Single Unix specification的組成部分。進(jìn)一步討論多個(gè)進(jìn)程間如何共享文件。

文件描述符

當(dāng)打開或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符,通過這個(gè)文件描述符對(duì)文件進(jìn)行讀寫。

open和openat函數(shù)

  • open函數(shù)的幾個(gè)參數(shù)(后面后面使用代碼詳細(xì)測(cè)試):
    [圖片上傳失敗...(image-46bae1-1530629898456)]
  • 由open和openat返回的一定是最小的未使用的描述符數(shù)值。如:可以先關(guān)閉標(biāo)準(zhǔn)輸出,然后使用標(biāo)準(zhǔn)輸出(描述符為1)來打開另外一個(gè)文件,代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define FILE_PATH "/root/open.tmp"

int
main(int argc, char *argv[])
{
    close(STDOUT_FILENO);
    int fd = open(FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (-1 != fd) {
        fprintf(stderr, "fd of opening %s is: %d\n", FILE_PATH, fd);
        close(fd);
    } else {
        fprintf(stderr, "open file error!!\n");
    }

    exit(EXIT_SUCCESS);
}

result:
[root@localhost part_3]# ./3_3
fd of opening /root/open.tmp is: 1
  • openat解決的問題:
    • 讓線程可以使用相對(duì)路徑名打開目錄中的文件,而不再只能打開當(dāng)前目錄工作目錄。因?yàn)榫€程共享相同的當(dāng)前工作目錄,因此很難讓同一進(jìn)程的多個(gè)不同線程在同一時(shí)間工作在不同的目錄中
    • 避免TOCTTOU錯(cuò)誤:如果有兩個(gè)基于文件的函數(shù)調(diào)用,其中第二個(gè)調(diào)用依賴于第一個(gè)調(diào)用的結(jié)果,兩個(gè)調(diào)用不是原子操作,可能兩個(gè)操作之間文件改變了。導(dǎo)致結(jié)果錯(cuò)誤。比如一個(gè)進(jìn)程打開一個(gè)目錄中的文件,如果這個(gè)目錄發(fā)生變化(卸載之類的),此時(shí)如果繼續(xù)讀寫就會(huì)和預(yù)期不一致,他可能會(huì)創(chuàng)建一個(gè)新的文件來進(jìn)行操作。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>

#define FILE_PATH  "/root/open.tmp"
#define FILE_DIR    "/root"
#define FILENAME    "open.tmp"
#define CUR_FILE_PATH  "/root/source/part_3/open.tmp"

#define CUR_DIR    "/root/source/part_3"

int
main(int argc, char *argv[])
{
    // 1.open和openat的第一種情況:都打開其他路徑下的一個(gè)文件,使用絕對(duì)路徑
    // 1.1使用open
    int open_fd = open(FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd < 0) {
        printf("open open %s error\n", FILE_PATH);
    } else {
        printf("open open %s fd is: %d\n", FILE_PATH, open_fd);
        close(open_fd);
    }

    // 1.2 使用openat
    DIR *d = opendir(FILE_DIR);
    int dir_fd = dirfd(d);
    int openat_fd = openat(dir_fd, FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (openat_fd < 0) {
        printf("openat open %s error\n", FILE_PATH);
    } else {
        printf("openat open %s fd is: %d\n", FILE_PATH, openat_fd);
        close(openat_fd);
    }

    // 2.open和openat的第一種情況:都打開其他路徑下的一個(gè)文件,使用相對(duì)路徑
    // 2.1使用open
    open_fd = open(FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd < 0) {
        printf("open open %s error\n", FILE_PATH);
    } else {
        printf("open open %s fd is: %d\n", FILE_PATH, open_fd);
        close(open_fd);
    }

    // 2.2 使用openat
    d = opendir(FILE_DIR);
    dir_fd = dirfd(d);
    openat_fd = openat(dir_fd, FILENAME, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (openat_fd < 0) {
        printf("openat open %s error\n", FILE_PATH);
    } else {
        printf("openat open %s fd is: %d\n", FILE_PATH, openat_fd);
        close(openat_fd);
    }

    // 3.open和openat的第一種情況:都打開其他路徑下的一個(gè)文件,使用相對(duì)路徑,但openat使用AT_FDCWD特殊字符
    // 3.1使用open
    open_fd = open(CUR_FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd < 0) {
        printf("open open %s error\n", CUR_FILE_PATH);
    } else {
        printf("open open %s fd is: %d\n", CUR_FILE_PATH, open_fd);
        close(open_fd);
    }

    // 3.2 使用openat,使用相對(duì)路徑,但openat使用AT_FDCWD特殊字符
    d = opendir(FILE_DIR);
    dir_fd = dirfd(d);
    openat_fd = openat(AT_FDCWD, FILENAME, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (openat_fd < 0) {
        printf("openat open %s error\n", CUR_FILE_PATH);
    } else {
        printf("openat open %s fd is: %d\n", CUR_FILE_PATH, openat_fd);
        close(openat_fd);
    }
    exit(EXIT_SUCCESS);
}

result:
[root@localhost part_3]# ./3_4 
open open /root/open.tmp fd is: 3
openat open /root/open.tmp fd is: 4
open open /root/open.tmp fd is: 4
openat open /root/open.tmp fd is: 5
open open /root/source/part_3/open.tmp fd is: 5
openat open /root/source/part_3/open.tmp fd is: 6

creat函數(shù)

吐槽一哈,當(dāng)時(shí)寫這個(gè)函數(shù)的人,把這個(gè)單詞寫錯(cuò)了

  • 這個(gè)creat創(chuàng)建函數(shù)有局限性,所以還不如用open和openat函數(shù)

close函數(shù)

  • 關(guān)閉一個(gè)文件描述符會(huì)釋放在該文件上的記錄鎖。進(jìn)程終止時(shí)會(huì)自動(dòng)關(guān)閉所有的文件

lseek函數(shù)

  • 如果文件描述符指向的是一個(gè)管道、fifo或者網(wǎng)絡(luò)套接字,則不能設(shè)置偏移(是否能設(shè)置偏移在于文件描述符指向的文件是否能夠被設(shè)置偏移),還得注意某些文件是支持負(fù)的偏移量的
  • 測(cè)試標(biāo)注你輸入能否設(shè)置偏移量
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[])
{
    if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
        printf("cannot set STDIN_FILENO seek\n");
    else
        printf("set STDIN_FILENO seek OK\n");

    exit(EXIT_SUCCESS);
}

result:
// 這個(gè)標(biāo)準(zhǔn)輸入指向的是bash
[root@localhost part_3]# ./3_6 
cannot set STDIN_FILENO seek

// 這個(gè)標(biāo)準(zhǔn)輸入指向的是/etc/passwd
[root@localhost part_3]# ./3_6  < /etc/passwd
set STDIN_FILENO seek OK

// 這個(gè)標(biāo)準(zhǔn)輸入指向的是./open.tmp .一個(gè)普通的文本文件
[root@localhost part_3]# ./3_6  < ./open.tmp 
set STDIN_FILENO seek OK

// bash的管道作為標(biāo)注輸入
[root@localhost part_3]# cat /etc/passwd | ./3_6 
cannot set STDIN_FILENO seek

// 管道文件作為標(biāo)準(zhǔn)輸入,運(yùn)行程序后,往管道中輸入一些數(shù)據(jù)
[root@localhost part_3]# ./3_6  < ./fifo.tmp 
[root@localhost part_3]# echo sdf > fifo.tmp
cannot set STDIN_FILENO seek
  • 設(shè)置偏移量大于文件長(zhǎng)度,這樣會(huì)創(chuàng)建一個(gè)空洞文件(第4章我們?cè)龠M(jìn)一步說明空洞文件)
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";

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

    if ((fd = creat("file.hole", 0666)) < 0)
        printf("create file.hole error\n");

    if (write(fd, buf1, 10) != 10)
        printf("write buf1 error\n");
    /* now the offset of the file = 10 */

    if (lseek(fd, 20, SEEK_SET) == -1)
        printf("lseek the file.hole error\n");
    /* now the offset of the file = 20 */

    if (write(fd, buf2, 10) != 10)
        printf("write buf2 error\n");

    /* now the offset of the file = 30 */
    exit(EXIT_SUCCESS);
}

result:
[root@localhost part_3]# od -c file.hole 
0000000  a  b  c  d  e  f  g  h  i  j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  A  B  C  D  E  F  G  H  I  J
0000036

read函數(shù)

  • 有多重情況可使實(shí)際讀到的字節(jié)數(shù)少于要求讀的字節(jié)數(shù)
    • 讀普通文件,在讀到要求字節(jié)數(shù)之前已經(jīng)達(dá)到了文件文件尾端。如:在到達(dá)文件尾端之前有30個(gè)字節(jié),要求讀100字節(jié),則read返回30,下次在調(diào)用read時(shí),他將返回0(文件尾端)
    • 測(cè)試代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

#define FILE_PATH "/root/source/part_3/open.tmp"

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

    int fd = open(FILE_PATH, O_RDWR, 0666);
    if (-1 != fd) {
        fprintf(stderr, "fd of opening %s is: %d\n", FILE_PATH, fd);
    } else {
        fprintf(stderr, "open file error!!\n");
    }

    off_t off = lseek(fd, 0, SEEK_SET);
    if (off < 0)
        perror("lseek error");
#if (defined(_SC_V7_LP64_OFF64) || (defined(_SC_V7_LP64_OFF64BIG)))
    else
        printf("set lseek = %ld\n", off);
#else
    else
        printf("set lseek = %d\n", off);
#endif

    char buffer[100] = {0};
    size_t read_size = 100;
    if (read_size > SSIZE_MAX)
        read_size = SSIZE_MAX;

    ssize_t size = read(fd, buffer, read_size);
    if (size <= 0) {
        perror("first reading error");
    } else {
        printf("first reading successfully, size = %ld\n", size);
    }

    char buffer2[100] = {0};
    size = read(fd, buffer2, read_size);
    if (size <= 0) {
        perror("second reading error");
    } else {
        printf("second reading successfully, size = %ld\n", size);
    }
    close(fd);
    exit(EXIT_SUCCESS);
}

result:
[root@localhost part_3]# ./3_8 
fd of opening /root/source/part_3/open.tmp is: 3
set lseek = 0
first reading successfully, size = 10
second reading error: Success

函數(shù)write

  • write的出錯(cuò)的常見原因是因?yàn)槲募呀?jīng)寫滿,就或者超過了一個(gè)給定進(jìn)程的文件長(zhǎng)度限制

I/O的效率

  • 以下代碼從標(biāo)準(zhǔn)輸入讀,寫到標(biāo)準(zhǔn)輸出,但是這個(gè)BUFFSIZE如何選取,需要做實(shí)驗(yàn)來驗(yàn)證,apue給出了一個(gè)結(jié)果,如下圖:
    [圖片上傳失敗...(image-a59ad6-1530629898456)]
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFSIZE 4096
int
main(int argc, char *argvp[])
{
    int n;
    char buf[BUFFSIZE];
    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
        if (write(STDOUT_FILENO, buf, n) != n)
            printf("write error\n");
    if (n < 0)
        printf("read error\n");
    exit(EXIT_SUCCESS);
}
  • 我將以以下代碼做一個(gè)實(shí)驗(yàn),運(yùn)行之前先準(zhǔn)備一個(gè)516581760 bytes大小的文件(注:這里咱不考慮gcc優(yōu)化,文件中的數(shù)據(jù)不能保證隨機(jī))
比如使用:
dd if=/dev/zero of=/tmp/test bs=516581760 count=1
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define BYTES (1 * 516581760L) 
// #define BYTES (1 * 2) 
#define LOOP_COUNT(buffer_size) (BYTES / buffer_size)
#define FILE_PATH "/tmp/test"
#define FILE_PATH2 "/tmp/test2"

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

int
main(int argc, char *argv[])
{
    for (int i = 20; i > 0; i--) {
        cal_func_time(test_buffer, 1 << (i - 1) );
    }
    return 0;
}

void
cal_func_time(void (*func)(int), 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);
    // do func
    func(arg);
    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);

    // printf("child user time: %lf\n",
    // (end_tms.tms_cutime - begin_tms.tms_cutime) / (double)sc_clk_tck);
    // printf("child sys time: %lf\n",
    // (end_tms.tms_cstime - begin_tms.tms_cstime) / (double)sc_clk_tck);    
}

void
test_buffer(int size)
{
    ssize_t n;
    char buf[size];
    printf("++++++++++++++++++++++++\n");
    printf("data_sum = %ld, buf_size = %d, loop = %ld\n", BYTES, size, LOOP_COUNT(size));

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

    int open_fd2 = open(FILE_PATH2, O_RDWR | O_CREAT | O_TRUNC, 0666);
    if (open_fd2 < 0) {
        printf("open open %s error\n", FILE_PATH2);
        return;
    }
    
    for (int i = 0; i < LOOP_COUNT(size); i++) {
        n = read(open_fd, buf, size);
        if ( n <= 0 ) {
            printf("read error\n");
            break;
        }
        else if (write(open_fd2, buf, n) != n) {
            printf("write error\n");
            break;
        }
    }

    close(open_fd);
    close(open_fd2);
}

centos7上xfs文件系統(tǒng)測(cè)試result:
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 524288, loop = 985
real time: 15.610000
user time: 0.000000
sys time: 1.070000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 262144, loop = 1970
real time: 12.570000
user time: 0.000000
sys time: 0.910000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 131072, loop = 3941
real time: 11.530000
user time: 0.010000
sys time: 0.740000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 65536, loop = 7882
real time: 9.700000
user time: 0.010000
sys time: 0.790000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 32768, loop = 15764
real time: 8.320000
user time: 0.000000
sys time: 0.700000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 16384, loop = 31529
real time: 10.860000
user time: 0.020000
sys time: 0.800000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 8192, loop = 63059
real time: 13.040000
user time: 0.020000
sys time: 0.930000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 4096, loop = 126118
real time: 10.390000
user time: 0.050000
sys time: 1.050000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 2048, loop = 252237
real time: 16.830000
user time: 0.100000
sys time: 1.490000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 1024, loop = 504474
real time: 34.600000
user time: 0.320000
sys time: 5.280000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 512, loop = 1008948
real time: 24.540000
user time: 0.410000
sys time: 3.280000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 256, loop = 2017897
real time: 30.740000
user time: 0.650000
sys time: 5.560000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 128, loop = 4035795
real time: 27.330000
user time: 1.290000
sys time: 9.530000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 64, loop = 8071590
real time: 29.070000
user time: 2.230000
sys time: 16.970000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 32, loop = 16143180
real time: 43.730000
user time: 4.260000
sys time: 31.630000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 16, loop = 32286360
real time: 72.980000
user time: 8.350000
sys time: 62.530000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 8, loop = 64572720
real time: 141.430000
user time: 16.640000
sys time: 124.310000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 4, loop = 129145440
real time: 280.300000
user time: 32.960000
sys time: 245.860000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 2, loop = 258290880
real time: 553.320000
user time: 65.580000
sys time: 485.670000
++++++++++++++++++++++++
data_sum = 516581760, buf_size = 1, loop = 516581760
real time: 1108.320000
user time: 130.450000
sys time: 976.340000
最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 文件描述符 所有打開的文件都通過文件描述符引用。操作(讀寫)該文件描述符就相當(dāng)于操作該文件。文件描述符是一個(gè)非負(fù)的...
    Myth52125閱讀 647評(píng)論 0 0
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,715評(píng)論 0 5
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡(jiǎn)單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 8,133評(píng)論 0 27
  • 93年2月出生,今天的我二十幾歲。小時(shí)候家里窮,下雨天穿著漏水的鞋去學(xué)校,坐在教室里心思全在泡在濕鞋里的腳上,我有...
    三點(diǎn)三十二閱讀 256評(píng)論 0 0
  • 會(huì)不會(huì)是因?yàn)槲覜]有告訴他們,要給我燒什么面額的? 所以,他們要么沒給我燒,要么給我燒的面額都太大了,而且?guī)欧N在陰間...
    李爹閱讀 558評(píng)論 7 2

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