龙芯平台fio异步无法测试问题

概要

fio是一个非常灵活的io测试工具,他可以通过多线程或进程模拟各种io操作

随着块设备的发展,特别是SSD盘的出现,设备的并行度越来越高。要想利用好这些设备,有个诀窍就是提高设备的iodepth, 一次喂给设备更多的IO请求,让电梯算法和设备有机会来安排合并以及内部并行处理,提高总体效率。

应用程序使用IO通常有二种方式:同步和异步。 同步的IO一次只能发出一个IO请求,等待内核完成才返回,这样对于单个线程iodepth总是小于1,但是可以通过多个线程并发执行来解决,通常我们会用16-32个线程同时工作把iodepth塞满。 异步的话就是用类似libaio这样的linux native aio一次提交一批,然后等待一批的完成,减少交互的次数,会更有效率。

调试环境

  • 验证系统
    loongnix1.0版本

  • 验证工具版本
    fio-3.12
    libaio-0.3.112

问题说明

在龙芯平台上,使用fio同步测试是没有问题的,但异步是无法跑起来的,报错是调用io_submit接口时传的参数不正确,需要说明的是,fio异步使用的是libaio库实现的,实际使用的是内核中的接口。

调试步骤

从fio源码中定位

通过报错位置找到了td_verror(td, -ret, "io commit");,说明td->io_ops->commit(td); 出问题了
fio/ioengines.c

 
  1. void td_io_commit(struct thread_data *td)
  2. {
  3. int ret;
  4.  
  5. dprint(FD_IO, "calling ->commit(), depth %d\n", td->cur_depth);
  6.  
  7. if (!td->cur_depth || !td->io_u_queued)
  8. return;
  9.  
  10. io_u_mark_depth(td, td->io_u_queued);
  11.  
  12. if (td->io_ops->commit) {
  13. ret = td->io_ops->commit(td);
  14. if (ret)
  15. td_verror(td, -ret, "io commit");
  16. }
  17.  
  18. /*
  19. * Reflect that events were submitted as async IO requests.
  20. */
  21. td->io_u_in_flight += td->io_u_queued;
  22. td->io_u_queued = 0;
  23. }

查找函数指针td->io_ops->commit(td);初始化的地方,通过打印判定是static int fio_libaio_commit(struct thread_data *td)

fio/engines/libaio.c

 
  1. static int fio_libaio_commit(struct thread_data *td)
  2. {
  3. struct libaio_data *ld = td->io_ops_data;
  4. struct iocb **iocbs;
  5. struct io_u **io_us;
  6. struct timespec ts;
  7. int ret, wait_start = 0;
  8.  
  9. if (!ld->queued)
  10. return 0;
  11.  
  12. do {
  13. long nr = ld->queued;
  14.  
  15. nr = min((unsigned int) nr, ld->entries - ld->tail);
  16. io_us = ld->io_us + ld->tail;
  17. iocbs = ld->iocbs + ld->tail;
  18.  
  19. ret = io_submit(ld->aio_ctx, nr, iocbs);
  20. if (ret > 0) {
  21. fio_libaio_queued(td, io_us, ret);
  22. io_u_mark_submit(td, ret);
  23.  
  24. ld->queued -= ret;
  25. ring_inc(ld, &ld->tail, ret);
  26. ret = 0;
  27. wait_start = 0;
  28. } else if (ret == -EINTR || !ret) {
  29. if (!ret)
  30. io_u_mark_submit(td, ret);
  31. wait_start = 0;
  32. continue;
  33. } else if (ret == -EAGAIN) {
  34. /*
  35. * If we get EAGAIN, we should break out without
  36. * error and let the upper layer reap some
  37. * events for us. If we have no queued IO, we
  38. * must loop here. If we loop for more than 30s,
  39. * just error out, something must be buggy in the
  40. * IO path.
  41. */
  42. if (ld->queued) {
  43. ret = 0;
  44. break;
  45. }
  46. if (!wait_start) {
  47. fio_gettime(&ts, NULL);
  48. wait_start = 1;
  49. } else if (mtime_since_now(&ts) > 30000) {
  50. log_err("fio: aio appears to be stalled, giving up\n");
  51. break;
  52. }
  53. usleep(1);
  54. continue;
  55. } else if (ret == -ENOMEM) {
  56. /*
  57. * If we get -ENOMEM, reap events if we can. If
  58. * we cannot, treat it as a fatal event since there's
  59. * nothing we can do about it.
  60. */
  61. if (ld->queued)
  62. ret = 0;
  63. break;
  64. } else
  65. break;
  66. } while (ld->queued);
  67.  
  68. return ret;
  69. }

