C中异步IO浅析之三:深入理解异步IO的基本数据结构-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

C中异步IO浅析之三:深入理解异步IO的基本数据结构

简介:

 一个函数库或一段代码的数据结构之间的关系,既展示了数据的行踪,同时又隐含了函数的调用顺序和使用方法。libaio内部的多个数据结构尤其如此,哪怕我们找不到文档或者帮助手册,只要深刻领悟头文件中定义的数据结构及其内在联系,再加一点代码的验证,就可以达到对libaio的API的深入理解,并且完全不需要死记硬背。

 

在上一篇简单介绍异步IO的相关数据结构和接口函数的基础上,这一篇就比较深入地介绍最重要的几个数据结构的内部成员的初始化方法及内部联系。

 

1. iocb数据结构 

 

struct iocb {

    PADDEDptr(void *data, __pad1);  /* Return in the io completion event */ /// will passed to its io finished events, channel of callback

    PADDED(unsigned key, __pad2);   /* For use in identifying io requests */ /// identifier of which iocb, initialized with iocb address?

    short       aio_lio_opcode;  // io opration code, R/W SYNC/DSYNC/POOL and so on

    short       aio_reqprio; // priority

    int     aio_fildes; // aio file descriptor, show which file/device the operation will take on

 

     union {

        struct io_iocb_common       c; // most important, common initialization interface

        struct io_iocb_vector       v;

        struct io_iocb_poll     poll;

        struct io_iocb_sockaddr saddr;

    } u;

};

iocb是异步IO库里面最重要的数据结构,大部分异步IO函数都带着这个参数。

成员 data: 当前iocb对应的IO完成之后,这个data的值会自动传递到这个IO生成的event的data这个域上去

成员 key: 标识哪一个iocb,通常没用

成员 ail_lio_opcode:指示当前IO操作的类型,可以初始化为下面的类型之一:

typedef enum io_iocb_cmd { // IO Operation type, used to initialize  aio_lio_opcode

    IO_CMD_PREAD = 0,

    IO_CMD_PWRITE = 1,

 

    IO_CMD_FSYNC = 2,

    IO_CMD_FDSYNC = 3,

 

    IO_CMD_POLL = 5, /* Never implemented in mainline, see io_prep_poll */

    IO_CMD_NOOP = 6,

    IO_CMD_PREADV = 7,

    IO_CMD_PWRITEV = 8,

} io_iocb_cmd_t;

 

 

 

成员 aio_reqprio: 异步IO请求的优先级,可能和io queue相关

成员 aio_filds:      接受IO的打开文件描述符,注意返回这个描述符的open()函数所带的CREATE/RDWR参数需要和上面的opcod相匹配,满足读写权限属性检查规则。

成员 u:         指定IO对应的数据起始地址,或者IO向量数组的起始地址

为了方便的获取IO提交后完成的IO数量,libaio提供了下面的函数:

 

 

2.  io_iocb_common 数据结构

struct io_iocb_common { // most import structure, either io and iov are both initialized based on this

    PADDEDptr(void  *buf, __pad1); // pointer to buffer for io, pointer to iov for io vector

    PADDEDul(nbytes, __pad2); // number of bytes in one io, or number of ios for io vector

    long long   offset; //disk offset

    long long   __pad3;

    unsigned    flags; // interface for set_eventfd(), flag to use eventfd

    unsigned    resfd;// interface for set_eventfd(), set to eventfd

};  /* result code is the amount read or -'ve errno */

 

io_iocb_common是也是异步IO库里面最重要的数据结构,上面iocb数据结构中的联合u,通常就是按照io_iocb_common的格式来初始化的。

 

成员 buf:  在批量IO的模式下,表示一个iovector 数组的起始地址;在单一IO的模式下,表示数据的起始地址;

成员 nbytes:  在批量IO的模式下,表示一个iovector 数组的元素个数;在单一IO的模式下,表示数据的长度;

成员 offset:   表示磁盘或者文件上IO开始的起始地址(偏移)

成员 _pad3:   填充字段,目前可以忽略

成员 flags:     表示是否实用eventfd 机制

成员 resfd:    如果使用eventfd机制,就设置成之前初始化的eventfd的值, io_set_eventfd()就是用来封装上面两个域的。

 

为了方便初始化上面的两个数据结构,libaio封装了四个inline函数:

