深入理解计算机系统(CSAPP)
  • 本电子书信息
  • 出版信息
    • 出版者的话
    • 中文版序一
    • 中文版序二
    • 译者序
    • 前言
    • 关于作者
  • 第 1 章:计算机系统漫游
    • 1.1 信息就是位 + 上下文
    • 1.2 程序被其他程序翻译成不同的格式
    • 1.3 了解编译系统如何工作是大有益处的
    • 1.4 处理器读并解释储存在内存中的指令
    • 1.5 高速缓存至关重要
    • 1.6 存储设备形成层次结构
    • 1.7 操作系统管理硬件
    • 1.8 系统之间利用网络通信
    • 1.9 重要主题
    • 1.10 小结
  • 第一部分:程序结构和执行
    • 第 2 章:信息的表示和处理
      • 2.1 信息存储
      • 2.2 整数表示
      • 2.3 整数运算
      • 2.4 浮点数
      • 2.5 小结
      • 家庭作业
    • 第 3 章:程序的机器级表示
      • 3.1 历史观点
      • 3.2 程序编码
      • 3.3 数据格式
      • 3.4 访问信息
    • 第 4 章:处理器体系结构
    • 第 5 章:优化程序性能
    • 第 6 章:存储器层次结构
  • 第二部分:在系统上运行程序
    • 第 7 章:链接
      • 7.1 编译器驱动程序
      • 7.2 静态链接
      • 7.3 目标文件
      • 7.4 可重定位目标文件
      • 7.5 符号和符号表
      • 7.6 符号解析
      • 7.7 重定位
      • 7.8 可执行目标文件
      • 7.9 加载可执行目标文件
      • 7.10 动态链接共享库
      • 7.11 从应用程序中加载和链接共享库
      • 7.12 位置无关代码
      • 7.13 库打桩机制
      • 7.14 处理目标文件的工具
      • 7.15 小结
      • 家庭作业
    • 第 8 章:异常控制流
      • 8.1 异常
      • 8.2 进程
      • 8.3 系统调用错误处理
      • 8.4 进程控制
      • 8.5 信号
      • 8.6 非本地跳转
      • 8.7 操作进程的工具
      • 8.8 小结
      • 家庭作业
    • 第 9 章:虚拟内存
      • 9.1 物理和虚拟寻址
      • 9.2 地址空间
      • 9.3 虚拟内存作为缓存的工具
      • 9.4 虚拟内存作为内存管理的工具
      • 9.5 虚拟内存作为内存保护的工具
      • 9.6 地址翻译
      • 9.7 案例研究:Intel Core i7 / Linux 内存系统
      • 9.8 内存映射
      • 9.9 动态内存分配
      • 9.10 垃圾收集
      • 9.11 C 程序中常见的与内存有关的错误
      • 9.12 小结
      • 家庭作业
  • 第三部分:程序间的交互和通信
    • 第 10 章:系统级 I/O
      • 10.1 Unix I/O
      • 10.2 文件
      • 10.3 打开和关闭文件
      • 10.4 读和写文件
      • 10.5 用 RIO 包健壮地读写
      • 10.6 读取文件元数据
      • 10.7 读取目录内容
      • 10.8 共享文件
      • 10.9 I/O 重定向
      • 10.10 标准 I/O
      • 10.11 综合:我该使用哪些 I/O 函数?
      • 10.12 小结
      • 家庭作业
    • 第 11 章:网络编程
      • 11.1 客户端—服务器编程模型
      • 11.2 网络
      • 11.3 全球 IP 因特网
      • 11.4 套接字接口
      • 11.5 Web 服务器
      • 11.6 综合:TINY Web 服务器
      • 11.7 小结
      • 家庭作业
    • 第 12 章:并发编程
      • 12.1 基于进程的并发编程
      • 12.2 基于 I/O 多路复用的并发编程
      • 12.3 基于线程的并发编程
      • 12.4 多线程程序中的共享变量
      • 12.5 用信号量同步线程
      • 12.6 使用线程提高并行性
      • 12.7 其他并发问题
      • 12.8 小结
      • 家庭作业
  • 附录 A:错误处理
  • 参考文献
  • 实验
    • 实验总览
      • 常见问题
    • 实验 1:Data Lab
      • README(讲师版)
      • README(学生版)
      • Writeup
    • 实验 2:Bomb Lab
      • README(讲师版)
      • Writeup
    • 实验 3:Attack Lab
    • 实验 4:Architechture Lab
    • 实验 5:Cache Lab
    • 实验 6:Performance Lab
    • 实验 7:Shell Lab
    • 实验 8:Malloc Lab
    • 实验 9:Proxy Lab
