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

实验环境介绍

  • 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

本章描述的函数被称为不带缓冲的I/O(将与第5章的标准I/O函数对照),不带缓冲的I/O函数不是ISO C的组成部分。是POSIX.1和Single Unix specification的组成部分。进一步讨论多个进程间如何共享文件。

文件描述符

当打开或者创建一个新文件时,内核向进程返回一个文件描述符,通过这个文件描述符对文件进行读写。

open和openat函数

  • open函数的几个参数(后面后面使用代码详细测试):
    [图片上传失败...(image-46bae1-1530629898456)]
  • 由open和openat返回的一定是最小的未使用的描述符数值。如:可以先关闭标准输出,然后使用标准输出(描述符为1)来打开另外一个文件,代码如下:
#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解决的问题:
    • 让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前目录工作目录。因为线程共享相同的当前工作目录,因此很难让同一进程的多个不同线程在同一时间工作在不同的目录中
    • 避免TOCTTOU错误:如果有两个基于文件的函数调用,其中第二个调用依赖于第一个调用的结果,两个调用不是原子操作,可能两个操作之间文件改变了。导致结果错误。比如一个进程打开一个目录中的文件,如果这个目录发生变化(卸载之类的),此时如果继续读写就会和预期不一致,他可能会创建一个新的文件来进行操作。
#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的第一种情况:都打开其他路径下的一个文件,使用绝对路径
    // 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的第一种情况:都打开其他路径下的一个文件,使用相对路径
    // 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的第一种情况:都打开其他路径下的一个文件,使用相对路径,但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,使用相对路径,但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函数

吐槽一哈,当时写这个函数的人,把这个单词写错了

  • 这个creat创建函数有局限性,所以还不如用open和openat函数

close函数

  • 关闭一个文件描述符会释放在该文件上的记录锁。进程终止时会自动关闭所有的文件

lseek函数

  • 如果文件描述符指向的是一个管道、fifo或者网络套接字,则不能设置偏移(是否能设置偏移在于文件描述符指向的文件是否能够被设置偏移),还得注意某些文件是支持负的偏移量的
  • 测试标注你输入能否设置偏移量
#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:
// 这个标准输入指向的是bash
[root@localhost part_3]# ./3_6 
cannot set STDIN_FILENO seek

// 这个标准输入指向的是/etc/passwd
[root@localhost part_3]# ./3_6  < /etc/passwd
set STDIN_FILENO seek OK

// 这个标准输入指向的是./open.tmp .一个普通的文本文件
[root@localhost part_3]# ./3_6  < ./open.tmp 
set STDIN_FILENO seek OK

// bash的管道作为标注输入
[root@localhost part_3]# cat /etc/passwd | ./3_6 
cannot set STDIN_FILENO seek

// 管道文件作为标准输入,运行程序后,往管道中输入一些数据
[root@localhost part_3]# ./3_6  < ./fifo.tmp 
[root@localhost part_3]# echo sdf > fifo.tmp
cannot set STDIN_FILENO seek
  • 设置偏移量大于文件长度,这样会创建一个空洞文件(第4章我们再进一步说明空洞文件)
#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函数

  • 有多重情况可使实际读到的字节数少于要求读的字节数
    • 读普通文件,在读到要求字节数之前已经达到了文件文件尾端。如:在到达文件尾端之前有30个字节,要求读100字节,则read返回30,下次在调用read时,他将返回0(文件尾端)
    • 测试代码如下:
#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

函数write

  • write的出错的常见原因是因为文件已经写满,就或者超过了一个给定进程的文件长度限制

I/O的效率

  • 以下代码从标准输入读,写到标准输出,但是这个BUFFSIZE如何选取,需要做实验来验证,apue给出了一个结果,如下图:
    [图片上传失败...(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);
}
  • 我将以以下代码做一个实验,运行之前先准备一个516581760 bytes大小的文件(注:这里咱不考虑gcc优化,文件中的数据不能保证随机)
比如使用:
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文件系统测试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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

  • 文件描述符 所有打开的文件都通过文件描述符引用。操作(读写)该文件描述符就相当于操作该文件。文件描述符是一个非负的...
    Myth52125阅读 589评论 0 0
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,380评论 0 5
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,790评论 0 27
  • 93年2月出生,今天的我二十几岁。小时候家里穷,下雨天穿着漏水的鞋去学校,坐在教室里心思全在泡在湿鞋里的脚上,我有...
    三点三十二阅读 215评论 0 0
  • 会不会是因为我没有告诉他们,要给我烧什么面额的? 所以,他们要么没给我烧,要么给我烧的面额都太大了,而且币种在阴间...
    李爹阅读 482评论 7 2