APUE读书笔记-03文件输入输出(1)

简介

我们以文件输入输出操作函数为开始,对UNIX系统进行讲解。文件输入输出函数包括:打开文件、读取文件、写文件等等。大多数UNIX系统上面的文件输入输出操作,都可以使用5个函数来完成: openreadwritelseek ,以及 close 。我们之后也会看到,对读写文件时候,设置不同的缓存大小会有什么效果。

本章所述的函数一般都被称作无I/O缓冲的函数,和我们第5章所讲述的标准I/O函数相对。非缓冲的意思就是说,每次 read 或者 write 会产生内核中的一次系统调用。这些非缓冲的I/O函数并不是 ISO C 的一个部分,但是却是 POSIX.1the Single UNIX Specification 的一个部分。

当我们讲到多进程之间的资源共享的时候,原子操作会变得非常重要。我们在讨论文件I/O以及 open 函数的参数的时候会对此进行讲述。这会就引出了多个进程之间如何共享文件,以及内核包含什么样的数据结构,这样的话题。讲述这些之后,我们会继续讲述 dupfcntlsyncfsyncioctl 函数。

译者注

原文参考

参考: APUE2/ch03lev1sec1.html

1、文件描述符号

内核用文件描述符来引用所有打开文件。文件描述符是一个非负整数。当打开已有文件或创建新文件时,内核就向进程返回一个文件描述符。当读、写一个文件时,用 opencreat 返回的文件描述符标识该文件,将其作为参数传送给 readwrite

按照惯例,UNIX shell使用文件描述符0表示进程的标准输入,文件描述符1表示标准输出,文件描述符2表示标准错误输出。按照这个惯例,在 POSIX.1 应用程序中,幻数0、1、2应被代换成符号常数 STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO ,这样能够提高程序的可移植特性。这些常数都定义在头文件 <unistd.h> 中。另外,文件描述符的范围是 0~OPEN_MAX (表示一个进程最多可以打开的文件数目),具体取值依据系统有所不同。

后面将对基本文件操作函数做简单介绍,具体可以运行 man 2 <functionname> 参见我们自己系统上的用户手册。

译者注

原文参考

参考: APUE2/ch03lev1sec2.html

2、 open 函数

调用 open 函数可以打开或创建一个文件。其具体声明如下:

#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode   */ );

返回:如果成功返回文件描述符号,如果错误返回1。

(注意,这里出错的时候返回值为1,经过网上搜索发现,一般错误的时候,返回-1,也就是说,前面说的“如果错误返回1”中的“1”其实是布尔值,其实际值一般为-1,并且产生错误的时候,还会将错误编码记录到 errno 全局变量中。本书好多地方都这样说返回值,虽然简洁,但是可能会带来一些困扰所以读者应当注意,具体需要细究的时候,还需亲自查找手册上对错误情况返回值的说明)

这里,第三个参数写为 ... ,这是 ANSI C 表示余下参数的数目和类型可以变化的方法。 pathname 是要打开或创建的文件的名字。 oflag 参数可用来说明此函数的多个选择项。

oflag 必须指定如下三个值之一:

  • O_RDONLY 只读打开。
  • O_WRONLY 只写打开。
  • O_RDWR 读、写打开。

下列常数则是可选择的:

  • O_APPEND 每次写时都加到文件的尾端。
  • O_CREAT 若此文件不存在则创建它。使用此选择项时,需同时指明第三个参数 mode ,以说明该新文件的存取许可权位。
  • O_EXCL 如果同时指定了 O_CREAT ,而文件已经存在,那么就出错。这样可以测试文件是否存在,不存在则创建,并且这个操作是原子的。
  • O_TRUNC 如果此文件存在,而且为只读或只写方式打开,则将其长度截短为0。
  • O_NOCTTY 如果 pathname 指的是终端设备,则不将此设备分配作为此进程的控制终端。
  • O_NONBLOCK 如果 pathname 指的是一个 FIFO 、一个块特殊文件或一个字符特殊文件,则本次打开操作和后续的 I/O 操作设置非阻塞方式。
  • O_SYNC 使每次 write 都等到物理 I/O 操作完成。

