Support O_NONBLOCK when opening #I chans
authorBarret Rhoden <brho@cs.berkeley.edu>
Sun, 19 Jul 2015 13:41:13 +0000 (09:41 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 24 Jul 2015 07:05:14 +0000 (03:05 -0400)
Applications can set O_NONBLOCK when cloning and can getfl() to see
O_NONBLOCK, but they cannot toggle O_NONBLOCK via setfl().  Getting via
getfl() is of dubious value, since only the cloned chan will have the
mark.  To dynamically change, they need to use the ctl message.  I
wasn't interested in having device ops that translate a setfl() call or
something.  That's the purpose of the ctls, after all.

It's up to individual devices if they want to handle O_NONBLOCK when a
chan is opened.  They are free to throw an error() from dev->open.

Note that dial() currently does not support open flags to pass through
to the clone.  If you want to play around with this, you'll need send
the ctl message.

Also note that the non-blocking nature of the chan is part of the conv
(the queues in this case), and not really part of the chan.  If a
process writes a "nonblock" ctl message into the conv, all other chans
will now behave in a O_NONBLOCK-ing manner, since the underlying conv
changed.  This is different than the unix way of dealing with
O_NONBLOCK, where each socket/chan can be treated differently.

It's tempting to just remove the O_NONBLOCK completely and only allow a
ctl message, and fake O_NONBLOCK in userspace (e.g. in open() or the 9ns
networking shims in glibc), but that's at least one extra syscall for
every opened connection, both for clones and (in future commits) for
listen/accept4().

I strongly considered allowing O_NONBLOCK to set "nonblock on" for *any*
 #I file, specifically ctl and data files.  That way, you could just do:

open("/net/tcp/6/data", O_RDWR | O_NONBLOCK);

and then have non-blocking I/O.  It's what a user might expect from
open() with O_NONBLOCK.  The deciding point against this was that I
didn't want data to have any control side-effects, even on open().  This
is only really important if you drop a file in #s, and then someone who
is allowed to read/write to it could suddenly change the nonblocking
status.

Finally, note that these changes support non-blocking qio in #I,
specifically on conversation inbound and outbound queues.  Depending on
your medium, you could still block *below* the IP stack on a write().
For instance, ipoput4() calls the various bwrites, and eventually
etheroq().  It is possible to block there, unless the Ethernet device is
set to nonblocking.  Check out etherwrite() for specifics.

kern/include/ip.h
kern/include/ns.h
kern/src/net/devip.c
kern/src/ns/sysfile.c

index b779371..9a00150 100644 (file)
@@ -54,6 +54,7 @@ struct conv {
        uint32_t ttl;                           /* max time to live */
        uint32_t tos;                           /* type of service */
        int ignoreadvice;                       /* don't terminate connection on icmp errors */
+       bool nonblock;                          /* set to nonblocking, O_NONBLOCK style */
 
        uint8_t ipversion;
        uint8_t laddr[IPaddrlen];       /* local IP address */
@@ -360,6 +361,7 @@ struct Proto *Fsrcvpcolx(struct Fs *, uint8_t unused_uint8_t);
 char *Fsstdconnect(struct conv *, char **unused_char_pp_t, int);
 char *Fsstdannounce(struct conv *, char **unused_char_pp_t, int);
 char *Fsstdbind(struct conv *, char **unused_char_pp_t, int);
+void Fsconvnonblock(struct conv *, bool);
 uint32_t scalednconv(void);
 
 /* 
index 753d77e..6cb8b5e 100644 (file)
@@ -315,7 +315,8 @@ enum {
        CCEXEC =                O_CLOEXEC,      /* (prob should be on the FD, 9ns has it here) */
        CRCLOSE =               O_REMCLO,       /* remove on close (also, maybe should be on FD) */
        CAPPEND =               O_APPEND,       /* append on write */
-       CEXTERNAL_FLAGS = (CCEXEC | CRCLOSE | CAPPEND),
+       CNONBLOCK =     O_NONBLOCK,     /* don't block, can't be set via setfl */
+       CEXTERNAL_FLAGS = (CCEXEC | CRCLOSE | CAPPEND | CNONBLOCK),
 };
 
 #define NS_IPCK_SHIFT  2
index 1a2f7d3..4ac2c6d 100644 (file)
@@ -432,6 +432,9 @@ static struct chan *ipopen(struct chan *c, int omode)
                                error(Enodev);
                                break;
                        }
