下图是在使用RDMAStack时的IO路径,可以看出,整个异步读写流程都是EventCenter在承担主要工作。
Send IO
消息发送过程可以分为几个关键流程:
–s1–>AsyncMessenger::send_message(Message m)是Msg模块提供给上层提交IO请求的接口,IO请求封装在Message中。
–s2–>AsyncMessenger::submit_message()根据目标地址获取AsyncConnection对象,将Message传入该AsyncConnection对象中继续处理
–s3–>AsyncConnection::send_message(m)是IO请求在AsyncConnection实例中的入口,该函数主要将Message放在优先级队列AsyncConnection::out_q,并调用dispatch_event_external()将write_handler入队。
–s4–>当Worker线程又被唤醒并执行这个write_handler时,就会调用AsyncConnection::handle_write(),该函数主要是从out_q中依照优先级将一个Message出队,并通过AsyncConnection::write_message()把Mesage中的payload、middle、data都拷贝到AsyncConnection::outcoming_bl中,并调用AsyncConnection::_try_send()
–s5–>AsyncConnection::_try_send()作为Send IO在Async层的最后一个接口,主要是将outcoming_bl向下层传递,最终会到达RDMAStack,调用其中的RDMAConnectedSocketImpl::send()。
–s6–>RDMAStack内部会维持一个pending_bl,将RDMAConnectedSocketImpl::send()上层传入的outcoming_bl”接”在其后,调用的Chunk::write()启动发送
–s7–>ibv_post_send()是verbs中发送数据的接口,发送过程至此结束。
Receive IO
在RDMA协议中,收发完成(!=成功)接收消息首先会在CQ(Completion Queue)中放入CQE(CQ Entry)来通知上层有事件完成,verbs标准支持轮询和通知两种机制,Ceph RDMAStack使用的是轮询机制
–r1–>RDMADispatcher::polling线程不断的调用ibv_poll_cq()来轮询事件是否完成。
–r2–>如果发现一个底层读事件完成,就会通过conn = get_conn_lockless(response->qp_num)来获取其Connection对象,进而找到其关联的Worker和EventCenter,写EventCenter对象的notify_fd,由于该fd已经注册在file_events中,此举将唤醒Worker线程。
–r3–>EventCenter::Worker被唤醒后回调notify_fd的readcb,其中核心函数是AsyncConnection::process();
–r4–>AsyncConnection::process()线程会申请一块buffer用于接收收到的数据,这个buffer最终会被封装到bufferlist中并进一步被封装为Message供上层使用。
–r5–>AsyncConnection本身有一个读缓冲区:recv_buf,该buffer只给AsyncConnection::read_util()使用,until,顾名思义,就是该接口一定会读到想读取的长度,该接口首先试图从recv_buf中获取请求的数据,当recv_buf已有数据不能满足请求时,就要依情况从底层读取,对于recv_buf可以承载的数据长度,会调用底层接口先将recv_buf填满,再将从中读取所需;对于recv_buf不足的数据长度,直接从底层获取,从底层读取的接口是read_bulk()
–r6–>AsyncConnection::read_bulk()最终会带着传入的buffer,层层调用,直到RDMAStack中RDMAConnectedSocketImpl::read()–>Chunk::read()–>memcpy()将获取的数据填充到为Message准备号的buffer中
–r7–>AsyncConnection::process()经过层层调用已为Message的构造封装好了数据,接下来就只是构造一个Message,入dispatch_queue,交给上层处理。