深入理解计算机系统(六):系统级IO

深入理解计算机系统(六):系统级I/O

​ ​ ​ ​ ​ 输入/输出(I/O)是在主存和外部设备(如磁盘驱动器、终端和网络)之间复制数据的过程。输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。

​​ ​ ​ ​ ​ 其实呢,C语言已经定义了一组高级的输入输出(标准I/O库,比Unix高级一些),这个标准库提供了打开/关闭文件的函数(fopen、fclose),读写字节的函数(fread、fwrite)、读和写字符串的函数(fgets、fputs),以及复杂的格式化IO函数(scanf和printf)。

​ ​ ​ ​ ​ 但实际上其还是基于Unix I/O实现的,Unix I/O模型实在操作系统内核中实现的。其实还有一种较高级别的RIO,你可以理解为是健壮I/O,不过本篇博客并不涉及,有兴趣的读者可以自行查阅资料,开篇我们先把这三种I/O包放张图来梳理和理解一下。

在这里插入图片描述

打开和关闭文件

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//返回值:若成功则为新文件的描述符,否则为-1
int open(char *filename,int flags,mode_t mode);

​ ​ ​ ​ ​ filename会转化为一个文件描述符fd,并且返回描述符数字。

​ ​ ​ ​ ​ flags参数指的是进程打算如何访问该文件,该参数也支持一个或者多个掩码的或,下面给出flags的计中取值:

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWR:可读可写
  • O_CREAT:如果文件不存在,就创建它的一个截断的空文件
  • O_TRUNC:如果文件存在,截断它
  • O_APPEND:在每次写操作前,设置文件位置到结尾处

​ ​ ​ ​ ​ 下面两种用法都是可以的:

fd=Open("foo.txt",O_WRONLY|O_APPEND,0);
fd=Open("foo.txt",O_WDONLY,0);

​ ​ ​ ​ ​ mode参数制定了新文件的访问权限位。

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

​ ​ ​ ​ ​ 当文件使用完毕,进程通过调用close函数关闭一个打开的文件,注意关闭一个已经关闭的描述符会出错

#include <unistd.h>

//返回值:成功为0,否则-1
int close(int fd);

读写文件

​ ​ ​ ​ ​ 读写文件是通过read函数和write函数来执行输入和输出的。

#include <unistd.h>

//返回值:若成功则为读的字节数,要是EOF,那么返回0,出错为-1
ssize_t read(int fd,void *buf,size_t n);
//返回值:若成功则为写的字节数,出错为-1
ssize_t write(int fd,const void *buf,size_t n);

read函数:从描述符为fd的当前文件位置复制最多 n n n个字节到内存位置buf。

write函数:从内存位置buf复制最多 n n n个字节到描述符为fd的当前文件位置。

​ ​ ​ ​ ​ 相信心细的小伙伴发现了一丝丝端倪,这两个函数的返回值是ssize_t类型,而它们的参数 n n nsize_t类型,那么这两个类型有什么关联呢?在x86-64系统中,size_t被定义为unsigned long,而ssize_t被定义为long。也就是说这两个函数返回的都是一个有符号的大小值,不是无符号的,因为出错的时候需要返回-1。

​ ​ ​ ​ ​ 在某些情况下,read和write传送的字节比应用程序要求的要。出现这样的情况有:

  • 读时遇到EOF。
  • 从终端读文本行。
  • 读和写网络套接字。

读取文件信息

​ ​ ​ ​ ​ 通过调用statfstat函数,可以检索到关于文件的信息。

#include <unistd.h>
#include <sys/stat.h>

int stat(const char*filename,struct stat *buf);
int fstat(int fd,struct stat *buf);

​ ​ ​ ​ ​ stat类型的数据结构主要会讨论其中的两个成员,一个是st_mode还有一个是st_size。其中前者表示文件的字节数大小,后者则表示文件访问权限位(mode参数可取的值)和文件类型,文件类型其实在第一节中也介绍了,在Linux中有3种,分别是普通文件、目录文件以及网络套接字。

下面通过一个小例子,展示一下如何使用宏和stat函数读取和解释一个文件的st_mode位。

#include <csapp.h>

int main(int argc,char **argv){
    struct stat stat;
    char *type,*readok;
    
    Stat(argv[1],&stat);
    if(S_ISREG(stat.st_mode))
        type="regular";
    else if(S_ISDIR(stat.st_mode))
        type="directory";
    else
        type="other";
    //check read access
    if((stat.st_mode & S_IRUSR))
        readok="yes";
    else
        readok="ok";
    printf("type:%s,read:%s\n",type,readok);
    exit(0);
}

读取目录内容

​ ​ ​ ​ ​ 通过readdir系列函数来读取目录的内容。

#include <sys/types.h>
#include <dirent.h>

//返回值:若成功,则为处理的指针,出错则返回null
DIR *opendir(const char *name);

​ ​ ​ ​ ​ 参数为路径名,返回值是指向目录流的指针。

#include <dirent.h>

//返回值:若成功,则为指向下一个目录项的指针,如果没有更多的目录项或者出错,返回null
struct dirent *readdir(DIR *dirp);

共享文件

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

  • 描述符表:每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
  • 文件表。所有进程共享文件表,文件表的表项组成包括当前的文件位置、引用计数,以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。知道引用计数为0,内核才会删除这个文件表。
  • v-node表。和文件表一样,所有的进程共享这张v-node表,每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。

I/O重定向

​ ​ ​ ​ ​ Linux shell提供了I/O重定向操作符,允许用户将磁盘文件和标注输入输出联系起来。

linux> ls>foo.txt

​ ​ ​ ​ ​ shell加载和执行ls程序的时候,会将标准输出重定向到磁盘文件foo.txt。I/O重定向一种实现方式是通过dup2函数。

#include <unistd.h>

//返回值:成功返回非负的描述符,出错则返回-1
int dup2(int oldfd,int new fd);

​ ​ ​ ​ ​ 很容易猜到,这个函数就是复制oldfd到newfd,覆盖newfd之前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值