编程语言研究书库

文件 I/O 基础 — open/read/write/close 与一次完整的性能实验

文件 I/O 基础 — open/read/write/close 与一次完整的性能实验

这一章讲什么: 系统程序最基础的动作——读写文件——在 Unix 里只用 四个系统调用:open()、read()、write()、close()。它们对磁盘文件、 终端、打印机一视同仁,这就是 Unix 的「万能 I/O」。本章把四个调用 每个讲透,配上仿写 cp 的完整项目;然后用一组实测回答一个真问题: 拷贝程序的缓冲区到底开多大? 答案藏在系统调用开销和内核缓冲的 博弈里。

1. 顶层全景:三个对象,四个调用

Unix I/O 的全部机制可以压成一张图:

  进程的文件描述符表          全系统的打开文件表            每个文件系统的
  (每个进程一张)              (file structure table)        活动 inode 表
  ┌───┬─────┐               ┌───┬────────────┐
  │ 0 │ ────┼──────────────►│FS0│ 文件偏移    │──► inode ──► 磁盘上的文件
  │ 1 │ ────┼──► (stdout)   │FS1│ 标志/模式   │
  │ 2 │ ────┼──► (stderr)   │FS2│ inode 指针  │
  │ 3 │ ────┼──────────────►│FS3│ 引用计数    │
  └───┴─────┘               └───┴────────────┘
       打开:在表里新建一个 FS 项,返回下标 = 文件描述符
       读/写:按 fd 找到 FS 项 → 按 inode 指针找数据 → 移动偏移
       关闭:删掉描述符项;FS 项引用计数归零才真正释放

图说:文件描述符只是进程私有表里的下标;真正的连接状态(偏移、标志) 在内核的全局打开文件表里;inode 才把连接落到磁盘上的文件1。 这套结构解释了本章大部分「为什么」:为什么同一文件能被多个进程同时开 (各自一条 FS 项)、为什么两次 open 互不影响偏移、为什么 close 删的只是 连接、以及第 13 章 dup 是怎么让两个描述符共享一个偏移的。

三个术语钉死:连接 = 打开文件描述(open file description, OFD); 引用 = 文件描述符(file descriptor, 一个小的非负整数); 访问模式 = 只读 / 只写 / 读写(存在 OFD 里)2。

主线例子:spl_cp1——仿写 cp 命令。一个具体输入:把一个 57,569,256 字节 的文件拷到新文件,缓冲区 4096 字节。每一步的状态变化都会标出来。

2. 先补两门前置课:umask 与进程的身份证

umask:创建文件时的权限过滤器

第 1 章说过权限,这里是它在创建时刻的机制。每个进程有一个文件创建 掩码,9 位,通称 umask:某位为 1,表示「创建时把对应的权限位关掉」。 应用方式是按位与: 实际权限 = mode & ~umask3。走一遍原书的例子:

  进程想要 mode  = 110 110 010   (rwxrw-r-x 的位表示)
  umask          = 000 010 010   (八进制 022)
  ~umask         = 111 101 101
  ────────────────────────────
  mode & ~umask  = 110 100 000   →  rw-r----- …即用户读写、组只读、其他人没有

图说:022 是常见默认值——「别人可读但不可写」4。想更狠一点用 066: 其他人的读写全关。不想记补码,可以记「减法」形式:umask 里有的位就从 mode 里抠掉(0-1=0)5。登录时 shell 拿到默认 umask(常是 022),进程从 shell 继承、再传给它的子进程;umask 命令查看(-S 看符号形式), umask 033 修改,写进 .bashrc 永久生效6。

进程的四个用户 ID 与 setuid

权限检查要回答「你是谁」——但「你」不止一个 ID。Linux 上每个进程有 四个用户 ID:真实用户 ID(real)、有效用户 ID(effective)、 保存的 set-user-ID(saved)、文件系统用户 ID(filesystem,Linux 特有)。 平时四者相等;内核做权限判断用的是有效用户 ID(Linux 理论上用 filesystem UID,但它恒等于 effective UID,结果一样)7。

