先看一张图,稍后将围绕这张图展开描述。图中的fd table、open file table以及两个inode table均可以不用理解,只须要知道它们体现出来的文件描述符和磁盘文件之间的对应关系:文件描述符fd(例如图中的fd=3)是对应磁盘上文件的。node
在Linux下,咱们常常会在IO操做时不可避免的涉及到文件描述符,由于Linux下的全部IO操做都是经过文件描述符来完成的。可是,文件描述符是一个很是底层的概念,经过它操做的数据,都是二进制数据,因此经过文件描述符完成IO的模式一般也称为裸IO(Raw IO)。并且,直接经过底层的文件描述符进行编程会比较麻烦,由于是二进制数据,它缺乏不少功能,好比没法指定编码,没法指定换行符(换行符有多种:\n、\n\r、\r)等等。注意fd是用户空间的,它仅仅是一个数值而已,并非想象中感受比较底层就在内核空间。数据库
因此,现代高级语言(好比C、Python、Java、Golang)都提供了比文件描述符更高一层次的标准IO库,好比C的标准IO库是stdio,Python的标准IO库是IO模块,等等。使用这些标准IO库中的函数进行IO操做时,都会使用比文件描述符更高一层次的对象,例如C中称为IO流(io stream),其它面向对象的语言中通常称为IO对象,为了方便说明,这里统称为IO对象。上图中的F就是文件对象。编程
标准IO库能够看做是文件描述符的更高层次的封装,提供了比文件描述符操做IO更多的功能。例如,能够在IO对象上指定编码、指定换行符,此外还在用户空间提供了一个标准IO库的缓冲空间,一般可称为stdio buffer或IO buffer,而这些功能在文件描述符上都是没有的。另外,标准IO库既然是高层封装,固然也会提供用户不使用这些功能(好比不使用IO Buffer),而是直接使用文件描述符,那么这时候的文件对象就至关因而文件描述符了,这时候的IO操做模式也就是裸IO模式。函数
全部从硬件读取或写入到硬件的数据,默认都会通过操做系统维护的这个Kernel Buffer。正如上图中描述的是读数据过程。性能
例如,cat进程想要读取a.log文件,cat进程是用户空间进程,它自身没有权限打开文件以及读文件数据,它只能经过系统调用的方式陷入内核,请求操做系统帮助读取数据,操做系统读取数据后会将数据放入到page cache(刚才已说明,对于普通文件维护的Kernel buffer称为page cache或buffer cache)。而后还要将内核空间page cache中的数据拷贝到用户空间的IO Buffer缓冲空间(由于cat程序的源代码中使用了标准IO库stdio),而后cat进程从本身的IO Buffer中读取数据。这就是整个读数据的过程。优化
须要注意的是,虽然这两段缓冲空间都在内存中,但仍然有拷贝操做,由于内核的内存空间和用户进程的虚拟内存空间是隔离的,用户空间进程没有权限访问到内核空间的内存,可是内核具备最高权限,容许访问任何内存地址。换句话说,在将Kernel Buffer的数据拷贝到IO Buffer空间的过程当中,须要陷入到内核,OS须要掌控CPU。编码
此外,Linux也提供了所谓的直接IO模式,只需使用一个称为O_DIRECT的标记便可,这时会绕过Kernel Buffer,直接将硬件数据拷贝到用户空间。虽然看上去直接IO少了一个层次的参与感受性能会更优秀,但实际上并不是如此,操做系统为内核缓冲空间作了很是多的优化,使得并不会所以而下降性能。最典型且常见的一个优化是预读功能,它表示在读数据时,会比所请求要读取的数据量多读一点放入到Kernel Buffer,这样在下次读取接下来的一段数据时能够直接从Kernel Buffer中取数据,而无需再和硬件IO交互。因此,使用直接IO模式的场景是很是少的,通常只会在自带了完整缓冲模型的大型软件(好比数据库系统)上可能会使用直接IO模式。操作系统
上面所描述的都是读操做,关于写操做,这里再也不多花篇幅去描述,总体过程和读是相似的,都会通过IO Buffer和Kernel Buffer,只是其中一些细节有所不一样,若是感兴趣,能够阅读《Linux/Unix系统编程手册》的第13章。对象