2017-2018-1 20155339 《信息安全系统设计基础》第14周学习总结

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_readnrio_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_readnrio_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小时

  • 改进情况:

(有空多看看现代软件工程 课件
软件工程师能力自我评价表
)

参考资料

posted @ 2017-12-23 17:08  20155339平措卓玛  阅读(272)  评论(0编辑  收藏  举报