-
IO
-
定义
IO(输入/输出)是在主存和外部设备(磁盘驱动器、终端、网络)之间拷贝数据的过程
输入:IO设备 --> 主存
输出:主存 --> IO设备
-
所有语言都提供较高级别的IO工具
eg.
printf/scanf --- C cin >> / cout << --- C++
但本质是由Unix内核提供的系统级Unix IO函数实现的
-
-
Unix IO
-
所有的IO设备,例如网络、磁盘、终端等,都可以被模型化为文件,而所有的IO设备的输入和输出都可以被当作对相应文件的读和写来执行
—> 这种方式使得Unix内核可以定义简单统一的接口处理各种设备的IO
扫描二维码关注公众号,回复: 8569815 查看本文章 -
Unix IO的低级应用接口
(1) 打开文件 open
返回一个unsigned int fd作为文件描述符
内核记录文件信息,应用程序记录文件描述符
(2) 改变当前文件的位置 seek
文件位置:从文件开头起始的字节偏移量
(3) 读写文件 read/write
读操作:从文件拷贝n个字节到存储器
写操作:从当前文件位置开始,从存储器拷贝n个字节到文件
(4) 关闭文件 close
内核释放文件信息数据结构,回收文件描述符
只要一个进程终止,无论何种方式,打开的文件资源都会被释放
-
-
打开文件 open
-
每个进程开始时,默认会打开三个文件
作用 名称 fd 标准输入 STDIN_FILENO 0 标准输出 STDOUT_FILENO 1 标准错误 STDERR_FILENO 2 -
函数接口
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(char* filename, int flags, mode_t mode); 返回值:成功:fd; 失败:-1 flags:进程访问文件的方式,可以是一系列掩码的或(O_RDONLY, O_WRONLY, O_RDWR) mode:新文件的访问权限 (u, g, o 的 r, w, x)
-
-
关闭文件 close
-
函数接口
#include <unistd.h> int close(int fd); 返回值:成功:0 错误:1
-
-
读写文件 read/write
-
read 函数接口
#include <unistd.h> ssize_t read(int fd, void* buf, size_t n); buf: 存储器位置 n: 最多从当前文件位置拷贝n个字节 返回值:错误:-1 EOF:0 成功:实际拷贝的字节数
-
write 函数接口
#include <unistd.h> ssize_t write(int fd, const void* buf, size_t n); fd, buf, n同read 返回值:返回值:错误:-1 成功:实际拷贝的字节数
-
read/write的返回值和参数n 是小于等于的关系(小于的情况称为不足值),原因是
(1) read文件时遇到EOF
(2) 从终端read每次传送一个文本行
(3) 从网络read/write套接字socket时,内部缓冲约束和较长的网络延迟会引起read/write数量不够
注:读写磁盘文件时,不会遇到不足值(除非读磁盘文件结尾的EOF)
-
-
Rio(Robust I/O)进行健壮读写
-
无缓冲的输入函数
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 */ }
返回值:成功:传送的字节数 EOF:0 失败:-1
注:这个方法被中断后会手动重启(套在while循环里)
-
无缓冲的输出函数
ssize_t rio_writen(int fd, void* usrbuf, size_t n) { size_t nleft = n; ssize_t nwritten; char *bufp = usrbuf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) <= 0) { if (errno == EINTR) /* Interrupted by sig handler return */ { nwritten = 0; /* and call write() again */ } else { return -1; /* errno set by write() */ } } nleft -= nwritten; bufp += nwritten; } return n; }
返回值:成功:传送的字节数 失败:-1
注:这个方法不会返回不足值,因为会在while循环中重试
-
带缓冲的输入函数
(1) 目标:每次读取一行,根据’\n’确定
(2) 主要思想:首先定义一个结构体
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;
这个结构体和一个fd绑定,定义一个rio_buf用于保存字符,rio_cnt代表buffer中未读的字符,rio_bufptr指向未读的起始字符位置;
笨办法是每次读一个字节,然后每次判断这个字符是不是’\n’决定是否换行;这个方法的缺点是效率低,因为每次请求读字节都会陷入内核;
带有buffer的读法是:每当rio_t->rio_cnt小于等于0时,就读sizeof(rio_buf)这么多个字节,把buffer填满;然后取的时候根据要读的数量取,此时取这一行为发生在用户态,不需要经过内核,所以效率提高了。
代码比较长,不贴了
-
-
读取文件元数据
-
使用 stat 和 fstat 读取文件元数据
#include <unistd.h> #include <sys/stat.h> int stat(const char* filename, struct stat* buf); int fstat(int fd, struct stat* buf);
stat结构体很复杂,包含了一个文件的各种信息
-
文件
(1) 普通文件
包括文本文件和二进制文件,对于内核来说文本文件和二进制文件是一样的
(2) 目录文件
(3) 套接字
-
宏指令可以判断文件类型
S_ISREG() S_ISDIR() S_ISSOCK()
-
-
共享文件
-
内核维护三种数据结构,用于描述打开的文件
(1) 描述符表:文件描述符的集合
fd 文件表表项地址 0 文件A 1 文件B … … (2) 文件表:打开文件的集合
文件 文件位置 引用计数 v-node表项地址 文件A xxx 1 nodeA 文件B xxx 2 nodeB … … … … (3) v-node表:文件信息,基本等同于stat结构体,包括文件访问权限、大小、类型等
-
多个描述符可以通过不同的文件表表项,引用同一个文件;也可以引用不同的文件
即,两个fd不一样,文件表中的文件名也不一样,但是v-node表征的实际文件是同一个
注:这样设计的原因是,每个fd都有自己的文件位置,对不同fd的读操作可以从文件的不同位置获取数据
-
父子进程共享fd,并且共享文件位置
-
-
IO重定向
-
目标:将磁盘文件和标准输入输出联系起来
-
原理
使用 dup2 方法
#include <unistd.h> int dup2(int oldfd, int newfd);
调用 dup2 之前
oldfd ---> 文件A ---> nodeA newfd ---> 文件B ---> nodeB
调用 dup2 之后
oldfd ---> 文件B ---> nodeB newfd ---> 文件B ---> nodeB
所以,对oldfd的输出,在底层由nodeA切换到了nodeB
例如
ls > a.txt
从 stdout --> a.txt
-
几个示例
cmd1 | cmd2 : pipe,将cmd1的标准输出作为cmd2的标准输入 > file :将标准输出重定向到file < file :将file作为标准输入 >> file :将标准输出重定向到file,如果file存在,append到文件中,即附加到文件的后面,而不是覆盖文件
-
-
标准IO
-
标准IO库将一个打开的文件模型化为一个流
-
一个流是一个指向File类型的指针
-
Unix IO、标准IO、RIO的关系
(1) Unix IO:通过系统调用来访问
open/read/write/lseek/stat/close
(2) RIO:这本书对Unix IO封装了一下写了一个库,用于自动处理不足值的情况
(3) 标准IO:封装Unix IO,带缓冲
注:输入和输出都带缓冲,因为不带缓冲总是要陷入内核进行系统调用比较耗时
-
绝大多数情况下都不应该直接用Unix IO
对于磁盘和终端设备,标准IO函数就很适合;
-
但是标准IO不适合Socket,因为标准IO流是全双工的,而Socket有限制,不能全双工
所以Socket应该用Unix IO
-
chapter11_系统级IO
猜你喜欢
转载自blog.csdn.net/captxb/article/details/103482245
今日推荐
周排行