open 返回的文件描述符一定是最小的没有使用的描述符数字。如果一个应用程序先关闭标准输出(通常是文件描述符1),然后打开另一个文件,那么就能知道该文件一定会在文件描述符1上打开,这个方法也是一个很常用的方法。后面讲到 dup2 函数时,会了解到有更好的方法来保证在一个给定的描述符上打开一个文件。

当用 append 标记打开文件的时候,还是可以用 lseek 定位到任何地方来读取文件内容的,但是如果写的话,文件的 offset 会自动地定位到文件的结尾并且写。也就是说,用 append 的话,写操作不会写到除了结尾之外的地方(即使是用 lseek 定位也不会,这里的内容最好也参考原书3章13节,以及练习3.6)。

译者注

原文参考

参考: APUE2/ch03lev1sec3.html

3、 create 函数

也可用 creat 函数创建一个新文件。声明如下:

#include <fcntl.h>
int creat(const char *pathname, mode_t mode);

返回:如果成功返回一个只写的文件描述符浩,如果错误返回1。

此函数等效于:

open(pathname,O_WRONLY|O_CREAT|O_TRUNC, mode)。

译者注

原文参考

参考: APUE2/ch03lev1sec4.html

4、 close 函数

close 函数关闭一个打开文件。声明如下:

#include <unistd.h>
int close(int filedes);

返回:如果成功返回0,如果错误返回1。

进程结束的时候内核会自动关闭它打开的所有文件;关闭文件的时候,会自动释放当前进程持有的所有在那个被关闭文件上面的锁。很多程序都使用这一功能而不显式地用 close 关闭打开的文件。

译者注

原文参考

参考: APUE2/ch03lev1sec5.html

5、 lseek 函数

每个被打开的文件都有一个与其相关联的“当前文件偏移量” 。其值为从文件开始到当前位置的字节数。通常,读、写操作都从当前文件偏移量开始,并每次读写之后,会相应地对移量进行增加。系统默认,打开一个文件时,偏移量为0(除非指定 O_APPEND 选项)。

我们可以通过函数 lseek 来设置文件偏移量,这个函数声明如下:

#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);

返回:如果成功返回新的文件偏移,如果错误返回1。

对参数 offset 的含义根据参数 whence 的值有不同的解释:

  • whenceSEEK_SET ,则 offset 是相对文件开始计算的偏移字节数。
  • whenceSEEK_CUR ,则 offset 是相对文件当前偏移值计算的偏移字节数(可正可负)。
  • whenceSEEK_END ,则 offset 是相对文件末尾开始计算的偏移字节数(可正可负)。

lseek 成功执行,则返回新的文件偏移量。如下方法可以确定一个打开文件的当前偏移量:

off_t    currpos;
currpos = lseek(fd, 0, SEEK_CUR);

这种方法也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或 FIFO ,则 lseek 返回-1,并将 errno 设置为 EPIPE

pipe , = FIFO= , = socket= 等这样不能 lseek 的文件当被 lseek 的时候,会返回1并且置 errnoESPIPE.lseek 返回值对于设备文件等特殊的文件可能为负数,对于正规文件非负,所以检测时候要小心。检测方法大致如下:

if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
        printf("cannot seek\n");
else
        printf("seek OK\n");

另外,原文在第4章12节中提到,当 lseek 的位置大于文件的大小,然后在写入数据,会扩展文件大小,并且在原来的结尾和 lseek 处创建文件“空洞”。文件空洞并不会消耗磁盘空间,查看文件空洞可以用 od 。具体还是参见上面的网址。后面会继续说道文件“空洞”。大致是 ls -lsizedusize 不是一样的,似乎前者包含了 hole 的,而 du 是不包含空洞的。 cat 就会把 hole 用空字符打印出来,所以一个包含了 hole 的文件 core ,用:

