From owner-freebsd-smp Wed Jun 30 18:54:47 1999 Delivered-To: freebsd-smp@freebsd.org Received: from apollo.backplane.com (apollo.backplane.com [209.157.86.2]) by hub.freebsd.org (Postfix) with ESMTP id AC2E714D62 for ; Wed, 30 Jun 1999 18:54:42 -0700 (PDT) (envelope-from dillon@apollo.backplane.com) Received: (from dillon@localhost) by apollo.backplane.com (8.9.3/8.9.1) id SAA42283; Wed, 30 Jun 1999 18:54:39 -0700 (PDT) (envelope-from dillon) Date: Wed, 30 Jun 1999 18:54:39 -0700 (PDT) From: Matthew Dillon Message-Id: <199907010154.SAA42283@apollo.backplane.com> To: Terry Lambert Cc: bright@rush.net (Alfred Perlstein), smp@FreeBSD.ORG, tlambert@primenet.com Subject: Re: async call gates References: <199907010100.SAA13352@usr09.primenet.com> Sender: owner-freebsd-smp@FreeBSD.ORG Precedence: bulk X-Loop: FreeBSD.org 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