这一篇文章主要是对DPDK中的进程间通信(Inter-Process Communication)进行介绍。
由于个人水平所限,若所写的博文中存在错误,希望大家能帮忙指出。
前面的文章
涉及到多个进程之间的相互协作,就必然会涉及到进程间通信(Inter-Process Communication, 缩写为IPC), 就目前阅读到的代码而言,主要采用了两种IPC的方式:共享内存通信(memory share),Socket。
DPDK在初始化时创建一个控制线程用来监听来自其他进程的消息,接收到消息后,会根据消息的类型(同步或者异步)进行不同的处理,详情见rte_mp_channel_init(), process_msg()(位于文件lib/librte_eal/common/eal_common_proc.c)。
这一篇博文主要介绍DPDK中采用Socket的进程间通信方式。
下面还是先对一些结构体作简单的说明 :
********************rte_eal.h********************
struct rte_mp_msg {
char name[RTE_MP_MAX_NAME_LEN]; //用来指明消息的类型
int len_param; //指明param的长度
int num_fds; //指明fds的长度
uint8_t param[RTE_MP_MAX_PARAM_LEN]; //用来存放消息的相关参数
int fds[RTE_MP_MAX_FD_NUM]; //用于进程间传递fd
};
struct rte_mp_reply {
int nb_sent; //发送出去的消息的个数
int nb_received; //收到回复的消息的个数
struct rte_mp_msg *msgs; /* caller to free */ //用于存放收到回复的消息
};
********************eal_common_proc.c********************
//消息的四种类型
enum mp_type {
MP_MSG, /* Share message with peers, will not block */
MP_REQ, /* Request for information, Will block for a reply */
MP_REP, /* Response to previously-received request */
MP_IGN, /* Response telling requester to ignore this response */
};
struct mp_msg_internal {
int type; //消息的类型
struct rte_mp_msg msg; //对应的消息
};
//用于pending request中的async
struct async_request_param {
rte_mp_async_reply_t clb; //收到异步消息的reply时,所调用的callback函数
struct rte_mp_reply user_reply; //用于记录消息的发送次数和接收次数,存放接收到的reply
struct timespec end; //用于记录异步消息超时的时间点
int n_responses_processed; //用于记录已经处理的reply个数
};
//进程在发送一个消息时,需要将这个消息封装到pengding_request中, 并且保留在一个pending request list
//等收到reply后,再从list找到对应的pending_request,调用callback(异步通信机制)或者唤醒对应的线程(同步通信机制)
struct pending_request {
TAILQ_ENTRY(pending_request) next;
enum {
REQUEST_TYPE_SYNC,
REQUEST_TYPE_ASYNC
} type;
char dst[PATH_MAX];
struct rte_mp_msg *request;//用于存放request消息
struct rte_mp_msg *reply; //用于存放一个reply消息
int reply_received; //用于指示是否正确接收到了reply
RTE_STD_C11
union {
struct {
struct async_request_param *param; //对应于异步通信,用于记录相关的信息
} async;
struct {
pthread_cond_t cond; //对应于同步通信,用于唤醒对应的线程
} sync;
};
};
1,将上层的消息(对于内存分配是struct malloc_mp_req)封装到struct rte_mp_msg的param字段中。
2,再继续封装在struct mp_msg_internal的msg字段。
3,最后将struct mp_msg_internal封装在 struct msghdr(而struct rte_mp_msg的fds作为msghdr控制信息)中。
4,使用系统提供的接口 sendmsg() 发送出去。
(由于是本机的IPC,所以会在msghdr中指定一个目的路径,这样接收者就能通过读取对应的文件描述符来获得信息)
DPDK的IPC通信过程实现了同步通信和异步通信
下面介绍一下pending_request的作用 :
在一次事务的过程中,发送方发送消息时会将未完成的请求封装到pending reuqest中,
并且将其加入到pending request list。然后就收到回复时,根据对应的pending request采用对应的操作,
然后将回复加入到对应的位置(如果是同步通信,则由用户在调用rte_mp_request_sync(),有一个参数
用于指定位置,如果是异步通信,则会创建一个struct async_request_param作为消息存放的位置)。
在同步通信的场景下 :
1,发送消息时会阻塞,直到收到回复以后才继续执行后面的指令。
2,若primary process同时给多个secondary process发送消息,则每发送一个消息,则会阻塞,
直到收到回复后才给下一个secondary process发送消息。
同步消息的实现:
1,某个线程调用rte_mp_request_sync(), 会创建一个类型为REQUEST_TYPE_SYNC的pending request。
并加入到pending request list中。
2,发送同步消息后,会阻塞在pending request中cond字段(条件变量)。
3,在接收消息的线程接收到回复时,根据回复查找相应的penging request, 将回复消息存放在pending request的reply字段。
并通过条件变量唤醒对应的线程。
4,被唤醒的线程继续执行后面的指令,将回复消息(即pending request中的reply字段)复制到用户指定的位置。
在异步通信的场景下 :
发送消息时会将所有消息都发送出去,然后继续执行后面的指令,或者其他功能。
异步消息的实现 :
1,某个线程调用rte_mp_request_async()会传入一个callback和超时时长。
接着创建一个类型为REQUEST_TYPE_ASYNC的pending request并加入到pending request list中。
2,发送异步消息后(会设置一个alarm用于处理超时),线程可以继续执行后面的指令。
3,在接收消息的线程接收到回复时,根据回复查找相应的pending request,将回复消息存放在pending request的reply字段。
最后会将回复消息复制到async->param.user_reply中。
4,若已经收到了全部的消息,则会触发callback函数,将user_reply作为函数,处理所有的回复消息。
(一个技巧,若全部消息发送失败,则会加入一个dummy pending request, 用于触发callback,保证异步通信一定能够触发callback函数的执行。但是本人目前尚未找到触发dummy pending request的代码在哪一块)
下面通过一个内存管理的场景(其他场景如hotplug设备移除时通知其他process,或者使用vfio中container,iommu type同步时都会使用IPC)说明DPDK中IPC的应用。
下面给出secondary process向primary process申请内存的全部过程:
一),若secondary process在调用rte_malloc()分配内存时,此时需要向OS申请更多的内存页,那么会调用request_to_primary()发送一个RTE_TYPE_ALLOC请求给primary process(下图第1步)。
二),primary process接收到请求后,会向OS申请更多的内存页,并更新其rte_memseg_list的信息(下图第2步)。若申请失败,则发送一个fail消息给secondary process, 否则发送一个sync request(下图第3步),让secondary process将其local rte_memseg_list 与 primary process的rte_memseg_list进行同步。
三),若secondary process收到失败的消息,直接退出。若收到sync request, 则进行同步操作,若同步成功,则发送sync success的请求,否则发送sync failure的请求(下图第4,5步)。
四) ,若primary process接收到sync success的请求,则发送alloc success的消息。否则,primary process执行rollback操作,释放第二步向OS申请的内存(下图第6步),然后继续向secondary proocess发送rollback request(下图第7步)。
五), 若secondary process收到sync success, 直接退出。若收到rollback request, 则将其local rte_memseg_list 与 primary process的rte_memseg_list进行同步。然后将同步的结果发送给primary process(下图第8,9步)。
六),primary process收到同步的结果后,无论成功与否,将结果发送给secondary process(下图第10步)。
七), secondary process收到结果后,退出。
下面的图展示了上面加粗的字体的过程(其他过程比较简单,在此不给出图例):
下面给出secondary process请求primary process释放内存页的过程:
一), 若secondary process在调用rte_malloc()释放内存时,此时若能够将某些内存页归还给OS,那么会调用request_to_primary()发送一个RTE_TYPE_FREE请求给primary process(下图第1步)。
二), primary process接收到请求后,会将内存页释放给OS(下图第2步),并更新其rte_memseg_list的信息。若释放失败,则发送一个fail消息给secondary process, 否则发送一个sync request(下图第3步),让secondary process将其local rte_memseg_list 与 primary process的rte_memseg_list进行同步。
三),若secondary process收到失败的消息,直接退出。若收到sync request, 则进行同步操作,若同步成功,则发送sync success的请求,否则发送sync failure的请求(下图第4,5步)。
四), primary process收到同步的结果后,无论成功与否,将结果发送给secondary process(下图第6步)。
五), secondary process收到结果后,退出.
本文简单地介绍了DPDK的进程间通信机制:
1,DPDK系统中使用了多种IPC方式,本文主要介绍了socket这一种方式。
2,DPDK通信过程自定义了一套消息格式,向用户隐藏了处理消息的方式。
3,DPDK中支持同步通信和异步通信,并且在secondary process向primary process申请/释放内存时,两种通信方式都会使用。
4,对内存管理的一个通信场景进行了说明(其他的通信场景相对于内存管理的通信场景而言比较简单)。
下面给出的是进程间通信,以及内存管理的进程通信过程相关的代码的函数调用图(所阅读的代码是来自DPDK官方最新的代码https://github.com/DPDK/dpdk, 如果官方代码更新,文章也会尽快地更新)
1,eal_common_proc : http://naotu.baidu.com/file/6f6d2149caf10b47c8063238ccb3161f?token=08dd7291ce6cb49c
2,malloc_mp : http://naotu.baidu.com/file/d9b0035af29d5f065383f96a3f8d0c8a?token=6c8dade8235b6a62