#cat core>core.copy

之后,再:

#du -s core.copy

根据显示的大小会发现比原来的大小(用 du -s core 看)大了。

译者注

原文参考

参考: APUE2/ch03lev1sec6.html

6、 read 函数

read 函数用来从打开的文件当中读取数据。声明如下:

#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);

返回:如果成功则返回读取的字节数目,如果遇到文件结尾则返回0,如果错误返回1。

read 读取,是从当前的文件偏移开始读取的。(对于返回1的时候如何确定是正确读取还是错误,经过网上搜索发现,一般错误的时候, read 返回 -1 ,具体情况前面讲述 open 函数的时候说明了对于返回值是如何处理的)

译者注

原文参考

参考: APUE2/ch03lev1sec7.html

7、 write 函数

write 函数用来想打开的文件写入数据。声明如下:

#include <unistd.h>
ssize_t write(int filedes, const void *buf, size_t nbytes);

返回:如果成功返回写入字节数目,如果错误返回1。(对于返回1如何确定是正确写入还是错误,经过网上搜索发现,一般错误的时候, write 返回 -1 ,具体情况前面讲述 open 函数的时候说明了对于返回值是如何处理的)

write 写入,是从当前的文件偏移开始写入的。

译者注

原文参考

参考: APUE2/ch03lev1sec8.html

8、 I/O 的效率

对于如下代码片断:

#include "apue.h"
#define BUFFSIZE 4096
int main(void)
{
    int    n;
    char   buf[BUFFSIZE];
    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
        if (write(STDOUT_FILENO, buf, n) != n)
            err_sys("write error");
    if (n < 0)
        err_sys("read error");
    exit(0);
}

这个代码的解释详细参见参考资料。对于这里的 readwrite 调用,其 BUFFSIZE 表示每次调用的时候,尝试读、写的字节数目。这个 BUFFSIZE 取不同的值,会导致 readwrite 调用次数的不同,一般来说, BUFFSIZE 取值越大,则调用次数越少,调用次数越少,则消耗的系统时间越小。但是当 BUFFSIZE 大到一定程度的时候,就不会对 I/O 效率有更多的改善了。下面给出的一个表格,对比了各种 BUFFSIZEI/O 效率的影响。

通过不同的缓存大小进行 read 所消耗的时间

