20145229《信息安全系统设计基础》第9周学习总结
教材学习内容总结
系统级I/O
- 输入/输出是在主存和外部设备之间拷贝数据的过程
- 所有语言的运行时系统都提供执行I/O的较高级别的工具,高级别I/O函数可以满足大多数情况,但是有时候除了使用Unix I/O别无选择
- I/O是系统操作不可或缺的一部分,要理解I/O,必须先理解进程
10.1 Unix I/0
- Unix I/O:一种将所有的I/O设备模型化为文件,将所有输入输出当作文件的读和写来执行,允许内核引出一个应用接口的方式
1.打开文件
应用程序要求内核打开文件表示它想访问一个I/O设备,内核返回一个描述符(非负整数)作为回应。描述符在之后的操作中标识这个文件,应用程序只需要记住标识符
每个进程开始打开的三个文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2).头文件定义的常量为:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。它们可以代替描述符
2.改变当前文件的位置
- 每一个打开的文件,都有一个文件位置K,初始为0,代表了文件起始地字节偏移量。应用程序可执行seek设置文件当前位置为k
3.读写文件
- 读文件就是从文件拷贝n个字节到存储器,从k到k+n。给一个m字节的文件,k>m时执行读会触发EFO,应用程序可以检测到,但是结尾不会标记出来
- 写文件就是从存储器拷贝n个字节到文件,从k开始,然后更新K
4.关闭文件
- 当应用完成后会通知内核关闭文件,内核会释放文件打开时的数据结构并将描述符放回可用状态。不管如何终止进程,内核都会关闭所有文件并释放存储器资源
10.2 打开和关闭文件
-
进程是通过调用open函数打开一个已存在的文件或者创建一个新文件
-
open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符数字是进程中没有打开的最小描述符。
-
flags参数:O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写 -
flags参数:0_CREAT:如果文件不存在,就创造它的截断的空文件
0_trunc:如果文件存在,截断
0_APPEND:每次操作之前,设置文件位置到文件结尾处 -
mode参数指定了文件的访问权限位,每个文件都有一个umask,通过调用umask设置
-
当文件通过带有umask的open函数创建文件,文件的访问权限为 mode &~umask
10.3 读与写文件
- 应用程序通过分别调用read和write函数来执行输入和输出
- ssize_t read(int fd, void *buf, size_t n);//0 EOF,-1 错误,n实际传送的字节数。
- ssize_t write(int fd, const void *buf, size_t n);// size_t是unsigned ssize_t有符号
- read进行读取的时候,返回-1代表错误,返回0代表EOF。否则返回的值代表实际传送的字节数量
- write进行输出的时候,通过调用lseek函数来显式的修改文件当前的位置
- 当read和write传送的字节比要求少时,这些不足值不表示有错误:
1.读取时遇到EOF,此后的read将用过返回值为0来发出EOF信号
2.从终端读文本行,一次传送一个文本行,不足值为文本行的大小
3.读和写网络套接字,内部缓冲约束和网络延迟会导致返回不足值;Unix调用read和write时也会返回不足值 - 实际上除了EOF都会遇到不足值。若需要强健的网络应用则需要反复调用read和write
ssize_t和size_t的区别
size_t是一个无符号int,ssize_t是一个有符号int,因为错误的时候必须返回-1,-1是一个有符号数,所以read函数返回必须用ssize_t,且这样的返回会使read的最大值从4GB减到2GB
10.4 用RIO健壮的读写
- RIO包将会处理不足值
- RIO包:
无缓冲的输入输出函数在:直接在存储器和文件中传送数据(二进制数据写到网络和从网络中读写二进制)
带缓冲的输入函数:高效的从文件中读取文本行和二进制数据,文件内容可缓存在应用级缓冲区 - 带有缓冲的RIO输入函数是线程安全的,在同一个描述符上可以被交替的调用
10.4.1 RIO无缓冲的输入输出函数
- 通过调用rio_readn和rio_writen函数,直接在存储器和文件中传送数据
- rio_readn遇到EOF时只能返回一个不足值,rio_writen不会返回不足值。同一个描述符可以任意交错调用rio_readn和rio_writen
- 若函数被程序的返回中断,函数会手动重启,我们允许被中断的系统调用,在必要的时候重启他们
10.4.2 RIO带缓冲的输入函数
- 一个文本行是一个由换行符结尾的ASCII码序列,换行符’ ‘与LF相同,数字值为0x0a
- 包装函数(rio_readlineb)从读缓冲区中读取文件,缓冲区空的时候自动调用read重新填满
- rio_readlineb将描述符和地址处的rio_t读缓冲区联系起来,rio_readlineb每次最多读maxlen-1,留一个给结尾的空字符,超过的被截断
- rio_readnb最多读n个字节。同一个描述符对rio_readlineb和rio_readnb可以交叉使用
- RIO读程序的核心是rio_read函数,rio_read是Unix read带缓冲区的版本,若缓冲区为空则read调用会收到不足值且不是错误。缓冲区非空的话,从缓冲区中拷贝并返回拷贝字节数
- 对于一个应用程序,rio_read和Unix read有同样的语意。出错时返回-1,并设置errno。EOF时返回0.若字节超过缓冲区未读字节,返回不足值。
10.5 读取文件元数据
- 应用程序通过调用stat和fstat来检索文件信息(元数据)
- stat以一个文件名作为输入,fstat用文件描述符作为输入
- st_size表示文件的大小,st_mode表示编译的文件许可位和文件类型。Unix识别文件类型。普通文件包括文本数据和二进制文件。目录文件包含其他文件的信息。套接字是用来通过网络和其他进程的文件
- Unix提供的宏指令根据st_mode确定文件的类型
10.6 共享文件
三种数据结构表示打开的文件:
-
描述符表。每个进程都有独立的描述符表
-
文件表。所有进程共享一个文件表。文件表由当前文件位置、引用计数、指向v-node表中对应的指针。内核不会删除表项,除非引用计数为0
-
v-node表:所有进程共享v-node表,表项包含stat的大多数信息
-
描述符各自引用不同的文件,没有共享文件
-
多个描述符通过不同的文件表表项引用同一个文件
-
子进程继承父进程打开文件
10.7 I/O重定向
- Unix外壳提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来
- 重定向工作方式:使用dup2函数
include<unistd.h>
int dup2(int oldfd,int newfd); - dup2函数拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述表表项newfd以前的内容。若newfd已经打开,dup2会在拷贝oldfd之前关闭newfd。
练习题
10.1
include "csapp.h"
int main()
{
int fd1,fd2;
fd1=Open("foo.txt",O_RDONLY,0);
Close(fd1);
fd2=Open("baz.txt",O_RDONLY,0);
printf("fd2=%d
",fd2);
exit(0);
}
unix进程生命周期开始的时候,就已经有了三个打开的描述符:标准输入、标准输出、标准错误,分别为0,1,2。所以fd2的值应该是3
10.3就像前面那样,磁盘文件foobar.txt由6个ASCII码字符“foobar”组成。那么,下列程序的输出是什么?
include "csapp.h"
int main()
{
int fd;
char c;
fd=Open("foobar.txt",O_RDONLY,0);
if(Fork()==0)
{
Read(fd,&c,1);
exit(0);
}
Wait(NULL);
Read(fd,&c,1);
printf("c=%c
",c);
exit(0);
}
父子进程共享相同的文件表表项,因此依次读取的是“f”和“o”。输出为o。
10.4 如何使用dup2将标准输入重定向到描述符5?
重定向标准输入(描述符0)到描述符5,可以调用dup2(5,0)进行重定向。
10.5假设磁盘文件foobar.txt由6个ASCII码字符“foobar”组成。那么,下列程序的输出是什么?
include "csapp.h"
int main()
{
int fd1,fd2;
char c;
fd1=Open("foobar.txt",O_RDONLY,0);
fd2=Open("foobar.txt",O_RDONLY,0);
Read(fd1,&c,1);
Read(fd2,&c,1);
printf("c=%c
",c);
exit(0);
}
描述符fd1和fd2都有各自的打开文件表表项,所以有它们各自的文件位置(也就是说互不影响,不会因为fd1先执行就使得fd2打开的文件位置推后)。则fd2打开文件读出的第一个字母还是f。
代码调试中的问题和解决过程
运行10.1的时候出现了错误
参考了论坛里卢肖明同学和高其同学解决的途径解决了
缺少csapp.h的头文件,这是书的作者编写的一个头文件,使用的时候要把此头文件csapp.h和csapp.c文件包含到你的系统中。先到网上下载这两个文件,下载地址(http://download.csdn.net/detail/tzasd89812/4206284);
在命令行下输入sudo mv csapp.h csapp.c /usr/include指令将文件移到/usr/include中;打开csapp.h头文件,在#end if前面加上一句#include <csapp.c>
本周代码托管截图
其他(感悟、思考等,可选)
这周的学习内容跟以前相比少了一点,本周学习了系统的输入、输出,了解了一些I/O函数,相较于之前几周感觉轻松不少。但是,页数少并不代表不重视,认真去理解系统的输入、输出对于我们理解程序的运行至关重要,只有掌握了其中的原理我们才能在实践中更加有意义。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 130/130 | 1/1 | 17/17 | |
第二周 | 90/270 | 1/1 | 16/16 | |
第三周 | 120/390 | 2/2 | 16/16 | |
第四周 | 89/479 | 1/1 | 17/17 | |
第五周 | 120/599 | 1/1 | 16/16 | |
第六周 | 110/709 | 1/1 | 18/18 | |
第七周 | 128/837 | 1/1 | 18/18 | |
第八周 | 5/842 | 1/1 | 16/16 | |
第九周 | 65/907 | 1/1 | 13/13 |