2017-2018-1 20155339 《信息安全系统设计基础》第14周学习总结
教材学习内容总结
这周老师要求我们学的最差的一章,思来想去,决定写第十章,因为觉得第十章实践的较少再加上在我自己编写程序的过程中,每次只要与文件有关,就需要拿出教材翻阅才可以完成,所以借此机会弥补一下自己的这个不足。
输入/输出是在主存和外部设备(如磁盘驱动器、终端和网络)之间拷贝数据的过程。输入操作时从I/O设备拷贝数据到主存,而输出操作时从主存拷贝数据到I/O设备。
Unix I/O
一个Linux文件就是一个m个字节的序列。
所有的I/O设备,如网络、磁盘和终端,都被模型化为文件,而所有的输入和输出都被当做对相应的文件的读和写来执行。称为Unix I/O。
- 所有的输入输出都能以一种统一且一致的方式来执行:
1.打开文件;
2.Linux shell创建的每个进程开始时都有三个打开的文件;
3.改变当前文件的位置;
4.读写文件;
5.关闭文件。 打开文件。一个应用程序通过要求内核打开相应的文件来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,称为描述符,在后续对此文件的所有操作中标识这个文件。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2)。
- 头文件```<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。
- 读操作:从文件拷贝n>0个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。
- 写操作:从存储器拷贝n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件:应用在完成了对文件的访问之后,通知内核关闭文件,内核释放文件打开时的数据结构,恢复描述符,释放存储器资源。
在文件系统内,会对文件类型进行标记,以表明其种类。其中一种用来表示普通数据文件,人们常称之为"普通文件"或"纯文本文件",以示与其他种类的文件有所区别。其他文件类型包括设备、管道、套接字、目录以及符号链接。
目录可包含指向文件或其他目录的链接。路径间的链接建立起如图所示的目录层级。
其中每个目录的作用和意义如下图:
- 每个目录至少包含两条记录:.和..,前者是指向目录自身的链接,后者是指向其上级目录——父目录的链接。除根目录外,每个目录都有父目录。对于根目录而言,..是指向根目录自身的链接。
- 路径名是由一系列文件名组成的字符串,彼此以"/"分隔,首字符可以为"/"。
- 路径名应按从左至右的顺序阅读,路径名中每个文件名之前的部分,即为该文件所处目录。可在路径名中任意位置后引入字符串".." ,用以指代路径名中当前位置的父目录。
- 路径名描述了单根目录层级下的文件位置,又可分为绝对路径名和相对路径名:
1.绝对路径名以"/"开始,指明文件相对于根目录的位置。
2.相对路径名定义了相对于进程当前工作目录的文件位置,与绝对路径名相比,相对路径名缺少了起始的"/"。 - 可使用cd命令来改变shell的当前工作目录,如下图:
ls
以及ls -l
指令
- ls用颜色代表不同文件:蓝色表示目录,绿色表示可执行文件,红色表示压缩文件,浅蓝色表示链接文件,灰色表示其他文件。
ls -l
以长格式形式在每行显示一个目录或文件。- 为了访问文件,系统把用户分为3类:文件的属主(有时,也称为文件的用户)、与文件组(group)ID相匹配的属组成员用户以及其他用户。可为以上3类用户分别设置3种权限(共计9种权限位):只允许查看文件内容的读权限;允许修改文件内容的写权限;允许执行文件的执行权限。
在
ls -l
显示结果中,第一个字符表示文件的类型,如下图:
打开和关闭文件
- 打开一个已存在的文件或者创建一个新文件:
int open(char *filename,int flags,mode_t mode) (若成功则返回新文件描述符,若出错为-1)
- open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件:
1.O_RDONLY:只读;
2.O_WRONLY:只写;
3.O_RDWR:可读可写。 - flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示:
1.O_CREAT:如果文件不存在,就创建它的一个截断的(空的)文件;
2.O_TRUNC:如果文件已经存在,就截断它;
3.O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。 mode参数指定了新文件的访问权限位。这些位的符号名字如下图所示:
- 作为上下文的一部分,每个进程都是一个umask,它是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置为mode & ~umask。
关闭文件:
close()
函数:若成功则返回0,不成功则为-1。
#include<unistd.h>
int close(int fd); //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。否则,返回值表示的是实际传送的字节数量。
- write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。
- 教材上的小练习,代码如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
char c;
while(read(STDIN_FILENO,&c,1)!=0)
write(STDOUT_FILENO,&c,1);
exit(0);
}
运行结果如下:
一个练习从终端读数据再写回终端,代码如下:
#include <unistd.h>
#include <stdlib.h>
int main(void) {
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
运行结果如下:
- lseek函数:应用程序能够显式地修改当前文件的位置。
在某些情况下,read和write传送的字节比应用程序要求的要少,其原因如下:
1.读时遇到EOF。假设我们猪呢比读一个文件,该文件从当前文件位置开始只含有20多个字节,而我们以50个字节的片进行读取。这样一来,下一个read返回的不足值为20,此后的read将通过返回不足值0来发出EOF信号。
2.从终端读文本行。如果打开文件是与终端相关联的(如键盘和显示器),那么每个read函数将以此传送一个文本行,返回的不足值等于文本行的大小。
3.读和写网络套接字。如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。对Unix管道调用read和write时,也有可能出现不足值,这种进程间的通信机制不在我们讨论的范围之内。
用RIO包健壮地读写
- RIO包会自动处理不足值。
- RIO提供了两类不同的函数:
1.无缓冲的输入输出函数。这些函数直接在存储器和文件之间传送数据,没有应用级缓冲,他们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
2.带缓冲的输入函数。这些函数允许你高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内,类似于像printf这样的标准I/O函数提供的缓冲区。是线程安全的,它在同一个描述符上可以被交错地调用。
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_readn
函数从描述符fd的当前文件位置最多传送n和字节到存储器位置usrbuf。类似地,rio_writen
函数从位置usrbuf传送n个字节到描述符fd。rio_readn
函数在遇到EOF时只能返回一个不足值。rio_writen
函数绝不会返回不足值。对于同一个描述符,可以任意交错地调用rio_readn
和rio_writen
。
RIO的带缓冲的输入函数
- 包装函数
rio_readlineb
,它从一个内部读缓冲区拷贝一个文本行,当缓冲区变空时,会自动地调用read
重新填满缓冲区。对于既包含文本行也包含二进制数据的文件,我们也提供了一个rio_readn
带缓冲区的版本,叫做rio_readnb
,它从和rio_readlineb
一样的读缓冲区中传送原始字节。
#include "csapp.h"
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_t n);
- 每打开一个描述符,都会调用一次
rio_readinitb
函数。它将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来。 rio_readlineb
函数从文件rp中读出一个文本行,包括换行符,拷贝到存储器位置usrbuf,并用空字符结束这个文本行。最多读到maxlen-1个字节,最后一个给结尾的空字符。rio_readnb
函数从文件rp中读取最多n个字符到内存位置usrbuf中。- 一次一行地从标准输入复制一个文本文件到标准输出,代码如下:
#include "csapp.h"
int main(int argc,char **argv)
{
int n;
rio_t rio;
char buf[MAXLINE];
rio_readinitb(&rio,STDIN_FILENO);
while((n=rio_readlineb(&rio,buf,MAXLINE))!=0)
rio_writen(STDOUT_FILENO,buf,n);
}
- 运行结果如下:
读取文件元数据
- 应用程序能通过调用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成员编码了文件访问许可位和文件类型。
- Lnix提供的宏指令来确定st_mode成员的文件类型:
1.S_ISREG():普通文件;
2.S_ISDIR():目录文件;
3.S_ISSOCK():网络套接字。
读取目录内容
- 应用程序可以用readdir系列函数来读取目录的内容。
#include<sys/types.h>
#include<dirent.h>
DIR *opendir (const char *name);
- 函数
opendir()
以路径名为参数,返回指向目录流的指针。 - 相对应的就有
readdir()
函数:
#include<dirent.h>
struct dirent* readdir(DIR* dirp);
- 一个练习代码如下:
#include<stdio.h>
#include<dirent.h>
int main(void)
{
DIR *dirptr=NULL;
int i=1;
struct dirent *entry;
if((dirptr = opendir("."))==NULL)
{
printf("opendir failed!\n");
return 1;
}
else
{
while(entry=readdir(dirptr))
{
printf("filename%d=%s\n",i,entry->d_name);
i++;
}
closedir(dirptr);
}
return 0;
}
- 运行结果如下:
共享文件
- 内核用三个相关的数据结构来表示其打开的文件:
1.描述符表:表项由进程打开的文件描述符来索引的,每个打开的描述符表指向文件表中的一个表项,每个进程有其独立的描述符表。
2.文件表:打开文件的集合,所有的进程共享,包括当前的文件地址、引用计数及一个指向v-node中对应项的指针,相当于总表。
3.v-node表:所有进程共享这张表,包含stat结构中的大多数信息,包括st_mode和st_size成员。
上图中描述符1和4通过不同的打开文件表表项来引用两个不同的文件,这是一个典型的情况,是一个没有共享的例子。
- 上图阐释了多个描述符可以通过不同的文件表表项来引用一个文件。
而父子进程的共享则可以用下图表示
I/O重定向
Linux shell提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来,例如:
重定向使用dup2函数:
#include<unistd.h>
int dup2(int oldfd,int newfd);
//返回:成功返回非负的描述符,失败返回-1
练习题
- 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\n",fd2);
exit(0);
}
实践结果:
- 解析:Unix进程生命周期开始时,打开的描述符赋给了stdin(描述符0)、stdout((描述符1)和stderr(描述符2)。open函数总是返回最低的未打开的描述符,所以第一次调用open会返回描述符3,调用close函数会释放描述符3。最后对open的调用会返回描述符3,因此程序的翰出是“fd2=3”。
10.2 假设磁盘文件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\n",c);
exit(0);
}
实践结果:
- 解析:因为是不同的两个文件描述符,每个各自打开了各自的文件表表项,并不共享,所以互不影响,输出仍为f。
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\n",c);
exit(0);
}
实践结果:
- 解析:由于子进程会继承父进程的描述符表,因此父子进程打开的是同一个表项,因此无论谁操作下一个就会是在上一个操作的结果上继续操作,因此此处,子进程已经读取了1字节,父进程就在此基础上再读取1字节,因此输出为o。
- 10.4 如何用dup2将标准输入重定向到描述符5?
- 首先需要明确标准输入stdin描述符为0,所以应该是
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(fd2,&c,1);
dup2(fd2,fd1);
read(fd1,&c,1);
printf("c=%c\n",c);
exit(0);
}
实践结果:
解析:由于重定向了,所以相互影响,输出为o。
教材学习中的问题和解决过程
(一个模板:我看了这一段文字 (引用文字),有这个问题 (提出问题)。 我查了资料,有这些说法(引用说法),根据我的实践,我得到这些经验(描述自己的经验)。 但是我还是不太懂,我的困惑是(说明困惑)。【或者】我反对作者的观点(提出作者的观点,自己的观点,以及理由)。 )
- 问题1:重定向除了教材中指出的
ls > file
,之类的,还有哪些? - 问题1解决方案:上网搜索,还有如下图所示命令:
实践:
代码调试中的问题和解决过程
问题1:在实践自己编写的从终端读取数据并打印的代码中,当键入hello world 时就会出错,如下图:
- 问题1解决方案:上网搜索其原因,发现读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定,在此处hello调用read时睡眠等待,直到终端设备输入了换行符才从read返回,read只读走前几个字符,剩下的字符仍然保存在内核的终端设备输入缓冲区中。hello
进程打印并退出,这时Shell进程恢复运行,Shell继续从终端读取用户输入的命令,于是读走了终端设备输入缓冲区中剩下的字符d和换行符,把它当成一条命令解释执行,结果发现执行不了,没有d这个命令。在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞,可以打开设备文件/dev/tty(表示当前终端),在打开时指定 O_NONBLOCK标志。 问题2:在解决上述问题,编写非阻塞代码时,总是出现下图所示问题:
问题2解决方案:上网搜索,说是要匹配一个fcntl函数,修改并没有解决,也没有找到别的解决方法,待解决。
代码托管
(statistics.sh脚本的运行结果截图)
上周考试错题总结
- Y86-64中()指令没有访存操作.
A . rrmovl
B . irmovq
C . rmmovq
D . pushq
E . jXX
F . ret - 解析:A、B、E,xxmovl是一系列的数据传送指令,jxx
条件跳转指令。 - 有关磁盘操作,说法正确的是()
A . 对磁盘扇区的访问时间包括三个部分中,传送时间最小。
B . 磁盘以字节为单位读写数据
C . 磁盘以扇区为单位读写数据
D . 读写头总处于同一柱面 - 解析:A、C、D,磁盘上任何时候,所有的续写都位于同一柱面上。
- 有关RAM的说法,正确的是()
A .
SRAM和DRAM掉电后均无法保存里面的内容。
B .
DRAM将一个bit存在一个双稳态的存储单元中
C .
一般来说,SRAM比DRAM快
D .
SRAM常用来作高速缓存
E .
DRAM将每一个bit存储为对一个电容充电
F .
SRAM需要不断刷新
G .
DRAM被组织为二维数组而不是线性数组 - 解析: A C D E G
结对及互评
点评模板:
- 博客中值得学习的或问题:
- xxx
- xxx
- ...
- 代码中值得学习的或问题:
- xxx
- xxx
- ...
- 其他
本周结对学习情况
- [20155301](https://home.cnblogs.com/u/fengxingck/)
- 结对学习内容
- 对自己不足的内容进行了结对学习。
其他(感悟、思考等,可选)
对于自己比较薄弱的文件的内容进行了一次学习,相信对我以后的文件的编程会有所帮助。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 300/1300 | 2/9 | 30/90 | |
第五周 | 300/1300 | 2/9 | 30/90 | |
第六周 | 706/2006 | 1/10 | 50+/140+ | |
第七周 | 838/2838 | 1/11 | 23/163 | |
第八周 | 150/3088 | 2/13 | 40/203 | |
第九周 | 1235/4323 | 3/16 | 27/280 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
计划学习时间:20小时
实际学习时间:22小时
改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)