《ORANGE-s-一个操作系统实现》进程间通信(6-1)

微内核与宏内核

  • 微内核:让内核只负责它必须负责的工作,比如进程调度,将内核工作简单化的思想,便是微内核的基本思想。
  • 宏内核:所有工作通过系统调用扔给内核态的做法,完成具体任务时,用户进程通过系统调用让内核来做事。 宏内核的优势在于其逻辑简单,直接了当,实现起来容易些,而且也因为它的直接,避免了像微内核那样在消息传递时占用资源。而微内核的优势在于,它的逻辑虽然相对复杂但是非常严谨,结构上显得非常优雅精致,而且程序更容易模块化,从而更容易移植。
    所以我们首要的任务就是实现一个进程间的通信机制(IPC)。

    IPC

    IPC是Inter—Process Communication的缩写,是进程间通信的意思,就是进程间发消息。IPC有同步和异步之分,同步是消息发送发和消息接收方都会一直等待消息,它的好处是:
  • 操作系统不需要另外维护缓冲区来存放正在传递的消息
  • 操作系统不需要保留一份消息副本
  • 操作系统不需要维护接受队列
  • 发送者和接受者都可以在任何时刻清晰且容易的知道消息是否送达
  • 从实现系统调用的角度看,同步IPC更加合理,当使用系统调用时,我们的确需要等待内核返回结果之后再继续

    实现IPC

    IPC的机制已经清楚了,它的核心在int SYSVEC这个软中断以及与之对应的sys_call()这个函数。
    下面是新的系统调用sendrec:
    1
    2
    3
    4
    5
    6
    7
    sendrec:
    mov eax, _NR_sendrec
    mov ebx, [esp + 4] ; function
    mov ecx, [esp + 8] ; src_dest
    mov edx, [esp + 12] ; p_msg
    int INT_VECTOR_SYS_CALL
    ret

sys_sendrec()这个函数被设计得很简单,就是send消息交给msg_send()处理,把RECEIVE消息交给msg_receive()处理。

msg_send()和msg_receive()

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
/*****************************************************************************
* ldt_seg_linear
*****************************************************************************/
/**
* <Ring 0~1> Calculate the linear address of a certain segment of a given
* proc.
*
* @param p Whose (the proc ptr).
* @param idx Which (one proc has more than one segments).
*
* @return The required linear address.
*****************************************************************************/
PUBLIC int ldt_seg_linear(struct proc* p, int idx)
{
struct descriptor * d = &p->ldts[idx];

return d->base_high << 24 | d->base_mid << 16 | d->base_low;
}

/*****************************************************************************
* va2la
*****************************************************************************/
/**
* <Ring 0~1> Virtual addr --> Linear addr.
*
* @param pid PID of the proc whose address is to be calculated.
* @param va Virtual address.
*
* @return The linear address for the given virtual address.
*****************************************************************************/
PUBLIC void* va2la(int pid, void* va)
{
struct proc* p = &proc_table[pid];

u32 seg_base = ldt_seg_linear(p, INDEX_LDT_RW);
u32 la = seg_base + (u32)va;

if (pid < NR_TASKS + NR_PROCS) {
assert(la == (u32)va);
}

return (void*)la;
}

/*****************************************************************************
* reset_msg
*****************************************************************************/
/**
* <Ring 0~3> Clear up a MESSAGE by setting each byte to 0.
*
* @param p The message to be cleared.
*****************************************************************************/
PUBLIC void reset_msg(MESSAGE* p)
{
memset(p, 0, sizeof(MESSAGE));
}

/*****************************************************************************
* block
*****************************************************************************/
/**
* <Ring 0> This routine is called after `p_flags' has been set (!= 0), it
* calls `schedule()' to choose another proc as the `proc_ready'.
*
* @attention This routine does not change `p_flags'. Make sure the `p_flags'
* of the proc to be blocked has been set properly.
*
* @param p The proc to be blocked.
*****************************************************************************/
PRIVATE void block(struct proc* p)
{
assert(p->p_flags);
schedule();
}

/*****************************************************************************
* unblock
*****************************************************************************/
/**
* <Ring 0> This is a dummy routine. It does nothing actually. When it is
* called, the `p_flags' should have been cleared (== 0).
*
* @param p The unblocked proc.
*****************************************************************************/
PRIVATE void unblock(struct proc* p)
{
assert(p->p_flags == 0);
}

/*****************************************************************************
* deadlock
*****************************************************************************/
/**
* <Ring 0> Check whether it is safe to send a message from src to dest.
* The routine will detect if the messaging graph contains a cycle. For
* instance, if we have procs trying to send messages like this:
* A -> B -> C -> A, then a deadlock occurs, because all of them will
* wait forever. If no cycles detected, it is considered as safe.
*
* @param src Who wants to send message.
* @param dest To whom the message is sent.
*
* @return Zero if success.
*****************************************************************************/
PRIVATE int deadlock(int src, int dest)
{
struct proc* p = proc_table + dest;
while (1) {
if (p->p_flags & SENDING) {
if (p->p_sendto == src) {
/* print the chain */
p = proc_table + dest;
printl("=_=%s", p->name);
do {
assert(p->p_msg);
p = proc_table + p->p_sendto;
printl("->%s", p->name);
} while (p != proc_table + src);
printl("=_=");

return 1;
}
p = proc_table + p->p_sendto;
}
else {
break;
}
}
return 0;
}

