本文先简单介绍应用程序对内存的使用以及I/O系统对内存的使用的基本原理,这对理解上述系统调用和库函数的实现有很大帮助。
1 内存管理基础
Linux对物理内存的管理是以页为单位的,通常页大小为4KB,Linux在初始化时为所有物理内存也分配了管理数据结构,管理所有物理页面。
每一个应用程序有独立的地址空间,当然这个地址是虚拟的,通过应用程序的页表可以把虚拟地址转化为实际的物理地址进行操作,虽然系统可以实现从虚拟地址到物理地址的转换,但并非应用程序的每一块虚拟内存都对应一块物理内存。Linux使用一种按需分配的策略为应用程序分配物理内存,这种按需分配是使用缺页异常实现的。比如一个应用程序动态分配了10MB的内存,这些内存在分配时只是在应用程序的虚拟内存区域管理结构中表示这一区间的地址已经被占用,内核此时并没有为之分配物理内存,而是在应用程序使用(读写)该内存区时,发现该内存地址对应得物理内存并不存在,此时产生缺页异常,促使内核为当前访问的虚拟内存页分配一个物理内存页。
一个已经分配给应用程序的物理页,在某些情况下也会被系统回收作为其他用途,比如对于上述的动态分配的内存,其内容可能被换到交换分区,系统暂时回收物理页面,当应用程序再次使用这个内存页时,系统再分配物理页面,把该页的内容从交换分区上换回到这个物理页,再重新建立页表映射关系。不同类型的虚拟内存页对应的物理内存的分配回收处理过程是不同的,在分析具体系统调用细节时,我们再做详细说明。
2 文件系统I/O原理
操作系统I/O部分不仅涉及到对普通块设备的操作,也涉及到对字符设备和网络设备的操作,本文只涉及对普通块设备的描述。
应用程序对文件的操作基本可以通过两种方式实现:普通的read/write和mmap方式,但这两种方式都并不是应用程序在读写文件内容时直接操作块设备(有一些特殊的例外情况),而是经过了操作系统层的page cache,即,无论应用程序以哪种方式读文件数据时,都是由操作系统把这部分文件数据加载到内核层,应用程序再通过不同的方式操作在内存中的文件数据,写的过程也一样,应用程序实际上只是把数据写到文件在内存中所对应的页上,然后在一定的时机或强行回写到块设备上。
我们需要对page cache作一些说明,page cache可以理解为对所有文件数据的缓冲,在一般情况下,对文件操作都需要通过page cache这个中间层(特殊情况我们下面会描述),page cache并不单单只是一个数据中转层,在page cache层,内核对数据做了有效的管理,暂时不使用的数据在内存允许的情况下仍然放在page cache中,所有的应用程序共用一个page cache,不同的应用程序对同一块文件数据的访问,或者一个应用程序对一块数据的多次访问都不需要多次访问块设备获得,这样就加快了I/O操作的性能。
不同的系统调用对文件数据的访问区别在于page cache之上对数据的访问方式的不