Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 30 Jun 1999 18:54:39 -0700 (PDT)
From:      Matthew Dillon <dillon@apollo.backplane.com>
To:        Terry Lambert <tlambert@primenet.com>
Cc:        bright@rush.net (Alfred Perlstein), smp@FreeBSD.ORG, tlambert@primenet.com
Subject:   Re: async call gates
Message-ID:  <199907010154.SAA42283@apollo.backplane.com>
References:   <199907010100.SAA13352@usr09.primenet.com>

next in thread | previous in thread | raw e-mail | index | archive | help
    In one of my embedded operating systems I came up with a great
    idea in how to handle system calls involving I/O.  Actually, the idea 
    has its roots with the Amiga's I/O subsystem.

    Basically instead of making a system call per-say, you build a message
    and send it, then wait for a reply.

    Now one might think that would be rather slow for system calls that can
    run synchronously.  This is where the idea from the Amiga's I/O subsystem
    comes into play.

    Simply put, the Amiga had the notion of userland being able to request
    asynchronous or synchronous operation, and then having deviceland be
    able to run a request either asynchronously or synchronously, separate
    from what the userland requested.  In otherwords, the type of operation
    is decoupled.

    When you make the I/O call you ask it to run either synchronously or 
    asynchronously.  The device takes the request into account and then
    decides how to actually run it, depending on whether the device thinks
    it will need to block or not.  In the Amiga, the device's decision 
    was *independant* of the request.

    So in my embedded OS I had a domsg() and a sendmsg() API.  The user 
    process constructs the message and calls one or the other entry point.
    If the user requested a synchronous call AND the device happens to run 
    the request synchronously, the OS doesn't bother even queueing the 
    message up to the device.  It calls the device entry, sees that the
    device ran the message synchronously, and returns.  The result is an
    operation that runs just as quickly as a normal system call might since
    the added complexity of queueing and dequeueing the message just doesn't
    happen.

    If the API code sees that the device has done something different, it
    makes the appropriate adjustments to the message to make it appear to
    the user process that the device did it the way the user process
    requested.  So, for example, if the user process calls the domsg()
    entry point and the device entry indicates that it is running the
    request asynchronously, the domsg() entry point will then block waiting
    for the request to complete.  If the user process calls the sendmsg()
    entry point and the device entry indicates that it ran the request
    synchronously, the sendmsg() entry point will then queue the message
    on the return list which is what the user process is requesting.

    So:

    int
    domsg(msg)
    {
	int r;
	...
	msg->flags |= MSGF_SYNCHRONOUS;
	device->entry(msg);
	if ((msg->flags & MSGF_SYNCHRONOUS) == 0)
	    waitmsg(msg);
	return(msg->result);
    }

    void
    sendmsg(msg, replyport)
    {
	int r;
	...
	msg->flags &= ~(MSGF_RETURNED | MSGF_SYNCHRONOUS);
	msg->rport = replyport;
	device->entry(msg);
	if (msg->flags & MSGF_SYNCHRONOUS) {
	    replymsg(msg, msg->result);	/* note: clears MSGF_SYNCHRONOUS */
	}
    }

    int
    waitmsg(msg)
    {
	if ((msg->flags & MSGF_SYNCHRONOUS) == 0) {
		while ((msg->flags & MSGF_RETURNED) == 0)
		    ... block on message ...
	}
	return(msg->result);
    }

    void
    replymsg(msg, result)
    {
	msg->result = result;
	if (msg->flags & MSGF_SYNCHRONOUS) {
	    msg->rport = NULL;
	    msg->flags &= ~MSGF_SYNCHRONOUS;
	}
	if (msg->rport) {
	    switch(msg->rport->type) {
	    case PTYPE_NORMAL:
		addlist(&msg->rport->queue, &msg->qnode);
		wakeup(msg->rport);
		break;
	    case PTYPE_INTERRUPT:
		... queue interrupt thread ...
		break;
	    case PTYPE_USER_VECTORED_INTERRUPT:
		... queue task software interrupt ...
		break;
	    case PTYPE_RETCALL:
		msg->rport->retfunc(msg);
		break;
	    ...
	    }
	}
	msg->flags |= MSGF_RETURNED;
    }

    Pretty simple, eh?  Yet it completely decouples the requester from the
    requestee.  The requester can even request that upon completion the
    message be returned to a completion port, or generate a software interrupt,
    or even run a supervisory function synchronously.  The requestee doesn't
    care what the requestor wants, it is all encapsulated in the API.

    struct port {
	struct list queue;
	int	type;
	int	pri;
	void	(*func)(struct msg *msg);
    };

    struct msg {
	struct node qnode;
	struct port *rport;
	int	flags;
	off_t	result;
	int	cmd;
	void	*data;
	int	len;
	off_t	arg1;
	off_t	arg2;
	int	arg3;
    };

					-Matt



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-smp" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199907010154.SAA42283>