--------------------------------------------------------
IPC 消息机制的建立
--------------------------------------------------------
宏内核 VS 微内核
将内核完成的任务交给专门的任务进程来完成:例如 get_ticks 系统调用,在宏内核中 get_ticks 是作为一个系统调用函数存在的,通过系统调用函数表,内核在 ring0 执行函数 get_ticks,get_ticks 返回,这次系统调用才算结束,而在微内核中,get_ticks 不是系统调用,而是作为某个任务进程将要执行的功能函数而存在的,内核在 ring0 通过发送消息唤醒任务进程之后就立即返回!
一,建立消息通信机制
·sys_send 函数:
PRIVATE int msg_send(struct proc *current, int dest, MESSAGE *m)// current 是谁要发 { // dest 是发给谁 // m 是要发送的消息
struct proc *sender = current;
struct proc *p_dest = proc_table + dest;
// 确保 sender 不是给自己发
assert(dest != proc2pid(sender));
/* 确保没有死锁 */
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); // p_dest->p_msg 的内存必须要开辟好
assert(m);
phys_copy(va2la(dest, p_dest->p_msg), // p_dest 的 msg 的线性地址同样是等于:[p_dest 的 DS : offset(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; // 清空 p_dest 上次是因为等待谁而阻塞!
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; // m 其实就是 sender 要发送给 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;
}
——sys_send 首先简单地检查是否产生死锁,然后检查如果 recv 方正在 recving 并且 want recv 就是自己(或者 any),就将消息赋值给 recv 方,然后唤醒对方!否则 recv 方没打算接收或者想接收的不是自己,就进行阻塞前的准备设置然后阻塞!
·sys_receive 函数:
PRIVATE int msg_receive(struct proc *current, int src, MESSAGE *m) // m:将消息往回收到 m 中
{ // sec:从哪里收
// current:谁想回收
struct proc *p_who_wanna_recv = current;
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_frome 就是 p_who_wanna_recv 等待队列上的第一个
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 if (src >= 0 && src < NR_TASKS + NR_PROCS)
{
/* 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
*/
// 这里在 p_who_wanna_recv 的 q_sending 队列上找到那个具体的进程
// 估计要做更新等待列表的工作
while (p)
{
assert(p_from->p_flags & SENDING);
if (proc2pid(p) == src) /* if p is the one */
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.
*/
// update 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;
p_who_wanna_recv->p_recvfrom = src; // recv 方阻塞自己的时候指明它原本是想从哪里接收数据!
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;
}
——msg_recv 首先检查是否有中断通知,如果有,就“接受”中断通知然后返回,如果没有中断通知,就判断 recv 是想从 any 接收还是从具体的进程接收,从 any 接收好办,选择 recv 的发送队列的第一个 send 然后接收他的消息就行了,如果是从具体进程 recv ,那么就要判断那个具体的进程是否正在给 recv 发送,如果正在,那么一拍即合(否则就 recv 就会阻塞的),拿走那个 send 的数据然后激活 send ,自己返回!
测试消息机制:
开始 TestA 阻塞在从 TestB 接收数据,并且 TestC 阻塞在想从 ANY 接收数据,然后 TestB 发消息给 TestA,TestA 接收到 TestB 的消息了之后又给 TestC 发送消息!
二,宏内核改微内核
消息机制是微内核的核心,我们既然已经有了消息机制,就可以搭建微内核了,上面消息机制的测试中,TestX 都是发送或者接收了一次消息之后就不再发送或者接收消息了,如果我们弄一个进程,在那里不间断地 recv ,再给 MESSAGE 注明类型,我们就可以说:哪个进程给哪个系统任务发送了消息,请求的是这个系统任务的哪种类型的服务!(服务的类型取决于系统任务支持什么类型的服务,不能你虽然把我唤醒了,但请求的不是我力所能及的服务,可以猜猜 sys_server 会做什么反应 :))
1,添加一个系统任务:sys_server
2,增加 sys_server 提供的一种服务类型(GET_TICKS)
3,TestB 和 TestC 之间互相传递消息
·main.c 节选
PUBLIC int get_ticks()
{
MESSAGE msg;
reset_msg(&msg);
msg.type = GET_TICKS;
send_recv(BOTH, TASK_SERVER, &msg);
return msg.RETVAL;
}
void TestA()
{
while (TRUE)
{
printf("%d ", get_ticks());
milli_delay(300);
}
}
void TestB()
{
MESSAGE _m;
_m.RETVAL = 1;
while (TRUE)
{
send_recv(SEND, 4, &_m);
send_recv(RECEIVE, 4, &_m);
printf("TestB number is [%d]\n", ++_m.RETVAL);
milli_delay(2000);
}
}
void TestC()
{
MESSAGE _m;
while (TRUE)
{
send_recv(RECEIVE, 3, &_m);
printf("TestC number is [%d]\n", ++_m.RETVAL);
send_recv(SEND, 3, &_m);
}
}
·sys_task.c
// ----------------------------
// <systask.c>
// Jack Zheng
// Comment:December 7, 2019
// ----------------------------
#include "type.h"
#include "const.h"
#include "protect.h"
#include "string.h"
#include "proc.h"
#include "tty.h"
#include "console.h"
#include "global.h"
#include "keyboard.h"
#include "proto.h"
void task_server()
{
MESSAGE _m;
while (TRUE)
{
send_recv(RECEIVE, ANY, &_m);
int src = _m.source;
switch (_m.type)
{
case GET_TICKS: /* 某个进程请求的这个服务 */
_m.RETVAL = ticks;
send_recv(SEND, src, &_m);
break;
default:
panic("unknown Message type");
break;
}
}
}
运行:
sys_server 的 GET_TICKS 是在接收到消息之后才解析消息类型然后进行相应的处理!所以进程请求系统任务服务的大致手段是:唤醒系统任务,系统任务解析服务类型,服务!
OK,可以看到,我们的内核从宏内核改为微内核之后优雅了许多,所谓一切都建立在消息上!