20145218 《信息安全系统设计基础》第九周学习总结
20145218 《信息安全系统设计基础》第九周学习总结
教材学习内容总结
10.1 Unix I/O
-
输入输出(I/O)是在主存和外部设备之间拷贝数据的过程。一个Unix文件就是一个m个字节的序列,所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对应文件的读和写来执行。
-
打开文件。一个应用程序通过要求内核打开相应的文件来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,称为描述符,在后续对此文件的所有操作中标识这个文件。
unix外科创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。
-
Unix外壳创建的每个进程开始时都有三个打开的文件,可以用来
-
改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。通过执行seek操作设置文件位置为k。
-
读写文件。读操作:从文件拷贝n>0个字节到存储器,从当前文件位置k开始,增加到k+n。当k>=文件长度大小时会触发一个称为end-of-file(EOF)的条件。写操作:从存储器拷贝n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
-
关闭文件。内核释放文件打开时创建的数据结构。
10.2 打开和关闭文件
- 打开一个已存在的文件或者创建一个新文件:
int open(char *filename,int flags,mode_t mode) (若成功则返回新文件描述符,若出错为-1)
- open函数将filename转换为一个文件描述符,并且返回描述符数字。
- flags参数指明了进程如何访问文件。
- mode参数指定了新文件的访问权限位。
- 关闭一个打开的文件:
int close(int fd)(若成功则为0,若出错则为-1)
关闭一个已关闭的描述符会出错
10.3 读和写文件
- 应用程序是通过分别调用read和write函数来执行输入和输出的。
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
- 读文件:read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf。返回值表示实际传送的字节数量,错误返回-1,EOF返回0。
- 写文件:write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。
include "csapp.h"
int main(void)
{
char c;
while(Read(STDIN_FILENO,&c,1) != 0)
Write(STDOUT_FILENO,&c,1);
exit(0);
}
- lseek函数:应用程序能够显式地修改当前文件的位置。
不足值
- 读时遇到EOF:文件末尾剩余的字节数不足读取文件的字节片大小。
- 从终端读文本行:若打开文件与终端相关联,则每个read函数将一次传送一个问本行。返回的不足值等于文本行的大小。
- 读和写网络套接字(socket):若打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。
10.4 用RIO包健壮地读写
RIO的无缓冲的输入输出函数
- 通过调用rio _ readn和rio _ writen函数,应用程序可以在存储器和文件之间直接传送数
据。
ssize_t rio_readn(int fd,void *usrbuf,size_t n);
ssize_t rio_writen(int fd,void *usrbuf,size_t n);
(若成功则返回传送成功的字节数,若EOF则为0(只对rio_readn而言),若出错 则为-1)
RIO的带缓冲的输入函数
- 一个文本行就是一个有换行符结尾的ASCII码字符序列。 在Unix系统中,换行符(‘\n’)与ASCII码换行符(LF)相同,数字值为0x0a。
- rio _ readlineb和rio _ readnb函数从一个内部读缓冲区拷贝一个文本行,当缓冲区变空时,会自动调用read重新填满缓冲区。
void rio_readinitb(rio_t *rp,int fd);(无返回)
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_n);
(若成功则返回传送成功的字节数,若EOF则为0,若出错 则为-1)
- rio_ readn和rio_ writen函数详细代码如下:
ssize_t rio_readn(int fd,void *usrbuf,size_t n)
{
size_t nleft = 0;
sszie_t nread;
char *bufp = usrbuf;
while(nleft > 0)
{
if((nread = read(fd,bufp,nleft))<0)
{
if (errno == EINTR)
nread = 0;
else
return -1;
}
else if (nread == 0)
break;
nleft -= nread;
bufp += nread;
}
return (n-nleft);
}
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)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
bufp += nwrittenl;
}
return n;
}
- 打开每一个描述符都会调用一次rio _ readinitb函数,他将描述符fd和地址rp处的一个类型为rio _ t的读缓冲区联系起来。
- 带缓冲的函数的调用不应该和无缓冲的rio _ readn交叉使用。
10.5 读取文件元数据
- 应用程序能通过调用stat和fstat函数,检索到关于文件的信息(元数据)。stat函数以一个文件名作为输入,fstat函数以文件描述符作为输入。
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename,struct stat *buf);
int fstat(int fd,struct stat *buf);
- st _ size成员包含了文件的字节数大小。st _ mode成员则编码了文件访问许可位和文件类型。
- 普通文件包括某种类型的二进制或文本数据。
- 目录文件包含关于其他文件的信息。
- 套接字是一种用来通过网络与其他进程通信的文件。
10.6 共享文件
- 内核用三个相关的数据结构来表示其打开的文件。
- 描述符表:表项由进程打开的文件描述符来索引的,每个打开的描述符表指向文件表中的一个表项,每个进程有其独立的描述符表。
- 文件表:打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。包括:当前的文件位置、引用计数、以及一个指向v-node表中对应表项的指针。
- v-node表:每个表项包含stat结构中的大多数信息,;包括st_mode和st_size成员,所有进程共享。
- 多个描述符可以通过不同的文件表表项来引用同一个文件。 关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。
- 在内核删除相应文件表项之前,父子进程必须都关闭了它们的描述符。
10.7 I/O重定向
- I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。
unix> ls > foo.txt
- I/O重定向是依靠dup2函数工作的。dup2函数拷贝描述符表表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开,dup2会在拷贝oldfd之前关闭newfd。
#include <unistd.h>
int dup2(int oldfd,int newfd);
10.8 标准I/O
- 标准I/O库将一个打开的文件模型化为一个流,也就是一个指向FILE类型的结构的指针。
#include <stdio.h>
extern FILE *stdin; /*标准输入,文件描述符为0*/
extern FILE *stdout; /*标准输出,文件描述符为1*/
extern FILE *stderr; /*标准错误,文件描述符为2*/
- 类型为file的流是对文件描述符和流缓冲区的抽象,目的是使开销较高的Unix I/O系统调用的数量尽可能小。
10.9 I/O函数的使用
- 应用程序可以通过open、close、lseek、read、write和stat这样的函数来访问Unix I/O。
RIO函数:read和write的健壮的包装函数,自动处理不足值,为读文本行提供一种高效的带缓冲的方法。 - 标准I/O函数:提供了Unix I/O函数的一个更加完整的带缓冲的替代品,包括格式化的I/O例程。是磁盘和终端设备I/O之选。
- 套接字描述符:Unix对网络的抽象是一种称为套接字的文件类型,被称为套接字描述符。应用进程通过读写套接字描述符来与运行在其他计算机上的进程通信。
- 对流I/O限制是:
- 跟在输出函数之后的输入函数,必须在其中间插入fflush、fseek、fsetpos或者rewind函数,后三个函数使用Unix I/O中的lseek函数来重置当前的文件位置。
- 跟在输入函数之后的输出函数,必须在中间插入fseek、fsetpos或者rewind的调用,一个输出函数不能跟随在一个输入函数之后,除非该输入函数遇到了一个EOF。
- 解决对流I/O限制的方法是:
- 采用在每个输入操作前刷新缓存区这样的规则来满足。
- 对同一个打开的套接字描述符打开两个流,一个用来读,一个用来写。
- 对套接字使用lseek函数是非法的。
- 在网络套接字上,使用RIO函数更常见。
教材学习中的问题
- flags参数:
- O_ CERAT:如果文件不存在就创建它的一个截断的(空)文件。
- O_TRUNC:如果文件已经存在,就截断它。
其中“截断”是什么含义?和删除的区别是什么?写文件时设置这样的标志位有何意义?
有时候我们需要在文件尾端处截取一些数据以缩短文件。文件清空为0,是一个特例。在打开文件时候使用O_TRUNC标志就可以做到这一点。文件没有被删除。
代码调试中的问题和解决过程
练习题10.1
-
一开始将代码输入进去运行时出现了一堆错误,如下图所示
-
后来通过上网查询发现,为了exit(0)能正常运行,要添加”stdlib.h”,此外还要将open所需的头文件加上,不然会返回出错。而且Open和Close不可以是大写。修改完成后代码如下图所示
-
成功运行结果如下图所示(注意 要提前添加两个txt文件)
大家可以试一下把close(fd1)去掉,也输出fd1看看,结果应该是fd1=3;fd2=4
本周代码托管截图
代码托管链接:https://git.oschina.net/senlinmilelu/IS20145218
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 3/4 | 18/38 | |
第三周 | 500/1000 | 4/7 | 22/60 | |
第四周 | 300/1300 | 4/9 | 30/90 | |
第五周 | 200/200 | 5/10 | 20/20 | |
第六周 | 300/500 | 6/11 | 18/38 | |
第七周 | 500/1000 | 7/12 | 22/60 | |
第八周 | 300/1300 | 8/13 | 30/90 | |
第九周 | 300/1300 | 9/14 | 30/90 |