由 GitBook 提供支持
在本页
  1. 第三部分:程序间的交互和通信
  2. 第 10 章:系统级 I/O

10.4 读和写文件

应用程序是通过分别调用 read 和 write 函数来执行输入和输出的。

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t n);
// 返回:若成功则为读的字节数,若 EOF 则为0,若出错为 -1。

ssize_t write(int fd, const void *buf, size_t n);
// 返回:若成功则为写的字节数,若出错则为 -1。

read 函数从描述符为 fd 的当前文件位置复制最多 n 个字节到内存位置 buf。返回值 -1 表示一个错误,而返回值 0 表示 EOF。否则,返回值表示的是实际传送的字节数量。

write 函数从内存位置 buf 复制至多 n 个字节到描述符 fd 的当前文件位置。图 10-3 展示了一个程序使用 read 和 write 调用一次一个字节地从标准输入复制到标准输出。

#include "csapp.h"

int main(void)
{
    char c;

    while(Read(STDIN_FILENO, &c, 1) != 0)
        Write(STDOUT_FILENO, &c, 1);
    exit(0);
}

图 10-3 一次一个字节地从标准输入复制到标准输出

通过调用 lseek 函数,应用程序能够显示地修改当前文件的位置,这部分内容不在我们的讲述范围之内。

旁注 - ssize_t 和 size_t 有些什么区别?

你可能已经注意到了,read 函数有一个 size_t 的输入参数和一个 ssize_t 的返回值。那么这两种类型之间有什么区别呢?在 x86-64 系统中,size_t 被定义为 unsigned long,而 ssize_t(有符号的大小)被定义为 long。read 函数返回一个有符号的大小,而不是一个无符号大小,这是因为出错时它必须返回 -1。有趣的是,返回一个 -1 的可能性使得 read 的最大值减小了一半。

在某些情况下,read 和 write 传送的字节比应用程序要求的要少。这些不足值(short count)不表示有错误。出现这样情况的原因有:

  • 读时遇到 EOF。假设我们准备读一个文件,该文件从当前文件位置开始只含有 20 多个字节,而我们以 50 个字节的片进行读取。这样一来,下一个 read 返回的不足值为 20,此后的 read 将通过返回不足值 0 来发出 EOF 信号。

  • 从终端读文本行。如果打开文件是与终端相关联的(如键盘和显示器),那么每个 read 函数将一次传送一个文本行,返回的不足值等于文本行的大小。

  • 读和写网络套接字(socket)。如果打开的文件对应于网络套接字(11.4 节),那么内部缓冲约束和较长的网络延迟会引起 read 和 write 返回不足值。对 Linux 管道(pipe)调用 read 和 write 时,也有可能出现不足值,这种进程间通信机制不在我们讨论的范围之内。

实际上,除了 EOF,当你在读磁盘文件时,将不会遇到不足值,而且在写磁盘文件时,也不会遇到不足值。然而,如果你想创建健壮的(可靠的)诸如 Web 服务器这样的网络应用,就必须通过反复调用 read 和 write 处理不足值,直到所有需要的字节都传送完毕。

上一页10.3 打开和关闭文件下一页10.5 用 RIO 包健壮地读写

最后更新于4年前