文章目录
Unix I/O
Linux文件就是一个m个字节的序列,所有的IO设备(例如网络、磁盘和终端)都被模型化为文件,
而所有的输入和输出都被当作对相应的文件进行读和写。这种将设备优雅的映射为文件的方式,
允许Linux内核引出一个简单、低级的应用接口,称为 Unix I/O
这使得所有的输入和输出都能以一种统一且一致的方式来执行。
1.打开文件
一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。
内核返回一个小的非负整数,叫做描述符<file descriptor>,用于标识这个文件。
内核记录有关这个打开文件的所有信息,应用程序只需记住这个描述符。
Linux shell创建的每个进程开始时都有三个打开的文件:(需头文件<unistd.h>)
0<->STDIN_FILENO
1<->STDOUT_FILENO
2<->STDERR_FILENO
2.读写文件
3.关闭文件
通知内核关闭该文件,内核会释放文件打开时创建的数据结构,并将该描述符恢复到可用
的描述符池中。无论一个进程因何种原因终止,内核都会关闭所有打开的文件并释放
他们的内存资源。
1.打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
返回:
若成功则返回新文件描述符,失败返回-1。
返回的描述符总是在进程当前没有打开的最小描述符。
flags参数指明了进程打算如何访问这个文件:
可是以一个多个掩码的或。
O_RDONLY: 只读
O_WRONLY: 只写
O_CREAT : 如果文件不存在,就创建一个截断的(truncated)(空)文件。
O_TRUNC : 如果文件已存在,就截断它(长度被截为0,属性不变)
O_APPEND: 在每次写操作前,设置文件位置到文件的结尾
O_RDWR: 可读可写
mode参数指定了新文件的访问权限位。
2.关闭文件
close
函数关闭一个打开的文件
#include <unistd.h>
int close(int fd);
//返回: 若成功则为0,若出错则为-1
关闭一个已关闭的描述符会出错。
3.读和写文件
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
//read函数从描述符fd的当前文件位置拷贝最多n个字节到存储器buf
返回:若成功则为读的字节数,若EOF则为0,若出错为 -1.
ssize_t write(int fd,const void *buf,size_t n)
//write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置
返回:若成功则为写的字节数,若出错则为-1
ssize_t is long
size_t is unsigned long
read return:
return -1 :错误
return 0 :遇到EOF(end of file)
return >0:返回实际传送的字节数量
.
wrire return
return -1:错误
return >0:返回实际传送的字节数量
Short counts:不足值
比如规定max读200字节,实际文件只有100字节,那么第一次读的时候,会返回100,然后读进去。第二次读的时候会返回0,表示遇到EOF,即上次已经读到文件末尾了。
RIO Package
1.RIO无缓冲输入输出函数
rio_readn
和rio_writen
都会一直循环地读或写,直到不足值被妥善处理。
rio_readn
:从fd文件的当前位置最多传送n个字节到内存位置usrbuf,在遇到EOF只能返回一个不足值。
/*********************************************************************
* The Rio package - robust I/O functions
**********************************************************************/
/*
* rio_readn - robustly read n bytes (unbuffered)
*/
/* $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 */
// 如果是被中断了造成的错误,read这种慢系统调用不会读
nread = 0; /* and call read() again */
else
// 如果是别的错误,直接return -1.
return -1; /* errno set by read() */
}
else if (nread == 0)
break; /* EOF */
// nread>0正常读了时,修改nleft(还需要读的字节数)和bufp
nleft -= nread;
bufp += nread;
}
// 读到EOF或者读了n个字节后,return (n - nleft)
return (n - nleft); /* return >= 0 */
}
/* $end rio_readn */
在rio_readn
中,会一直调用read,直到出错(除了EINTR外,若是EINTR会继续去调用read)或者读到EOF或者读了n个字节后才会停止。
rio_writen
:从usrbuf传送n个字节到描述符fd,不会返回不足值。
/*
* rio_writen - robustly write n bytes (unbuffered)
*/
/* $begin rio_writen */
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; /* errorno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
/* $end rio_writen */
在rio_writen
中,与rio_readn
非常像,会一直调用write,直到出错(除了EINTR外,若是EINTR会继续去调用read)或者写了n个字节后才会停止。
2.RIO带缓冲输入输出函数
在代码中建立一个小的缓冲区,用来存放已读但还未被应用程序使用的字节,或用来在程序中存放一些字节以便未来输出到文件或网络中。
如果用户程序想要从文件读取一些字节,会先看看缓冲区Buffer中是否还有未读取的字节,如果有就接着读,如果没有,就重新填满Buffer。好处
就是不用每次需要一个或少量字符时,都调用一次操作系统,而是先调用把字符缓存起来,后续只需要从缓冲区读取,避免再去调用操作系统.
假设我们要编写一个程序来计算文本文件中文本行的数量,该如何实现呢?一种方法是用read函数来依次一个字节地从文件传送到用户内存,检查每个字节来查找换行符,这种方法显然效率极低。
更好的办法是调用一个包装函数(rio_readlineb
),从一个内部读缓冲区
复制一个文本行,当缓冲区变空时,自动调用read来重新填满缓冲区。
/* $begin cpfile */
#include "csapp.c"
int main(int argc, char **argv)
{
int n;
rio_t rio;
char buf[MAXLINE];
Rio_readinitb(&rio, STDIN_FILENO);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
/* $end cpfile */
exit(0);
/* $begin cpfile */
}
/* $end cpfile */
2.1 struct rio_t
/* Persistent state for the robust I/O (Rio) package */
/* $begin rio_t */
#define RIO_BUFSIZE 8192
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;
/* $end rio_t */
rio_fd
:关联的描述符fd
char rio_buf[]
: 缓冲区数组
rio_cnt
:剩余可读字节数
rio_bufptr
:下一次读时开始的指针
2.2 readinitb
初始化缓冲区 关联一个文件描述符fd
/*
* rio_readinitb - Associate a descriptor with a read buffer and reset buffer
*/
/* $begin rio_readinitb */
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
/* $end rio_readinitb */
2.3 rio_read
这是Linux read
函数的带缓冲的版本。当调用rio_read
要求读n
个字节时,读缓冲区内有rp->rio_cnt
个未读字节。如果缓冲区为空 --> 通过read来填满它。
一旦缓冲区非空,rio_read
就从读缓冲区复制n
和rp->rio_cnt
中较小值个字节到用户缓冲区,
并返回复制的字节数。
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
int cnt;
while (rp->rio_cnt <= 0) { /* refill if buf is empty */
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
sizeof(rp->rio_buf));
if (rp->rio_cnt < 0) {
if (errno != EINTR) /* interrupted by sig handler return */
return -1;
}
else if (rp->rio_cnt == 0) /* EOF */
return 0;
else
rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */
}
/* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
cnt = n;
if (rp->rio_cnt < n)
cnt = rp->rio_cnt;
memcpy(usrbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
}
/* $end rio_read */
2.4 rio_readlineb
从文件rp读出下一个文本行(包括结尾的换行符),将它复制到usrbuf,并用NULL(零)字符来结束这个文本行。
/*
* rio_readlineb - robustly read a text line (buffered)
*/
/* $begin rio_readlineb */
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;
for (n = 1; n < maxlen; n++) {
if ((rc = rio_read(rp, &c, 1)) == 1) {
*bufp++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return 0; /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return -1; /* error */
}
*bufp = 0;
return n;
}
/* $end rio_readlineb */
2.5 rio_readnb
/*
* rio_readnb - Robustly read n bytes (buffered)
*/
/* $begin rio_readnb */
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nread = rio_read(rp, bufp, nleft)) < 0) {
if (errno == EINTR) /* interrupted by sig handler return */
nread = 0; /* 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 */
}
/* $end rio_readnb */
在有了上述的函数之后,就可以运行我们的main函数啦!
/* $begin cpfile */
#include "csapp.c"
int main(int argc, char **argv)
{
int n;
rio_t rio;
char buf[MAXLINE];
Rio_readinitb(&rio, STDIN_FILENO);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
/* $end cpfile */
exit(0);
/* $begin cpfile */
}
/* $end cpfile */
跟踪一下运行:
可以看到这种缓冲区的优势就是读写的次数很少。