1.概述
Fiasco的进程间通信主要通过do_ipc函数完成,其原型:
void Thread::do_ipc(L4_msg_tag const &tag, bool have_send, Thread *partner, bool have_receive, Sender *sender, L4_timeout_pair t, Syscall_frame *regs, L4_fpage::Rights rights) |
从定义可以看出do_ipc是属于class Thread。一个线程一般同时具有发和收两种特性,所以在do_ipc函数中可以同时完成收、发两个动作。如参数所示:
发:bool have_send(是否有msg需要发送), Thread *partner(接收线程指针),
收:bool have_receive(是否需要接收msg), Sender *sender(发送者,NULL为不指定发送者)
当然,也可以只做一个动作,比如只发送时,可以讲“have_receive”参数置为false。
下面具体分析下这个函数的实现。
2.发送流程
首先分析发送流程(have_send == true),其又分为两种情况:发送和接收线程在同一CPU;发送和接收线程在不同CPU。
2.1 同一CPU内发送流程
2.1.1 握手
首先要做的事情是和receiver线程进行握手:handshake_receiver,其原型如下:
inline Thread::Check_sender Thread::handshake_receiver(Thread *partner, L4_timeout snd_t) |
在握手函数里会调用receiver的check_sender函数,来判断发送线程是符合条件的。
inline Thread::Check_sender Thread::check_sender(Thread *sender, bool timeout) |
那么需要符合什么条件呢?我们继续跟代码,看看sender_ok函数。
inline Receiver::Rcv_state Receiver::sender_ok(const Sender *sender) const { unsigned ipc_state = state() & Thread_ipc_mask; // If Thread_send_in_progress is still set, we're still in the send phase if (EXPECT_FALSE(ipc_state != Thread_receive_wait)) return vcpu_async_ipc(sender); // Check open wait; test if this sender is really the first in queue if (EXPECT_TRUE(!partner() && (_sender_list.empty() || sender->is_head_of(&_sender_list)))) return Rs_ipc_receive; // Check closed wait; test if this sender is really who we specified if (EXPECT_TRUE(sender == partner())) return Rs_ipc_receive; return Rs_not_receiving; } |
贴上源码的原因是三条注释已经将函数体表述的非常清楚:
首先,如果发送进程还处于上一个发送过程中,那么调用vcpu_async_ipc函数让其把流程走完;
其次,如果接收进程处于open wait状态(即,未指定特定的发送者<partner()为0>),同时发送队列为空或是发送者位于发送队列的head位置,都是满足条件的,返回Rs_ipc_receive;
最后,如果接收进程处于closed wait状态(即,指定了发送者),而且发送线程正好是其指定的发送者(sender == partner()),同样是满足要求的,返回Rs_ipc_receive。
握手成功后,sender将partner的标志设置为Thread_ipc_transfer,保证不会有其他sender向同一个partner发送消息。
2.1.2 消息拷贝
第二步就是调用Thread::transfer_msg进行UTCB的拷贝。拷贝完成后,清除partner相关标志后,唤醒partner,整个消息传递过程结束。
2.2 CPU间发送流程
如果sender和receiver处于不同的cpu上,sender无法直接通过调度让receiver运行,只能通过drq的方式来通知receiver运行。其大致思路如下:
1. 当sender在handshake步骤时, 先发送drq通知receiver。在等待drq响应过程中,sender都是处于Thread_send_wait状态;
2. Receiver响应drq,查询自身的状态和sender是否符合close-wait要求。如果可以的话,自身就进行ipc消息的拷贝(通过remote_ipc_send代码注释,应该ipc消息不包含buffer register时,就可以不持有锁,因此可以在drq上下文中拷贝),并在拷贝完成后更新receiver状态,表示已经收到ipc消息。无论是否拷贝,都需要唤醒sender。
3. Sender唤醒后,如果receiver没有拷贝消息,则进行消息拷贝,并通过drq唤醒receiver。如果已经拷贝了消息,直接结束消息发送处理。
3.接收流程
接收流程相对简单,因为它会借助于sender的发送流程来完成自己的接收。
首先,将自身状态设置为Thread_receive_wait,表示自己已经做好接收准备;
然后,在保证自身线程的发送流程已经结束的前提下,找到自己的sender:
next = get_next_sender(sender); |
这里跟发送流程握手阶段一样,有两种情况:如果sender有值,则表示closed wait,在确保sender在发送列表中,并且阻塞在发送,则返回sender本身,继续接收流程;另外一种情况,sender为NULL则表示open wait,则在发送列表中取出第一个,继续接收流程。
接下来,就是设置当前线程为Thread_receive_in_progress状态,调用发送者(前面确定的next对象)的ipc_send_msg方法,通过UTCB拷贝,实现消息传递。