最近笔者在对kernel cephfs客户端进行fio direct随机大io读测试时发现,在numjobs不变的情况下,使用libaio作为ioengine,无论怎么调节iodepth,测试结果都变化不大,咦,这是为什么呢?
一、撇开fio,单看libaio的使用
......
rc = io_setup(maxevents, &ctx);
for (j = 0; j < IO_COUNT; j++) {
......
io_prep_pread(iocb, fd, (void *)buf_, io_size, offset);
}
rc = io_submit(ctx, IO_COUNT, &iocbray[count]);
......
rc = io_getevents(ctx, IO_COUNT, IO_COUNT, events, &timeout);
......
}
代码中,io_setup函数创建一个异步io的上下文,io_prep_pread函数准备了IO_COUNT个读请求,通过io_submit函数批量提交IO_COUNT个读请求,最后通过io_getevents函数等待请求的返回。
笔者通过该代码来对kernel cephfs客户端进行direct随机读测试,发现io_submit函数非常耗时,这完全不符合笔者对libaio的预期(io_submit提交请求应该非常快,时间应该耗费在io_getevents等待io结束上)。
笔者决定一探究竟...
二、探索libaio源码
SYSCALL_DEFINE3(io_submit, aio_context_t, ctx_id, long, nr,struct iocb __user * __user *, iocbpp){
return do_io_submit(ctx_id, nr, iocbpp, 0);
}
long do_io_submit(aio_context_t ctx_id, long nr,struct iocb __user *__user *iocbpp, bool compat){
...
for (i=0; i<nr; i++) {
ret = io_submit_one(ctx, user_iocb, &tmp, compat);
}
...
}
static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,struct iocb *iocb, bool compat){
...
ret = aio_run_iocb(req, compat);
...
}
static ssize_t aio_run_iocb(struct kiocb *req, bool compat){
...
case IOCB_CMD_PREADV:
rw_op = file->f_op->aio_read;
...
}
// 后面的代码不再赘述
这段是3.10.107内核的io_submit系统调用的源码,并不复杂,总结下就是,对于批量的读请求,io_submit会逐个通过io_submit_one函数进行提交,而io_submit_one最终是调用底层文件系统的aio_read函数进行请求提交。
这里说的底层文件系统当然是cephfs文件系统,不妨来看下它的aio_read函数。
三、探索kernel cephfs源码
const struct file_operations ceph_file_fops = {
...
.read = do_sync_read,
.write = do_sync_write,
.aio_read = ceph_aio_read,
.aio_write = ceph_aio_write,
.mmap = ceph_mmap,
...
};
通过上述代码可以看出,kernel cephfs的aio_read上注册的是ceph_aio_read函数,让我们看看该函数。
static ssize_t ceph_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos){
...
if ((got & (CEPH_CAP_FILE_CACHE|CEPH_CAP_FILE_LAZYIO)) == 0 ||
(iocb->ki_filp->f_flags & O_DIRECT) ||
(inode->i_sb->s_flags & MS_SYNCHRONOUS) ||
(fi->flags & CEPH_F_SYNC))
/* hmm, this isn't really async... */
ret = ceph_sync_read(filp, base, len, ppos, &checkeof);
else
ret = generic_file_aio_read(iocb, iov, nr_segs, pos);
...
}
相信你已经注意到了这条注释 /* hmm, this isn't really async... */,在direct读模式下,当上层的io_submit调用到这里时,并没有进行async的调用,而是sync调用,即请求发送后需等待结果返回。
在3.10.107内核下,由于kernel cephfs没有实现真正的aio,导致批量提交的请求,io_submit会逐一处理提交,然后等待请求结果,再处理下一请求,而非批量提交请求,批量等待请求结果,这便是io_submit耗时的原因。
四、回到fio
理解了io_submit为什么费时,也就能理解fio下以libaio作为ioengine,无论怎么调节iodepth,测试结果都变化不大的原因。所以,当底层文件系统不支持aio时,fio测试时,libaio跟sync是几乎没有差别的。
五、聊聊cephfs、libaio、fio
4.14内核上,kernel cephfs在实现上支持了libaio,笔者分别做了以sync和libaio为ioengine的fio direct随机大io读测试。
对于sync:
在numjobs数达到一定的值后,fio的带宽已经到达了瓶颈(远小于客户机的万兆网卡带宽、集群有36个sata osd),再提高numjobs数已经不再起作用,这一点笔者非常费解,原因不得而知,知晓原因的朋友可以评论中告知笔者,万分感谢。
对于libaio:
在numjobs设置成较小值(4、8)时,通过增大iodepth就可以打满kernel cephfs客户机的万兆网卡(测试文件较小,集群osd足以将其缓存)。因此,通过libaio,我们可以向ceph集群提交大量的io,这样便可以测出集群的io极限。
网友评论