+                       /* we only honor nonblock on a clone */
+                       if (c->flag & CNONBLOCK)
+                               Fsconvnonblock(cv, TRUE);
                        mkqid(&c->qid, QID(p->x, cv->x, Qctl), 0, QTFILE);
                        break;
                case Qdata:
@@ -741,6 +744,8 @@ static long ipread(struct chan *ch, void *a, long n, int64_t off)
                        x = f->p[PROTO(ch->qid)];
                        c = x->conv[CONV(ch->qid)];
                        sofar = (*x->state) (c, buf, Statelen - 2);
+                       sofar += snprintf(buf + sofar, Statelen - 2 - sofar, "nonblock %s\n",
+                                         c->nonblock ? "on" : "off");
                        rv = readstr(offset, p, n, buf);
                        kfree(buf);
                        return rv;
@@ -1081,6 +1086,13 @@ char *Fsstdbind(struct conv *c, char *argv[], int argc)
        }
 }
 
+void Fsconvnonblock(struct conv *cv, bool onoff)
+{
+       qnonblock(cv->wq, onoff);
+       qnonblock(cv->rq, onoff);
+       cv->nonblock = onoff;
+}
+
 static void bindctlmsg(struct Proto *x, struct conv *c, struct cmdbuf *cb)
 {
        char *p;
@@ -1093,6 +1105,22 @@ static void bindctlmsg(struct Proto *x, struct conv *c, struct cmdbuf *cb)
                error(p);
 }
 
+static void nonblockctlmsg(struct conv *c, struct cmdbuf *cb)
+{
+       if (cb->nf < 2)
+               goto err;
+       if (!strcmp(cb->f[1], "on"))
+               Fsconvnonblock(c, TRUE);
+       else if (!strcmp(cb->f[1], "off"))
+               Fsconvnonblock(c, FALSE);
+       else
+               goto err;
+       return;
+err:
+       set_errno(EINVAL);
+       error("nonblock [on|off]");
+}
+
 static void tosctlmsg(struct conv *c, struct cmdbuf *cb)
 {
        if (cb->nf < 2)
@@ -1163,6 +1191,8 @@ static long ipwrite(struct chan *ch, void *v, long n, int64_t off)
                                announcectlmsg(x, c, cb);
                        else if (strcmp(cb->f[0], "bind") == 0)
                                bindctlmsg(x, c, cb);
+                       else if (strcmp(cb->f[0], "nonblock") == 0)
+                               nonblockctlmsg(c, cb);
                        else if (strcmp(cb->f[0], "ttl") == 0)
                                ttlctlmsg(c, cb);
                        else if (strcmp(cb->f[0], "tos") == 0)
@@ -1356,6 +1386,7 @@ retry:
        c->restricted = 0;
        c->ttl = MAXTTL;
        c->tos = DFLTTOS;
+       c->nonblock = FALSE;
        qreopen(c->rq);
        qreopen(c->wq);
        qreopen(c->eq);
index f71fdb4..6b3d264 100644 (file)
@@ -1642,6 +1642,11 @@ int fd_setfl(int fd, int flags)
                set_errno(EINVAL);
                error("can't toggle O_CLOEXEC with setfl");
        }
+       if (cexternal_flags_differ(flags, c->flag, O_NONBLOCK)) {
+               /* If we want to let them toggle NONBLOCK, it'd require a device op */
+               set_errno(EINVAL);
+               error("can't set O_NONBLOCK, use a device-specific ctl command");
+       }
        c->flag = (c->flag & ~CEXTERNAL_FLAGS) | (flags & CEXTERNAL_FLAGS);
        cclose(c);
        poperror();