io_prep_pread/io_prep_pwrite/io_prep_preadv/io_prep_pwritev

 

3. io_event 数据结构

struct io_event {

    PADDEDptr(void *data, __pad1); //internally intialized by iocb::data, which is initialized as callback function pointer, refer line 170: iocb->data = (void *)cb;

    PADDEDptr(struct iocb *obj,  __pad2); // pointer back to the iocb which generate this event

    PADDEDul(res,  __pad3);

    PADDEDul(res2, __pad4);

};

成员 data: 用来接收产生这个event的iocb传递进来的data,可以是数据结构指针,也可以是函数地址

成员 obj  :  指向产生这个event的iocb的地址,用以跟踪这个event是谁产生的。

成员 res   : 返回IO时间完成后传输的数据量,以byte为单位,如果出错,就是错误码,但有用它是ul类型,所有gdb p/x看到是类似0xfffffff4的类型。

int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

 

上面io_event中的obj指向产生它的iocb的这种思想,是程序可调试性设计的一个好例子,对调试程序很有用。请看下面的一段gdb片段:

 

gdb) p *events[0].obj

$33 = {data = 0x7fa76ddf40c0, key = 0, __pad2 = 0, aio_lio_opcode = 1, aio_reqprio = 0, aio_fildes = 4, u = {c = {buf = 0x7fa6ec000a00, nbytes = 4096,

      offset = 16384, __pad3 = 0, flags = 0, resfd = 0}, v = {vec = 0x7fa6ec000a00, nr = 4096, offset = 16384}, poll = {events = -335541760, __pad1 = 32678},

    saddr = {addr = 0x7fa6ec000a00, len = 4096}}}

(gdb) p *events[1].obj

$34 = {data = 0x7fa76ddf4140, key = 0, __pad2 = 0, aio_lio_opcode = 1, aio_reqprio = 0, aio_fildes = 4, u = {c = {buf = 0x7fa7e66e8200, nbytes = 4096,

      offset = 278528, __pad3 = 0, flags = 0, resfd = 0}, v = {vec = 0x7fa7e66e8200, nr = 4096, offset = 278528}, poll = {events = -428965376,

      __pad1 = 32679}, saddr = {addr = 0x7fa7e66e8200, len = 4096}}}

(gdb) p *events[0].obj.u.c.buf

Attempt to dereference a generic pointer.

(gdb) p (char*)events[0].obj.u.c.buf

$35 = 0x7fa6ec000a00 ""

(gdb) p (char*)events[1].obj.u.c.buf

$36 = 0x7fa7e66e8200 <Address 0x7fa7e66e8200 out of bounds>

(gdb) p diskIo

$37 = (DiskIO *) 0x7fa76ddf4140

(gdb) p *diskIo

$38 = {type = ioTypeW, iov = 0x7fa70d7f99d0, count = 1, offset = 262144, numBytes = 4096, iocb = {data = 0x7fa76ddf4140, key = 0, __pad2 = 0,

    aio_lio_opcode = 1, aio_reqprio = 0, aio_fildes = 4, u = {c = {buf = 0x7fa7e66e8200, nbytes = 4096, offset = 278528, __pad3 = 0, flags = 0, resfd = 0},

      v = {vec = 0x7fa7e66e8200, nr = 4096, offset = 278528}, poll = {events = -428965376, __pad1 = 32679}, saddr = {addr = 0x7fa7e66e8200, len = 4096}}},

  cbFunc = 0x401bce <AsyncCB>, cbArg = 0x7fa7b1df80c0, elem = {stqe_next = 0x7fa76ddf41c0}}

 

可以看到,上面利用event可以回溯iocb的机制,发现两个相邻event的buf的地址相差很大,0x7fa7e66e8200 - 0x7fa6ec000a00 接近4G,并且有个地址用gdb print其内容,出现明显越界,据此可以判断这个evnet 对应的iocb的u.c.buf的初始化一定有问题,根据这个逻辑去检查代码,果然发现源代码里默认分配的buffer数组空间太小了,初始化后续的iov的base_address用了越界之后的地址所致。

 

根据上面对三种最主要的数据结构的介绍和一个iocb 中buf越界的示例可以看到,一旦深入理解了数据结构中各个域的含义及其之间的关系,对调试程序将会有非常大的帮助。

 















本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/1976909 ,如需转载请自行联系原作者


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章
最新文章
相关文章