20145227《信息安全系统设计基础》第九周学习总结
20145227《信息安全系统设计基础》第九周学习总结
第十章 系统级I/O
unix i/o
一个Unix文件就是一个m个字节的序列,所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对应文件的读和写来执行。这被称为UnixI/O,使得所有的输入和输出能以一种统一且一致的方式来执行:
1、打开文件
应用程序向内核发出请求→要求内核打开相应的文件→内核返回文件描述符
Unix外壳创建的每个进程开始时都有三个打开的文件:
标准输入——0(STDIN_FILENO)
标准输出——1(STDOUT_FILENO)
标准错误——2(STDERR_FILENO)
2、改变当前的文件位置
对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。通过执行seek操作设置文件位置为k。
3、读写文件
(1)读操作
读操作就是从文件拷贝n>0个字节到存储器,并且改变文件当前位置。(如果当前位置是k,则改变为k+n)
EOF的来源:
文件结尾处没有明确的EOF信号,是当文件当前位置的数值超过了文件大小时,会处罚一个称为end-of-file的条件,能够被应用程序检测到,这就是所谓的EOF信号。
(2)写
写操作是从存储器拷贝n>0个字节到一个文件,然后更新当前文件位置。
打开和关闭文件
- open函数将filename转换为一个文件描述符,并且返回描述符数字。
打开一个已存在的文件或者创建一个新文件:
#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_RDWR:可读可写O_CREAT:文件不存在,就创建新文件
O_TRUNC:如果文件存在,就截断它
O_APPEND:写操作前设置文件位置到结尾处 -
mode:指定了新文件的访问权限位,符号名字如下所示:
- 关闭一个打开的文件:
#include <unisted.h>
int close(int fd)(若成功则为0,若出错则为-1)
返回值:成功返回0,出错返回-1
关闭一个已经关闭的描述符会出错
fd:即文件的描述符。
读和写文件
- 读文件:read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf。返回值表示实际传送的字节数量,错误返回-1,EOF返回0。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t n);
- 写文件:write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t n);
-
通过调用lseek函数,应用程序可以显示地修改当前文件的位置。
-
出现不足值(指在某些情况下,read和write传送的字节比应用程序要求的要少)的原因:
- 读的时候遇到EOF:文件末尾剩余的字节数不足读取文件的字节片大小。
- 从终端读文本行:若打开文件与终端相关联,则每个read函数将一次传送一个问本行。返回的不足值等于文本行的大小。
- 读和写socket:若打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。
用RIO包健壮地读写
1、RIO的无缓冲的输入输出函数
rio_readn函数和rio_writen函数,应用程序可以在存储器和文件之间直接传送数据:
#include "csapp.h"
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。
2.RIO的带缓冲的输入函数
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_readinitb函数,他将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来。
读取文件元数据
应用程序能通过调用stat和fstat函数,检索到关于文件的信息(元数据)。
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename, struct stat *buf);
int fstat(int fd,struct stat *buf);
返回值:成功为0,错误为-1
st_size:包含文件的字节数大小
st_mode:包编码文件访问许可位和文件类型。
-
普通文件包括某种类型的二进制或文本数据。
-
目录文件包含关于其他文件的信息。
-
套接字是一种用来通过网络与其他进程通信的文件。
共享文件
内核用三个相关的数据结构来表示其打开的文件:
-
描述符表:表项由进程打开的文件描述符来索引的,每个打开的描述符表指向文件表中的一个表项,每个进程有其独立的描述符表。
-
文件表:打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。包括:当前的文件位置、引用计数、以及一个指向v-node表中对应表项的指针。
-
v-node表:每个表项包含stat结构中的大多数信息,;包括st_mode和st_size成员,所有进程共享。
I/O重定向
- I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。
unix> ls > foo.txt
- I/O重定向是依靠dup2函数工作的。dup2函数拷贝描述符表表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开,dup2会在拷贝oldfd之前关闭newfd。
标准I/O
- 标准I/O库将一个打开的文件模型化为一个流,也就是一个指向FILE类型的结构的指针。
#include <stdio.h>
extern FILE *stdin; /*标准输入,文件描述符为0*/
extern FILE *stdout; /*标准输出,文件描述符为1*/
extern FILE *stderr; /*标准错误,文件描述符为2*/
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函数更常见。
遇到的问题和解决过程
问题:运行教材P598练习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>
- 由于csapp.c中包含线程的一部分,所以编译的时候要加上-lpthread选项,否则很多错误,此时编译运行:
- 还是出错了。按照提示将代码中的Open,Close换成了open和close,之后成功运行:
实践
main函数的定义:int main(int argc, char *argv[]){}
经查阅得知,argc是用来表示在命令行下输入命令时的参数个数,包括指令本身;argv[]是用来取得你输入的参数。
涉及到的头文件的用处:
stdio.h 标准输入输出
stdlib.h C标准函数库
unistd.h Unix类系统定义符号常量
fcntl.h 定义了很多宏和open,fcntl函数原型
sys/types.h 基本系统数据类型
dirent.h unix类目录操作的头文件,包含了许多UNIX系统服务的函数原型,例如opendir函数、readdir函数。
termios.h 在Posix规范中定义的标准接口
自己在虚拟机里实现了一些代码的功能,具体代码已经上传到码云中。以下是实践结果:
who
这个代码的思想是,从UTMP_FILE文件中读取想要的信息到存储器中,然后再用标准输出函数打印到屏幕上,最后关闭文件。
spwd
这个代码的功能是列出当前所在目录。结果如下图:
fileinfo
这个代码的功能是用来显示文件信息的,建立了一个stat数据结构。
- 先判断命令是否有操作数,有的话才能继续进行下去,如果没有报错就打印出来相关文件信息,报错就用perror将报错信息打印出来。
filesize
这段代码的功能是用st_size成员来计算文件的字节数大小。
本周代码托管链接
https://git.oschina.net/20145227/IS-Design-20145227/tree/master/ch09
本周代码总数
其他(感悟、思考等,可选)
这周学习的是Unix I/O,以及各种I/O包,对文件的打开和关闭、读和写等操作又有了更深的理解。学习中遇到的一个问题就是程序以及练习题代码中出现的csapp.h这个头文件,由于“csapp.h”这个是教材编写的头文件,并不是计算机自带的,要从网上下载下来才能测试代码。这一章的内容从页码上看起来很少,但是需要实践的代码还是很多的。很多代码我并不太懂是什么意思,查了很多资料才能大概有个了解,导致花费了很多时间。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0 | 2/2 | 20/20 | |
第二周 | 100/100 | 1/3 | 20/40 | |
第三周 | 200/300 | 1/4 | 22/62 | |
第五周 | 200/500 | 1/5 | 22/84 | |
第六周 | 274/774 | 1/6 | 22/106 | |
第七周 | 127/901 | 2/8 | 22/128 | |
第八周 | 50/951 | 2/10 | 22/150 | |
第九周 | 418/1369 | 2/12 | 22/172 |