C中异步IO浅析之五:异步IO的调试
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. 总结
异步IO调试要求深入理解最重要的两个数据结构iocb/event之间的关联和数据流程,还需要代码实现上的配合,再加上对event.res的熟练检查,才能快速定位问题,找到root cause.
本文转自存储之厨51CTO博客,原文链接: http://blog.51cto.com/xiamachao/2053722,如需转载请自行联系原作者