什么时候不等?文件模式里有个 setuid 位(12 个模式位中的最高位)。 一个可执行文件设了 setuid 位,别人运行它时,进程的有效 UID 变成文件 所有者的 UID,真实 UID 仍是运行者8。教科书案例是 passwd:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 Nov 24 07:05 passwd        ← 用户位是 s
$ ps -o euid,ruid,pid,args -C passwd
 EUID RUID      PID COMMAND
    0   500 14561 passwd                          ← 有效 0( root ),真实 500(你)

图说:普通用户凭什么改 /etc/shadow?凭的是「此刻我体内住着 root 的 有效身份」;而 saved set-user-ID 存着这个有效身份,供程序临时降权再 升回来时用9。「程序修改自己有效 UID」这类程序就叫 setuid 程序—— 安全史上漏洞最多的程序类别,第 11 章会再遇见。

判断(我们的,不是书里的): 「有效 UID 才是办事的身份证」这条, 是整个 Unix 权限模型里最常被半懂的人误解的一点——很多人以为文件 「属于谁」由真实 UID 决定。记住口令:open() 创建文件时,文件所有者 记的是有效 UID(man 页原文),不是真实 UID。setuid 程序创建的文件的 归属,就是这个规则的直接推论。 如果错,会错在: Linux 特有的 filesystem UID 在极少数场景 (NFS、显式 setfsuid)下与 effective UID 短暂分离;那时「按 effective 判断」这句话在文件访问上就不精确——但日常系统编程几乎碰不到。

3. open():连接的诞生

找函数:apropos 的筛选

老流程:apropos -s2 open 列出候选——creat、open、openat、 openat2。后两个是 open 的扩展版(openat 相对目录打开;openat2 更灵活 但没有 glibc 包装、Linux 独有,本书不用);creat() 本质是 open 的 特例。结论:研究 open()10。一个细节:这几个系统调用声明在 <fcntl.h> 而不是 <unistd.h>(第 2 章说过系统调用声明「几乎」都在 unistd.h——这是例外之一)11。

flags:一扇大门上的三组开关

int open(const char *pathname, int flags, ... /* mode_t mode */);

第二个参数是位掩码,必须含三个访问模式之一:O_RDONLY / O_WRONLY / O_RDWR;再按需或上创建标志12。创建相关的三个13:

标志作用组合后的行为
O_CREAT不存在就创建只加它:已存在则从开头覆盖、尾部保留(想不到吧)
O_TRUNC存在则截断成 0 长O_WRONLY|O_CREAT|O_TRUNC = 覆盖或新建(最常见的「写文件」组合)
O_EXCL与 O_CREAT 同用O_WRONLY|O_CREAT|O_EXCL = 新建,已存在则报错(锁文件/临时文件的套路)

图说:三种组合对应三种意图——覆盖写、原子新建、追加(追加没有标志, 要到第 11 章用 O_APPEND)。 还有反面教材:O_RDWR|O_CREAT|O_TRUNC 会把文件清空再读写——「打开来读,结果内容没了」,务必确认真想要14。

第三个参数 mode 只在创建时生效,用 <sys/stat.h> 的符号常量按位或 (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH = rw-r--r--)15。两条纪律: 传了 O_CREAT 就必须传 mode(不传则权限不可预测);最终权限还要过 umask 这一关16。失败返回 -1,errno 说原因(不存在、没权限、目录没写 权限、O_EXCL 撞车……),处理模板就是 fatal_error(errno, ...)17。

4. close():断开连接,以及为什么 write 成功不等于落盘

close(fd) 关的是描述符;只有它是最后一个指向该 OFD 的引用时, OFD 才释放18。man 页里还有一句重物:若 fd 是对一个已被 unlink 的 文件的最后一个引用,文件就被删除——第 7 章讲链接时会兑现。

