理解unix 系统I/O --csapp读书笔记

来源:互联网 发布:手机wifi控制器软件 编辑:程序博客网 时间:2024/06/06 00:22

昨天,面百度,其中有一个问题是:open的返回值是什么?从你调用open函数到Linux下出现了一个文件,这中间发生了什么?

第二个问题,当时就懵逼了,后来仔细想想,发现自己其实看过,其实讲文件描述符相关~为此,自己又重新看了APUE第二章,同时对应看了CSAPP第10章,因此做一个总结~


IO是什么?

i/o是input,output的缩写,也就是输入输出的意思。在计算机中,I/O就是在主存和外部设备(如磁盘,终端,网络)之间拷贝数据的过程。

输入:从IO设备拷贝数据到主存 IO->主存
输出:从主存拷贝数据到IO设备 主存->IO

在Unix/Linux系统中,我们可以使用由内核提供的系统调用函数实现IO,比如read,write。。。(由于Unix和Linux在很多地方都相似,我们下面统称Linux)

在Linux中,一个文件就是一个字节序列。而Linux有一个设计思想:Linux下一切皆文件

因此,我们上述所讲的IO设备(如磁盘,终端,网络)都可以看作是文件,因此所有的输入输出都可以看作是对文件的读和写,这样的一层抽象保证了所有的输入输出都能以一种统一和一致的方式来执行。

那么它是如何实现的呢?概括来说,它是通过一个叫文件描述符的东西来进行操作的,具体如下:

这里写图片描述

相关函数接口

一般来说,打开文件,读写文件,关闭文件,对文件操作等,只需要用到5个函数,open,read,write,lseek,close,我们也可以通过man手册来进行查阅~

ps:creat函数也可以用于创建文件,而open是打开一个已有文件或者创建一个新文件

ssize_t read(int fd, void *buf, size_t count);ssize_t write(int fd, const void *buf, size_t count);int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);int creat(const char *pathname, mode_t mode);off_t lseek(int fd, off_t offset, int whence);int close(int fd);

函数声明如上,具体函数是如何使用,CSAPP,APUE都有着详细的说明,本文不再赘述,我们重点谈谈上面所讲的文件描述符。

当我们利用open,creat函数创建一个文件成功,它们会返回一个整型数字,这就是文件描述符fd,后面所有的读写等操作都必须用到这个fd,我们看上面的函数,除了创建函数等,其他函数第一个参数都是fd也印证了我们所说的这一点。

共享文件

Linux系统支持在不同进程之间共享打开的文件,内核维护了3种数据结构表示打开的文件

  1. 文件描述符表
  2. 文件表
  3. v-node表

下面我们重点来讲一讲这三者的概念以及彼此的关系,这对我们理解文件IO,共享有着关键性的作用。

文件描述符表

每个进程都具有它独立的文件描述符表,每个描述符占有一项,与每个文件描述符相关联的是:

  1. 文件描述符标志
  2. 指向文件表的指针

其中,0,1,2分别对应标准输入,标准输出,标准错误,因此,从标准输入读可以使用read(0, buf, sizeof(buf))

这里写图片描述

文件表

内核为每个打开文件都维护一个文件表,所有进程都共享这张表。它的表项有很多,包括文件位置,引用计数(即当前有多少个指向该表的描述表项数),以及一个指向v-node中对应表项的指针。

关闭一个描述符(close(fd))会减少对应的文件表表项的引用计数,直到这个引用计数为0,内核才会删除这个文件件表项。

注意到:它的表项有很多,对于共享文件,我们主要关注引用计数,实际上APUE写的更为详细:

这里写图片描述

这里写图片描述

在此,我们忽略这些。

v-node表

每个打开文件(设备也是文件)都有一个v-node结构,这个v-node结点包含了文件类型等等。对于大多数文件,v结点还包含了该文件的i-node结点(索引结点),这些信息是打开文件时候从磁盘上读入内存的。

这里写图片描述

和文件表一样,所有进程共享这张v-node表。

三者关系

这里写图片描述

这里写图片描述

这里写图片描述

CSAPP给出几道题帮我们理解这些概念:

这里写图片描述

其中10.2题就对应第二张图,我们对同一个filename文件“foobar.txt”打开两次,就有两个文件描述符fd1,fd2,但是因为每个fd都有自己的文件表表项,因此对于foobar.txt都有它自己文件位置,所以放到
fd2读完,最后结果是c = f。

而10.3题则对应第三张图,子进程会继承父进程的描述符表,同时所有进程共享同一个打开文件表,因此父子进程都指向同一个打开文件表表项:

这里写图片描述

当子进程读取一个字符,文件偏移位置+1,父进程会读取第二个字节,因此结果是 c = o

重定向

在这篇理解重定向之dup,dup2博客里面我总结了重定向,同时给出如何实现重定向以及恢复重定向。

但是,我当时理解不够,所以有些地方不太准,因此,我在这儿对于某些不太准备的地方做出解释:

以dup2(oldfd, newfd)为例,它是将oldfd拷贝到newfd中,如果newfd打开就会先关闭newfd再拷贝fd。实际上呢,就是将newfd所对应的指针指向oldfd的指向的地方。

以dup(4,1)为例:

这里写图片描述


与C语言中标准IO的区别

ANSI也定义一组高级IO函数,称为标准IO库,也可以用来读写,比如printf,scanf,fread,fwrite等,那么它们有什么区别?该如何选择?

最主要的区别就是C语言读取文件自带一个缓冲区,而read,write这些是不带缓冲区的,实际上包括fread,fwrite这些c语言函数必然调用read这些系统调用。

标准C将文件抽象为流,所谓的流就是指向FILE*类型的指针,和unix类似,它也有三个流对应标准输入输出错误。

这里写图片描述

类似为FILE的流是对文件描述符和流缓冲区的抽象,所谓的缓冲区目的实际上就是尽可能少使用开销较大的系统调用,比如我们想用getc返回读取文件的下一个字符,我们可以只调用一次read来填充缓冲区,因此读取字符只需要到缓冲区内读,而非系统调用。

如何选择

实际上大部分时候,我们会选择标准IO,因为它自带缓冲区,不需要频繁使用系统调用(这是有代价的),从而提高效率,当然在某些时候需要用到fflush函数刷新缓冲区。

然而对于网络这样是不行的,前面我们说过Linux下一切皆文件,因此对于网络的IO,我们抽象为一种称为套接字的文件类型,和其他文件一样,对套接字文件操作也是基于文件描述符的,应用进程通过对套接字描述符读写与其他计算机上的进程通信。

然而不幸的是,标准IO和网络文件有很多不兼容性,因此我们更多的会去选择使用read,write这种低级IO。

原创粉丝点击