IPC

IPC是Inter-Process Communication的缩写,直译为进程间通信,说白了就是进程间发消息。我们在上一节中把这种消息传递比作邮政系统,但实际上这种比喻并不全对。有的消息机制是很像收发邮件的,这种叫做异步IPC,意思是说,发信者发完就去干别的了,收信者也一样,看看信箱里没信,也不坐在旁边傻等。而有另一种消息机制正好相反,被称为同步IPC,它不像邮寄,倒像接力赛,发送者一直等到接收者收到消息才肯放手,接收者也一样,接不到就一直等着,不干别的。
当然你可以把同步IPC也比作邮寄,只不过寄信的人从把信投到信箱里的那一刻开始,就住在邮局不走了,其他什么也不干了,就等着邮局说:“哥们儿,你的信对方已经收到了,放心回家吧!”这才恋恋不舍地离开。收信的也一样,一旦决定收信,就守在自家信箱前面不走了,一直等,连觉也不睡,望穿秋水,等信拿在手里了,这才回屋,每收一次信,就得瘦个十几斤。
我们都是性情中人,我们选择傻等,或曰同步IPC。
同步IPC有若干的好处,比如:
• 操作系统不需要另外维护缓冲区来存放正在传递的消息;
• 操作系统不需要保留一份消息副本;
• 操作系统不需要维护接收队列(发送队列还是需要的);
• 发送者和接收者都可在任何时刻清晰且容易地知道消息是否送达;
• 从实现系统调用的角度来看,同步IPC更加合理──当使用系统调用时,我们的确需要等待内核返回结果之后再继续。
这些特性读者可能无法一下子全部明白,不要紧,我们接下来写完代码,你就全都明白了。
实现IPC
Minix的IPC机制我们已经明白了,它的核心乃在于“int SYSVEC”这个软中断以及与之对应的sys_call()这个函数。增加一个系统调用对我们来讲已是信手拈来的事,按照表7.6一步一步来就好了。我们把这个新的系统调用起名为sendrec。sendrec和sys_sendrec的函数体分别见下面两段代码:
25 sendrec:
26         mov eax, _NR_sendrec
27         mov ebx, [esp + 4] ; function
28         mov ecx, [esp + 8] ; src_dest
29         mov edx, [esp + 12] ; p_msg
30         int INT_VECTOR_SYS_CALL
31         ret
53 /*****************************************************************************
54 * sys_sendrec
55 *****************************************************************************/
56 /**
57 * <Ring 0> The core routine of system call ‘sendrec()’.
58 *
59 * @param function SEND or RECEIVE
60 * @param src_dest To/From whom the message is transferred.
61 * @param m Ptr to the MESSAGE body.
62 * @param p The caller proc.
63 *
64 * @return Zero if success.
65 *****************************************************************************/
66 PUBLIC int sys_sendrec(int function, int src_dest, MESSAGE* m, struct proc* p)
67 {
68         assert(k_reenter == 0); /* make sure we are not in ring0 */
69         assert((src_dest >= 0 && src_dest < NR_TASKS + NR_PROCS) ||
70         src_dest == ANY ||
71         src_dest == INTERRUPT);
72
73         int ret = 0;
74         int caller = proc2pid(p);
75         MESSAGE* mla = (MESSAGE*)va2la(caller, m);
76         mla->source = caller;
77
78         assert(mla->source != src_dest);
79
80         /**
81         * Actually we have the third message type: BOTH. However, it is not
82         * allowed to be passed to the kernel directly. Kernel doesn’t know
83         * it at all. It is transformed into a SEND followed by a RECEIVE
84         * by ‘send_recv()’.
85         */
86         if (function == SEND) {
87                 ret = msg_send(p, src_dest, m);
88                 if (ret != 0)
89                 return ret;
90         }
91         else if (function == RECEIVE) {
92                 ret = msg_receive(p, src_dest, m);
93                 if (ret != 0)
94                 return ret;
95         }
96         else {
97                 panic(”{sys_sendrec}␣invalid␣function:␣”
98                 ”%d␣(SEND:%d,␣RECEIVE:%d).”, function, SEND, RECEIVE);
99         }
100       
101         return 0;
102 }
表7.6的最后一步中提到,如果参数个数与以前的系统调用比有所增加,则需要修改kernel.asm中的sys_call。额外要注意,我们新加的参数是通过edx这个参数传递的,而save这个函数中也用到了寄存器dx,所以我们同时需要修改save,见如下代码:
   311 ; =============================================================================
   312 ; save
   313 ; =============================================================================
   314 save:
   315         pushad ; ‘.
   316         push ds ; |
   317         push es ; | 保存原寄存器值
   318         push fs ; |
   319         push gs ; /
   320
   321         ;; 注意,从这里开始,一直到‘mov esp, StackTop’,中间坚决不能用push/pop 指令,
   322         ;; 因为当前esp 指向proc_table 里的某个位置,push 会破坏掉进程表,导致灾难性后果!
   323
=> 324         mov esi, edx ; 保存edx,因为edx 里保存了系统调用的参数
   325         ;(没用栈,而是用了另一个寄存器esi)
   326         mov dx, ss
   327         mov ds, dx
   328         mov es, dx
   329         mov fs, dx
   330
=> 331         mov edx, esi ; 恢复edx
   332
   333         mov esi, esp                         ;esi = 进程表起始地址
   334
   335         inc dword [k_reenter]                ;k_reenter++;
   336         cmp dword [k_reenter], 0             ;if(k_reenter ==0)
   337         jne .1                               ;{
   338         mov esp, StackTop                    ; mov esp, StackTop <--切换到内核栈
   339         push restart                         ; push restart
   340         jmp [esi + RETADR - P_STACKBASE]     ; return;
   341 .1:                                          ;} else { 已经在内核栈,不需要再切换
   342         push restart_reenter                 ; push restart_reenter
   343         jmp [esi + RETADR - P_STACKBASE]     ; return;
   344                                              ;}
   345
   346
   347 ; =============================================================================
   348 ; sys_call
   349 ; =============================================================================
   350 sys_call:
   351         call save
   352
   353         sti
   354         push esi
   355
   356         push dword [p_proc_ready]
=> 357         push edx
   358         push ecx
   359         push ebx
   360         call [sys_call_table + eax * 4]
=> 361         add esp, 4 * 4
   362
   363         pop esi
   364         mov [esi + EAXREG - P_STACKBASE], eax
   365         cli
   366
   367         ret
sys_sendrec()这个函数被设计得相当简单,它可以描述为:把SEND消息交给msg_send()处理,把RECEIVE消息交给msg_receive()处理。
msg_send()和msg_receive()这两个函数我们过一会儿细细分解,先来看看之前没出现过的assert()和panic()。这两个函数虽然起的是辅助作用,但绝对不是可有可无,因为在我们接下来要处理的消息收发中,有一些编程细节还真容易让人迷糊,这时候assert()就大显神威了,它会在错误被放大之前通知你。panic()的作用也类似,用于通知你发生了严重的错误。

posted @ 2009-05-26 14:05  我的javaIT  阅读(508)  评论(0编辑  收藏  举报