close 也会失败,原因值得枚举19:

  • EBADF:fd 本身非法;
  • EINTR:内核在 close 执行中被急事打断;POSIX 不规定此时 fd 到底 关没关——有的实现关了,有的没关要重试,而重试在多线程程序里有风险; man 页建议:失败后不要重试 close();
  • EIO/ENOSPC/EDQUOT:磁盘满了、配额超了——对写打开的文件, 有些错误要到 close 才冒头(数据其实还在内核缓冲里没写下去)。

于是有了「认真版」收尾:fsync() 先把缓冲区的数据强刷到设备, 错误在 close 之前暴露;再 close。fsync 只对磁盘文件有效(否则 EINVAL, 忽略它)20。这正是主线例子里 spl_cp1 对目标文件做的:fsync(target) → close(target)21。write 成功的瞬间,数据多半只是进了内核缓冲 (buffer cache),何时落盘由内核决定——这是全章最重要的「隐性契约」, 性能实验和 close 错误都源于此22。

5. read() 与 write():搬运字节,以及「短读不是错」

read():可能比你要的少

ssize_t read(int fd, void *buf, size_t nbytes);   /* <unistd.h> */

从 fd 当前偏移起,至多读 nbytes 字节到 buf,返回实际读到的字节数; 返回 0 = 读到文件尾;返回 -1 = 错,errno 有细节23。三条要点:

  1. 返回值可能小于请求值,而且这不叫错误。 文件只剩 256 字节时,三次 read(100) 依次返回 100、100、56(再读返 0)。如果以为「要 100 就得 100」, 拷贝程序会静默丢数据。
  2. 每次 read 从「上次结束的地方」接着读——OFD 里的文件偏移自动前进, 这是顺序遍历文件的全部机制。
  3. buf 只是临时货仓:下次调用会覆盖它,数据要用就得赶紧搬走。

标准读循环(主线例子的核心):

while ( (num_bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0 ) {
    /* buffer[0 .. num_bytes_read-1] 是本次数据,立刻处理(比如 write 出去) */
}
if ( num_bytes_read == -1 )
    fatal_error(errno, "error reading");

write():对称,也有「短写」

ssize_t write(int fd, const void *buf, size_t nbytes);

成功返回实际写出的字节数;不为 -1 不代表写全了——短写可能由文件到顶、 磁盘满、配额尽引起。认真的写法是三个分支都处理: -1 报错、 返回值 < nbytes 报「部分写」、全等才算过24。

标准文件描述符:0、1、2

进程从 shell 出生就带三个打开的连接:0 = 标准输入(stdin,键盘)、 1 = 标准输出(stdout,屏幕)、2 = 标准错误(stderr,屏幕); 程序里用符号常量 STDIN_FILENO / STDOUT_FILENO / STDERR_FILENO (定义在 unistd.h)而不是裸数字25。进程终止时它们自动关闭——这就是 你从没显式关过 stdout 的原因26。它们只是「预先打开的三个 fd」, 完全可以当文件用:write(1, ...) 就是打印,read(0, ...) 就是读键盘, 重定向(第 18 章)就是把它们改指别处。

6. 主线例子走查:spl_cp1 拷一个 57,569,256 字节的文件

完整程序的逻辑骨架(错误处理从简,只留结构):

#define BUFFER_SIZE 4096
#define PERMISSIONS S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH   /* rw-rw-r-- */

if ( argc != 3 ) usage_error("spl_cp1 source destination");

source_fd = open(argv[1], O_RDONLY);                          /* ① 开源:只读 */
target_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, PERMISSIONS);
                                                              /* ② 开目标:覆盖或新建 */
