20145221 《信息安全系统设计基础》第9周学习总结
系统级I/O
- 输入/输出(I/O)是在主存和外部设备之间拷贝数据的过程,输入操作是从I/O设备拷贝数据到主存,输出操作是从主存拷贝数据到I/O设备。
Unix I/O
- I/O设备:网络、磁盘和终端
- 描述符:打开文件时,内核返回一个小的非负整数。
- Unix外壳创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2)。
- 改变当前的文件位置:文件位置为k,初始为0。
- seek操作:显式地设置文件的当前位置为k。
-关闭文件:内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的存储器资源。
打开和关闭文件
- open函数:打开一个已存在的文件或者创建一个新文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename,int flags,mode_t mode);
- 对于open函数的说明:
- 返回值:open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
- flags:指明了进程打算如何访问这个文件
- mode:指定了新文件的访问权限位文件的访问权限位被设置为
mode & ~umask
- close函数:关闭一个打开的文件
#include <unistd.h>
int close(int fd);
读和写文件
- 应用程序是通过分别调用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表示一个错误;0表示EOF;否则,返回值表示的是实际传送的字节数量。
- 从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。返回值:若成功则为写的字节数,若出错则为-1。
- lseek函数:应用程序能够显式地修改当前文件的位置。
- 不足值:read和write传送的字节比应用程序要求的少。
- 读时遇到EOF
- 从终端读文本行
- 读和写网络套接字
用RIO包健壮地读写
- RIO包的实质:I/O包
- RIO包提供的两种函数:
- 无缓冲的输入输出函数
- 带缓冲的输入函数(线程安全)
RIO的无缓冲的输入输出函数
- 应用程序通过调用rioreadn和riowritten函数可以在存储器和文件之间直接传送数据。
#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);
- rio_ readn函数在遇到EOF时,只能返回一个不足值;
- rio_ writen函数绝不会返回不足值。
RIO的带缓冲的输入函数
- 一个文本行就是一个由换行符结尾的ASCII码字符序列。在Unix系统中,换行符(‘
')与ASCII码换行符(LF)相同,数字值为
0x0a
。 - 计算文本文件中文本行的数量,更好地方法是:
- 调用一个包装函数(rio、readlineb),它从一个内部读缓冲区拷贝一个文本行,当缓冲区变空时,会自动地调用read重新填满缓冲区。
#include "csapp.h"
//每打开一个描述符都会调用一次该函数,它将描述符fd和地址rp处的类型为rio_t的缓冲区联系起来。
void rio_readinitb(rio_t *rp,int fd);
//从文件rp中读取一个文本行(包括结尾的换行符),将它拷贝到存储器位置usrbuf,并用空字符来结束这个文本行。
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
//从文件rp中最多读n个字节到存储器位置usrbuf。对同一描述符,rioreadnb和rioreadlineb的调用可以交叉进行。
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);
读取文件元数据
- 检索文件信息(元数据):应用程序能够通过调用stat和fstat函数
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename,struct stat *buf);
//stat函数以一个文件名作为输入,填写一个stat数据结构中的各个成员。
int fstat(int fd,struct stat *buf);
//fstat函数以文件描述符而不是文件名作为输入。
- st_size成员包含了文件的字节数大小
- st_mode成员编码了文件访问许可位和文件类型
- Unix提供的宏指令根据st_mode成员来确定文件的类型
宏指令 | 描述 |
---|---|
S_ISREG() | 这是一个普通文件吗? |
S_ISDIR() | 这是一个目录文件吗? |
S_ISSOCK() | 这是一个网络套接字吗? |
共享文件
- 内核使用三个相关的数据结构来表示打开的文件:
- 描述符表:每个打开的描述符表项指向文件表中的一个表项
- 文件表:所有进程共享这张表,每个表项包括文件位置,引用计数,以及一个指向v-node表对应表项的指针
- v-node表:所有进程共享这张表,包含stat结构中的大多数信息
- 关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。
I/O重定向
-
Unix外壳提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。
unix> ls > foo.txt
-
、I/O重定向是依靠dup2函数工作的。
#include <unistd.h>
int dup2(int oldfd,int newfd);
- dup2函数拷贝描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。若newfd已经打卡了。dup2会在拷贝oldfd之前关闭newfd。
标准I/O
- 标准I/O库将一个打开的文件模型化为一个流,一个流就是一个指向FILE类型的结构的指针。每个ANSIC程序开始都有三个打开的流stdin、stdout和stderr,分别对应于标准输入、标准输出、标准错误。
- 类型为FILE的流是对文件描述符和流缓冲区的抽象。流缓冲区的目的和RIO读缓冲区的一样,就是开销较高的Unix I/O系统调用的数量尽量能的小。
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
这个头文件不是Linux系统自带的一个头文件,可能是作者自己定义的一个头文件,而我们又知道对于文件的操作需要许多许多的头文件,作者可能是为了方便表述以及编书的需要,将本章内容可能用到的头文件集合为一个头文件csapp.h
-
csapp.h
:下载地址 -
我们所需要的是
csapp.h
和csapp.c
这两个文件,下载完毕后进行如下操作即可 -
方法一:
- 将
csapp.h
和csapp.c
拷贝到与待编译代码10.1.c
同一个文件夹下 - 先对
csapp.c
和10.1.c
进行编译:gcc -c csapp.c 10.1.c
- 再进行链接:
gcc -o test csapp.o 10.1.o -lpthread
- 注意:由于csapp.c文件中有关于线程的部分,所以在使用gcc编译时必须加上选项
-lpthread
,否则会报错。
- 将
-
方法二:
- 将
csapp.h
和csapp.c
拷贝到/usr/include
目录下,因为是系统文件夹,不允许界面操作来复制文件,所以在终端中输入指令:sudo mv csapp.* /usr/include
,这样可以把2个文件同时给拷过去 - 打开csapp.h头文件,在#end if前面加上一句
#include <csapp.c>
,因为头文件要把csapp.c包含进去 - 一切准备就绪后,执行编译指令:
gcc 10.1.c -o test -lpthread
- 运行可执行文件test:
./test
- 将
-
方法三:
- 查看代码发现仅用到open函数和close函数,所以将
csapp.h
头去掉,换成俩函数应有的那几个头文件即可(同时须将Open和Close首字母改为小写,变为标准的Unix I/O) - 不过细看代码应该不难发现,源代码中其实不是open函数和close函数,而是Open和Close,这并不是源代码错了,在特别说明中我有详细说明原因。
- 查看代码发现仅用到open函数和close函数,所以将
-
个人认为第二种解决方法更好一些,将所需头文件拷贝到
include
文件夹中,以后就可以直接使用该头文件了,一劳永逸 -
最后还要补上2个文本文件:
baz.txt
和foo.txt
,得到正确输出结果3
,否则因为打开失败,返回结果为-1
-
特别说明:代码中的
Open函数
和Close函数
是不用更改的(包括后面所有函数均不用更改,源码全部正确),相关函数均在csapp.c中有定义,已经将Unix I/O封装好了。 -
相关操作的截图如下所示:
本周代码托管
-
本周托管代码:
-
代码统计如下(其中csapp.c下载的代码有820行):
其他(感悟、思考等,可选)
- 通过这一章的学习,了解到了Linux下是怎样操作文件,进行输入输出的,学习是可以比较windows下的C来学习,更好的理解相关代码的实现
- 同时,练习题10.1对于我们来说是一个很好的启发,作者通过自定义一个头文件,将I/O代码中繁琐的头文件全部“省去”,取而代之的只有一个“csapp.h”的头文件,但是这个头文件在功能上是可以替代原有的头文件的,这种思想值得我们学习借鉴。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | 学习了Linux常用命令 |
第二周 | 79/279 | 1/3 | 30/50 | 了解vim,gcc,gdb基本操作 |
第三周 | 182/461 | 1/4 | 25/75 | 更深层次了解信息处理 |
第四周 | 36/497 | 2/6 | 3/78 | 第二章知识简单的运用 |
第五周 | 194/691 | 1/7 | 28/106 | 汇编知识与了解逆向 |
第六周 | 520/1211 | 1/8 | 27/133 | Y86处理器,了解ISA抽象 |
第七周 | 85/1296 | 1/9 | 21/153 | 理解了局部性原理 |
第八周 | 0/1296 | 2/11 | 20/173 | 期中总结 |
第九周 | 234/1530 | 3/14 | 30/203 | 了解Linux是怎样操作文件 |