+-------------------------------------------------------------------------------------------+
| BUFFSIZE | User CPU (seconds) | System CPU (seconds) | Clock time (seconds) |   #loops    |
|----------+--------------------+----------------------+----------------------+-------------|
|        1 |             124.89 |               161.65 |               288.64 | 103,316,352 |
|----------+--------------------+----------------------+----------------------+-------------|
|        2 |              63.10 |                80.96 |               145.81 | 51,658,#176 |
|----------+--------------------+----------------------+----------------------+-------------|
|        4 |              31.84 |                40.00 |                72.75 |  25,829,088 |
|----------+--------------------+----------------------+----------------------+-------------|
|        8 |              15.17 |                21.01 |                36.85 |  12,914,544 |
|----------+--------------------+----------------------+----------------------+-------------|
|       16 |               7.86 |                10.27 |                18.76 |   6,457,272 |
|----------+--------------------+----------------------+----------------------+-------------|
|       32 |               4.13 |                 5.01 |                 9.76 |   3,228,636 |
|----------+--------------------+----------------------+----------------------+-------------|
|       64 |               2.11 |                 2.48 |                 6.76 |   1,614,318 |
|----------+--------------------+----------------------+----------------------+-------------|
|      128 |               1.01 |                 1.27 |                 6.82 |     807,159 |
|----------+--------------------+----------------------+----------------------+-------------|
|      256 |               0.56 |                 0.62 |                 6.80 |     403,579 |
|----------+--------------------+----------------------+----------------------+-------------|
|      512 |               0.27 |                 0.41 |                 7.03 |     201,789 |
|----------+--------------------+----------------------+----------------------+-------------|
|    1,024 |               0.17 |                 0.23 |                 7.84 |     100,894 |
|----------+--------------------+----------------------+----------------------+-------------|
|    2,048 |               0.05 |                 0.19 |                 6.82 |      50,447 |
|----------+--------------------+----------------------+----------------------+-------------|
|    4,096 |               0.03 |                 0.16 |                 6.86 |      25,223 |
|----------+--------------------+----------------------+----------------------+-------------|
|    8,192 |               0.01 |                 0.18 |                 6.67 |      12,611 |
|----------+--------------------+----------------------+----------------------+-------------|
|   16,384 |               0.02 |                 0.18 |                 6.87 |       6,305 |
|----------+--------------------+----------------------+----------------------+-------------|
|   32,768 |               0.00 |                 0.16 |                 6.70 |       3,152 |
|----------+--------------------+----------------------+----------------------+-------------|
|   65,536 |               0.02 |                 0.19 |                 6.92 |       1,576 |
|----------+--------------------+----------------------+----------------------+-------------|
|  131,072 |               0.00 |                 0.16 |                 6.84 |         788 |
|----------+--------------------+----------------------+----------------------+-------------|
|  262,144 |               0.01 |                 0.25 |                 7.30 |         394 |
|----------+--------------------+----------------------+----------------------+-------------|
|  524,288 |               0.00 |                 0.22 |                 7.35 |         198 |
+-------------------------------------------------------------------------------------------+

(我们可以这样理解,使用 read 或者 write 等系统调用直接对文件操作,如果我们自己知道那个临界的 BUFFERSIZE ,那么就能够找到最优效率的参数来调用它;而不使用系统调用且使用标准库函数进行读写的话,标准库函数中自动设置了一个比较通用的 BUFFERSIZE ,不用要求我们自己设置大小了,这样可以得到较优的读写效率,这也是库函数和系统调用的一个不同)。

总之,直接使用系统调用的 readwrite 进行文件输入输出操作,没有自动指定缓存大小(需要手动设置每次读取的大小);而使用库函数的话就有缓存了,缓存的大小设置为和磁盘块大小一样最省时间。也就是说,一次 read 的数据如果是在磁盘块大小之内的话,时间是差不多的,所以最好把缓存设置为和磁盘块一样大小。

另外,一般读写文件的时候,操作系统会自动尝试把文件缓存到内核中,这样下次操作同样文件的时候会比较快一些,所以测试文件操作时间的时候使用不同的文件会比较准确。这也是 coredump 的来源。使用 sync 可以将缓存的数据刷新到磁盘上面。有许多类型的 sync ,有的只刷新文件数据,有的连文件属性也刷新了。

译者注

原文参考

参考: APUE2/ch03lev1sec9.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容

  • 所有执行I/O操作的系统调用都以文件描述符(一个非负整数)来指代打开的文件。包括pipe,FIFO,socket,...
    loopppp阅读 650评论 0 0
  • [转]C/C++ 文件读写操作总结 在编程的过程中,文件的操作是一个经常用到的问题,在C++Builder中,可以...
    天之道天知道阅读 5,197评论 0 7
  • 第三章 文件i/o 3.1引言 不带缓冲的io(unix系统在内核中设有缓冲区,这个不带缓冲意思是用户不自己缓冲)...
    m风满楼阅读 996评论 0 0
  • 最近在读unix环境高级编程,每天都会做做笔记! UNIX标准及实现 UNIX编程环境的标准化已经取得了很大进展。...
    MrTrans阅读 433评论 0 1
  • 相关链接:https://developer.apple.com/library/archive/document...
    ngugg阅读 395评论 0 0