现在的位置: 首页 > 综合 > 正文

linux IO层以及相关的IO系统调用回顾

2013年08月27日 ⁄ 综合 ⁄ 共 3709字 ⁄ 字号 评论关闭

 

 

Linux下,I/O处理的层次可分为4层:

1、系统调用层,应用程序使用系统调用指定读写哪个文件,文件偏移是多少等等

2、文件系统层,写文件时将用户态中的buffer拷贝到内核态下,并由cache缓存该部分数据

            文件系统最上方时VFS,它是应用层和具体文件系统之间的接口,屏蔽不同文件系统的差异,以一种统一的方式给上层调用。

3、块层,管理块设备I/O队列,对I/O请求进行合并、排序

                在块层下其实有一个DM层。比如LVM,蔽了底层硬盘带来的复杂性使得文件系统和硬盘的关系更为灵活,可以做统一的进行磁盘管理,做动态的磁盘扩展,条带化,镜像,快照。

4、设备层,通过DMA与内存直接交互,将数据写到磁盘

这里先重点讲一下IO相关的系统调用,其他的层次后面再详解

IO层次的系统调用可以有多种方式

readahead:

需要读取一块数据的时候,如果后继的操作是连续读,可以在多读一些数据到page cache中,这样下次访问的连续数据的时候,这些数据已经在page cache中了,就无需I/O操作,这样会大大提高数据访问的效率

buffer io的read/write:

read过程:把需要读取得数据转换成对应的页,对需要读入的每一个页执行如下过程:首先调用page_cache_readahead(如果预读打开),根据当前预读的状态和执行预读策略(预读状态结构根据命中情况和读模式动态调整,预读策略也动态调整),预读过程会进行I/O操作也可能不会,预读过程完毕之后,首先检查page cache中是否已经有所需数据,如果没有,说明预读没有命中,调用handle_ra_miss调整预读策略,进行I/O操作把该页数据读入内存并加入page cache,当该页数据读入page cache之后(或者之前就在page
cache中),标记该页mark_page_accessed,然后把该页数据拷贝到应用程序地址空间。

write过程:和read过程一样,需要把需要写的数据转换成对应页,从应用程序地址空间把数据拷贝到对应页,并标记该页状态为dirty,调用mark_page_accessed,如果没有指定为同步写,写操作至此就返回了。如果文件在打开时指定了O_SYNC,系统会把本次写过程所有涉及到的dirty页回写到块设备中,这个过程是阻塞的

direct IO(文件以O_DIRECT方式打开进行,调用read/write,或者是操作原始设备):

应用程序在打开文件时指定了O_DIRECT,操作系统在读写文件时会完全绕过page cache,读的时候数据直接从块设备传送到应用程序指定的缓存中,写的时候数据也是直接从应用程序指定的缓存中写到块设备中,由于没有经过page cache层,这种方式的写总是同步写。

在操作原始(RAW)设备时,也是这种方式,直接访问原始设备,没有经过文件系统cache。

O_DIRECT 和 RAW设备最根本的区别是O_DIRECT是基于文件系统的,也就是在应用层来看,其操作对象是文件句柄,内核和文件层来看,其操作是基于inode和数据块,这些概念都是和ext2/3的文件系统相关,写到磁盘上最终是ext3文件。

而RAW设备写是没有文件系统概念,操作的是扇区号,操作对象是扇区,写出来的东西不一定是ext3文件(如果按照ext3规则写就是ext3文件)。

一般基于O_DIRECT来设计优化自己的文件模块,是不满系统的cache和调度策略,自己在应用层实现这些,来制定自己特有的业务特色文件读写。但是写出来的东西是ext3文件,该磁盘卸下来,mount到其他任何linux系统上,都可以查看。

而基于RAW设备的设计系统,一般是不满现有ext3的诸多缺陷,设计自己的文件系统。自己设计文件布局和索引方式。举个极端例子:把整个磁盘做一个文件来写,不要索引。这样没有inode限制,没有文件大小限制,磁盘有多大,文件就能多大。这样的磁盘卸下来,mount到其他linux系统上,是无法识别其数据的。

两者都要通过驱动层读写;在系统引导启动,还处于实模式的时候,可以通过bios接口读写raw设备。

mmap:

绕过文件系统,mmap映射某个文件(或者文件的一部分)到进程的地址空间时,并没有加载文件的数据,而只是在进程的虚拟地址空间划分出一块区域,标记这块区域用于映射到文件的数据区域,mmap的操作就完成了。

