信息安全系统设计基础第九周学习总结

第十章 系统级I/O

10.1 Unix I/O

一个Unix文件就是一个m字节的序列,所有的I/O设备,都被模型化为文件,所有的输入和输出都被当作对相应的文件的读和写来执行。

Unix中输入和输出的执行:

  1.打开文件:应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个很小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关打开文件的所有信息,应用程序只需记住这个描述符。

  描述符:标准输入(0),标准输出(1),标准错误(2)

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

  3.读写文件:读操作就是从文件拷贝n>0个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。

        写操作就是从存储器拷贝n>0个字节到文件,从当前文件位置k开始,然后更新k。

  4.关闭文件:应用完成对文件的访问够,它通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池当中。

10.2 打开和关闭文件

打开文件:

  fd = Open("文件名", flag参数, mode参数)

  1.open函数将文件名转换为一个文件描述符,并返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符。

  2.flag参数指明了进程打算如何访问这个文件:只读(O_RDONLY)  只写(O_WRONLY) 读写(O_RDWR)

  3.flag参数也可以是一个或者更多位掩码的或,为写提供一些额外的指示:

    O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件

    O_TRUNC:如果文件已经存在,就截断它

    O_APPEND:在每次写操作前,设置文件位置到文件的结尾处

  4.mode参数指定了新文件的访问权限位。作为上下文的一部分,每个进程都有一个umask,它通过调用umask函数来设置,当进程通过带某个带mode参数的open函数用来创建一个新文件的时候,文件的访问权限位被设置为mode & ~umask

练习题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进程生命周期开始时已经调用了012这三个描述符,调用第一个Open函数返回了描述符3,调用Close释放了描述符3,调用第二个Open返回了描述符3,所以输出的是3

10.3 读和写文件

读文件:

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

  read函数从描述符为fd的当前文件位置拷贝最多n个字节到存储器位置buf,返回值-1表示一个错误,返回值0表示EOF,别的返回值表示实际传送的字节数量。

  EOF:从要读的文件开始读的位置到文件结束含有的字节数比要读的字节数少。

写文件:

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

  write函数从存储器位置buf拷贝最多n个字节到描述符fd的当前文件位置,返回值-1表示出错,成功的返回值是写的字节数。

10.4 用RIO包健壮的读写

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

直接在存储器和文件之间传送数据,没有应用级缓冲。

  ssizet rioreadn(int fd,const void *usrbuf,size_t n);

  ssizet riowriten(int fd,const void *usrbuf,size_t n);

  若成功返回值为传送的字节数,若出错返回值为-1,对于readn,若EOF则返回值为0.

10.4.2 RIO的带缓冲的输入函数

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

具体使用的函数:

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

    rio_readinitb(riot *rp,int fd);

    每打开一个描述符都会调用一次该函数,它将描述符fd和地址rp处的类型为rio_t的缓冲区联系起来。

  2.ssizet rio_readlineb(riot *rp,void *usrbuf,size_t maxlen);

    从文件的rp读出一个文本行(包括结尾换行符),将它拷贝到存储器位置userbuf,并且用空(零)字符结束这个文本行,超过maxlen-1字节的文本行被截断。

  3.ssizet rio_readnb(riot *rp,void *usrbuf,size_t n) ;

    从文件rp中最多读n个字节到存储器位置usrbuf。对同一描述符,rioreadnb和rioreadlineb的调用可以交叉进行。

  4.static ssize_t rio_read(rio_t *rp,char *usrbuf,size_t n)

  {
    int cnt;
    while(rp->rio_cnt<=0)//如果缓冲区为空
    {
      rp->rio_cnt=read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf));//调用read函数填满缓冲区
      if(rp->rio_cnt<0)
      {
        if(error != EINTR)//出错
        {
          return -1;
        }
      }
      else if(rp->rio_cnt=0)//EOF
      return 0;
      else
      rp->rio_bufptr = rp->rio_buf;//更新现在读到的位置
    }
    cnt=n;
    if(rp->rio_cnt<n)
    cnt=rp->rio_cnt;//将n与rp->rio_cnt中较小的值赋给cnt
    memcpy(usrbuf,rp->rio_bufptr,cnt);//将内部缓存区的内容拷贝到用户缓存区
    rp->rio_bufptr+=cnt;
    rp->rio_cnt-=cnt;
    return cnt;
  }

10.5 读取文件元数据

应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(也称元数据)。

  #include <unistd.h>

  #include <sys/stat.h>

  int stat(cost char *filename,struc sta *buf);

  int fstat(int fd,struct stat *buf);

  stat函数以一个文件名作为输入,fstat以文件描述符作为输入,并填写stat数据结构中的各个成员,st_size成员包含了文件的字节数大小,st_mode成员编译了文件访问许可位和文件类型。

  文件类型:普通文件:二进制或文本数据。

       目录文件:关于其他文件的信息。

       套接字:用来通过网络与其他进程通信的文件。

10.6 共享文件

内核用三个相关的数据结构来表示打开的文件:

  描述符表:每个进程都有独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述表项指向文件表中的一个表项。

  文件表:所有进程共享这张表。每个文件表的表项组成包括当前的文件位置,引用计数(即指向该表项的描述符表项数),一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数,内核不会删除这个文件表表项,直到它的引用计数为0.

  v-node表:所有进程共享这张表,每个表项包含stat结构中的大多数信息,包括st_size成员和st_mode成员。

文件的共享:

  1.没有共享:不同的描述符不同的打开文件表表项引用不同的文件

  2.文件共享:不同的描述符不同的打开文件表表象引用同一个文件,每个描述符都有自己的文件位置,对不同描述符的读操作可以从文件的不同位置获得数据。

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

fd1和fd2都有自己的打开文件表表项,每个描述对于foobar.txt都有自己的文件位置,fd2操作会读取foobar.txt第一个字节,并输出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);
}

子进程继承父进程的描述符表,所有的进程共享打开同一个文件表,描述符fd在父子进程中都指向同一个打开文件表表项,子进程读取文件的第一个字节时,文件位置为1,父进程会读取第二个字节,输出就是第二个字符o。

参考资料

教材

posted @ 2015-11-08 17:55  20125221银雪纯  Views(248)  Comments(0Edit  收藏  举报