1. 背景

异步IO函数在使用的时候,由于它本身异步的特点,在提交IO请求的时候通常是不知道是否有错误,更谈不上返回错误类型了,只有等到有对应的event生成之后,才知道这个IO请求是否成功。 那么如何判断IO请求成功完成?如果出错,如何调试呢?

2. 原理

根据《C中异步IO浅析之三:深入理解异步IO的基本数据结构》中iocb/event之间的关系,可以看到:evnet数据结构拥有一个表示当前完成的IO请求的信息的指针,同时event数据结构中又记载着为这IO请求已传输的数据长度,根据这些信息就可以判读IO是否成功完成。更具体,就是比较期望完成传输的数据长度是否和已经完成的一致,如果一致就是顺利完成,否则就失败了。

3. 过程

根据上面的分析,可以按照下面的流程来确认IO是否成功完成:

3.1 代码部分

  • iocb初始化的时候,设置iocb.data,以便让对应的event能够知道具体的IO请求起始地址和长度信息;
  • event检查的时候,如果event.res != 应该完成的IO请求长度,assert(0);

3.2 调试过程

如果上面assert触发了core,就说明异步IO执行失败,可以参考下面的步骤开始调试:

  • gdb ./binary core-file-from-above
  • p event[0].res // 确定依据传输的数量 len_done
  • 执行类似p event[0].data[0] 命令 找到期望传输的数据的长度len_expecte
  • 根据len_done的取值进行处理:

    如果gdb p/x len_done显示的是一个0xfffffffx的值,表示传输出错,通常应该检查下面几项:

  • a. iov初始化的时候某个内存起始地址非法;
  • b. iov长度非法

    如果gdb p/x len_done值正常,但是比len_expecte小 ,通常应该检查下面几项:

  • a. iov初始化指定的用到的内存区域不够,已经用完,即将越界;
  • b. iov初始化指定的物理区域不够,已经用完,即将越界;

4. 示例

下面看两个具体的例子, 这样可以加深理解。

4.1 示例一

gdb现场如下:

  gdb) p events[0]
$21 = {data = 0x7fabcbe862c0, obj = 0x7fabcbe862e8, res =**5664**, res2 = 0}
(gdb) p diskIo->iocb
$22 = {data = 0x7fabcbe862c0, key = 0, __pad2 = 0, aio_lio_opcode = 7, aio_reqprio = 0, aio_fildes = 5, u = {c = {buf = 0x7fac25e8c260, nbytes = 2, offset = 73728, __pad3 = 0,
  flags = 0, resfd = 0}, v = {vec = 0x7fac25e8c260, nr = 2, offset = 73728}, poll = {events = 636011104, __pad1 = 32684}, saddr = {addr = 0x7fac25e8c260, len = 2}}}
(gdb) p diskIo
$23 = (DiskIO *) 0x7fabcbe862c0
(gdb) p diskIo[0]
$24 = {type = ioTypeR, iov = 0x7fac25e8c260, count = 2, offset = 69632, numBytes = **65536**, iocb = {data = 0x7fabcbe862c0, key = 0, __pad2 = 0, aio_lio_opcode = 7, aio_reqprio = 0,
aio_fildes = 5, u = {c = {buf = 0x7fac25e8c260, nbytes = 2, offset = 73728, __pad3 = 0, flags = 0, resfd = 0}, v = {vec = 0x7fac25e8c260, nr = 2, offset = 73728}, poll = {events = 636011104, __pad1 = 32684}, saddr = {addr = 0x7fac25e8c260, len = 2}}}, cbFunc = 0x40c797 <CbGrp_EntCb>, cbArg = 0x7fac0fe8a280, elem = {stqe_next =0x0}}`

可以看到实际已经完成IO的数据的长度是 res = 5664, 而期望传输的数据长度是 numBytes = 65536,根据上面规则检查,发现是因为这个IO vector的数据申请的内存空间小于iov->len的值,据此修改,很快通过。

4.2 示例二

gdb现场如下:

$65 = (struct io_event *) 0x7fab7c0008c0
(gdb) p events[0]
$66 = {data = 0x7fabcbe86340, obj = 0x7fabcbe86368, res = 18446744073709551602, res2 = 0}
(gdb) p events[0].obj
$67 = (struct iocb *) 0x7fabcbe86368
(gdb) p events[0].obj[0]
$68 = {data = 0x7fabcbe86340, key = 0, __pad2 = 0, aio_lio_opcode = 0, aio_reqprio = 0, aio_fildes = 6, u = {c = {buf = 0x7ffc4ede49e0, nbytes = 131072, offset = 8192, __pad3 = 0,
  flags = 0, resfd = 0}, v = {vec = 0x7ffc4ede49e0, nr = 131072, offset = 8192}, poll = {events = 1323190752, __pad1 = 32764}, saddr = {addr = 0x7ffc4ede49e0, len = 131072}}}
(gdb) p diskIo[0]
$69 = {type = ioTypeR, iov = 0x7fac25e8c280, count = 1, offset = 4096, numBytes = 131072, iocb = {data = 0x7fabcbe86340, key = 0, __pad2 = 0, aio_lio_opcode = 0, aio_reqprio = 0,
(gdb) p/x 18446744073709551602
$1 = 0xfffffffffffffff2

可以看到实际已经完成IO的数据的长度是 res = 18446744073709551602,它其实是-14 而期望传输的数据长度是 numBytes = 131072,根据上面规则检查,发现果然是地址参数初始的时候就错了。

5. 总结