zoukankan      html  css  js  c++  java
  • 20145305 《信息安全系统设计基础》第9周学习总结

    教材学习内容总结

    第十章 系统级I/O

    输入/输出(I/O)是在主存和外部设备之间拷贝数据的过程。
    输入操作:从I/O设备拷贝数据到主存
    输出操作:从主存拷贝数据到I/O设备

    所有语言运行时系统都提供执行I/O的较高级别的工具,为什么还要学习Unix I/O呢?
    了解Unix I/O将帮助你理解其他的系统概念。I/O是系统操作不可或缺的一部分
    有时你除了使用Unix I/O以外别无选择

    10.1 Unix I/O

    一个Unix文件就是一个m个字节的序列,所有的I/O设备,都被模型化为文件,而所有的输入输出都被当作对相应文件的读和写来执行,Unix内核引出一个简单、低级的应用接口:Unix I/O。

    所有的输入输出都能以一种统一且一致的方式来执行:
    打开文件:一个应用程序想访问I/O设备时会要求内核打开相应的文件,内核返回一个小的非负整数,叫做描述符。
    它在后续对此文件的所有操作中标识这个文件。内核记录有关这个文件的所有信息,应用程序只需记住描述符。
    每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2)

    改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,文件位置是从文件开头起始的字节偏移量。
    应用程序能够通过执行seek操作,显示的设置文件的当前位置为k。

    读写文件:读操作是从文件拷贝n>0个字节到存储器,从当前位置k开始,将k增加到k+n
    当k>=m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。
    写操作就是从存储器拷贝n>0个字节到一个文件,从当前位置k开始,然后更新k。

    关闭文件:当应用完成对文件的访问,会通知内核关闭文件。
    作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。
    无论进程因何种原因终止,内核都会关闭所有打开的文件并释放他们的存储器资源。

    10.2 打开和关闭文件

    通过调用open函数来打开一个已存在的文件或创建一个新文件:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int open(char *filename,int flags,mode_t mode);
    

    返回:若成功则为新文件描述符,若出错为-1
    open函数将filename转换为一个文件描述符并返回。

    flags指明了进程打算如何访问这个文件:

    O_RDONLY:只读
    O_WRONLY:只写
    O_RDWR:可读可写
    

    flags参数也可以是一个或更多位掩码的或:

    O_CREAT:如果文件不存在就创建一个截断的(空)文件
    O_TRUNC:如果文件已经存在,就截断它。
    O_APPEND:每次写操作前,设置文件位置到文件的结尾处。
    

    每个进程都有一个umask,是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置为mode & ~umask

    进程通过调用close函数关闭一个打开的文件:

    #include <unistd.h>
    
    int close(int fd);
    

    返回:若成功则为0,若出错则为-1

    10.3 读和写文件

    应用程序是通过分别调用read和write函数来执行输入输出的:

    #include <unistd.h>
    
    ssize_t read(int fd,void *buf,size_t n);
    

    返回:若成功则为读的字节数,若EOF则为0,若出错为-1

    ssize_t write(int fd,const void *buf,size_t n);
    

    返回:若成功则为写的字节数,若出错则为-1

    read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf。

    write函数从存储器位置buf拷贝至多n个字节到描述符fd的当前文件位置。

    在某些情况下,read和write传送的字节比应用程序要求的要少,这些不足值不表示有错误,原因如下:

    读时遇到EOF
    
    从终端读文本行
    
    读和写网络套接字(socket)
    

    10.4 用RIO包健壮地读写

    RIO包会自动处理不足值,提供了方便、健壮和高效的I/O。RIO提供了两类不同的函数:
    无缓冲的输入输出函数:直接在存储器和文件之间传送数据,没有应用级缓冲。
    对二进制数据读写尤其有用。

    带缓冲的输入函数:高效的从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内。
    是线程安全的,它在同一个描述符上可以被交错的调用。

    10.4.1 RIO的无缓冲的输入输出函数

    通过调用rio_readn和rio_writen函数,应用程序可以直接在存储器和文件之间传送数据。

    #include "csapp.h"
    
    ssize_t rio_readn(int fd,void *usrbuf,size_t n);
    

    返回:若成功则为读的字节数,若EOF则为0,若出错为-1

    ssize_t rio_writen(int fd,void *usrbuf,size_t n);
    

    返回:若成功则为写的字节数,若出错则为-1

    rio_readn函数从描述符为fd的当前文件位置传送最多n个字节到存储器位置usrbuf。

    rio_writen函数从存储器位置usrbuf传送n个字节到描述符fd的当前文件位置。

    对同一个描述符,可以任意交错的调用上述两函数。

    如果rio_readn和rio_writen函数被一个从应用信号处理程序的返回中断,那么每个函数都会手动地重启read或write。

    10.4.2 RIO的带缓冲的输入函数

    文本行是由换行符结尾的ASCII码字符序列。

    包装函数rio_readlineb:从一个内部读缓冲区拷贝一个文本行,当缓冲区变空时,会自动地调用read重新填满缓冲区。

    rio_readn带缓冲区的版本

    rio_readnb:对于既包含文本行也包含二进制数据的文件,也是从读缓冲区中传送原始字节。

    #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);
    

    返回:若成功则为读的字节数,若EOF则为0,若出错为-1

    每打开一个描述符都会调用一次rio_readinitb函数

    将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来。

    rio_readlineb函数从文件rp读出一个文本行,包括结尾换行符,拷贝到存储器位置usrbuf,并用空字符来结束这个文本行。最多读maxlen-1个字节,余下的一个留给结尾的空字符。超过maxlen-1个字节的文本行被截断并用空字符结束。

    rio_readnb函数从文件rp最多读n个字节到存储器位置usrbuf。

    对同一描述符,可以任意交叉调用上述两函数。但带缓冲的不应和无缓冲的交叉使用。

    RIO读程序的核心是rio_read函数。

    rio_read函数是Unix read函数的带缓冲的版本。

    当调用rio_read函数要求读n个字节时,读缓冲区内有rp->rio_cnt个未读字节。
    如果缓冲区为空,会调用read填满它。
    一旦缓冲区非空,rio_read就从读缓冲区拷贝n和rp->rio_cnt中较小值个字节到用户缓冲区,并返回拷贝字节数。

    10.5 读取文件元数据

    应用程序能够通过调用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.

    stat函数以一个文件名作为输入,并填写一个stat数据结构中的各个成员。

    fstat函数是相似的,只不过是以文件描述符作为输入。

    st_size成员包含了文件的字节数大小
    st_mode成员编码了文件访问许可位和文件类型:普通文件/目录文件/套接字
    

    Unix提供的宏指令根据st_mode成员来确定文件类型:

    S_ISREG():这是一个普通文件吗?
    S_ISDIR():这是一个目录文件吗?
    S_ISSOCK():这是一个网络套接字吗?
    

    10.6 共享文件

    内核用三个相关的数据结构来表示打开的文件:
    描述符表:每个进程都有独立的描述符表,由进程打开的文件描述符来索引,每个打开的描述符表项指向文件表中的一个表项。

    文件表:所有进程共享,表项组成包括当前文件位置、引用计数、一个指向v-node表中对应表项的指针。引用计数为0时,内核会删除这个文件表表项。

    v-node表:所有进程共享,每个表项包含stat结构中的大多数信息。

    10.7 I/O重定向

    I/O重定向:使用dup2函数

    #include <unistd.h>
    
    int dup2(int oidfd,int newfd);
    

    返回:若成功则为非负的描述符,若出错则为-1

    dup2函数拷贝描述符表表项oldfd到newfd,覆盖newfd以前的内容,如果newfd已经打开了,dup2会在拷贝之前关闭newfd。

    10.8 标准I/O

    标准I/O库(libc):高级输入输出函数

    fopen/fclose:打开和关闭文件
    fread/fwrite:读和写字节
    fgets/fputs:读和写字符串
    scanf/printf:复杂格式化的I/O函数
    

    标准I/O库将一个打开的文件模型化为一个流,一个流就是一个指向FILE类型的结构指针。

    每个程序开始时都有三个打开的流:

    stdin:标准输入
    stdout:标准输出
    stderr:标准错误
    

    类型为FILE的流是对文件描述符和流缓冲区的抽象,流缓冲区的目的:使开销较高的Unix I/O系统调用的数量尽可能的小。

    附录A 错误处理

    错误处理包装函数:给定某个基本的系统级函数foo,定义一个有相同参数的包装函数Foo。包装函数调用基本函数并检查错误。如果发现错误就打印一条信息并终止进程,否则返回调用者。即如果没有错误,包装函数的行为与基本函数完全一样。

    包装函数被封装在一个源文件(csapp.c)中,被编译和链接到每个程序中。

    A.1 Unix系统中的错误处理

    1、Unix风格的错误处理

    if((pid = wait(NULL)) < 0) {
        fprintf(stderr,"wait error:%s
    ",strerror(errno));
        exit(0);
    }
    

    函数返回值既包括错误代码又包括有用的结果。如果遇到错误就返回-1,并将全局变量errno设置为指明错误原因的错误代码。如果成功就返回有用的结果。

    2、Posix风格的错误处理

    if((retcode = pthread_create(&tid,NULL,thread,NULL)) != 0) {
        fprintf(stderr,"pthread_create error:%s
    ",strerror(retcode));
        exit(0);
    }
    

    只用返回值来表明成功(0)或者失败(非0)。任何有用的结果都返回在通过引用传递进来的函数参数中。

    3、DNS风格的错误处理

    if((p = gethostbyname(name)) == NULL) {
        fprintf(stderr,"gethostbyname error:%s
    :",hstrerror(h_errno));
        exit(0);
    }
    

    在失败时返回NULL指针,并设置全局变量h_errno。

    4、错误报告函数小结

    #include "csapp.h"
    
    void unix_error(char *msg);
    void posix_error(int code,char *msg);
    void dns_error(char *msg);
    void app_error(char *msg);
    

    app_error函数是为了方便报告应用错误,它只是简单地打印它的输入,然后终止。

    A.2 错误处理包装函数

    Unix风格的错误处理包装函数

    Posix风格的错误处理包装函数

    DNS风格的错误处理包装函数

    实践过程

    WHO

    who命令是查询当前登录的每个用户,输出包括用户名、终端类型、登录日期及远程主机。
    man一下who,可以看到,who命令是读取/var/run/utmp文件来得到以上信息的。
    man一下utmp,知道utmp这个文件,是二进制文件,里面保存的是结构体数组,这些数组是struct utmp结构体的。

    伪代码:
    打开记录所在文件:utmp
    将文件中的记录逐条读取
    每一条读取的记录都要在屏幕上打印
    关闭文件

    实践的代码:

    老师的代码:

    问题和解决过程

    解决对齐问题:
    由于user的长度不一,导致错位,可以换成固定长度,不足补0的方法显示

    解决显示记录多的问题:
    utmp中保存的用户,不仅仅是已经登陆的用户,还有系统的其他服务所需要的,所以在显出所有登陆用户的时候,应该过滤掉其他用户,只保留登陆用户。在utmp结构中的ut_type可以区别,登陆用户的ut_type是USER_PROCESS,加一个判断就可以了

    代码托管

    代码托管连接

    学习进度条

    代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
    目标 5000行 30篇 400小时
    第一周 200/200 2/2 30/30
    第二周 300/500 1/3 20/50
    第三周 100/600 1/4 20/70
    第五周 300/900 1/5 30/100
    第六周 136/1036 1/6 20/120
    第七周 124/1160 1/7 20/140
    第八周 0/1160 3/10 20/160
    第九周 338/1498 1/11 25/185

    参考资料

  • 相关阅读:
    Eclipse 远程调试
    大数据处理方法bloom filter
    sicily 1259 Sum of Consecutive Primes
    sicily 1240. Faulty Odometer
    sicily 1152 简单马周游 深度优先搜索及回溯算法
    sicily 1050 深度优先搜索解题
    sicily 1024 邻接矩阵与深度优先搜索解题
    sicily 1156 二叉树的遍历 前序遍历,递归,集合操作
    sicily 1443 队列基本操作
    sicily 1006 team rankings 枚举解题
  • 原文地址:https://www.cnblogs.com/summerharper/p/6049153.html
Copyright © 2011-2022 走看看