再议O_DIRECT, O_DIRECT与logical_block_size

引言

之前我们说过一篇关于O_DIRECT问题的文章http://blog.csdn.net/zr_lang/article/details/40982663,那篇是在编译的时候需要注意的问题,编译之后就要运行,关于DIRECT I/O的使用可能很少有人会关注,特别是在C语言里直接使用。如果不是遍写很底层的I/O代码可能不会用到direct io来做什么操作,大部分也都是用默认的buffer io。前不久我们在测试的时候发现LTP也有使用DIRECT I/O的错误,在某些体系结构上会造成运行错误,当然那个问题已经被我旁边的同事发patch修了。但是我前不久在写一个程序的时候也遇到了DIRECT I/O读写失败的问题,一段在x86_64等机器上运行良好的程序放在s390上时偶尔会出错,经过调试发现问题就是出现DIRECT I/O的使用上。下面让我们来详细说一下这个问题。


首先我们来看一段在Buffer I/O中最常见的程序:

#define BUF_SIZE 1024*1024
char *buf = NULL;

buf = malloc(BUF_SIZE);
memset(buf, 0, BUF_SIZE);

fd = open(filename, O_RDONLY);
if (fd < 0) {
    perror("open");
    exit(1);
}

ret = read(fd, buf, count);
if (ret < 0) {
    perror("read");
    exit(1);
}

这可能是Buffer I/O中很常见的写法,但是仅仅这样是不能满足Direct I/O的需要的。

DIRECT I/O操作

条件

Direct I/O至少需要先满足两个条件:

1、 buf必须是一个以一倍或几倍于logical_block_size的大小对齐。

2、 每一次read的count必须是logical_block_size的倍数。

根据这两条规则我们需要对上面的程序做修改。


得到logical_block_size

首先我们需要知道logical_block_size的大小,logical_block_size跟设备本身相关,所以需要通过kernel得到device的logical_block_size。从用户空间有这样几种方式:

1、 blockdev --getss $devname。

2、 cat /sys/block/$(basename $devname)/queue/logical_block_size。

3、 通过ioctl系统调用的BLKSSZGET命令。

目前我知道这三种方式,但是应该不限于此。这三个方法算比较通用的,写脚本可以使用前两种,写C程序可以读/sys/下的文件,也可以用ioctl系统调用。我们用ioctl来举例,代码如下:

#include <sys/ioctl.h>
#include <linux/fs.h>

int lbs = 0;

fd = open(devname, O_RDONLY);
ioctl(fd, BLKSSZGET, &lbs);

ioctl的BLKSSZGET命令读取devname的logical_block_size存入lbs中。BLKSSZGET定义在#include <linux/fs.h>中。

内存对齐

有了logical_block_size的数值,我们需要按照其大小对齐内存,我们借助posix_memalign来分配一段以logical_block_size对齐的内存空间,代码如下:

char *buf;

posix_memalign((void **)&buf, lbs, BUF_SIZE);

posix_memalign将分配BUF_SIZE大小的空间给buf,并且以lbs大小对齐。


DIRECT I/O的改动

#define BUF_SIZE 1024*1024
char *buf = NULL;
int lbs = 0;
int fd;

fd = open(devname, O_RDONLY);
if (fd < 0) {
    perror("open devname");
    exit(1);
}
if (ioctl(fd, BLKSSZGET, &lbs) < 0) {
    perro("ioctl");
    exit(1);
}
close(fd);

posix_memalign((void **)&buf, lbs, BUF_SIZE);
memset(buf, 0, BUF_SIZE);

fd = open(filename, O_RDONLY);
if (fd < 0) {
    perror("open filename");
    exit(1);
}

ret = read(fd, buf, lbs);
if (ret < 0) {
    perror("read");
    exit(1);
}

对齐不一定非要以一倍lbs的大小对齐,也可以是几倍,read的时候也是如此。而且read可以加上循环,我这里只是示意一下代码。


结尾

以上以read为例简单解释了一下DIRECT I/O的操作,write也请参考以上的叙述。 另外还有一个问题,我在ppc64和s390上测试发现如果在ioctl的时候传入的最后一个变量是long类型的,那么得到的logical_block_size的数值将存在long类型变量的高32位上。目前我还不知道这是否是一个bug,后续我将确认此事,如果后续的kernel不再存在这一问题也请知道的人告诉我。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页