CSAPP Note chap10
来源:互联网 发布:死神漫画单行本淘宝 编辑:程序博客网 时间:2024/05/21 00:52
CSAPP Note chap10
CSAPP 读书笔记系列chap10
chap 10 系统级IO
这一次说的是I/O,先说明大概,也就是 Unix I/O 和其他 I/O 的关系。
其结构如下:
图中底层是Unix I/O,通过系统调用来进行文件操作,
- Unix I/O,例如:
- 打开和关闭 :open, close
- 读和写:write, read
- 改变访问顺序:lseek
- 文件信息 stat
在这之上是 C 的标准 I/O 库,其为基于Unix I/O 实现的,
一些对应的函数为:
- C standard I/O :
- fopen, fdopen, fread, fwrite, fscanf, fprintf, sscanf, sprintf, fgets, fputs, fflush, fseek, fclose等
RIO 是专为本书开发的read和write的函数,也为基于Unix I/O 实现
- rio_readn,rio_writen, rio_readlineb 和 rio_readnb
Unix I/O
Unix I/O 是最通用最底层的 I/O 方法,其他的 I/O 包都是在 Unix I/O 的基础上进行构建的.
在 Linux 中,一切皆文件,文件实际上可以看做是字节的序列。
### 文件
为了区别Linux中不同文件的类型,会有一个 类型type 来进行区别:
普通文件:包含任意数据
- 二进制文件和文本文件对内核而言没有区别
- 文本文件: 只含ASCII或Unicode编码的文件
- 二进制文件: 所以其他文件
目录:相关一组文件的索引
- 套接字 Socket:和另一台机器上的进程通信的类型
其实还有一些比较特别的类型,不深入了解:
- 命名管道Named pipes(FIFOs)
- 符号链接Symbolic links
- 字符及设备Character and block devices
文件操作
打开文件open()
函数原型
#include <fcntl.h>int open(char *name ,int flags, int perms);
参数的定义可以看K&R的第八章Unix系统接口或man 2 open
例如:
int fd; // 文件描述符 file descriptorif ((fd = open("/etc/hosts", O_RDONLY)) < 0){ perror("open"); exit(1);}
返回值是一个小的整型称为文件描述符(file descriptor),如果这个值等于 -1 则说明发生了错误。每个由 Linux shell 创建的进程都会默认打开三个文件(注意这里的文件概念):
- 0: standard input(stdin)
- 1: standard output(stdout)
- 2: standar error(stderr)
注意文件描述符(file descriptor) 和文件指针 FIFE * 的区别。
关闭文件close()
#include <unistd.h>int close(int fd);
使用完毕之后同样需要通知内核关闭文件:
如果在此关闭已经关闭了的文件,会出大问题。所以一定要检查返回值,哪怕是 close() 函数
读写文件
读取文件read
在打开和关闭之间就是读取文件,实际上就是把文件中对应的字节复制到内存中,并更新文件指针:
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
例子如下
char buf[512];int fd;int nbytes;// 打开文件描述符,并从中读取 512 字节的数据if ((nbytes = read(fd, buf, sizeof(buf))) < 0){ perror("read"); exit(1);}
返回值是读取的字节数量,是一个 ssize_t 类型(其实就是一个有符号整型,size_t为无符号整数),如果 nbytes < 0 那么表示出错。nbytes < sizeof(buf) 这种情况(short counts) 是可能发生的,而且并不是错误。
写入文件write
写入文件是把内存中的数据复制到文件中,并更新文件指针:
#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
例如:
char buf[512];int fd;int nbytes;// 打开文件描述符,并向其写入 512 字节的数据if ((nbytes = write(fd, buf, sizeof(buf)) < 0){ perror("write"); exit(1);}
返回值是写入的字节数量,如果 nbytes < 0 那么表示出错。nbytes < sizeof(buf) 这种情况(short counts) 是可能发生的,而且并不是错误。
short counts 指的是不足值,
其发生在:
- 在读取的时候遇到 EOF(end-of-file)
- 从终端中读取文本行
- 读取和写入网络 sockets
但是在下面的情况下不会发生 - 从磁盘文件中读取(除 EOF 外)
- 写入到磁盘文件中
一般为允许short count
文件信息(元数据)
元数据是用来描述数据的数据,由内核维护,可以通过 stat 和 fstat 函数来访问,其结构是:
#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>int stat(const char *pathname, struct stat *buf);int fstat(int fd, struct stat *buf);//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}
RIO
RIO 指的是 Roubst I/O 鲁棒的I/O:-)
其分为:
- 无缓冲的IO:Unbuffered input and output of binary data
- rio_readn 和 rio_writen - 有缓冲的IO Buffered input of text lines and binary data
- rio_readlineb 和 rio_readnb
其中,带缓冲的为线程安全和相互兼容的
- rio_readlineb 和 rio_readnb
rio_readn
其实现为:
/* $begin rio_readn */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 */}
文件共享和重定向
文件共享
一个文件在内核打开的过程如下,有三个数据结构:
- 描述表符(Descriptor table):每个进程都有自己独立的描述符表,会指向文件表
- 文件表: 表示打开文件的集合,所有进程共享。里面表项会有文件的一些信息如引用计数,并指向v-node表
- v-node表:所有进程共享,包含文件的大多数信息例如stat;
每个进程都有自己的描述符表(Descriptor table),然后 Descriptor 1 指向终端,Descriptor 4 指向磁盘文件,如下图所示:
而对于fork 函数:
子进程实际上和父进程的指向是一样的,这里需要注意的是会把引用计数加 1
重定向
调用 dup2(oldfd, newfd) 函数。
只要改变文件描述符指向的文件,也就完成了重定向的过程,下图中把原来指向终端的文件描述符指向了磁盘文件,也就把终端上的输出保存在了文件中:
C标准IO
C 标准库中包含一系列高层的标准 IO 函数,比如
- 打开和关闭文件: fopen, fclose
- 读取和写入字节: fread, fwrite
- 读取和写入行: fgets, fputs
- 格式化读取和写入: fscanf, fprintf
标准 IO 会用流的形式打开文件,这里的流(stream)指的是文件描述符和缓冲区(buffer)在内存中的抽象,是一个指向FILE文件指针结构的指针。
一个FILE 的结构可以如下:
struct FILE{ int cnt; // 剩余的字符数 char *ptr; //下一个字符的位置 char *base; //缓冲区的位置 int flag; // 文件访问模式 int fd; //文件描述符}
C 程序一般以三个流开始,如下所示:
#include <stdio.h>extern FILE *stdin; // 标准输入 descriptor 0extern FILE *stdout; // 标准输出 descriptor 1extern FILE *stderr; // 标准错误 descriptor 2
使用缓冲区的原因是:
程序经常会一次读入或者写入一个字符,比如 getc, putc, ungetc,同时也会一次读入或者写入一行,比如 gets, fgets。如果用 Unix I/O 的方式来进行调用,是非常昂贵的,比如说 read 和 write 因为需要内核调用,需要大于 10000 个时钟周期。
解决的办法就是利用 read 函数一次读取一块数据,然后再由高层的接口,一次从缓冲区读取一个字符(当缓冲区用完的时候需要重新填充)
使用IO的建议:
Unix I/O 是最通用最底层的 I/O 方法,其他的 I/O 包都是在 Unix I/O 的基础上进行构建的。
Unix I/O 中的方法都是异步信号安全(async-signal-safe)的,也就是说,可以在信号处理器中调用。
一些建议
- 尽可能使用C标准IO。
- 对于磁盘或设备终端的IO来说,C标准IO不受Unix IO的困扰
- 要得到文件的元信息时,就还是得使用 Unix I/O 中的 stat 函数
不要使用C标准IO来读取二进制文件
C标准 I/O 不适合用于处理网络套接字。
- 另外标准 C I/O 中的函数都不是异步信号安全(async-signal-safe)的,所以并不能在信号处理器中使用。
- 因为流和套接字的限制,因为TCP是连续的字符流传输协议,一个方向发一个方向收。而C标准 I/O是全双工的。
- 限制一:跟在输出函数之后的输入函数。socket的一个输入函数如果没刷新缓冲区不能跟在输出函数之后。套接字不能使用lseek函数,只能每次都刷新缓冲区
- 限制二:跟在输入函数之后的输出函数,若若中间没有插入对fseek,fflush,fsetops,rewind的调用,输出函数不能跟在输入函数之后,除非该输入函数遇到文件结束。- 对于限制二,只能同时打开两个文件,一个方向发一个方向收。但会在多线程中出错,所以这个问题留给以后。
- CSAPP Note chap10
- Note of Chap10
- CSAPP Note chap1
- CSAPP Note chap2
- CSAPP Note chap3
- CSAPP Note chap3 attacklab
- CSAPP Note chap6
- CSAPP Note chap7
- CSAPP Note chap8
- CSAPP Note chap4 & chap 5
- chap10-queue
- chap10-DLinkList
- CSAPP
- chap10 name control
- chap10 list 单链表实现
- chap10 单链表倒置
- 英语学习--chap10 介词
- note
- viewpager 加载更多
- jFinal上传文件的方法 ,上传到oss
- 第10章-窗口函数
- spark系列文章
- BottomTabBar简单使用
- CSAPP Note chap10
- java.lang.IllegalStateException: No thread-bound request found Spring中遇见问题,说是并流了
- Windows程序设计:位图的显示
- 安卓第一课
- 后台程序升级流程
- Day012
- 1034. 有理数四则运算(20)
- BZOJ4025: 二分图(线段树+并查集)
- JSP变成Servlet