File: [local] / prex-old / sys / ipc / msg.c (download)
Revision 1.1.1.1 (vendor branch), Tue Jun 3 09:38:46 2008 UTC (16 years, 2 months ago) by nbrk
Branch: MAIN, KOHSUKE
CVS Tags: PREX_0_7_BASE, HEAD Branch point for: PREX_0_8_BASE
Changes since 1.1: +0 -0 lines
Yeah, this is an initial import of Prex, portable real-time microkernel
operating system. I wanna hack it for non-profit but fun, so let it in.
|
/*-
* Copyright (c) 2005-2007, Kohsuke Ohtani
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* msg.c - routines to transmit a message.
*/
/*
* Messages are sent to the specific object by using msg_send().
* The transmission of a message is completely synchronous with
* this kernel. This means the thread which sent a message is blocked
* until it receives a response from another thread. msg_receive()
* performs reception of a message. msg_receive() is also blocked
* when no message is reached to the target object. The receiver
* thread must answer the message using msg_reply() after it finishes
* its message processing.
*
* The receiver thread can not receive another message until it
* replies to the sender. In short, a thread can receive only one
* message at once. Once the thread receives message, it can send
* another message to different object. This mechanism allows threads
* to redirect the sender's request to another thread.
*
* The message is copied from thread to thread directly without any
* kernel buffering. If sent message contains a buffer, sender's memory
* region is automatically mapped to the receiver's memory in kernel.
* Since there is no page out of memory in this system, we can copy the
* message data via physical memory at anytime.
*/
#include <kernel.h>
#include <queue.h>
#include <event.h>
#include <kmem.h>
#include <sched.h>
#include <thread.h>
#include <task.h>
#include <vm.h>
#include <ipc.h>
#define min(a,b) (((a) < (b)) ? (a) : (b))
/* forward declarations */
static thread_t msg_dequeue(queue_t);
static void msg_enqueue(queue_t, thread_t);
/* event for IPC operation */
static struct event ipc_event;
/*
* msg_send - send a message.
* @obj: object ID to send a message.
* @msg: pointer to the message buffer
* @size: size of the message buffer.
*
* The current thread will be blocked until any other thread receives
* the message and calls msg_reply() for the target object.
* When new message has been reached to the object, it will be received
* by highest priority thread waiting for that message.
* A thread can send a message to any object if it knows the object id.
*/
int
msg_send(object_t obj, void *msg, size_t size)
{
thread_t th;
void *kmsg;
int rc;
if (!user_area(msg))
return EFAULT;
if (size < sizeof(struct msg_header))
return EINVAL;
sched_lock();
if (!object_valid(obj)) {
sched_unlock();
return EINVAL;
}
if (obj->owner != cur_task() && !task_capable(CAP_IPC)) {
sched_unlock();
return EPERM;
}
/*
* A thread can not send a message when the thread is
* already receiving from the target object. This will
* obviously cause a deadlock.
*/
if (obj == cur_thread->recv_obj) {
sched_unlock();
return EDEADLK;
}
/*
* Translate message address to the kernel linear address.
* So that a receiver thread can access the message via
* kernel pointer. We can catch the page fault here.
*/
if ((kmsg = kmem_map(msg, size)) == NULL) {
/* Error - no physical address for the message */
sched_unlock();
return EFAULT;
}
/*
* Fill sender task ID in the message header.
* So, the receiver can trust this ID.
*/
((struct msg_header *)kmsg)->task = cur_task();
/* Save the data for message block. */
cur_thread->msg_addr = kmsg;
cur_thread->msg_size = size;
/*
* If receiver already exists, wake it up. Highest priority
* thread will get this message.
*/
if (!queue_empty(&obj->recvq)) {
th = msg_dequeue(&obj->recvq);
sched_unsleep(th, 0);
}
/*
* Sleep until we get a reply message.
* Note: we can not touch the data in obj after we wakeup
* because it may be deleted during we were sleeping.
*/
cur_thread->send_obj = obj;
msg_enqueue(&obj->sendq, cur_thread);
rc = sched_sleep(&ipc_event);
if (rc == SLP_INTR)
queue_remove(&cur_thread->ipc_link);
cur_thread->send_obj = NULL;
sched_unlock();
/*
* Check sleep result.
*/
switch (rc) {
case SLP_BREAK:
return EAGAIN; /* Receiver has been terminated */
case SLP_INVAL:
return EINVAL; /* Object has been deleted */
case SLP_INTR:
return EINTR; /* Exception */
}
return 0;
}
/*
* Receive a message.
*
* A thread can receive a message from the object which was created
* by any thread belongs to same task. If the message has not arrived
* yet, it blocks until any message comes in.
*
* The size argument specifies the "maximum" size of the message
* buffer to receive. If the sent message is larger than this size,
* the kernel will automatically clip the message to the receive buffer
* size.
*
* When message is received, the sender thread is removed from
* object's send queue. So, another thread can receive the subsequent
* message from that object. This is important for the multi-thread
* server which receives some messages simultaneously.
*/
int
msg_receive(object_t obj, void *msg, size_t size)
{
thread_t th;
int err, rc;
size_t len;
err = 0;
if (!user_area(msg))
return EFAULT;
sched_lock();
if (!object_valid(obj)) {
err = EINVAL;
goto out;
}
if (obj->owner != cur_task()) {
err = EACCES;
goto out;
}
/*
* Check if this thread finished previous receive operation.
* A thread can not receive different messages at once.
*/
if (cur_thread->recv_obj) {
err = EBUSY;
goto out;
}
cur_thread->recv_obj = obj;
/*
* If no message exists, wait until message arrives.
*/
while (queue_empty(&obj->sendq)) {
/*
* Sleep until message comes in.
*/
msg_enqueue(&obj->recvq, cur_thread);
rc = sched_sleep(&ipc_event);
if (rc == 0) {
/*
* Even if this thread is woken by the sender thread,
* the message may be received by another thread
* before this thread runs. This can occur when
* higher priority thread becomes runnable at that
* time. So, it is necessary to check the existence
* of the sender here again. The following line must
* be "continue" instead of "break" to check the
* queue again.
*/
continue;
}
/*
* Receive is failed by some reason.
*/
switch (rc) {
case SLP_INVAL:
err = EINVAL; /* Object has been deleted */
break;
case SLP_INTR:
queue_remove(&cur_thread->ipc_link);
err = EINTR; /* Got exception */
break;
default:
panic("msg_receive");
}
cur_thread->recv_obj = NULL;
goto out;
}
th = msg_dequeue(&obj->sendq);
/*
* Copy message to user space.
* The smaller buffer size is used as copy length
* between sender and receiver thread.
*/
len = min(size, th->msg_size);
if (len > 0) {
if (umem_copyout(th->msg_addr, msg, len)) {
msg_enqueue(&obj->sendq, th);
cur_thread->recv_obj = NULL;
err = EFAULT;
goto out;
}
}
/*
* Detach the message from the target object.
*/
cur_thread->sender = th;
th->receiver = cur_thread;
out:
sched_unlock();
return err;
}
/*
* Send a reply message.
*
* The target object must be an appropriate object that current thread
* has been received from. Otherwise, this function will be failed.
*
* Since the target object may already be deleted, we can not access
* the data of the object within this routine.
*/
int
msg_reply(object_t obj, void *msg, size_t size)
{
thread_t th;
size_t len;
int err = 0;
if (!user_area(msg))
return EFAULT;
sched_lock();
if (!object_valid(obj) || obj != cur_thread->recv_obj) {
sched_unlock();
return EINVAL;
}
/*
* Check if sender still exists
*/
if (cur_thread->sender == NULL) {
err = EINVAL;
goto out;
}
/*
* Copy message to the sender's buffer.
*/
th = cur_thread->sender;
len = min(size, th->msg_size);
if (len > 0) {
if (umem_copyin(msg, th->msg_addr, len)) {
sched_unlock();
return EFAULT;
}
}
/*
* Wakeup sender with no error.
*/
sched_unsleep(th, 0);
th->receiver = NULL;
out:
/* Clear transmit state */
cur_thread->sender = NULL;
cur_thread->recv_obj = NULL;
sched_unlock();
return err;
}
/*
* Clean up pending message operation of specified thread in order
* to prevent deadlock.
* This is called when the thread is killed.
* It is necessary to deal with the following conditions.
*
* If killed thread is sender:
* 1. Killed after message is received
* -> The received thread will reply to the invalid thread.
*
* 2. Killed before message is received
* -> The thread remains in send queue of the object.
*
* When thread is receiver:
* 3. Killed after message is sent
* -> The sender thread continues waiting for reply forever.
*
* 4. Killed before message is sent
* -> The thread remains in receive queue of the object.
*/
void
msg_cleanup(thread_t th)
{
sched_lock();
if (th->send_obj) {
if (th->receiver)
th->receiver->sender = NULL;
else
queue_remove(&th->ipc_link);
}
if (th->recv_obj) {
if (th->sender) {
sched_unsleep(th->sender, SLP_BREAK);
th->sender->receiver = NULL;
} else
queue_remove(&th->ipc_link);
}
sched_unlock();
}
/*
* Cancel all message operation relevant to the specified object.
* This is called when target object is deleted.
* All threads in message queue are woken to avoid deadlock.
* If the message has already been received, send/reply operation
* continue processing normally.
*/
void
msg_cancel(object_t obj)
{
queue_t head, q;
thread_t th;
sched_lock();
/*
* Force wakeup all thread in the send queue.
*/
head = &obj->sendq;
for (q = queue_first(head); !queue_end(head, q); q = queue_next(q)) {
th = queue_entry(q, struct thread, ipc_link);
sched_unsleep(th, SLP_INVAL);
}
/*
* Force wakeup all thread waiting for receive.
*/
head = &obj->recvq;
for (q = queue_first(head); !queue_end(head, q); q = queue_next(q)) {
th = queue_entry(q, struct thread, ipc_link);
sched_unsleep(th, SLP_INVAL);
}
sched_unlock();
}
/*
* Dequeue thread from specified queue.
* The most highest priority thread will be chosen.
*/
static thread_t
msg_dequeue(queue_t head)
{
queue_t q;
thread_t th, top;
q = queue_first(head);
top = queue_entry(q, struct thread, ipc_link);
while (!queue_end(head, q)) {
th = queue_entry(q, struct thread, ipc_link);
if (th->prio < top->prio)
top = th;
q = queue_next(q);
}
queue_remove(&top->ipc_link);
return top;
}
static void
msg_enqueue(queue_t head, thread_t th)
{
enqueue(head, &th->ipc_link);
}
void
msg_init(void)
{
event_init(&ipc_event, "ipc");
}