通过打印断定是io_submit出问题了,下面是io_submit的原型,查看发现就是宏定义接口,查看libaio源码中的接口

 
  1. #define io_syscall3(type,fname,sname,type1,arg1,type2,arg2,type3,arg3) \
  2. type fname(type1 arg1,type2 arg2,type3 arg3) \
  3. _body_io_syscall(sname, (long)arg1, (long)arg2, (long)arg3)
  4.  
  5. io_syscall3(int, io_submit, io_submit, io_context_t, ctx, long, nr, struct iocb **, iocbs)

查看libaio接口发现libaio实现的是数据结构,时间的接口调用的是内核的
libaio-0.3.112/src/libaio.h

 
  1. extern int io_setup(int maxevents, io_context_t *ctxp);
  2. extern int io_destroy(io_context_t ctx);
  3. extern int io_submit(io_context_t ctx, long nr, struct iocb *ios[]);
  4. extern int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt);
  5. extern int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);
  6. extern int io_pgetevents(io_context_t ctx_id, long min_nr, long nr,

查看内核中aio.c如下,发现到内核的数据不正确了

 
  1. long do_io_submit(aio_context_t ctx_id, long nr,
  2. struct iocb __user *__user *iocbpp, bool compat)
  3. {
  4. struct kioctx *ctx;
  5. long ret = 0;
  6. int i = 0;
  7. struct blk_plug plug;
  8. printk("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%d\n",__LINE__);
  9. printk(KERN_DEBUG "line%d,addr:%p,%p\n",__LINE__,*iocbpp,iocbpp);
  10. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  11. if (unlikely(nr < 0))
  12. return -EINVAL;
  13. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  14. if (unlikely(nr > LONG_MAX/sizeof(*iocbpp)))
  15. nr = LONG_MAX/sizeof(*iocbpp);
  16.  
  17. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  18. if (unlikely(!access_ok(VERIFY_READ, iocbpp, (nr*sizeof(*iocbpp)))))
  19. return -EFAULT;
  20.  
  21. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  22. ctx = lookup_ioctx(ctx_id);
  23. if (unlikely(!ctx)) {
  24. pr_debug("EINVAL: invalid context id\n");
  25. return -EINVAL;
  26. }
  27.  
  28. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  29. blk_start_plug(&plug);
  30.  
  31. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  32. /*
  33. * AKPM: should this return a partial result if some of the IOs were
  34. * successfully submitted?
  35. */
  36. for (i=0; i<nr; i++) {
  37. struct iocb __user *user_iocb;
  38. struct iocb tmp;
  39.  
  40. printk(KERN_DEBUG "line%d,addr:%p\n",__LINE__,user_iocb);
  41. printk(KERN_DEBUG "line%d,addr:%p\n",__LINE__,&tmp);
  42. if (unlikely(__get_user(user_iocb, iocbpp + i))) {
  43. ret = -EFAULT;
  44. break;
  45. }
  46.  
  47. printk(KERN_DEBUG "line%d,addr:%p\n",__LINE__,user_iocb);
  48. if (unlikely(copy_from_user(&tmp, user_iocb, sizeof(tmp)))) {
  49. ret = -EFAULT;
  50. break;
  51. }
  52. printk(KERN_DEBUG "line%d,addr:%p-%d\n",__LINE__,&tmp,sizeof(tmp));
  53.  
  54. printk(KERN_DEBUG "line:%d,func:%s\n",__LINE__,__func__);
  55. ret = io_submit_one(ctx, user_iocb, &tmp, compat);
  56. if (ret)
  57. break;
  58. }
  59. blk_finish_plug(&plug);
  60.  
  61. put_ioctx(ctx);
  62. return i ? i : ret;
  63. }

同时打印出内核和应用中的数据进行对比,发现了问题,内核中的数据长度是64byte,而应用中的数据长度是83byte,这样数据肯定是对不上的,所以查看原因,发现是libaio中的问题。

问题解决

libaio源码本身是部支持mips64el的,为了修改支持需要修改两个地方syscall.h和libaio.h,

syscall.h中添加mips64和syscall-mips64el.h mips64是gcc中定义的宏

 
  1. #if defined(__i386__)
  2. #include "syscall-i386.h"
  3. #elif defined(__x86_64__)
  4. #include "syscall-x86_64.h"
  5. #elif defined(__mips64)
  6. #include "syscall-mips64el.h"
  7. #elif defined(__ia64__)
  8. #include "syscall-ia64.h"
  9. #elif defined(__PPC__)
  10. #include "syscall-ppc.h"
  11. #elif defined(__s390__)
  12. #include "syscall-s390.h"
  13. #elif defined(__alpha__)
  14. #include "syscall-alpha.h"
  15. #elif defined(__arm__)
  16. #include "syscall-arm.h"
  17. #elif defined(__sparc__)
  18. #include "syscall-sparc.h"
  19. #elif defined(__aarch64__) || defined(__riscv)
  20. #include "syscall-generic.h"
  21. #else
  22. #warning "using system call numbers from sys/syscall.h"
  23. #endif

syscall-mips64el.h内容如下,从linux/include/asm-arm/unistd.h /usr/include/asm/unistd.h中获取

 
  1. #define __NR_Linux 5000
  2. #define __NR_io_setup (__NR_Linux + 200)
  3. #define __NR_io_destroy (__NR_Linux + 201)
  4. #define __NR_io_getevents (__NR_Linux + 202)
  5. #define __NR_io_submit (__NR_Linux + 203)
  6. #define __NR_io_cancel (__NR_Linux + 204)

libaio.h 添加内容如下,在64 bits处添加__mips64

 
  1. /* little endian, 32 bits */
  2. #if defined(__i386__) || (defined(__arm__) && !defined(__ARMEB__)) || \
  3. defined(__sh__) || defined(__bfin__) || (defined(__MIPSEL__) && defined(__mips32)) || \
  4. defined(__cris__) || (defined(__riscv) && __riscv_xlen == 32) || \
  5. (defined(__GNUC__) && defined(__BYTE_ORDER__) && \
  6. __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 4)
  7. #define PADDED(x, y) x; unsigned y
  8. #define PADDEDptr(x, y) x; unsigned y
  9. #define PADDEDul(x, y) unsigned long x; unsigned y
  10.  
  11. /* little endian, 64 bits */
  12. #elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__) || \
  13. (defined(__MIPSEL__) && defined(__mips64)) || \
  14. (defined(__aarch64__) && defined(__AARCH64EL__)) || \
  15. (defined(__riscv) && __riscv_xlen == 64) || \
  16. (defined(__GNUC__) && defined(__BYTE_ORDER__) && \
  17. __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && __SIZEOF_LONG__ == 8)
  18. #define PADDED(x, y) x, y
  19. #define PADDEDptr(x, y) x
  20. #define PADDEDul(x, y) unsigned long x

注意:
在libaio.h中32bit部分是有__MIPSEL__判断的,我们gcc中也有这个宏,所以需要添加其他判断来限制,因为32bit和64bit都有__MIPSEL__,添加__mips32 宏做限制,应用和内核数据对不上就是这块的原因

编译命令

libaio

make clean;make ;make install

fio

make clean;make

需要安装可以执行

make install

验证问题命令

fio -filename=/dev/sdb -direct=1 -iodepth=1 -thread -rw=write -ioengine=libaio -bs=4k -size=10G -numjobs=4 -runtime=120 -group_reporting -name=mytest

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值