Linux中直接IO机制的介绍
- 格式:pdf
- 大小:451.80 KB
- 文档页数:12
linux异步io实现方式Linux异步IO(Asynchronous I/O)是一种实现I/O操作的方式,它与传统的同步IO(Synchronous I/O)相比具有更高的效率和更好的性能。
本文将介绍Linux异步IO的实现方式。
Linux异步IO的实现方式主要有以下几种:多线程方式、信号方式、回调函数方式和事件驱动方式。
1. 多线程方式:在多线程方式中,主线程负责发起IO请求,然后创建一个或多个工作线程来处理这些请求。
主线程启动一个线程池,每个线程负责一个IO操作。
主线程将IO请求分配给空闲的工作线程,工作线程独立地进行IO操作。
这种方式的优点是简单易用,但需要管理线程池和线程间的同步和通信。
2. 信号方式:在信号方式中,主线程发起IO请求后,将信号设置为非阻塞模式,然后继续执行其他任务。
当IO操作完成时,内核会发送一个信号通知主线程。
主线程通过信号处理函数来处理完成的IO操作。
这种方式的优点是简单高效,但需要处理信号的并发性和可靠性。
3. 回调函数方式:在回调函数方式中,主线程发起IO请求后,将回调函数注册到内核中,并继续执行其他任务。
当IO操作完成时,内核会调用注册的回调函数来处理完成的IO操作。
这种方式的优点是灵活性高,但需要管理回调函数的注册和执行。
4. 事件驱动方式:在事件驱动方式中,主线程发起IO请求后,将IO事件添加到事件循环中,并继续执行其他任务。
事件循环会监听所有IO事件,并根据事件类型调用相应的处理函数。
这种方式的优点是高效灵活,但需要管理事件循环和事件处理函数。
总结起来,Linux异步IO的实现方式有多线程方式、信号方式、回调函数方式和事件驱动方式。
不同的方式适用于不同的场景,开发者可以根据实际需求选择合适的实现方式。
异步IO可以提高系统的并发性和性能,使系统能够更好地处理大量的IO操作。
libaio实现原理一、什么是libaiolibaio(Asynchronous I/O Library)是Linux内核提供的一个异步I/O接口库。
它允许应用程序以非阻塞的方式进行I/O操作,提高了I/O效率和系统的可伸缩性。
libaio可以在Linux系统上使用,尤其适用于需要高性能的服务器应用程序。
二、libaio的优势使用libaio的主要优势在于能够充分利用异步I/O的特性,提高系统的并发度和效率。
相比于传统的同步I/O,libaio具有以下几个优势:1.非阻塞操作:libaio支持非阻塞的I/O操作,应用程序可以在请求I/O之后立即开始进行其他工作,而不需要等待I/O操作完成。
这样可以充分利用CPU资源,提高系统的并发能力。
2.提高吞吐量:由于采用异步操作,libaio能够同时处理多个I/O请求,有效减少了I/O等待时间。
这样可以提高系统的吞吐量,减少用户等待的时间。
3.内核空间与用户空间之间的零拷贝:在进行I/O操作时,传统的方式是将数据从用户空间拷贝到内核空间,然后进行磁盘读写。
而libaio利用了Linux内核的直接I/O功能(Direct I/O),能够直接在用户空间和磁盘之间传输数据,避免了不必要的数据拷贝,提高了性能。
三、libaio的实现原理libaio的实现原理涉及到用户空间和内核空间之间的交互。
下面将详细介绍libaio的实现原理。
1. I/O Context在使用libaio进行异步I/O之前,首先需要创建一个I/O上下文(I/O Context)。
I/O Context是libaio维护并跟踪异步I/O请求的数据结构,所有的I/O请求都需要与一个I/O Context关联。
2. 事件驱动机制libaio采用了事件驱动的机制来处理异步I/O请求。
例如,当一个写入请求被提交之后,libaio会立即返回,并在写入完成后触发一个事件通知。
应用程序可以通过事件通知来获取I/O操作的结果。
io工作原理IO(输入/输出)是指计算机系统与外部设备进行信息交换的过程。
IO的工作原理主要包括以下几个步骤:1. 发送请求:当应用程序需要与外部设备交互时,它会发送一个IO请求给操作系统。
请求中包含了需要进行的IO操作(如读取、写入、打开、关闭等)以及相关的参数(如文件名、文件路径等)。
2. 调度处理:操作系统接收到IO请求后,会将其放入一个IO请求队列中进行调度处理。
调度算法根据一定的策略,如先进先出(FIFO)、优先级等,来确定下一个要处理的请求。
3. 总线传输:一旦某个IO请求被调度出队列,操作系统会将该请求发送给适当的设备控制器。
设备控制器负责控制外部设备的工作,将数据传输到或从外部设备中读取。
4. 缓冲处理:为了提高IO性能,计算机系统通常会使用缓冲区(Buffer)来暂时存储IO数据。
当IO设备读取或写入数据时,数据会首先存储在缓冲区中,然后根据需要进行处理。
5. 中断响应:设备控制器在数据传输完成或发生错误时会发出中断信号。
中断控制器接收到中断信号后,会通知操作系统有新的中断事件发生,并将控制权转交给相应的中断服务程序。
6. 数据交换:当IO设备完成数据传输后,系统会将数据从缓冲区中转移到应用程序的内存空间或文件系统中。
对于输入操作,数据会从设备控制器经过总线传输到缓冲区,然后移动到内存中。
对于输出操作,数据会从内存移动到缓冲区,然后经过总线传输到设备控制器输出到外部设备。
整个IO过程中,操作系统起到了协调和管理的作用,负责为应用程序提供统一的IO接口,调度IO请求,并处理中断事件。
外部设备和设备控制器负责实际的数据传输和处理。
通过这种方式,计算机系统实现了与外部设备的高效交互。
linux中iotop实现原理iotop是一个基于Linux系统的I/O监控工具,旨在帮助用户发现和分析系统中的I/O瓶颈和问题。
实现原理:当用户在Linux系统中执行iotop命令时,iotop工具会读取/proc目录下的I/O统计信息,并使用这些数据进行分析和显示。
具体来说,iotop会使用以下两个文件来获取I/O统计信息:1. /proc/diskstats:该文件中包含了磁盘的I/O统计信息,包括每个磁盘的读写操作次数、读写字节数、块设备队列长度等数据。
2. /proc/self/io:该文件中包含了当前进程的I/O统计信息,包括进程的读写操作次数、读写字节数、读写操作等待时间等数据。
通过读取这些文件,iotop可以获得系统中每个进程的I/O使用情况(包括磁盘的I/O使用情况),并将其按照I/O使用率进行排序和显示。
同时,iotop还可以通过参数的设置,实现不同维度的I/O监控,例如:-显示每个进程的I/O使用情况(默认模式);-显示每个磁盘的I/O使用情况(使用-d参数);-显示I/O使用情况的累计值(使用-a参数);-显示I/O使用情况的实时变化(使用-P参数)等。
总之,iotop通过读取系统中的I/O统计信息,结合各种参数的设置,实现了对系统中I/O使用情况的全面监控和分析。
总结:Iotop工具是一个基于Linux系统的I/O监控工具,通过读取/proc目录下的I/O统计信息来实现对系统中I/O使用情况的全面监控和分析。
其主要实现原理是通过读取/proc/diskstats和/proc/self/io等文件来获取系统中每个进程和磁盘的I/O使用情况,并将其排序和显示。
同时,通过参数的设置,可以实现不同维度的I/O监控。
Linux内核中_IO,_IOR,_IOW,_IOWR宏的⽤法与解析refmain在驱动程序⾥, ioctl() 函数上传送的变量 cmd 是应⽤程序⽤于区别设备驱动程序请求处理内容的值。
cmd除了可区别数字外,还包含有助于处理的⼏种相应信息。
cmd的⼤⼩为 32位,共分 4 个域:bit31~bit30 2位为 “区别读写” 区,作⽤是区分是读取命令还是写⼊命令。
bit29~bit15 14位为 "数据⼤⼩" 区,表⽰ ioctl() 中的 arg 变量传送的内存⼤⼩。
bit20~bit08 8位为 “魔数"(也称为"幻数")区,这个值⽤以与其它设备驱动程序的 ioctl 命令进⾏区别。
bit07~bit00 8位为 "区别序号" 区,是区分命令的命令顺序序号。
像命令码中的 “区分读写区” ⾥的值可能是 _IOC_NONE (0值)表⽰⽆数据传输,_IOC_READ (读), _IOC_WRITE (写) , _IOC_READ|_IOC_WRITE (双向)。
内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助⽣成上⾯的 cmd 。
下⾯分析 _IO() 的实现,其它的类似:在 asm-generic/ioctl.h ⾥可以看到 _IO() 的定义:#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)再看 _IOC() 的定义:#define _IOC(dir,type,nr,size) \(((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT) | \((nr) << _IOC_NRSHIFT) | \((size) << _IOC_SIZESHIFT))可见,_IO() 的最后结果由 _IOC() 中的 4 个参数移位组合⽽成。
linux中select、poll、epoll原理select、poll和epoll是Linux下常用的I/O多路复用技术,都用于实现高效的事件驱动型的网络编程。
1. select(选择)select是最古老的I/O多路复用机制,它通过在套接字上设置阻塞(阻塞方式)进行等待,一旦有文件描述符准备就绪(可读、可写等),则返回。
select使用fd_set集合来保存要监听的文件描述符,因此其监听的文件描述符数量受到系统给定的FD_SETSIZE限制。
select的实现原理是:在内核中创建一个称为“等待队列”的数据结构(fd_set),该队列保存了需要等待的文件描述符,当某个文件描述符就绪时,会通过和用户进程的映射表通知用户进程。
select通过轮询所有注册的文件描述符,检查哪些文件描述符已经准备好,并将准备好的文件描述符从用户态拷贝到内核态。
select的缺点是每次调用都需要轮询全部的注册文件描述符,效率较低。
2. poll(轮询)poll是在select的基础上进行改进的多路复用技术。
poll与select的最大区别在于,它没有限制文件描述符的数量,并且使用了一个pollfd结构体数组来保存每个文件描述符及其关注的事件。
poll在内核中创建一个称为“等待队列”的数据结构,该队列保存了需要等待的文件描述符,当某个文件描述符就绪时,会通过和用户进程的映射表通知用户进程。
poll的实现原理是:将用户进程注册要监听的文件描述符及其关注的事件存储在内核中的一个事件表中,当发生事件时,内核会将该事件存储在内核态的事件表中,并通知用户进程。
与select不同的是,poll只需在事件发生时拷贝某些信息到内核态,而不需要拷贝全部的文件描述符。
poll的缺点是,当注册的文件描述符数量较大时,每次调用poll都需要遍历整个事件表,效率较低。
3. epoll(事件通知)epoll是Linux特有的一种I/O多路复用机制,通过内核与用户空间的共享内存来实现高效的事件通知。
linux内核io error处理-回复Linux内核的I/O Error处理====================引言在操作系统中,I/O(输入/输出)操作是非常常见且重要的。
无论是在用户级或是内核级,I/O操作都需要进行错误检测和处理,以保证系统的稳定性和可靠性。
本文将围绕着Linux内核的I/O Error处理展开,详细介绍其工作原理和处理过程。
I/O Error的发生-I/O Error指的是在进行输入/输出操作时发生的错误。
它可能由多种原因引起,例如硬件故障、数据传输错误、文件系统损坏等等。
当I/O Error 发生时,内核会抛出一个错误码,表示具体的错误类型。
对于Linux内核而言,错误码通常用负数表示。
为了有效地处理这些I/O Error,Linux内核提供了一套完善的错误处理机制。
中断处理中断是Linux内核中处理I/O Error的重要手段之一。
当发生I/O Error时,硬件会产生一个中断信号来告知系统。
内核会立即停止当前的执行并跳转到一个中断处理函数中。
该中断处理函数负责处理该中断,并对系统进行错误处理。
中断处理函数是由内核开发者编写的一段代码,用于接收和响应中断信号。
为了使中断处理函数能够正常运行,内核必须首先注册该函数。
通常情况下,内核会在系统启动时进行中断处理函数的注册。
中断处理函数的主要任务是识别并处理I/O Error。
它首先会检查中断原因,即确定该中断是由何种错误引起的。
然后,根据错误类型进行相应的处理。
处理方式可以是打印错误信息、重试操作、恢复系统状态或者报告错误并终止操作。
错误处理机制-除了中断处理函数,Linux内核还提供了其他一些错误处理机制。
当发生I/O Error时,这些处理机制会根据具体的错误类型进行相应的操作。
一种常见的错误处理机制是重试。
当发生I/O Error时,内核会尝试重新执行相同的操作。
这通常是因为错误是由于临时的问题引起的,例如传输错误或设备暂时不可用。
linux 驱动的ioctl 详细说明摘要:1.概述ioctl 的作用和用法2.ioctl 的错误码及含义3.ioctl 的参数4.ioctl 的返回值及意义5.ioctl 在Linux 声卡驱动中的应用正文:一、概述ioctl 的作用和用法ioctl(input/output control)是Linux 系统中一种用于设备控制的系统调用,通过ioctl,用户进程可以对设备进行配置、查询和控制等操作。
ioctl 的用法通常为:```int ioctl(int fd, int request,...);```其中,fd 表示设备的文件描述符,request 表示设备驱动程序所支持的控制请求,后面的省略号表示可能的附加参数。
二、ioctl 的错误码及含义ioctl 系统调用可能返回以下错误码:- -1:表示发生了错误,此时errno 系统变量将包含具体的错误码。
- 0:表示操作成功完成。
- 其他大于0 的值:表示设备的某些特殊状态,具体含义需根据设备类型和驱动程序来确定。
三、ioctl 的参数ioctl 的参数主要包括以下几类:1.设备文件描述符fd:表示要控制的设备的文件描述符。
2.控制请求request:表示要执行的设备控制操作,如配置、查询、控制等。
3.附加参数:根据设备类型和控制请求的不同,可能需要提供不同的附加参数。
这些参数通常是设备驱动程序所支持的数据结构或整数变量。
四、ioctl 的返回值及意义ioctl 的返回值表示设备驱动程序处理控制请求的结果。
如果返回值为-1,则表示发生了错误;如果返回值为0,则表示操作成功完成;如果返回值为其他大于0 的值,则表示设备的某些特殊状态。
具体的错误码和含义可以通过errno 系统变量获取。
五、ioctl 在Linux 声卡驱动中的应用在Linux 声卡驱动中,ioctl 被广泛应用于配置声卡设备、查询声卡状态、控制声音播放等。
例如,通过ioctl 可以实现以下功能:- 获取声卡设备的信息,如设备型号、支持的采样率等。
io 多路复用机制IO 多路复用机制是指在一个线程内同时监控和处理多个IO事件的一种机制。
它可以大大提高系统的IO效率,节省IO资源,也可以减少服务器的开销。
本文将分步骤阐述IO多路复用机制的原理及其运作过程。
一、IO多路复用的基本知识1.1 IO多路复用的定义IO多路复用是一种高效的I/O模型,允许同时监视多个I/O事件并在就绪时立即处理它们。
它减少了线程/进程阻塞等待I/O操作完成的时间。
1.2 IO多路复用的实现方式常见的IO多路复用实现方式有:select ,poll 和 epoll。
其中,epoll是Linux 2.6内核的新IO多路复用机制。
它比赛和其他I/O多路复用机制的效率,尤其是在连接数量较大的情况下。
二、IO多路复用的工作原理2.1 应用程序发起IO请求当应用程序需要进行IO操作时,它会在内核中注册一个文件描述符,并将其加入到IO多路复用机制监视的事件列表中。
此时,该应用程序可以进行其他事情,而无需等待IO操作完成。
2.2 内核监视多个IO事件内核会持续监视多个IO事件,包括读写事件和异常事件。
当任意一个事件就绪时,内核会通知应用程序已经就绪的文件描述符并返回对应的事件,应用程序就可以进行操作。
2.3 应用程序处理IO操作一旦应用程序收到IO事件的通知,它可以调用相应的函数立即处理该IO操作。
如果事件仍未就绪,则应用程序将被挂起,直到该事件就绪并返回IO事件的通知。
三、IO多路复用的优点3.1 节约资源和提高效率使用IO多路复用机制,一个线程可以同时监控多个IO事件,而不需要为每个IO事件分配一个线程,这减少了线程的数量,节约了内存资源。
另外,由于IO多路复用机制可同时处理多个IO事件,因此可以大大提高系统的IO效率。
3.2 灵活性和扩展性IO多路复用机制设计的灵活性较高,可以在一个线程中同时处理网路事件和文件事件。
并且,IO多路复用机制的扩展性比较强,可以自定义事件类型,便于实现更加复杂的功能。
linux常见io调度算法在Linux操作系统中,IO调度算法被用来优化磁盘IO的性能和效率。
当多个进程同时发起IO请求时,IO调度算法决定了这些IO请求的处理顺序,以提高系统的整体性能。
常见的Linux IO调度算法包括:1. Completely Fair Queuing (CFQ):CFQ是Linux内核默认的IO调度算法。
它将IO请求放入不同的队列中,并根据进程的优先级和历史IO行为,以公平的方式分配磁盘IO资源。
它相对于其他调度算法来说,更适用于多任务环境,能够保证每个进程都能够获得公平的IO延迟。
2. Deadline:Deadline算法将IO请求放入读队列和写队列,并根据截止期限来决定哪个请求先被处理。
读请求的截止期限相对较短,写请求的截止期限相对较长。
这种算法能够确保IO 请求在一定时间内得到满足,同时提供更好的响应时间和吞吐量。
3. Noop:Noop算法是一种简单的IO调度算法,它不进行任何调度,只是按照请求的顺序进行处理。
这种算法适用于那些不需要复杂调度的高性能存储系统,如固态硬盘(Solid State Drive, SSD)。
4. Anticipatory:Anticipatory算法通过预测进程的IO行为来进行调度。
当一个请求到达时,它会估计下一个请求的位置,并尝试将磁盘头移动到正确的位置,以减少寻道时间。
这种算法适用于那些读写访问比较复杂的应用,如数据库系统。
5. Budget Fair Queuing (BFQ):BFQ是一种较新的IO调度算法,它在CFQ的基础上进行了改进。
它通过调度进程级IO请求而不是单个进程的请求,以实现更好的公平性和延迟保证。
BFQ 算法与CFQ算法相比,能够更好地应对高吞吐量和低延迟要求。
选择适合的IO调度算法需要考虑系统的具体需求和硬件环境。
一般来说,CFQ算法适用于大多数使用场景,但对于高吞吐量和低延迟要求的应用,可以考虑使用Deadline或BFQ算法。
Linux 中直接 I/O 机制的介绍/developerworks/cn/linux/l-cn-.../developerworks/cn/linux/l-cn-...当应用程序需要直接访问文件而不经过操作系统页高速缓冲存储器的时候,它打开文件的时候需要指定 O_DIRECT 标识符。
操作系统内核中处理 open() 系统调用的内核函数是 sys_open(),sys_open() 会调用 do_sys_open() 去处理主要的打开操作。
它主要做了三件事情:首先,它调用 getname() 从进程地址空间中读取文件的路径名;接着,do_sys_open() 调用get_unused_fd() 从进程的文件表中找到一个空闲的文件表指针,相应的新文件描述符就存放在本地变量 fd 中;之后,函数do_filp_open() 会根据传入的参数去执行相应的打开操作。
清单 1 列出了操作系统内核中处理 open() 系统调用的一个主要函数关系图。
清单 1. 主要调用函数关系图sys_open()|-----do_sys_open()|---------getname()|---------get_unused_fd()|---------do_filp_open()|--------nameidata_to_filp()|----------__dentry_open()函数 do_flip_open() 在执行的过程中会调用函数 nameidata_to_filp(),而 nameidata_to_filp() 最终会调用 __dentry_open()函数,若进程指定了 O_DIRECT 标识符,则该函数会检查直接 I./O 操作是否可以作用于该文件。
清单 2 列出了 __dentry_open()函数中与直接 I/O 操作相关的代码。
清单 2. 函数 dentry_open() 中与直接 I/O 相关的代码if (f->f_flags & O_DIRECT) {if (!f->f_mapping->a_ops ||((!f->f_mapping->a_ops->direct_IO) &&(!f->f_mapping->a_ops->get_xip_page))) {fput(f);f = ERR_PTR(-EINVAL);}}当文件打开时指定了 O_DIRECT 标识符,那么操作系统就会知道接下来对文件的读或者写操作都是要使用直接 I/O 方式的。
下边我们来看一下当进程通过 read() 系统调用读取一个已经设置了 O_DIRECT 标识符的文件的时候,系统都做了哪些处理。
函数read() 的原型如下所示:ssize_t read(int feledes, void *buff, size_t nbytes) ;操作系统中处理 read() 函数的入口函数是 sys_read(),其主要的调用函数关系图如下清单 3 所示:清单 3. 主调用函数关系图sys_read()|-----vfs_read()|----generic_file_read()|----generic_file_aio_read()|--------- generic_file_direct_IO()函数 sys_read() 从进程中获取文件描述符以及文件当前的操作位置后会调用 vfs_read() 函数去执行具体的操作过程,而vfs_read() 函数最终是调用了 file 结构中的相关操作去完成文件的读操作,即调用了 generic_file_read() 函数,其代码如下所示:清单 4. 函数 generic_file_read()ssize_tgeneric_file_read(struct file *filp,char __user *buf, size_t count, loff_t *ppos){struct iovec local_iov = { .iov_base = buf, .iov_len = count };struct kiocb kiocb;ssize_t ret;init_sync_kiocb(&kiocb, filp);ret = __generic_file_aio_read(&kiocb, &local_iov, 1, ppos);if (-EIOCBQUEUED == ret)ret = wait_on_sync_kiocb(&kiocb);return ret;}函数 generic_file_read() 初始化了 iovec 以及 kiocb 描述符。
描述符 iovec 主要是用于存放两个内容:用来接收所读取数据的用户地址空间缓冲区的地址和缓冲区的大小;描述符 kiocb 用来跟踪 I/O 操作的完成状态。
之后,函数 generic_file_read() 凋用函数 __generic_file_aio_read()。
该函数检查 iovec 中描述的用户地址空间缓冲区是否可用,接着检查访问模式,若访问模式描述符设置了 O_DIRECT,则执行与直接 I/O 相关的代码。
函数 __generic_file_aio_read() 中与直接 I/O 有关的代码如下所示:清单 5. 函数 __generic_file_aio_read() 中与直接 I/O 有关的代码if (filp->f_flags & O_DIRECT) {loff_t pos = *ppos, size;struct address_space *mapping;struct inode *inode;mapping = filp->f_mapping;inode = mapping->host;retval = 0;if (!count)goto out;size = i_size_read(inode);if (pos < size) {retval = generic_file_direct_IO(READ, iocb,iov, pos, nr_segs);if (retval > 0 && !is_sync_kiocb(iocb))retval = -EIOCBQUEUED;if (retval > 0)*ppos = pos + retval;}file_accessed(filp);goto out;}上边的代码段主要是检查了文件指针的值,文件的大小以及所请求读取的字节数目等,之后,该函数调用generic_file_direct_io(),并将操作类型 READ,描述符 iocb,描述符 iovec,当前文件指针的值以及在描述符 io_vec 中指定的用户地址空间缓冲区的个数等值作为参数传给它。
当 generic_file_direct_io() 函数执行完成,函数__generic_file_aio_read()会继续执行去完成后续操作:更新文件指针,设置访问文件 i 节点的时间戳;这些操作全部执行完成以后,函数返回。
函数 generic_file_direct_IO() 会用到五个参数,各参数的含义如下所示:rw:操作类型,可以是 READ 或者 WRITEiocb:指针,指向 kiocb 描述符 iov:指针,指向 iovec 描述符数组offset:file 结构偏移量nr_segs:iov 数组中 iovec 的个数函数 generic_file_direct_IO() 代码如下所示:清单 6. 函数 generic_file_direct_IO()static ssize_tgeneric_file_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov,loff_t offset, unsigned long nr_segs){struct file *file = iocb->ki_filp;struct address_space *mapping = file->f_mapping;ssize_t retval;size_t write_len = 0;if (rw == WRITE) {write_len = iov_length(iov, nr_segs);if (mapping_mapped(mapping))unmap_mapping_range(mapping, offset, write_len, 0);}retval = filemap_write_and_wait(mapping);if (retval == 0) {retval = mapping->a_ops->direct_IO(rw, iocb, iov,offset, nr_segs);if (rw == WRITE && mapping->nrpages) {pgoff_t end = (offset + write_len - 1)>> PAGE_CACHE_SHIFT;int err = invalidate_inode_pages2_range(mapping,offset >> PAGE_CACHE_SHIFT, end);if (err)retval = err;}}return retval;}函数 generic_file_direct_IO() 对 WRITE 操作类型进行了一些特殊处理,这在下边介绍 write() 系统调用的时候再做说明。
除此之外,它主要是调用了 direct_IO 方法去执行直接 I/O 的读或者写操作。
在进行直接 I/O 读操作之前,先将页缓存中的相关脏数据刷回到磁盘上去,这样做可以确保从磁盘上读到的是最新的数据。
这里的 direct_IO 方法最终会对应到 __blockdev_direct_IO()函数上去。
__blockdev_direct_IO() 函数的代码如下所示:清单 7. 函数 __blockdev_direct_IO()ssize_t__blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,struct block_device *bdev, const struct iovec *iov, loff_t offset,unsigned long nr_segs, get_block_t get_block, dio_iodone_t end_io,int dio_lock_type){int seg;size_t size;unsigned long addr;unsigned blkbits = inode->i_blkbits;unsigned bdev_blkbits = 0;unsigned blocksize_mask = (1 << blkbits) - 1;ssize_t retval = -EINVAL;loff_t end = offset;struct dio *dio;int release_i_mutex = 0;int acquire_i_mutex = 0;if (rw & WRITE)rw = WRITE_SYNC;if (bdev)bdev_blkbits = blksize_bits(bdev_hardsect_size(bdev));if (offset & blocksize_mask) {if (bdev)blkbits = bdev_blkbits;blocksize_mask = (1 << blkbits) - 1;if (offset & blocksize_mask)goto out;}for (seg = 0; seg < nr_segs; seg++) {addr = (unsigned long)iov[seg].iov_base;size = iov[seg].iov_len;end += size;if ((addr & blocksize_mask) || (size & blocksize_mask)) {if (bdev)blkbits = bdev_blkbits;blocksize_mask = (1 << blkbits) - 1;if ((addr & blocksize_mask) || (size & blocksize_mask))goto out;}}dio = kmalloc(sizeof(*dio), GFP_KERNEL);retval = -ENOMEM;if (!dio)goto out;dio->lock_type = dio_lock_type;if (dio_lock_type != DIO_NO_LOCKING) {if (rw == READ && end > offset) {struct address_space *mapping;mapping = iocb->ki_filp->f_mapping;if (dio_lock_type != DIO_OWN_LOCKING) {mutex_lock(&inode->i_mutex);release_i_mutex = 1;}retval = filemap_write_and_wait_range(mapping, offset,end - 1);if (retval) {kfree(dio);goto out;}if (dio_lock_type == DIO_OWN_LOCKING) {mutex_unlock(&inode->i_mutex);acquire_i_mutex = 1;}}if (dio_lock_type == DIO_LOCKING)down_read_non_owner(&inode->i_alloc_sem);}dio->is_async = !is_sync_kiocb(iocb) && !((rw & WRITE) &&(end > i_size_read(inode)));retval = direct_io_worker(rw, iocb, inode, iov, offset,nr_segs, blkbits, get_block, end_io, dio);if (rw == READ && dio_lock_type == DIO_LOCKING)release_i_mutex = 0;out:if (release_i_mutex)mutex_unlock(&inode->i_mutex);else if (acquire_i_mutex)mutex_lock(&inode->i_mutex);return retval;}该函数将要读或者要写的数据进行拆分,并检查缓冲区对齐的情况。