CSAPP:第10章 系统级IO 水深无声 2022-10-29 15:22 230阅读 0赞 ### CSAPP:第10章 系统级IO ### ### 文章目录 ### * * * CSAPP:第10章 系统级IO * * 10.1 Unix IO * 10.2 文件 * 10.3 打开和关闭文件 * 10.4 读和写文件 * 10.5 用RIO包健壮地读写 * * 10.5.1 RIO 的无缓冲的输入输出函数 * 10.5.2 RIO 的带缓冲的输入函数 * 10.6 读取文件元数据 * 10.7 读取目录的内容 * 10.8 共享文件 * 10.9 IO重定向 * 10.10 标准IO * 10.11 综合:我该使用哪些IO函数? #### 10.1 Unix IO #### * 所有的输人和输出都能以一种统一且一致的方式来执行: * 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O 设备 * 内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。 * 内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。 * Linux shell 创建的每个进程开始时都有三个打开的文件: * 标准输入(描述符为 0) * 标准输出(描述符为 1) * 标准错误(描述符为 2) * 头文件< unistd.h> 定义了常量 STDIN\_FILENO、STDOUT\_FILENO 和 STDERR\_ETLENO,它们可用来代替显式的描述符值。 * 改变当前的文件位置 * 对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行 seek 操作,显式地设置文件的当前位置为k。 * 读写文件 * 一个读操作就是从文件复制 n>0 个字节到内存,从当前文件k位置是开始,然后将k增加到k+n。 * 给定一个大小为m字节的文件,当k>=m时执行读操作,会出发EOF(end-of-file)条件——能被应用程序检测到 * 文件末尾没有明确的EOF符号 * 关闭文件。 * 当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。 * 无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。 #### 10.2 文件 #### * 普通文件(regular file):应用程序常常要区分文本文件(text file)和二进制文件(binary file): * 文本文件是只含有 ASCII 或 Unicode 字符的普通文件; * 二进制文件是所有其他的文件。 * 对内核而言,文本文件和二进制文件没有区别。 * 目录(directory) * 包含一组链接(link )的文件,其中每个链接都将一个文件名(filename)映射到一个文件,这个文件可能是另一个目录。 * 每个目录至少含有两个条目 * ‘.’表示当前目录 * '…'表示上一级目录 * 可以用 mkdir 命令创建一个目录,用 Is 查看其内容,用 rmdir 删除该目录。 * 套接字(socket) * 用来与另一个进程进行跨网络通信的文件 * Linux 内核将所有文件都组织成一个目录层次结构(directory hierarchy),下图显示了Linux 系统的目录层次结构的一部分。 * ![image-20210214222538523][] * 相对路径名(absolute pathname) * 以一个斜杠开始,表示从根节点开始的路径。 * 绝对路径名(relative pathname) * 以文件名开始,表示从当前工作目录开始的路径。 #### 10.3 打开和关闭文件 #### * 进程是通过调用 open 函数来打开一个已存在的文件或者创建一个新文件的: * ![image-20210214222650104][] * open 函数将 filename 转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。 * flags 参数指明了进程打算如何访问这个文件: * ![image-20210214222808151][] * ![image-20210214222906212][] * mode 参数指定了新文件的访问权限,而每个进程可通过`umask`函数来设置用户默认权限的补码,则文件最终的权限是通过`mode & ~umask`确定的。 * ![image-202102152310115 93][] * 最后,进程通过调用 close 函数关闭一个打开的文件。 * ![image-20210215231042403][] #### 10.4 读和写文件 #### * 应用程序是通过分别调用 read 和 write 函数来执行输入和输出。 * ![image-20210215231125231][] * read 函数从描述符为 fd的当前文件位置复制最多n个字节到内存位置 buf。 * 返回值一1表示一个错误 * 返回值 0 表示 EOF * 其他返回值表示的是实际传送的字节数量 * write 函数从内存位置 buf 复制至多 个字节到描述符 fd 的当前文件位置 * 调用一次一个字节地从标准输入复制到标准输出: * ![image-20210215231318977][] * 在某些情况下,read 和 write 传送的字节比应用程序要求的要少(不足值问题) * 读时遇到 EOF。 * 读完了。 * 从终端读文本行。 * 如果打开文件是与终端相关联的(如键盘和显示器),那么每个read 函数将一次传送一个文本行,返回的不足值等于文本行的大小。 * 读和写网络套接字(socket)。 * 内部缓冲约束和较长的网络延迟会引起 read 和 write 返回不足值。 * 在x86-64中,size\_t被定义为unsigned long,而 ssize\_t被定义为long。 #### 10.5 用RIO包健壮地读写 #### * 自动为你处理上文中所述的不足值 * RIO 提供了两类不同的函数: * 无缓冲的输入输出函数。 * 这些函数直接在内存和文件之间传送数据,没有应用级缓冲。 * 它们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。 * 带缓冲的输入函数。 * 这些函数允许你高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内,类似于为 printf 这样的标准 I/O 函数提供的缓冲区。 * 线程安全 ##### 10.5.1 RIO 的无缓冲的输入输出函数 ##### * 通过调用 rio\_readn 和 rio\_writen 函数,应用程序可以在内存和文件之间直接传送数据 * ![image-20210215232628549][] * rio\_readn 函数从描述符 fd 的当前文件位置最多传送《n个字节到内存位置 usrbuf。 * rio\_writen 函数从位置 usrbuf 传送n个字节到描述符 fd。 * rio\_read 函数在遇EOF的时只能返回一个不足值。 * rio\_writen 函数决不会返回不足值。 * 对于同一个描述符,可以任意交错地调用 rio\_readn 和 rio\_writen。 * 如果 rio\_readn 和 rio\_writen 函数被一个从应用信号处理程序的返回中断,那么每个函数都会手动地重启 read 或 write。 * 实现: * ![image-20210215233841954][] ##### 10.5.2 RIO 的带缓冲的输入函数 ##### * 内存读取文本——调用包装函数(ric readlineb), 它从一个内部读缓冲区复制一个文本行,当缓冲区变空时,会自动地调用 read 重新填满缓冲区。 * 对于既包含文本行也包含二进制数据的文件(例如 11.5.3 节中描述的 HTTP 响应),叫做 rio\_readnb( rio\_readn 带缓冲版) * ![image-20210215233755812][] * **rio\_readinneb** * 每打开一个描述符,都会调用一次 rio\_readinitb 函数。它将描述符 fd 和地址 rp处的一个类型为 rio\_t的读缓冲区联系起来 * rio\_readlineb函数从文件rp读出下一个文本行(包括换行符),复制到usrbuf,并用NULL结束此行 * rio\_readlineb最多读去maxlen-1个字节,余下的一个字符留给NULL,对于超过的部分,直接截断。 * rio\_readlineb: * ![image-20210216000047638][] * **rio\_readnb** * 函数从文件 rp 最多读 n 个字节到内存位置 usrbuf。 * 缓冲和无缓冲函数不可交叉使用 * **rio\_readnb**和**rio\_readinitb**可以任意交叉使用 * rio\_readnb: * ![image-20210216000108139][] * 缓冲区格式 * ![image-20210215234949696][] * 缓冲区结构如下,rio\_readlinitb创建一个空缓冲区,并且将一个打开的文件描述符和这个缓冲区联系起来。 typedef struct{ int rio_fd; //该缓冲区的文件描述符 int rio_cnt; //缓冲区中未读的字节数 char *rio_bufptr; //缓冲区中未读的下一个字节 char rio_buf[RIO_BUFSIZE]; //读缓冲区 } rio_t; void rio_readinitb(rio_t *rp, int fd){ rp->rio_fd = fd; rp->rio_cnt = 0; rp->rio_bufptr = rp->rio_buf; } * rio\_read的实现: * ![image-20210215235522488][] * 若buf为空,则重新填满 * 错误=-1,EOF=0 * memcpy将n个字节从缓冲区中复制到用户缓冲区,并返回cnt(复制到字节数,这玩意=n,不足值的时候会判断一下赋值) #### 10.6 读取文件元数据 #### * 应用程序能够通过调用 stat 和 fstat 函数,检索到关于文件的信息(有时也称为文件的元数据(metadata))。 * ![image-20210216092248640][] * stat 函数以一个文件名作为输入,并填写 stat 数据结构中的各个成员。 * fstat 函数是相似的,只不过是以文件描述符而不是文件名作为输人。 * stat结构体如下所示 * ![image-20210216092459203][] * 其中`st_size`包含了文件的字节数大小 * `st_mode`编码了文件访问许可位,我们可以通过`sys/stat.h`中定义的宏来确定该部分的信息: * `S_ISREG(st_mode)`: 是否为普通文件 * `S_ISDIR(st_mode)`:是否为目录文件 * `S_ISSOCK(st_mode)`:是否为套接字 #### 10.7 读取目录的内容 #### * 应用程序可以用 readdir 系列函数来读取目录的内容。 * ![image-20210216093746084][] \- 函数 opendir 以路径名为参数,返回指向目录流(directory stream)的指针。流是对条目有序列表的抽象,在这里是指目录项的列表 * ![image-20210216094635061][] \- 每次对 readdir 的调用返回的都是指向流 dirp 中下一个目录项的指针,或者,如果没有更多目录项则返回 NULL。 - 如果出错,则会返回NULL,并设置errno(这是区别流结束的唯一方法) - 目录项的结构如下: - ![image-20210216094712809][] * ![image-20210216105008052][] \- 函数 closedir 关闭流并释放其所有的资源。 #### 10.8 共享文件 #### * 内核用三个相关的数据结构来表示打开的文件: * 描述符表(descriptor table)。 * 每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。 * 每个打开的描述符表项指向文件表中的一个表项。 * 文件表(file table)。 * 打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。 * 表项包括:文件位置、 引用计数( reference count)(即当前指向该表项的描述符表项数),以及一个指向 v-node 表中对应表项的指针。 * v-node 表(v-node table)。 * 所有的进程共享这张 v-node 表。 * 每个表项包含 stat 结构中的大多数信息,包括 st\_mode 和 st\_size 成员。 * 此三者结构: * ![image-20210216114042718][] * 这里文件A B可以指向同一个v-node表项 * 父子进程共享文件:子进程会有一份父进程的描述符表副本,因此就又了后续指向(打开文件表和v-node表) #### 10.9 IO重定向 #### * Linux shell 提供了 I/O 重定向操作符,允许用户将磁盘文件和标准输人输出联系起来。:`linux> Is > foo.txt` * ![image-20210216115322437][] \- 可见>将ls的输出重定向到foo.txt中 * C语言中的I/O 重定向: * ![image-20210216115601082][] * dup2 函数复制描述符表表项 oldfd 到描述符表表项 newfd,覆盖描述符表表项 newfd以前的内容。如果 newfd 已经打开了,dup2会在复制 oldfd 之前关闭 newfd。 * #### 10.10 标准IO #### * C 语言定义了一组高级输人输出函数,称为标准 I/O 库,为程序员提供了 Unix I/O的较高级别的替代。 * 这个库(libc)提供了: * 打开和关闭文件的函数(fopen 和 fclose) * 读和写字节的函数(fread 和 fwrite) * 读和写字符串的函数(fgets 和 fputs) * 复杂的格式化的 I/O 函数(scanf 和 printf) * 标准 I/O 库将一个打开的文件模型化为一个流。对于程序员而言,一个流就是一个指向 FILE 类型的结构的指针。每个 ANSI C 程序开始时都有三个打开的流 stdin、stdout和 stderr,分别对应于标准输人、标准输出和标准错误 * #include <stdio.h> extern FILE *stdin; extern FILE *stdout; extern FILE *stderr; * 类型为 FILE 的流是对文件描述符和流缓冲区的抽象。 #### 10.11 综合:我该使用哪些IO函数? #### * 各种IO包与C语言的关系如下 * ![image-20210216120503637][] * 函数选用标准\\建议 * G1:只要有可能就使用标准 I/O。 * G2: 不要使用 scanf 或 rio\_readlineb 来读二进制文件。 * 这样的函数是专门用来读去文本文件的。 * G3: 对网络套接字的 I/O 使用 RIO 函数。 * 标准IO在网络中的输入输出中存在问题。 * 对于标准IO来说,一般认为是全双工的,除以下两种情况 * 限制一 :跟在输出函数之后的输入函数。 * 如果中间没有插人对 fflush(清空与流相关的缓冲区)、fseek、 fsetpos 或者 rewind (三者数使用 Unix I/O lseek 函数重置当前的文件位置)的调用,一个输人函数不能跟随在一个输出函数之后。 * 限制二:跟在输入函数之后的输出函数。 * 如果中间没有插人对 fseek、 fsetpos 或者 rewind 的调用,一个输出函数不能跟随在一个输人函数之后,除非该输入函数遇到了一个文件结束。 [image-20210214222538523]: /images/20221024/b3ca4e7201cb467f828c59967e8526ba.png [image-20210214222650104]: /images/20221024/ae2e6692b7df412b8910a4d51cf80a03.png [image-20210214222808151]: /images/20221024/71d27e45537542aea0bbca45c43a0b51.png [image-20210214222906212]: /images/20221024/dcbb47cc23234f0790b19487a4f634e2.png [image-202102152310115 93]: /images/20221024/ea86029e1d71402d9b812400ab1ab6ac.png [image-20210215231042403]: /images/20221024/f5f7f57c7b7a4980820d24576742cf76.png [image-20210215231125231]: /images/20221024/b5b2e4c2875942a1a8d883f0dd9eb148.png [image-20210215231318977]: /images/20221024/20342db4880141cd9ad11bc89373cbaa.png [image-20210215232628549]: /images/20221024/9ec2a1e27eb54574a2fd67c0b1d5bf4e.png [image-20210215233841954]: /images/20221024/ee402b7b65384bc8a23ab34fa8cfe969.png [image-20210215233755812]: /images/20221024/3d77ea2b58a447ba8f2f5cbd5851624d.png [image-20210216000047638]: /images/20221024/07b215922683472087963aa907a385ab.png [image-20210216000108139]: /images/20221024/9e14618d37e147dda9f5cfcc37fa4a88.png [image-20210215234949696]: /images/20221024/926ce7e7e5ec468ab1dcee09285d4c96.png [image-20210215235522488]: /images/20221024/8b307321b7de43eea4cbc7df6d13a396.png [image-20210216092248640]: /images/20221024/7fd84b94599d48429a85edb7c97be308.png [image-20210216092459203]: /images/20221024/c915d6fe3bcf4f4db1c2d2e155e33aa7.png [image-20210216093746084]: /images/20221024/1718ab8d565242948c0fc0efdef9b738.png [image-20210216094635061]: http://docker.aliyouth.cn/img/image-20210216094635061.png [image-20210216094712809]: /images/20221024/82e031377f054c9080578522c052aa99.png [image-20210216105008052]: [image-20210216114042718]: /images/20221024/7442cf3e1e5a4d5a91fb477ae4136cdd.png [image-20210216115322437]: /images/20221024/0185222aad2f4478ab43d2d85d05c3e2.png [image-20210216115601082]: /images/20221024/fe52e036867b4e699ba8d9fc9d259c30.png [image-20210216120503637]: /images/20221024/d7f16b95743247fba58e96dd4f275c65.png
相关 CSAPP:第3章 程序的机器级表示 CSAPP:第3章 程序的机器级表示 文章目录 CSAPP:第3章 程序的机器级表示 3.1 A Historical 秒速五厘米/ 2023年01月10日 11:50/ 0 赞/ 124 阅读
相关 CSAPP:第1章 计算机系统漫游 文章目录 1.1 information=bit+context 1.2 程序翻译 1 以你之姓@/ 2023年01月02日 03:24/ 0 赞/ 215 阅读
相关 CSAPP:第12章 并发编程 CSAPP:第12章 并发编程 文章目录 CSAPP:第12章 并发编程 12.1 基于进程的并发编程(Process 傷城~/ 2022年10月31日 01:47/ 0 赞/ 23 阅读
相关 CSAPP:第11章 网络编程 CSAPP:第11章 网络编程 文章目录 CSAPP:第11章 网络编程 11.1 客户端-服务器编程模型 待我称王封你为后i/ 2022年10月30日 05:24/ 0 赞/ 261 阅读
相关 CSAPP:第10章 系统级IO CSAPP:第10章 系统级IO 文章目录 CSAPP:第10章 系统级IO 10.1 Unix IO 水深无声/ 2022年10月29日 15:22/ 0 赞/ 231 阅读
相关 CSAPP:第9章 虚拟内存 CSAPP:第9章 虚拟内存 文章目录 CSAPP:第9章 虚拟内存 9.1 物理和虚拟寻址 谁践踏了优雅/ 2022年10月29日 11:27/ 0 赞/ 335 阅读
相关 CSAPP:第6章 存储器层次结构 CSAPP:第6章 存储器层次结构 文章目录 CSAPP:第6章 存储器层次结构 6.1 存储技术 Dear 丶/ 2022年10月28日 10:09/ 0 赞/ 244 阅读
相关 CSAPP:第4章 处理器体系结构 CSAPP:第4章 处理器体系结构 文章目录 CSAPP:第4章 处理器体系结构 Y86-84指令集体系结构 - 日理万妓/ 2022年10月24日 13:30/ 0 赞/ 354 阅读
还没有评论,来说两句吧...