<section style="font-size: 16px;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;"><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">0. 学习原因</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">大多时候,高级别I/O函数工作良好,没必要直接用Unix I/O,为何需学习?</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>了解Unix I/O将帮助理解其他系统概念。I/O是系统操作不可或缺的部分,因此经常遇到I/O和其他系统概念之间的循环依赖</p></li><li><p>有时必须用Unix I/O,用高级I/O不太可能或不合适,如标准I/O库没提供读取文件元数据的方式,此外I/O库存在一些问题</p></li></ul><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">1. Unix I/O</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">输入/输出(I/O)是主存和外部设备之间复制数据的过程,在 Linux 中,文件就是字节的序列。所有的 I/O 设备(如网络、内核、磁盘和终端等)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备映射为文件的机制,允许内核引出简单、优雅的应用接口Unix I/O,使得所有输入和输出都以统一的方式执行,如 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">open()</code>/<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">close()</code> 打开/关闭文件,<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">read()</code>/ <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">write()</code> 读/写文件。<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">seek()</code>改变当前文件位置。Unix I/O主要分为两大类:</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img" data-ratio="0.3220910623946037" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbeNC6beVdfhK1lXVrCJicP9LVicKzrrbD50HMrMWDVOMzehdBmCA7Cuibw/640?wx_fmt=png" data-type="png" data-w="1186" style="display: block; margin-right: auto; margin-left: auto; width: 677px !important; height: auto !important; visibility: visible !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-dbaff84a3a0d5022.png" crossorigin="anonymous" alt="图片" data-fail="0"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">为区分不同文件的类型,会有一个 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">type</code> 来进行区别:</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>普通文件:包含任意数据</p></li><li><p>目录:相关文件组的索引</p></li><li><p>Socket:用于另一台机器上的进程通信</p></li></ul><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">还有一些特别的类型仅做了解:命名管道(FIFOs)、符号链接、字符和块设备</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>普通文件</strong></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">普通文件包含任意数据,应用程序通常需区分文本文件和二进制文件。前者只包含 ASCII 或 Unicode 字符。除此之外的都是二进制文件(对象文件, JPEG 图片, 等等)。内核不能区分出区别。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">文本文件是文本行的序列,每行以 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">\n</code> 结尾,新行是 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">0xa</code>,和 ASCII 码中LF一样。不同系统判断行结束的符号不同(End of line, EOL),Linux & Mac OS是<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">\n</code>(0xa)等价line feed(LF),而Windows & 网络协议是<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">\r\n</code> (0xd 0xa)等价Carriage return(CR) followed by line feed(LF)</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>目录</strong></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">目录包含一个链接(link)数组,且每个目录至少包含两记录:<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">.</code>(dot) 当前目录、<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">..</code>(dot dot) 上层目录</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">操作命令主要有 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">mkdir</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ls</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">rmdir</code>。目录以树状结构组织,根目录是 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">/</code>(slash)。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">内核会为每个进程保存当前工作目录(cwd, current working directory),可用 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">cd</code> 命令进行更改。通过路径名来确定文件的位置,分为绝对路径和相对路径。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.4576719576719577" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbbctdqeg0vSg2rDaJx3B72ic7JZ82Zn1ryelXgxJomLndmZZoXRWK7NA/640?wx_fmt=png" data-type="png" data-w="756" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 301.775px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-f28f575f5aec1cb2.png" crossorigin="anonymous" alt="图片"></p><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">2. 文件操作</h1><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.1 打开文件</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">打开文件会通知内核已准备好访问该文件</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">int fd; // 文件描述符 file descriptor
if ((fd = open("/etc/hosts", O_RDONLY)) < 0)
{
perror("open");
exit(1);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">返回值是一个小的整型称为文件描述符(file descriptor),若该值等于 -1 则说明发生错误。每个由 Linux shell创建的进程都会默认打开三个文件(注意这里的文件概念):</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>0: standard input(stdin)</p></li><li><p>1: standard output(stdout)</p></li><li><p>2: standar error(stderr)</p></li></ul><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.2 关闭文件</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">关闭文件会通知内核已完成对该文件的访问</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">int fd; // 文件描述符
int retval; // 返回值
int ((retval = close(fd)) < 0)
{
perror("close");
exit(1);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">关闭一个已经关闭的文件是线程程序中的灾难(稍后会详细介绍),所以一定要检查返回值,哪怕是看似良好的函数如 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">close()</code></p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.3 读取文件</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">读取文件将字节从当前文件位置复制到内存,然后更新文件位置</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">char buf[512];
int fd;
int nbytes
// 打开文件描述符,并从中读取 512 字节的数据
if ((nbytes = read(fd, buf, sizeof(buf))) < 0)
{
perror("read");
exit(1);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">返回值是读取的字节数量,是一个 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ssize_t</code> 类型(其实就是一个有符号整型),如果 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">nbytes < 0</code> 那么表示出错。<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">nbytes < sizeof(buf)</code> 这种情况(short counts) 是可能发生的,而且并不是错误。</p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.4 写入文件</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">写入文件将字节从内存复制到当前文件位置,然后更新当前文件位置</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">char buf[512];
int fd;
int nbytes;
// 打开文件描述符,并向其写入 512 字节的数据
if ((nbytes = write(fd, buf, sizeof(buf)) < 0)
{
perror("write");
exit(1);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">返回值是写入的字节数量,如果 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">nbytes < 0</code> 表示出错。<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">nbytes < sizeof(buf)</code> 这种情况(short counts) 是可能发生的,且不是错误。</p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.5 读取目录</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">可用readdir系列函数读取目录的内容,每次对readdir的调用返回的都是指向流dirp中下一个目录项的指针,或没有更多目录项则返回NULL,每个目录项都有结构体:</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">struct dirent{
ino_t d_ino; /* inode number /
char d_name[256]; / filename */
};</code></pre><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">2.6 简单Unix I/O 例子</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">拷贝文件到标准输出,一次一个字节:</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">#include "csapp.h"
int main(int argc, char *argv[])
{
char c;
int infd = STDIN_FILENO;
if (argc == 2) {
infd = Open(argv[1], O_RDONLY, 0);
}
while(Read(infd, &c, 1) != 0)
Write(STDOUT_FILENO, &c, 1);
exit(0);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">前面提到的 short count 会在下面的情形下发生:</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>读取的时遇到 EOF(end-of-file)</p></li><li><p>从终端中读取文本行</p></li><li><p>读和写网络 sockets</p></li></ul><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">但下面的情况下不会发生</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>从磁盘文件中读取(除 EOF 外)</p></li><li><p>写入到磁盘文件中</p></li></ul><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">最好总是允许 short count,这样就可以避免处理这么多不同的情况。</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">#include "csapp.h"
#define BUFSIZE 64
int main(int argc, char *argv[])
{
char buf[BUFSIZE];
int infd = STDIN_FILENO;
if (argc == 2) {
infd = Open(argv[1], O_RDONLY, 0);
}
while((nread = Read(infd, buf, BUFSIZE)) != 0)
Write(STDOUT_FILENO, buf, nread);
exit(0);
}</code></pre><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">3. 元数据</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">元数据是用来描述数据的数据,由内核维护,可以通过 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">stat</code> 和 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fstat</code> 函数来访问,结构是:</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">struct stat
{
dev_t st_dev; // Device
ino_t st_ino; // inode
mode_t st_mode; // Protection & file type
nlink_t st_nlink; // Number of hard links
uid_t st_uid; // User ID of owner
gid_t st_gid; // Group ID of owner
dev_t st_rdev; // Device type (if inode device)
off_t st_size; // Total size, in bytes
unsigned long st_blksize; // Blocksize for filesystem I/O
unsigned long st_blocks; // Number of blocks allocated
time_t st_atime; // Time of last access
time_t st_mtime; // Time of last modification
time_t st_ctime; // Time of last change
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">对应的访问例子:</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">int main (int argc, char **argv)
{
struct stat stat;
char *type, *readok;
Stat(argv[1], &stat);
if (S_ISREG(stat.st_mode)) // 确定文件类型
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) // 检查读权限
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}</code></pre><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">3.1 共享文件</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">可用许多不同的方式共享Linux文件。要理解文件共享就得理解三个表示打开文件的数据结构。</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">描述符表:每个进程都有独立的描述符表,表项是进程打开的文件描述符索引,每个打开的描述符表指向文件表中的一个表项</p></li><li><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">文件表:打开文件的集合是由一张文件表表示,所有进程共享该表,每个文件表的表项组成包括当前文件位置、引用计数、指向v-node表中对应表项的指针。关闭描述符减少文件表项引用计数直到为0会删除文件表项</p></li><li><p>v-node表:同文件表一样所有进程共享该v-node表,每个表包含stat结构中的大多数信息,包括st_mode和st_size成员</p></li></ul><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">两个描述符引用两个不同的打开文件。描述符 1(stdout)指向终端,描述符 4 指向打开的磁盘文件</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.456369982547993" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbLYkweBFIyHRsbZMMNWWaOSJiaF4j51MRVSVZnp4NEKkJNLF2x3c7x3Q/640?wx_fmt=png" data-type="png" data-w="1146" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 300.922px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-35945d647ebf78ff.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">两个不同的描述符通过两个不同的打开文件表条目共享同一个磁盘文件,使用相同的文件名参数调用 open 两次,关键思想是每个描述符都有自己的文件位置,所以对不同描述符的读操作可从文件不同位置获取数据</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.4589160839160839" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbEGXKNpngUm4ticjhcVrBbj0YJQFdfYGhuKeicOUJrRiaQuzXSicvXWBziaA/640?wx_fmt=png" data-type="png" data-w="1144" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 302.59px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-8277dd7fc61b20e5.png" crossorigin="anonymous" alt="图片"></p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">3.2 进程如何共享文件:<code>fork</code></h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">子进程继承其父进程的打开文件,注意:exec 函数不会改变情况(使用 fcntl 来改变)</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.5889628924833492" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHboFrFlsd7iagicCDnxFmmSZglUjtmaOqj8wxRo6lkibsAeMIr7mMGyh3Ow/640?wx_fmt=png" data-type="png" data-w="1051" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 387.771px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-bd4bc0f9c4d7fe7e.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在 fork 之后,子进程实际上和父进程的指向是一样的,这里需要注意的是会把引用计数加 1</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.6781609195402298" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbmO9KPNu0hNyEYVVHyIbXgx2e4QsD8cFe7HZyb4Mzv3yzaKMuMYiaviaw/640?wx_fmt=png" data-type="png" data-w="1044" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 446.195px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-246fd26f9ba8218b.png" crossorigin="anonymous" alt="图片"></p><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">4. I/O重定向</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">了解了这个,就知道重定向如何实现。其实很简单,只要调用 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">dup2(oldfd, newfd)</code>函数即可。改变文件描述符指向的文件,就完成重定向,下图中我们把原来指向终端的文件描述符指向了磁盘文件,也就把终端上的输出保存在了文件中:</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.6834817012858556" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbibCfzN6VWNK7dQacwAKGJhiaN0VpKBjXdR7kZDV3ar912Sib1Ay5o0YQg/640?wx_fmt=png" data-type="png" data-w="1011" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 449.681px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-386f08beb980335c.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">具体来说:</p><ol style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">打开标准输出应该重定向到的文件,发生在执行 shell 代码的子进程中,在 exec 之前</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.49295774647887325" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbGYKQXmvPBtuiaTf5pwpUHq0Kezy5D2CnDJ5edlibBfNVNsbQPib4So6Mg/640?wx_fmt=png" data-type="png" data-w="1065" style="display: block; margin-right: auto; margin-left: auto; width: 632px !important; height: 312.563px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-ebfa5f9d551ad5ea.png" crossorigin="anonymous" alt="图片"></p></li><li><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">调用<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">dup2(4,1)</code>,导致fd=1 (stdout) 引用 fd=4 指向的磁盘文件,注意引用计数的变化</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.4885215794306703" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbxQJZwupQzLeNsMHxZLFicDbMhg3BfMbEOLCBPrVPcIibyp54wxQMEwhw/640?wx_fmt=png" data-type="png" data-w="1089" style="display: block; margin-right: auto; margin-left: auto; width: 632px !important; height: 309.769px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-96172b2e47da69ae.png" crossorigin="anonymous" alt="图片"></p></li></ol><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">4.1 I/O重定向的例子</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.6266318537859008" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHb9S3UISw6uvumibx3mhQKH1gzicpAhicv6fOHW9UwXD50vGB8E41cmYO0Q/640?wx_fmt=png" data-type="png" data-w="1149" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 412.444px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-4c0e10ca1ff51bbf.png" crossorigin="anonymous" alt="图片"></p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">4.2 进程控制和I/O</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.7203463203463204" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHb7SVKTqQichHqeGd7iaTlVayB9mdmGCF3Dh6W49MOKiakX7OiaaNJrvAuCw/640?wx_fmt=png" data-type="png" data-w="1155" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 473.827px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-5055dda980af4293.png" crossorigin="anonymous" alt="图片"></p><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">5. 标准I/O</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">C标准库中包含一系列高层的标准 IO 函数,比如</p><ul style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"><li><p>打开和关闭文件: <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fopen</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fclose</code></p></li><li><p>读取和写入字节: <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fread</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fwrite</code></p></li><li><p>读取和写入行: <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fgets</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fputs</code></p></li><li><p>格式化读取和写入: <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fscanf</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fprintf</code></p></li></ul><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">标准 IO 会用流的形式打开文件,所谓流(stream)实际是文件描述符和缓冲区(buffer)在内存中的抽象。C 程序一般以三个流开始,如下所示:</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">#include <stdio.h>
extern FILE stdin; // 标准输入 descriptor 0
extern FILE stdout; // 标准输出 descriptor 1
extern FILE stderr; // 标准错误 descriptor 2
int main()
{
fprintf(stdout, "Hello, Da Wang\n");
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">用缓冲区的原因,程序经常会一次读/写一个字符,比如 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">getc</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">putc</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ungetc</code>,同时也会一次读/写一行,比如 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">gets</code>, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">fgets</code>。如果用 Unix I/O 的方式来进行调用是非常昂贵的, <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">read</code> 和 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">write</code> 因需内核调用,需大于10000 个时钟周期。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">办法是用 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">read</code> 一次读取一块数据,再由高层的用户输入函数,一次从缓冲区读取一个字符(当缓冲区用完的时候需要重新填充)</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.06766917293233082" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbEv1GBQhibH8VEvZIBLxUdYylJVZ6dYL952picdzYzo8gZLwzAyFHhsqQ/640?wx_fmt=png" data-type="png" data-w="931" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 46.3233px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-fb8365451924f586.png" crossorigin="anonymous" alt="图片"></p><h2 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 22px;">5.1 标准I/O中的缓冲</h2><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">标准I/O函数使用缓冲的I/O,缓冲区通过<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">\n</code>刷新到输出fd,调用fflush或exit,或从main返回</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.7766990291262136" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbRbCNeiaa0zKM5uMZXgySdlADiajU0jcmd3AKIKOBAG0e5lGaWEugBb3g/640?wx_fmt=png" data-type="png" data-w="618" style="display: block; margin-right: auto; margin-left: auto; width: 618px !important; height: 480.447px !important;" _width="618px" src="https://upload-images.jianshu.io/upload_images/7483640-e3f748858b8f1f68.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">可通过strace程序看缓冲如何起作用</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.4075091575091575" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbB6DbScHUWztGHTkHXm4Y39d4KJicalsa7YsktO7kFQFaVzBJbkyiaialg/640?wx_fmt=png" data-type="png" data-w="1092" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 268.918px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-b11df719537778fd.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="1.4473282442748092" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbVqaIKlViaSSP1E67nS8sHoQmiaTmmYF04Ylh2flY0jrzSgLtGQ7axCGQ/640?wx_fmt=png" data-type="png" data-w="655" style="display: block; margin-right: auto; margin-left: auto; width: 655px !important; height: 947.105px !important;" _width="655px" src="https://upload-images.jianshu.io/upload_images/7483640-7d5942efae44c5cc.png" crossorigin="anonymous" alt="图片"></p><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">6. RIO</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">RIO和C准库IO库是在Unix I/O上构建的两个不兼容的库,RIO提供了方便高效的IO访问,可以从一个描述符中读二进制非缓存输入和输出、缓存的文本行和二进制数据(线程安全可在同一描述符上任意交叉使用)。有两类输入输出函数:</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.3231552162849873" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbiakaeVTr1AicEFV0tRE30YbJxcqkNAZ1w6nIVR1WnC9aX6fYeiaL8BURA/640?wx_fmt=png" data-type="png" data-w="1179" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 213.667px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-0bafec69a4c6c194.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>① 无缓冲输入输出:和Unix的read/write接口相同,对网络传输数据尤其有用</strong></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">对于同一个描述表,可以任意的交错调用rio_readn和rio_wiriten</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">/
rio_readn - Robustly read n bytes (unbuffered)
/
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char bufp = usrbuf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) / Interrupted by sig handler return /
nread = 0;/ and call read() again /
else
return -1;/ errno set by read() /
}
else if (nread == 0)
break;/ EOF /
nleft -= nread;
bufp += nread;
}
return (n - nleft);/ Return >= 0 */
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">从上面的代码不难看出,如果程序的信号处理程序返回中断,这个函数会手动重启read或者write。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><strong>② 带缓冲的输入函数</strong></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这个函数有一个好处是,它从内部读缓冲区拷贝的一行,能有效地从部分缓存在内部内存缓冲区中的文件中读取文本行和二进制数据,当缓冲区为空的时候,自动调用read填满缓冲区,效率很高。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);</code>,rio_readlineb从文件fd中读取最多maxlen字节的文本行,并将其存储在usrbuf中,当读到maxlen字节、EOF发生、<code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">\n</code>发生时停止。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(255, 53, 2);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">ssize_t rio_readnb(rio_t *rp, void usrbuf, size_t n);</code>,rio_readnb从文件fd读取最多n个字节,当读到n个字节、EOF发生时停止,对rio_readlineb和rio_readnb的调用可以在同一个描述符上任意交错</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">缓存IO实现</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">读文件时,文件有关联的缓冲区来保存已经从文件中读取但还没有被用户代码读取的字节</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.43993231810490696" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbdHjhMJmZ0DbaVTs8sCibUv5c5kicK2GLKJGeuu9MxAz9yB731ZvhfFFA/640?wx_fmt=png" data-type="png" data-w="1182" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 290.156px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-711d5e0325f5b6dd.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">缓存IO声明</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">typedef struct {
int rio_fd;/ descriptor for this internal buf /
int rio_cnt;/ unread bytes in internal buf */
char rio_bufptr;/ next unread byte in internal buf /
char rio_buf[RIO_BUFSIZE]; / internal buffer */
} rio_t;</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.19401544401544402" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbksVbkbvObQwyXBa6LWVDrQz2NtsgibtbWXacAv89Tva4R6rebBc2vNQ/640?wx_fmt=png" data-type="png" data-w="1036" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 129.08px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-7ed82404416c3406.png" crossorigin="anonymous" alt="图片"></p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">#include "csapp.h"
#define MLINE 1024
int main(int argc, char *argv[])
{
rio_t rio;
char buf[MLINE];
int infd = STDIN_FILENO;
ssize_t nread = 0;
if (argc == 2) {
infd = Open(argv[1], O_RDONLY, 0);
}
Rio_readinitb(&rio, infd);
while((nread = Rio_readlineb(&rio, buf, MLINE)) != 0)
Rio_writen(STDOUT_FILENO, buf, nread);
exit(0);
}</code></pre><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">复制文件到标准输出,用mmap加载整个文件</p><pre style="margin-top: 10px;margin-bottom: 10px;"><code style="display: -webkit-box;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;padding: 15px;background-color: rgb(240, 240, 240);overflow-x: scroll;">#include "csapp.h"
int main(int argc, char **argv)
{
struct stat stat;
if (argc != 2) exit(1);
int infd = Open(argv[1], O_RDONLY, 0);
Fstat(infd, &stat);
size_t size = stat.st_size;
char *bufp = Mmap(NULL, size, PROT_READ,MAP_PRIVATE, infd, 0);
Write(1, bufp, size);
exit(0);
}</code></pre><h1 style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 24px;">7. 总结</h1><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">Unix I/O 是最底层的,通过系统调用进行操作,C的标准I/O库建立在此之上,对应的函数为:</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.31191885038038886" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbL6ibAvlYibIRwNPXMqkUHxQy7zMmRG7jyESJBOkr859TLr01wlKlCXCg/640?wx_fmt=png" data-type="png" data-w="1183" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 206.307px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-67a5a863b5eb3207.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">###</p><table><thead><tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"><th style="border-top-width: 1px;border-color: rgb(204, 204, 204);text-align: center;background-color: rgb(240, 240, 240);min-width: 85px;">I/O</th><th style="border-top-width: 1px;border-color: rgb(204, 204, 204);text-align: center;background-color: rgb(240, 240, 240);min-width: 85px;">优点</th><th style="border-top-width: 1px;border-color: rgb(204, 204, 204);text-align: center;background-color: rgb(240, 240, 240);min-width: 85px;">缺点</th></tr></thead><tbody style="border-width: 0px;border-style: initial;border-color: initial;"><tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: white;"><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">Unix I/O</td><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">最通用最底层的I/O方法,异步信号安全,即可在信号处理器中调用,提供访问文件元数据的功能</td><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">底层和基础,故需处理的情况多且易错,高效率的读写需缓冲区,也易错,这是标准I/o要解决的问题</td></tr><tr style="border-width: 1px 0px 0px;border-right-style: initial;border-bottom-style: initial;border-left-style: initial;border-right-color: initial;border-bottom-color: initial;border-left-color: initial;border-top-style: solid;border-top-color: rgb(204, 204, 204);background-color: rgb(248, 248, 248);"><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">标准I/O</td><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">缓冲通过减少读和写系统调用的数量来提高效率,短计数即获得的字符没达到SIZE的问题</td><td style="border-color: rgb(204, 204, 204);text-align: center;min-width: 85px;">不提供文件元数据访问功能,标准I/O非异步信号安全,不适合信号处理和网络套接字输入/出</td></tr></tbody></table><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">选择I/O函数</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">通用规则:尽量用高层的I/O函数,但理解用的函数。当用磁盘或终端文件时,用<strong>标准I/O</strong>,在信号处理程序内部,极少数需要最高性能时用<strong>Unix I/O</strong></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img data-ratio="0.691470054446461" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHbpibRHzYseTjzC83ia3uKCzv9GTia5b6iaK5EslzmojWwyyNzCYdVfws47g/640?wx_fmt=png" data-type="png" data-w="1102" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 454.913px !important;" _width="677px" class="img_loading" src="https://upload-images.jianshu.io/upload_images/7483640-c9329e99f437a844.png" crossorigin="anonymous" alt="图片"></p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">处理二进制文件</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">任意字节序列,包含0x00的字节的二进制文件,永远不要用面向文本的I/O,如fgets、scanf等,而使用rio_readn或rio_readnb,以及字符串函数,如strlen、strcpy、strcat等。</p><p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;"><img class="rich_pages wxw-img img_loading" data-ratio="0.5461847389558233" data-src="https://mmbiz.qpic.cn/mmbiz_png/HCeAMgNq7tsusnFPmdYNQXYGADOEAfHb7SwZ99QUhLWU60kZceIqbyCdPs8UUuqzmq9blpyicYWVMfk2KUIxiapA/640?wx_fmt=png" data-type="png" data-w="996" style="display: block; margin-right: auto; margin-left: auto; width: 657px !important; height: 359.751px !important;" _width="677px" src="https://upload-images.jianshu.io/upload_images/7483640-e2c165ce6d8c386e.png" crossorigin="anonymous" alt="图片"></p></section><blockquote><p>本文使用 <a href="https://www.jianshu.com/p/5709df6fb58d" class="internal">文章同步助手</a> 同步</p></blockquote>
csapp之第10章:系统级I⁄O
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 输入/输出(I/O)是在主存和外部设备之间拷贝数据的过程。 是主存,和外部设备(磁盘、终端、网络)之间的。 主存是...
- 3.1 引言 本章开始讨论UNIX系统,先说明可用的文件I/O函数——打开文 件、读文件、写文件等。UNIX系统中...
- 文件描述符 非负整数,变化范围(0~OPEN_MAX-1,或许是63?) 取得描述符 #include<fcntl...