当进程试图读或者写文件映射区域时,如果没有对应的物理页面,系统发生缺页异常并进入缺页异常处理程序,缺页异常处理程序根据该区域内存的类型使用不同的策略解决缺页。对于使用mmap映射文件的虚拟内存区域,处理程序首先找到相关的文件的管理数据结构,确定所需页面对应的文件偏移,此时需要从文件中把对应数据加载到page_cache中。

“mmap与read/write两条路线对文件的访问比较
无论是通过mmap方式或read/write方式访问文件在内核中都必须经过两个缓存:一个是用a
ddress_space来组织的以页为基础的缓存;一个是以buffer来组织的缓存,但实际上这两
个缓存只是同一个缓冲池里内容的不同组织方式。当需要从文件读写内容时,都经过 add
ress_space_operation中提供的函数也就是说路径是一致的。
如果是用read/write方式,用户须向内核指定要读多少,内核再把得到的内容从内核缓冲
池拷向用户空间;写也须要有一个大致如此的过程。mmap的优势在于通过把文件的某一块
内容映射到用户空间上,用户可以直接向内核缓冲池读写这一块内容,这样一来就少了内
核与用户空间的来回拷贝所以通常更快。但 mmap方式只适用于更新、读写一块固定大小的
文件区域而不能做像诸如不断的写内容进入文件导到文件增长这类的事。

先说一下read/write系统调用,read/write系统调用会有以下的操作:
1.访问文件,这涉及到用户态到内核态的转换
2.读取硬盘文件中的对应数据,内核会采用预读的方式,比如我们需要访问100字节,内核实际会将按照4KB(内存页的大小)存储在page cache中
3.将read中需要的数据,从page cache中拷贝到用户缓冲区中

整个过程还是比较艰辛的,基本上涉及到用户内核态的切换,还有就是数据拷贝接下来继续说mmap吧,mmap系统调用是将硬盘文件映射到用内存中,说的底层一些是将page cache中的页直接映射到用户进程地址空间中,从而进程可以直接访问自身地址空间的虚拟地址来访问page cache中的页,这样会并涉及page cache到用户缓冲区之间的拷贝,mmap系统调用与read/write调用的区别在于:
1.mmap只需要一次系统调用,后续操作不需要系统调用
2.访问的数据不需要在page cache和用户缓冲区之间拷贝

从上所述,当频繁对一个文件进行读取操作时,mmap会比read高效一些。

不同于read,write系统调用的是,mmap的过程少了内核态copy数据到应用的开销。

sendfile:

sendfile把文件的从某个位置开始的内容送入另一个文件中(可能会是一个套接字),这种操作节省了数据在内存中的拷贝次数,如果使用read/write实现,会增加两次数据拷贝操作。其内核实现方法和read/write也没有太大区别

fsync/fdatasync/msync:

这三个系统调用都涉及把内存中的dirty page同步到的块设备上的文件中去,它们之间有一些区别。

fsync把文件在page cache中的dirty page写回到磁盘中去,一个文件在page cache中的内容包括文件数据也包括inode数据,当写一个文件时,除了修改文件数据之外,也修改了inode中的数据(比如文件修改时间),所以实际上有这两部分的数据需要同步,fsync把和指定文件相关的这两种dirty page回写到磁盘中。除了使用fsync强行同步文件之外,系统也会定期自动同步,即把dirty page回写到磁盘中。

Fdatasync只回写文件数据的dirty page到磁盘中,不回写文件inode相关的dirty page。

msync与fsync有所不同,在使用mmap映射文件到内存地址,向映射地址写入数据时如果没有缺页,就不会进入内核层,也无法设置写入页的状态为dirty,但cpu会自动把页表的dirty位置位,如果不设置页为dirty,其他的同步程序,如fsync以及内核的同步线程都无法同步这部分数据。msync的主要作用就是检查一个内存区域的页表,把dirty位置位的页表项对应的页的状态设置为dirty,如果msync指定了M_SYNC参数,msync还会和fsync一样同步数据,如果指定为M_ASYNC,则用内核同步线程或其他调用同步数据。

在munmap时,系统会对映射的区域执行类似msync的操作,所以如果不调用msync数据也不一定会丢失(进程在退出时对映射区域也会自动调用munmap),但写大量数据不调用msync会有丢失数据的风险。

 

抱歉!评论已关闭.