文件IO中的direct和sync标志位——O_DIRECT和O_SYNC详析

本文深入解析Linux文件I/O中O_DIRECT和O_SYNC标志位的作用。O_DIRECT用于尽量减少缓存影响,但无法确保数据落盘;而O_SYNC配合O_DIRECT使用,才能保证数据和元数据同步写入。direct_IO通过submit_bio直接写入,sync标志通过fsync强制刷新脏页。当direct和sync同时设置时,可能会导致额外的IO操作和资源浪费。
摘要由CSDN通过智能技术生成

man手册里介绍O_DIRECT标志是这么介绍的:

O_DIRECT (since Linux 2.4.10)
Try to minimize cache effects of the I/O to and from this file. In general this will degrade performance, but it is useful in special situations, such as when applications do their own
caching. File I/O is done directly to/from user-space buffers. The O_DIRECT flag on its own makes an effort to transfer data synchronously, but does not give the guarantees of the O_SYNC
flag that data and necessary metadata are transferred. To guarantee synchronous I/O, O_SYNC must be used in addition to O_DIRECT. See NOTES below for further discussion.

大意就是说,无法保证数据写入磁盘。如果想要保证数据写入磁盘,必须追加O_SYNC标志。

而大多数自缓存应用为了能够自己设计缓存架构,必须绕过页高速缓存。那么必然使用O_DIRECT标志。但实际上数据库等等这些自缓存应用可能也没有使用O_SYNC标志。实际上,在《深入理解linux内核》第三版上也有说明:即认为一般情况下direct_IO可以保证数据落盘。

这似乎形成了一个矛盾。我也被这个问题困扰了很久,在研究相关代码后,大概得出了结论。

首先我先大致介绍一下,之后贴出相应的代码,有耐心的朋友可以看完所有代码。
简单的调用关系可以表示如下:

|--ext4_file_write_iter
|--__generic_file_write_iter
| (direct)
|--generic_file_direct_write
|--direct_IO(ext4_direct_IO)
|--ext4_direct_IO_write
|--__blockdev_direct_IO(注册回调函数__blockdev_direct_IO)
|--do_blockdev_direct_IO
| (O_DSYNC)
|--dio_set_defer_completion(加入队列)
|--dio_await_completion

简单的分析如下:
direct标志在__generic_file_write_iter函数内检测并进入if分支。之后调用generic_file_direct_write进行写操作,最终调用submit_bio将该bio提交给通用块层。一般来说,提交之后,函数不会立即返回,而是在do_blockdev_direct_IO内调用dio_await_completion等待每个bio完成,在所有bio完成之后调用dio_complete完成整个dio。因此该direct_io能够保证下发的bio已经完成。但是具体通用块层之下是否有缓存无法感知。

而sync的处理还在__generic_file_write_iter函数的封装函数 里。如果__generic_file_write_iter返回大于零的值,即IO操作成功。之后会进入generic_write_sync函数内。该函数内会判断是否sync,如果设置了sync或者dsync,都将进入fsync函数的流程。

fsync的实现由各文件系统负责。以ext4为例,fsync的实现是ext4_sync_file。该函数会搜寻需要刷新的脏页,强制将所有的脏页最终通过submit_bio提交给调度器,并等待完成。

分析:direct是绕过页高速缓存,直接将数据通过submit_bio发送出去。sync标志是在处理完IO之后(不论这个IO是不是direct),调用fsync强制刷新。sync标志同样无法感知底层的缓存情况,如果底层有仍然有缓存,同样sync无法保证一定落入磁盘。

广泛意义上理解direct无法保证同步I/O的原因是什么呢?我认为是在这种情况下:

即当前IO为异步IO的情况下,异步IO在处理完成的情况下,不会进行等待。

在使用dd命令产生IO时,dd命令不使用aio接口,因此,仅仅direct的情况下,就可以保证数据的落盘,加上sync,多了额外的消耗,性能降低。测试如下所示:

1187168-20170822232312074-1376591907.png

在使用fio进行测试时,因为默认使用libaio引擎,因此会使用aio接口,产生异步aio,仅仅加上direct,不能保证数据落盘。但是也没有别的办法保证落盘。只能通过加上sync标志调用fsync来等待。可以看到,同样是仅仅只有direct,dd和fio的性能相差很大。但是仅调用sync的情况,相差不大。而既有direct又有sync的情况,和dd下只有direct的情况相差不大。

1187168-20170822232329246-1759569307.png

但是如果将引擎修改成sync引擎将变成于dd结果相近的数据。

1187168-20170822232340136-2137031255.png

上述同时有sync和direct 或者 只有sync的时候,性能差不多。我的理解是sync需要的时间大于不设置direct需要的时间,由于sync的fsync函数和真正的落盘几乎是并行的。因此可以看做sync时间包含了不使用direct多出来的时间。因此两者表现一致。但是这是我的个人理解,还有待商榷。

结论:如果direct和sync标志同时置位,实际上direct已经能保证数据的落盘(但是可能落入更下层的缓冲区)。加上sync标志,会浪费系统资源,搜索缓存池内的脏页,强制刷新到下层。由于搜索算法的原因,有可能会导致多余的IO操作。

下面将具体分析代码。

首先看__generic_file_write_iter函数,这个函数在每个IO路径上必然经过。

2790 ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
2791 {
2792         struct file *file = iocb->ki_filp;
2793         struct address_space * mapping = file->f_mapping;
2794         struct inode    *inode = mapping->host;                                                                                                                                                             
2795         ssize_t         written = 0;
2796         ssize_t         err;
2797         ssize_t         status;
2798 
2799         /* We can write back this queue in page reclaim */
2800         current->backing_dev_info = inode_to_bdi(inode);
2801         err = file_remove_privs(file);
2802         if (err)
2803                 goto out;
2804 
2805         err = file_update_time(file);
2806         if (err)
2807                 goto out;
2808 
2809         if (iocb->ki_flags & IOCB_DIRECT) {
2810                 loff_t pos, endbyte;
2811 
2812                 written = generic_file_direct_write(iocb, from);
2813                 /*
2814                  * If the write stopped short of completing, fall back to
2815                  * buffered writes.  Some filesystems do this for writes to
2816                  * holes, for example.  For DAX files, a buffered write will
2817                  * not succeed (even if it did, DAX does not handle dirty
2818                  * page-cache pages correctly).
2819                  */
2820                 if (written < 0 || !iov_iter_count(from) || IS_DAX(inode))
2821                         goto out;
2822 
2823                 status = generic_perform_write(file, from, pos = iocb->ki_pos);
2824                 /*
2825                  * If generic_perform_write() returned a synchronous error
2826                  * then we want to return the number of bytes which were
2827                  * direct-written, or the error code if that was zero.  Note
2828                  * that this differs from normal direct-io semantics, which
2829                  * will return -EFOO even if some bytes were written.
2830                  */                                                                                                                                                                                         
2831                 if (unlikely(status < 0)) {
2832                         err = status;
2833                         goto out;
2834                 }
2835                 /*
2836                  * We need to ensure that the page cache pages are written to
2837                  * disk and invalidated to preserve the expected O_DIRECT
2838                  * semantics.
2839                  */
2840                 endbyte = pos + status - 1;
2841                 err = filemap_write_and_wait_range(mapping, pos, endbyte);
2842                 if (err == 0) {
2843                         iocb->ki_pos = endbyte + 1;
2844                         written += status;
2845      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值