2017-2018-1 20155331 《信息安全系统设计基础》第十四周学习总结
学习目标
找出全书你认为学得最差的一章,深入重新学习一下。
学习第十章教材内容学习总结
第十章 系统级I/O
输入/输出(I/O)是在主存和外部设备(如磁盘驱动器、终端和网络)之间拷贝数据的过程。输入操作是从I/O设备拷贝数据到主存,而输出操作是从主存拷贝数据到I/O设备。
10.1 UnixI/O
一个Unix文件就是一个m字节的序列:
B0,B1,B2,B3,……,Bk,Bm-1
所有的I/O设备,如网络、磁盘和终端。都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Unix内核引出一个简单、低级的应用接口,称为UnixI/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:
·打开文件
·改变当前的文件位置
·读写文件
·关闭文件
10.2打开和关闭文件
进程通过调用open函数来打开一个已存在的文件或者创建一个新文件的:R
open函数将filename转换成一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中没有打开的最小描述符。
Flags参数指明了进程打算如何访问这个文件:
? O_RDONLY:只读
? O_WRONLY:只写
? O_RDWR:可读可写
Flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示:
? O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
? O_TRUNC: 如果文件已经存在,就截断他。
? O_APPEN D:在每次写操作前,设置文件位置到文件的结尾处。
Mode参数指定了新文件的访问权限位。
最后进程通过调用close函数关闭一个打开的文件。
关闭一个已关闭的描述符会出错。
10.3读和写文件
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t n);/*返回:若成功则为读的字数,0为EOF,-1为出错*/
ssize_ write(int fd,const void *buf,size_t n);/*返回:若成功则为写的字节数,若出错则为-1*/
1.read函数
从描述符fd的当前文件位置拷贝n个字节到存储位置buf。
2.write函数
从存储器位置buf拷贝至多个n字节到描述符fd的当前文件位置
3.lseek函数
通过调用lseek函数,应用程序能够显式地修改当前文件的位置。
△不足值
read和write传送的字节比应用程序要求的要少。
出现情况如下:
?读时遇到EOF
?从终端读文本行 (返回不足值为文本行大小)
?读和写网络套字(socket)
1.内存缓冲约束和较长的网络延迟
2.Unix管道
应用程序是通过分别调用系统函数 read和write函数来执行输入和输出的。
旁注:size_t是作为usigned int,而ssize_t是作为int。
在某些情况下,read和write传送的字节比应用程序要求的要少。出现这种情况的可能的原因有:
读时遇到EOF。假设该文件从当前文件位置开始只含有20个字节,而应用程序要求我们以50个字节的片进行读取,这样一来,这个read的返回的值是20,在此之后的read则返回0。
从终端读文本行。如果打开的文件是与终端相关联的,那么每个read函数将一次传送一个文本行,返回的不足值等于文本行的大小。
读和写socket。如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会导致read和write返回不足值。
10. 4 用RIO包健壮地读写
RIO包会自动为你处理上文中所述的不足值。
RIO提供了两类不同的函数:
? 无缓冲的输入输出函数。
? 带缓冲的输入函数。
10.4.1 RIO的无缓冲的输入输出函数
通过调用rio_readn和rio_writen函数,应用程序可以在存储器和文件之间直接传送数据。
注意:如果rio_readn和rio_writen函数被一个从应用信号处理程序的返回中断,那么每个函数都会手动地重启read或write。
10.4.2 rio的带缓冲的输入函数 600
?一个文本行就是一个由 换行符 结尾的ASCII码字符序列。在Unix系统中,换行符是‘\n’,与ASCII码换行符LF相同,数值为0x0a。假设我们要编写一个程序来计算文本文件中文本行的数量应该如何来实现呢?
一种方法是用read函数来一次一个字节地从文件传送到用户存储器,检查每个字节来查找换行符。这种方法的问题就是效率不高,每次取文件中的一个字节都要求陷入内核。
一种更好的方法是调用一个包装函数(rio_readlineb),它从一个内部缓冲区拷贝一个文本行,当缓冲区变空时,会自动的调用read系统调用来重新填满缓冲区。
?在带缓冲区的版本中,每打开一个描述符都会调用一次rio_readinitb函数,它将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来。
?rio_readinitb函数从文件rp读取一个文本行(包括结尾的换行符),将它拷贝到存储器位置usrbuf,并且用空字符来结束这个文本行。
?RIO读程序的核心是rio_read函数,rio_read函数可以看成是Unix read函数的带缓冲区的版本。当调用rio_read要求读取n个字节的时候,读缓冲区内有rp->rio_cnt个未读的字节。如果缓冲区为空的时候,就会调用read系统函数去填满缓冲区。这个read调用收到一个不足值的话并不是一个错误,只不过读缓冲区的是填充了一部分。
?一旦缓冲区非空,rio_read就从读缓冲区拷贝n和rp->rio_cnt中较小值个字节到用户缓冲区,并返回拷贝字节的数目。
?对于应用程序来说,rio_read和系统调用read有着相同的语义。出错时返回-1;在EOF时,返回0;如果要求的字节超过了读缓冲区内未读的字节的数目,它会返回一个不足值。rio_readlineb函数多次调用rio_read函数。每次调用都从读缓冲区返回一个字节,然后检查这个字节是否是结尾的换行符。
?rio_readlineb函数最多读取(maxlen-1)个字节,余下的一个字节留给结尾的空字符。超过maxlen-1字节的文本行被截断,并用一个空字符结束。
10.5 读取文件元数据
应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(元数据)
#include
10. 6 共享文件
内核用三个相关的数据结构来表示打开的文件:
? 描述符表。
? 文件表。
? V-node表。
10.7 i/o重定向 608
?Unix外壳提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。
?I/O重定向的工作方式: 一种是使用dup2函数。
?dup2函数拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在拷贝oldfd之前关闭newfd。
10.8 标准I/O
标准I/O库:
打开和关闭文件的函数fopen fclose
读和写字节的函数fgets fputs
复杂格式化的I/O函数scanf printf
流:一个流就是一个指向FILE类型结构的指针
每个ANSI C程序开始时都有三个打开流:
stdio 标准输出
stdout 标准输入
stderr 标准错误
FILE类型流是对文件描述符和流缓冲区的抽象
流缓冲区的目的:
使开销较高的Unix I/O系统调用的数量尽可能小。
课后习题总结
10.7
修改图10-5中所示的cpfile程序,使得它用RIO函数从标准输
入复制到标准输出,一次MAX-BUF个字节。
问题解答:
根据要求写得算法程序:
#include "csapp.h"
int main(int argc, char **argv)
{
int n;
rio_t rio;
char buf[MAXBUF];
Rio_readinitb(&rio, STDIN_FILENO);
while((n = Rio_readnb(&rio, buf, MAXBUF)) != 0)
Rio_writen(STDOUT_FILENO, buf, n);
}
解答:STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调用。
操作系统一级提供的文件API都是以文件描述符来表示文件。STDIN_FILENO就是标准输入设备(一般是键盘)的文件描述符。
所以所得出的结论就是:输入输出改为了键盘的输入,由屏幕输出,使用RIO输入输出提高其健壮性。
问题解决:出现的这个问题在于MAXBUF的宏定义上,因为宏定义是8192个字节,故在短短的输入一点的字符是不会有相应的输出的,将宏定义改为10就好了。
10.8
编写图10-10中的statcheck程序的一个版本,叫做fstatcheck
,它从命令行上取得一个描述符数字而不是文件名。
问题解答:
依据要求写得如下代码:
/* $begin statcheck */
#include "csapp.h"
int main (int argc, char **argv)
{
struct stat stat;
char type, readok;
/* $end statcheck */
if (argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
exit(0);
}
/* $begin statcheck */
fstat(atoi(argv[1]), &stat);
if (S_ISREG(stat.st_mode)) /* Determine file type */
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) /* Check read access */
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}
/* $end statcheck */
解答:其实整个我们只需要将stat那句话改为: fstat(atoi(argv[1]), &stat);
但当然,如果需要加其他处理的话(比如判断参数对错,fd是否存在等等)。
10.9
问题解决:
Shell执行的代码应该是这样的:
if (Fork() == 0) {/* Child */
int fd = open("foo.txt", O_RDONLY, 0);
dup2(fd, 1);
close(fd);
Execve("fstatcheck", argv, envp);
}