while ( (num_bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0 ) {
    num_bytes_written = write(target_fd, buffer, num_bytes_read);
    if ( errno != 0 ) fatal_error(errno, "copy");             /* ③ 循环:4096 字节一推 */
    if ( num_bytes_written != num_bytes_read )
        fatal_error(-1, "write error to target");             /*    部分写也要抓 */
}
if ( num_bytes_read == -1 ) fatal_error(errno, "error reading");

close(source_fd);
fsync(target_fd);                                             /* ④ 认真收尾 */
close(target_fd);

图说:57,569,256 ÷ 4096 = 14,055 次循环,每次「满读→整写」, 最后余 2,952 字节一次短读短写。 缓冲区大小用 #ifndef BUFFER_SIZE 包着,编译时用 -DBUFFER_SIZE=8192 可以在不改源码的情况下做实验27。

测试:正确性怎么证明

  • 空文件:du -b 显示 0 字节,拷完还是 0——走一遍,边界不炸。
  • 中等文件(约 55MB):拷完用 cmp 比——cmp 比 diff 强,二进制文件 也比得出,无输出即全同28。
  • 超大文件 + 写错误:把 13,129,809,920 字节的虚拟机镜像往只有 约 8GB 的盘上拷,程序报 write error to /temp/largefile—— errno==0 但返回值不足的「部分写」分支被命中;再补一次 write 就会拿到 ENOSPC("No space left on device")29。

万能 I/O 的实证:同一个程序拷终端

同一个 spl_cp1 二进制,零修改:

$ ./spl_cp1 testfile /dev/tty        → 文件内容出现在终端上(/dev/tty 就是终端设备文件)
$ ./spl_cp1 /dev/tty newfile         → 你敲的键盘内容存进 newfile(CTRL-D 结束)
$ ./spl_cp1 /dev/tty /dev/pts/2      → 窗口 1 输入,出现在窗口 2

图说:「系统调用对所有文件一视同仁」不是口号,是 spl_cp1 三个不同 运行方式证明的事实30。read 从终端返回 0 的唯一条件是 CTRL-D—— 终端没有「文件尾」,只有「这次输入到此为止」。

7. 性能实验一:三种时间和系统调用的真实开销

time 命令:real / user / sys

/usr/bin/time -p 命令(注意全路径——bash 有自己的 time 内建,行为 不同)输出三个数31:

值含义
real墙上时钟:从启动到结束的总秒数
user用户态 CPU 时间:你的代码跑的时长
sys内核态 CPU 时间:替你干活时内核跑的时长

real ≥ user + sys,差值是「不在 CPU 上的时间」:等 I/O(进程被 阻塞,block——没拿到必需资源就不许上 CPU)和在就绪队列里排队等 CPU。 实测拷 116MB:spl_cp1 用 real 0.73s(user 0.02 + sys 0.14), 0.57 秒不在 CPU 上——I/O 等待占大头32。

系统调用开销:28 倍的价格

两个微基准,各跑 1 亿次调用(Linux 5.15 内核):调 uname()(小而快的 系统调用)用 42.25 秒;调 rand()(等价的库函数,内部只是查表算数) 用 1.47 秒——差约 28 倍,就是 trap、搬参数、查向量表这些「进出内核」 步骤的价格33。推论:系统调用次数是 I/O 程序的关键性能变量。

8. 性能实验二:缓冲区多大合适(本章的高潮)

实验设计要控制一个大干扰:内核缓冲(buffer cache)。read 不是直接从 磁盘搬进你的内存,而是「磁盘 → 内核的 buffer cache → 你的地址空间」; write 同理先落 cache。好处:重复读同一文件第二次全走内存;坏处:你不 清空 cache,多次测量的就不是同一件事。所以测试脚本每次跑之前 umount /temp 再 mount /temp,强行清空缓冲34。

17 档缓冲区、每档跑 5 次取平均,结果(拷贝同一文件的总时间):

缓冲区real(s)sys(s)缓冲区real(s)sys(s)
1 B214.2155.61,0240.820.25
16 B14.09.84,0960.640.13
128 B2.271.348,1920.640.11
512 B1.010.4065,5360.610.11
256 B1.470.72———

图说:小缓冲区段,翻倍缓冲区≈减半系统时间(系统调用数减半); 4KB 之后曲线变平,因为总搬运时间成了地板35。机理分两层:

  • 系统调用有固定开销(实验一),调用次数 = 文件大小 ÷ 缓冲区,所以小 缓冲区时 sys 时间随缓冲区翻倍而对半砍;
  • 但总字节数不变,总搬运时间不变——把开销摊薄到零,也快不过搬运 本身。这就是收益递减;缓冲区超过文件大小纯属浪费(内核还得白配内存)36。

对照行业水平:GNU coreutils 的 cp(9.6 版)用 128KB(运行时由 io_blksize() 按块设备选),比我们随手写的 4096 大 32 倍37。 我们自己的程序可以自信地选 4KB–8KB,除非有证据需要更大。

9. 作者的判断与证据

有实验数据撑着的(本章的强项):

  • 系统调用比库函数贵约 28 倍(1 亿次对照)33;
  • 缓冲区-时间曲线全表,含清空 cache 的对照手法3435;
  • time() vs clock_gettime() 式的选型思路延续——实测而非迷信文档。

作者的判断:

  • 「spl_cp1 的正确性靠空/中/超大文件 + cmp 抽样「高度确信」」—— 这是工程上的实用主义:穷举不可能,分层测试可以把风险压到可接受28。
  • 「write 部分写后可以直接报错退出,也可以重写剩余」——两种处置都给了, 选哪种留给场景24。

10. 边界与局限

  • O_APPEND、文件锁、非阻塞 I/O 这些 flags 本章故意没讲(散在第 11、 12、17 章)。
  • 缓冲区实验的文件系统结论只对被测系统负责;别的文件系统、别的内核 版本的拐点可能不同——方法(清 cache、多次取平均)比数字值钱36。
  • close 的 EINTR 可移植处理被如实标为「难」,给了 Linux 上的简化版, 跨平台要自己再查19。
  • spl_cp1 不保留时间戳等属性(那是 cp -p 的领域,第 6 章的 stat 才能做)。

11. 可带走的

主线例子,一行复述: 开源头(O_RDONLY)→ 开目标(O_WRONLY|O_CREAT| O_TRUNC, mode=rw-rw-r-- 过 umask)→ while((n=read(src,buf,4096))>0) write(dst,buf,n) 并查部分写 → fsync + close 目标。

  1. Unix I/O = open/read/write/close 四调用,对一切非目录文件(含设备)通用。
  2. fd 是进程描述符表的下标;OFD 在内核全局表里,存偏移;inode 指向磁盘文件。
  3. read/write 都可能「短」——返回 ≠ 请求不是错误,是必须处理的常态。
  4. O_CREAT 必须配 mode;组合意图记三种:覆盖写(TRUNC)、原子新建(EXCL)、 追加(没有标志,第 11 章 O_APPEND)。
  5. umask = 创建时的权限过滤器(mode & ~umask);022 常见,066 更严。
  6. 权限按「有效 UID」判;setuid 位让进程的有效身份换主人(passwd 模式)。
  7. write 成功 ≠ 落盘;认真收尾 = fsync() 再 close(),close 失败别重试。
  8. 0/1/2 是 stdin/stdout/stderr,进程出生自带、自动关闭,可当普通文件用。
  9. 系统调用比库函数贵约 28 倍;I/O 循环的缓冲区 ≥ 4KB,coreutils 用 128KB。
  10. 测 I/O 性能前 umount/mount 清 buffer cache,否则测的是内存速度。
  11. time -p 的 real − (user+sys) = 阻塞等待 + 排队时间。

12. 原文地图

主题原书章原文位置
umask 位运算与传播Chapter 4: Basic Concepts of File I/Otext/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:93(搜「bitwise」) · :104(搜「common value」) · :166(搜「from the shell」)
四个用户 ID、setuid、passwd同上:182(搜「four user IDs」) · :204(搜「setuid bit」) · :223(搜「-rwsr-xr-x」) · :236(搜「EUID RUID」)
OFD、fd、I/O 三步同上:257(搜「open file description」) · :339(搜「three steps」)
三张内核表结构同上:288(搜「User space」) · :318(搜「file structure table」)
标准描述符 0/1/2同上:345(搜「inherits three」) · :354(搜「STDIN_FILENO」)
open 研究与 flags同上:387(搜「apropos -s2 open」) · :422(搜「these are system calls」) · :442(搜「bit mask」) · :490(搜「O_CREAT」) · :512(搜「O_TRUNC」) · :520(搜「O_EXCL」)
mode 常量与创建规则同上:563(搜「unpredictable」) · :571(搜「S_IRWXU」)
close 错误与 fsync同上:630(搜「close a file descriptor」) · :689(搜「should not retry」) · :694(搜「fsync」)
read 语义与读循环同上:779(搜「ssize_t read」) · :802(搜「Figure 4-2」) · :845(搜「done = FALSE」) · :893(搜「can't assume」)
write 与短写同上:930(搜「ssize_t write」) · :965(搜「predefined maximum」) · :989(搜「kernel buffers」)
spl_cp1 完整程序同上:1127(搜「BUFFER_SIZE 4096」) · :1182(搜「Flush data to device」)
测试策略与 ENOSPC同上:1248(搜「cmp」) · :1283(搜「13,129,809,920」)
万能 I/O 演示同上:1309(搜「/dev/tty」) · :1345(搜「/dev/pts/2」)
time 命令三时间同上:1375(搜「time -p」) · :1417(搜「blocked」)
系统调用开销实验同上:1480(搜「42.25」) · :1485(搜「1.47」)
buffer cache 与清缓存同上:1508(搜「storage area」) · :1553(搜「unmount」)
缓冲区实验数据同上:1582(搜「214.242」) · :1594(搜「4,096」) · :1611(搜「diminishing returns」)
coreutils 的 128KB同上:1625(搜「io_blksize」)

Footnotes

  1. 出处:「Chapter 4: Basic Concepts of File I/O」第 248–343 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:288,搜「User space」) · :318(搜「file structure table」)。图是原书 Figure 4-1 的重绘。 ↩

  2. 出处:「Chapter 4: Basic Concepts of File I/O」第 248–269 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:257,搜「open file description」)。 ↩

  3. 出处:「Chapter 4: Basic Concepts of File I/O」第 92–94 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:93,搜「bitwise」)。 ↩

  4. 出处:「Chapter 4: Basic Concepts of File I/O」第 95–105 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:104,搜「common value」)。 ↩

  5. 出处:「Chapter 4: Basic Concepts of File I/O」第 115–124 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:124,搜「filter」)。 ↩

  6. 出处:「Chapter 4: Basic Concepts of File I/O」第 126–172 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:143,搜「u=rwx」) · :166(搜「from the shell」)。 ↩

  7. 出处:「Chapter 4: Basic Concepts of File I/O」第 174–200 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:182,搜「four user IDs」)。 ↩

  8. 出处:「Chapter 4: Basic Concepts of File I/O」第 202–218 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:202,搜「setuid bit」)。 ↩

  9. 出处:「Chapter 4: Basic Concepts of File I/O」第 219–246 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:223,搜「-rwsr-xr-x」) · :236(搜「EUID RUID」)。 ↩

  10. 出处:「Chapter 4: Basic Concepts of File I/O」第 383–431 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:387,搜「apropos -s2 open」)。 ↩

  11. 出处:「Chapter 4: Basic Concepts of File I/O」第 412–427 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:424,搜「in fcntl.h」)。 ↩

  12. 出处:「Chapter 4: Basic Concepts of File I/O」第 433–458 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:442,搜「bit mask」)。 ↩

  13. 出处:「Chapter 4: Basic Concepts of File I/O」第 483–526 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:490,搜「O_CREAT」) · :512(搜「O_TRUNC」) · :520(搜「O_EXCL」)。 ↩

  14. 出处:「Chapter 4: Basic Concepts of File I/O」第 527–535 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1042,搜「truncates the file」)。 ↩

  15. 出处:「Chapter 4: Basic Concepts of File I/O」第 561–586 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:571,搜「S_IRWXU」)。 ↩

  16. 出处:「Chapter 4: Basic Concepts of File I/O」第 561–564 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:563,搜「unpredictable」)。 ↩

  17. 出处:「Chapter 4: Basic Concepts of File I/O」第 588–611 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:601,搜「fatal_error」)。 ↩

  18. 出处:「Chapter 4: Basic Concepts of File I/O」第 625–665 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:630,搜「close a file descriptor」)。 ↩

  19. 出处:「Chapter 4: Basic Concepts of File I/O」第 667–699 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:672,搜「EBADF」) · :686(搜「ENOSPC」) · :689(搜「should not retry」)。 ↩ ↩2

  20. 出处:「Chapter 4: Basic Concepts of File I/O」第 693–726 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:694,搜「fsync」)。 ↩

  21. 出处:「Chapter 4: Basic Concepts of File I/O」第 1124–1192 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1182,搜「Flush data to device」)。 ↩

  22. 出处:「Chapter 4: Basic Concepts of File I/O」第 987–991 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:989,搜「kernel buffers」)。 ↩

  23. 出处:「Chapter 4: Basic Concepts of File I/O」第 728–898 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:779,搜「ssize_t read」) · :894(搜「not an error」)。 ↩

  24. 出处:「Chapter 4: Basic Concepts of File I/O」第 960–985 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1290,搜「fewer than」)。 ↩ ↩2

  25. 出处:「Chapter 4: Basic Concepts of File I/O」第 344–356 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:348,搜「standard input」) · :354(搜「STDIN_FILENO」)。 ↩

  26. 出处:「Chapter 4: Basic Concepts of File I/O」第 364–366 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:364,搜「closed automati」)。 ↩

  27. 出处:「Chapter 4: Basic Concepts of File I/O」第 1117–1221 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1126,搜「#ifndef BUFFER_SIZE」) · :1198(搜「-DBUFFER_SIZE」)。 ↩

  28. 出处:「Chapter 4: Basic Concepts of File I/O」第 1224–1293 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1237,搜「identical copies」) · :1248(搜「cmp」)。 ↩ ↩2

  29. 出处:「Chapter 4: Basic Concepts of File I/O」第 1280–1293 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1283,搜「13,129,809,920」)。 ↩

  30. 出处:「Chapter 4: Basic Concepts of File I/O」第 1295–1353 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1309,搜「/dev/tty」) · :1345(搜「/dev/pts/2」)。 ↩

  31. 出处:「Chapter 4: Basic Concepts of File I/O」第 1355–1399 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1375,搜「time -p」)。 ↩

  32. 出处:「Chapter 4: Basic Concepts of File I/O」第 1400–1427 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1404,搜「chimera」) · :1423(搜「0.57」)。 ↩

  33. 出处:「Chapter 4: Basic Concepts of File I/O」第 1465–1488 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1480,搜「42.25」) · :1485(搜「1.47」)。 ↩ ↩2

  34. 出处:「Chapter 4: Basic Concepts of File I/O」第 1489–1556 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1515,搜「buffer cache」) · :1553(搜「unmount」)。 ↩ ↩2

  35. 出处:「Chapter 4: Basic Concepts of File I/O」第 1557–1621 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1582,搜「214.242」) · :1600(搜「roughly」)。 ↩ ↩2

  36. 出处:「Chapter 4: Basic Concepts of File I/O」第 1600–1621 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1611,搜「diminishing returns」) · :1619(搜「larger than the file」)。 ↩ ↩2

  37. 出处:「Chapter 4: Basic Concepts of File I/O」第 1622–1627 段(text/10-ch04-chapter-4-basic-concepts-of-file-i-o.txt:1625,搜「io_blksize」)。 ↩

On this page