/*****************************************************************************
* msg_send
*****************************************************************************/
/**
* <Ring 0> Send a message to the dest proc. If dest is blocked waiting for
* the message, copy the message to it and unblock dest. Otherwise the caller
* will be blocked and appended to the dest's sending queue.
*
* @param current The caller, the sender.
* @param dest To whom the message is sent.
* @param m The message.
*
* @return Zero if success.
*****************************************************************************/
PRIVATE int msg_send(struct proc* current, int dest, MESSAGE* m)
{
struct proc* sender = current;
struct proc* p_dest = proc_table + dest; /* proc dest */

assert(proc2pid(sender) != dest);

/* check for deadlock here */
if (deadlock(proc2pid(sender), dest)) {
panic(">>DEADLOCK<< %s->%s", sender->name, p_dest->name);
}

if ((p_dest->p_flags & RECEIVING) && /* dest is waiting for the msg */
(p_dest->p_recvfrom == proc2pid(sender) ||
p_dest->p_recvfrom == ANY)) {
assert(p_dest->p_msg);
assert(m);

phys_copy(va2la(dest, p_dest->p_msg),
va2la(proc2pid(sender), m),
sizeof(MESSAGE));
p_dest->p_msg = 0;
p_dest->p_flags &= ~RECEIVING; /* dest has received the msg */
p_dest->p_recvfrom = NO_TASK;
unblock(p_dest);

assert(p_dest->p_flags == 0);
assert(p_dest->p_msg == 0);
assert(p_dest->p_recvfrom == NO_TASK);
assert(p_dest->p_sendto == NO_TASK);
assert(sender->p_flags == 0);
assert(sender->p_msg == 0);
assert(sender->p_recvfrom == NO_TASK);
assert(sender->p_sendto == NO_TASK);
}
else { /* dest is not waiting for the msg */
sender->p_flags |= SENDING;
assert(sender->p_flags == SENDING);
sender->p_sendto = dest;
sender->p_msg = m;

/* append to the sending queue */
struct proc * p;
if (p_dest->q_sending) {
p = p_dest->q_sending;
while (p->next_sending)
p = p->next_sending;
p->next_sending = sender;
}
else {
p_dest->q_sending = sender;
}
sender->next_sending = 0;

block(sender);

assert(sender->p_flags == SENDING);
assert(sender->p_msg != 0);
assert(sender->p_recvfrom == NO_TASK);
assert(sender->p_sendto == dest);
}

return 0;
}


