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,多了额外的消耗,性能降低。测试如下所示:
在使用fio进行测试时,因为默认使用libaio引擎,因此会使用aio接口,产生异步aio,仅仅加上direct,不能保证数据落盘。但是也没有别的办法保证落盘。只能通过加上sync标志调用fsync来等待。可以看到,同样是仅仅只有direct,dd和fio的性能相差很大。但是仅调用sync的情况,相差不大。而既有direct又有sync的情况,和dd下只有direct的情况相差不大。
但是如果将引擎修改成sync引擎将变成于dd结果相近的数据。
上述同时有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