/*****************************************************************************
* msg_receive
*****************************************************************************/
/**
* <Ring 0> Try to get a message from the src proc. If src is blocked sending
* the message, copy the message from it and unblock src. Otherwise the caller
* will be blocked.
*
* @param current The caller, the proc who wanna receive.
* @param src From whom the message will be received.
* @param m The message ptr to accept the message.
*
* @return Zero if success.
*****************************************************************************/
PRIVATE int msg_receive(struct proc* current, int src, MESSAGE* m)
{
struct proc* p_who_wanna_recv = current; /**
* This name is a little bit
* wierd, but it makes me
* think clearly, so I keep
* it.
*/
struct proc* p_from = 0; /* from which the message will be fetched */
struct proc* prev = 0;
int copyok = 0;

assert(proc2pid(p_who_wanna_recv) != src);

if ((p_who_wanna_recv->has_int_msg) &&
((src == ANY) || (src == INTERRUPT))) {
/* There is an interrupt needs p_who_wanna_recv's handling and
* p_who_wanna_recv is ready to handle it.
*/

MESSAGE msg;
reset_msg(&msg);
msg.source = INTERRUPT;
msg.type = HARD_INT;
assert(m);
phys_copy(va2la(proc2pid(p_who_wanna_recv), m), &msg,
sizeof(MESSAGE));

p_who_wanna_recv->has_int_msg = 0;

assert(p_who_wanna_recv->p_flags == 0);
assert(p_who_wanna_recv->p_msg == 0);
assert(p_who_wanna_recv->p_sendto == NO_TASK);
assert(p_who_wanna_recv->has_int_msg == 0);

return 0;
}


/* Arrives here if no interrupt for p_who_wanna_recv. */
if (src == ANY) {
/* p_who_wanna_recv is ready to receive messages from
* ANY proc, we'll check the sending queue and pick the
* first proc in it.
*/
if (p_who_wanna_recv->q_sending) {
p_from = p_who_wanna_recv->q_sending;
copyok = 1;

assert(p_who_wanna_recv->p_flags == 0);
assert(p_who_wanna_recv->p_msg == 0);
assert(p_who_wanna_recv->p_recvfrom == NO_TASK);
assert(p_who_wanna_recv->p_sendto == NO_TASK);
assert(p_who_wanna_recv->q_sending != 0);
assert(p_from->p_flags == SENDING);
assert(p_from->p_msg != 0);
assert(p_from->p_recvfrom == NO_TASK);
assert(p_from->p_sendto == proc2pid(p_who_wanna_recv));
}
}
else {
/* p_who_wanna_recv wants to receive a message from
* a certain proc: src.
*/
p_from = &proc_table[src];

if ((p_from->p_flags & SENDING) &&
(p_from->p_sendto == proc2pid(p_who_wanna_recv))) {
/* Perfect, src is sending a message to
* p_who_wanna_recv.
*/
copyok = 1;

struct proc* p = p_who_wanna_recv->q_sending;
assert(p); /* p_from must have been appended to the
* queue, so the queue must not be NULL
*/
while (p) {
assert(p_from->p_flags & SENDING);
if (proc2pid(p) == src) { /* if p is the one */
p_from = p;
break;
}
prev = p;
p = p->next_sending;
}

assert(p_who_wanna_recv->p_flags == 0);
assert(p_who_wanna_recv->p_msg == 0);
assert(p_who_wanna_recv->p_recvfrom == NO_TASK);
assert(p_who_wanna_recv->p_sendto == NO_TASK);
assert(p_who_wanna_recv->q_sending != 0);
assert(p_from->p_flags == SENDING);
assert(p_from->p_msg != 0);
assert(p_from->p_recvfrom == NO_TASK);
assert(p_from->p_sendto == proc2pid(p_who_wanna_recv));
}
}

if (copyok) {
/* It's determined from which proc the message will
* be copied. Note that this proc must have been
* waiting for this moment in the queue, so we should
* remove it from the queue.
*/
if (p_from == p_who_wanna_recv->q_sending) { /* the 1st one */
assert(prev == 0);
p_who_wanna_recv->q_sending = p_from->next_sending;
p_from->next_sending = 0;
}
else {
assert(prev);
prev->next_sending = p_from->next_sending;
p_from->next_sending = 0;
}

assert(m);
assert(p_from->p_msg);
/* copy the message */
phys_copy(va2la(proc2pid(p_who_wanna_recv), m),
va2la(proc2pid(p_from), p_from->p_msg),
sizeof(MESSAGE));

p_from->p_msg = 0;
p_from->p_sendto = NO_TASK;
p_from->p_flags &= ~SENDING;
unblock(p_from);
}
else { /* nobody's sending any msg */
/* Set p_flags so that p_who_wanna_recv will not
* be scheduled until it is unblocked.
*/
p_who_wanna_recv->p_flags |= RECEIVING;

p_who_wanna_recv->p_msg = m;

if (src == ANY)
p_who_wanna_recv->p_recvfrom = ANY;
else
p_who_wanna_recv->p_recvfrom = proc2pid(p_from);

block(p_who_wanna_recv);

assert(p_who_wanna_recv->p_flags == RECEIVING);
assert(p_who_wanna_recv->p_msg != 0);
assert(p_who_wanna_recv->p_recvfrom != NO_TASK);
assert(p_who_wanna_recv->p_sendto == NO_TASK);
assert(p_who_wanna_recv->has_int_msg == 0);
}

return 0;
}

其中:

  • block():阻塞一个进程
  • unblock():解除一个进程阻塞
  • deadloch():简单的判断是否发生死锁。方法是判断消息的发送是否构成一个环,如果构成环则意味着发生死锁。
    假设有进程A想要想进程B发送M,那么过程将会是这样的:
  1. A首先准备好M
  2. A通过系统调用sendrec,最终调用msg_send
  3. 简单判断是否发生锁死
  4. 判断目标进程B是否等待来自A的消息:
  • 如果是:消息被复制给B,B被解除阻塞,继续运行;
  • 如果不是:A被阻塞,并被加入到B的发送队列中
    假设有进程B想要接送消息,那么过程将会是:
  1. B准备一个空的消息结构体M,用于接受消息。
  2. B通过系统调用sendrec,最终调用msg_receive
  3. 判断B是否有个来自硬件的消息,如果是,并且B准备接送户来自中断的消息或者准备接受任意消息,则马上准备一个消息给B,并返回。
  4. 如果B想接受来自任意进程的消息,则从自己的发送队列中选取第一个,将其消息复制给M。
  5. 如果B是想接受来自特定进程A的消息,则先判断A是否正在等待B发送消息,若是的话,将消息复给M
  6. 如果此时没有任何进程发消息给B,B会被阻塞。

增加消息机制之后的进程调度

现在每个进程增加了两种可能的状态:SENDING和RECEIVING。相应的,我们需要在进程调度的时候区别对待了。凡是处在SENDING和RECEIVING状态的进程,我们不再让它们获得CPU了,也就是说,将它们阻塞了。

您的支持将鼓励我继续创作!