Adds devalarm (#A)
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 20 Nov 2013 00:02:27 +0000 (16:02 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 20:27:06 +0000 (12:27 -0800)
It is an alarm service for processes.  Check out the top of
k/d/d/alarm.c and the test for more details.

Commented out right now, since we don't have a 9ns stack.

kern/drivers/dev/Kbuild [new file with mode: 0644]
kern/drivers/dev/alarm.c [new file with mode: 0644]
kern/include/devalarm.h [new file with mode: 0644]
kern/include/env.h
kern/src/process.c
tests/alarm.c [new file with mode: 0644]

diff --git a/kern/drivers/dev/Kbuild b/kern/drivers/dev/Kbuild
new file mode 100644 (file)
index 0000000..b878dda
--- /dev/null
@@ -0,0 +1 @@
+#obj-y                                         += alarm.o
diff --git a/kern/drivers/dev/alarm.c b/kern/drivers/dev/alarm.c
new file mode 100644 (file)
index 0000000..592067b
--- /dev/null
@@ -0,0 +1,425 @@
+/* Copyright (c) 2013 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * devalarm/#A: a device for registering per-process alarms.
+ *
+ * Allows a process to set up alarms, where the kernel will send an event to a
+ * posted ev_q at a certain TSC time.
+ *
+ * Every process has their own alarm sets and view of #A; gen and friends look
+ * at current's alarmset when it is time to gen or open a file.
+ *
+ * To use, first open #A/clone, and that gives you an alarm directory aN, where
+ * N is ID of the alarm.  ctl takes two commands: "evq POINTER" (to tell the
+ * kernel the pointer of your ev_q) and "cancel", to stop an alarm.  timer takes
+ * just the value (in absolute tsc time) to fire the alarm.
+ *
+ * While each process has a separate view of #A, it is possible to post a chan
+ * to Qctl or Qtimer to #s.  If another proc has your Qtimer, it can set it in
+ * the past, thereby triggering an immediate event.  More clever than useful.
+ *
+ * Notes on refcnting (the trickier parts here):
+ * - the proc_alarms have counted references to their proc
+ *             proc won't free til all alarms are closed, which is fine.  we close
+ *             all files in destroy.  if a proc drops a chan in srv, the proc will stay
+ *             alive because the alarm is alive - til that chan is closed (srvremove)
+ *
+ *             other shady ways to keep a chan alive: cd to it!  if it is ., we'd
+ *             keep a ref around.  however, only alarmdir *file* grab refs, not
+ *             directories.
+ *
+ * - proc_alarms are kref'd, since there can be multiple chans per alarm
+ *             the only thing that keeps an alarm alive is a chan on a CTL or TIMER (or
+ *             other file).  when you cloned, you got back an open CTL, which keeps the
+ *             alarm (and the dir) alive.
+ *
+ *             we need to be careful generating krefs, in case alarms are concurrently
+ *             released and removed from the lists.  just like with procs and pid2proc,
+ *             we need to sync with the source of the kref. */
+
+#include <kmalloc.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <error.h>
+#include <pmap.h>
+#include <sys/queue.h>
+#include <smp.h>
+#include <kref.h>
+#include <atomic.h>
+#include <alarm.h>
+#include <event.h>
+#include <umem.h>
+#include <devalarm.h>
+
+/* qid path types */
+#define Qtopdir                                        1
+#define Qclone                                 2
+#define Qalarmdir                              3
+#define Qctl                                   4
+#define Qtimer                                 5       /* Qctl + 1 */
+
+/* This paddr/kaddr is a bit dangerous.  it'll work so long as we don't need all
+ * 64 bits for a physical address (48 is the current norm on x86_64). */
+#define ADDR_SHIFT 5
+#define QID2A(q) ((struct proc_alarm*)KADDR(((q).path >> ADDR_SHIFT)))
+#define TYPE(q) ((q).path & ((1 << ADDR_SHIFT) - 1))
+#define QID(ptr, type) ((PADDR(ptr) << ADDR_SHIFT) | type)
+
+static void alarm_release(struct kref *kref)
+{
+       struct proc_alarm *a = container_of(kref, struct proc_alarm, kref);
+       struct proc *p = a->proc;
+       assert(p);
+       spin_lock(&p->alarmset.lock);
+       TAILQ_REMOVE(&p->alarmset.list, a, link);
+       spin_unlock(&p->alarmset.lock);
+       /* When this returns, the alarm has either fired or it never will */
+       unset_alarm(p->alarmset.tchain, &a->a_waiter);
+       proc_decref(p);
+       kfree(a);
+}
+
+static void proc_alarm_handler(struct alarm_waiter *a_waiter)
+{
+       struct proc_alarm *a = container_of(a_waiter, struct proc_alarm, a_waiter);
+       struct event_queue *ev_q = ACCESS_ONCE(a->ev_q);
+       struct event_msg msg;
+       if (!ev_q || !a->proc) {
+               printk("[kernel] proc_alarm, bad ev_q %p or proc %p\n", ev_q, a->proc);
+               return;
+       }
+       memset(&msg, 0, sizeof(struct event_msg));
+       msg.ev_type = EV_ALARM;
+       send_event(a->proc, ev_q, &msg, 0);
+}
+
+void devalarm_init(struct proc *p)
+{
+       TAILQ_INIT(&p->alarmset.list);
+       spinlock_init(&p->alarmset.lock);
+       /* Just running all the proc alarms on core 0. */
+       p->alarmset.tchain = &per_cpu_info[0].tchain;
+       p->alarmset.id_counter = 0;
+}
+
+static int alarmgen(struct chan *c, char *entry_name,
+                    struct dirtab *unused, int unused_nr_dirtab,
+                    int s, struct dir *dp)
+{
+       struct qid q;
+       struct proc_alarm *a_i;
+       struct proc *p = current;
+       /* Whether we're in one dir or at the top, .. still takes us to the top. */
+       if (s == DEVDOTDOT) {
+               mkqid(&q, Qtopdir, 0, QTDIR);
+               devdir(c, c->qid, "#A", 0, eve, 0555, dp);
+               return 1;
+       }
+       switch (TYPE(c->qid)) {
+               case Qtopdir:
+                       /* Generate elements for the top level dir.  We support a clone and
+                        * alarm dirs at the top level */
+                       if (s == 0) {
+                               mkqid(&q, Qclone, 0, QTFILE);
+                               devdir(c, q, "clone", 0, eve, 0666, dp);
+                               return 1;
+                       }
+                       s--;    /* 1 -> 0th element, 2 -> 1st element, etc */
+                       /* Gets the s-th element (0 index)
+                        * 
+                        * I would like to take advantage of the state machine and our
+                        * previous answer to get the sth element of the list.  We can get
+                        * at our previous run of gen from dp (struct dir), and use that to
+                        * get the next item.  I'd like to do something like:
+                        *
+                        * if (dp->qid.path >> ADDR_SHIFT)
+                        *              a_i = TAILQ_NEXT(QID2A(dp->qid), link);
+                        *
+                        * Dev would give us a 0'd dp path on the first run, so if we have a
+                        * path, we know we're on an iterative run.  However, the problem is
+                        * that we could have lost the element dp refers to (QID2A(dp->qid))
+                        * since our previous run, so we can't even access that memory to
+                        * check for refcnts or anything.  We need a new model for how gen
+                        * works (probably a gen_start and gen_stop devop, passed as
+                        * parameters to devwalk), so that we can have some invariants
+                        * between gen runs.
+                        *
+                        * Til then, we're stuck with arrays like in #I (though we can use
+                        * Linux style fdsets) or lousy O(n^2) linked lists (like #s).
+                        *
+                        * Note that we won't always start a gen loop with s == 0
+                        * (devdirread, for instance) */
+                       spin_lock(&p->alarmset.lock);
+                       TAILQ_FOREACH(a_i, &p->alarmset.list, link) {
+                               if (s-- == 0)
+                                       break;
+                       }
+                       /* As soon as we unlock, someone could free a_i */
+                       if (!a_i) {
+                               spin_unlock(&p->alarmset.lock);
+                               return -1;
+                       }
+                       snprintf(get_cur_genbuf(), GENBUF_SZ, "a%d", a_i->id);
+                       mkqid(&q, QID(a_i, Qalarmdir), 0, QTDIR);
+                       devdir(c, q, get_cur_genbuf(), 0, eve, 0555, dp);
+                       spin_unlock(&p->alarmset.lock);
+                       return 1;
+               case Qalarmdir:
+                       /* Gen the contents of the alarm dirs */
+                       s += Qctl;      /* first time through, start on Qctl */
+                       switch (s) {
+                               case Qctl:
+                                       mkqid(&q, QID(QID2A(c->qid), Qctl), 0, QTFILE);
+                                       devdir(c, q, "ctl", 0, eve, 0666, dp);
+                                       return 1;
+                               case Qtimer:
+                                       mkqid(&q, QID(QID2A(c->qid), Qtimer), 0, QTFILE);
+                                       devdir(c, q, "timer", 0, eve, 0666, dp);
+                                       return 1;
+                       }
+                       return -1;
+               /* Need to also provide a direct hit for Qclone and all other files (at
+                * all levels of the hierarchy).  Every file is both
+                * generated (via the s increments in their respective directories) and
+                * directly gen-able.  devstat() will call gen with a specific path in
+                * the qid.  In these cases, we make a dir for whatever they are asking
+                * for.  Note the qid stays the same.  I think this is what the old
+                * plan9 comments above devgen were talking about for (ii).
+                *
+                * We don't need to do this for the directories - devstat will look for
+                * the a directory by path and fail.  Then it will manually build the
+                * stat output (check the -1 case in devstat). */
+               case Qclone:
+                       devdir(c, c->qid, "clone", 0, eve, 0666, dp);
+                       return 1;
+               case Qctl:
+                       devdir(c, c->qid, "ctl", 0, eve, 0666, dp);
+                       return 1;
+               case Qtimer:
+                       devdir(c, c->qid, "timer", 0, eve, 0666, dp);
+                       return 1;
+       }
+       return -1;
+}
+
+static void alarminit(void)
+{
+}
+
+static struct chan *alarmattach(char *spec)
+{
+       struct chan *c = devattach('A', spec);
+       mkqid(&c->qid, Qtopdir, 0, QTDIR);
+       return c;
+}
+
+static struct walkqid *alarmwalk(struct chan *c, struct chan *nc, char **name,
+                                 int nname)
+{
+       return devwalk(c, nc, name, nname, 0, 0, alarmgen);
+}
+
+static long alarmstat(struct chan *c, uint8_t *db, long n)
+{
+       return devstat(c, db, n, 0, 0, alarmgen);
+}
+
+/* It shouldn't matter if p = current is DYING.  We'll eventually fail to insert
+ * the open chan into p's fd table, then decref the chan. */
+static struct chan *alarmopen(struct chan *c, int omode)
+{
+       struct proc *p = current;
+       struct proc_alarm *a, *a_i;
+       switch (TYPE(c->qid)) {
+               case Qtopdir:
+               case Qalarmdir:
+                       if (omode & ORCLOSE)
+                               error(Eperm);
+                       if (omode != OREAD)
+                               error(Eisdir);
+                       break;
+               case Qclone:
+                       a = kzmalloc(sizeof(struct proc_alarm), KMALLOC_WAIT);
+                       kref_init(&a->kref, alarm_release, 1);
+                       init_awaiter(&a->a_waiter, proc_alarm_handler);
+                       spin_lock(&p->alarmset.lock);
+                       a->id = p->alarmset.id_counter++;
+                       proc_incref(p, 1);
+                       a->proc = p;
+                       TAILQ_INSERT_TAIL(&p->alarmset.list, a, link);
+                       spin_unlock(&p->alarmset.lock);
+                       mkqid(&c->qid, QID(a, Qctl), 0, QTFILE);
+                       break;
+               case Qctl:
+               case Qtimer:
+                       /* the purpose of opening is to hold a kref on the proc_alarm */
+                       a = QID2A(c->qid);
+                       assert(a);
+                       assert(a->proc == current);
+                       /* this isn't a valid pointer yet, since our chan doesn't have a
+                        * ref.  since the time that walk gave our chan the qid, the chan
+                        * could have been closed, and the alarm decref'd and freed.  the
+                        * qid is essentially an uncounted reference, and we need to go to
+                        * the source to attempt to get a real ref.  Unfortunately, this is
+                        * another scan of the list, same as devsrv.  We could speed it up
+                        * by storing an "on_list" bool in the a_is. */
+                       spin_lock(&p->alarmset.lock);
+                       TAILQ_FOREACH(a_i, &p->alarmset.list, link) {
+                               if (a_i == a) {
+                                       /* it's still possible we're not getting the ref, racing
+                                        * with the release method */
+                                       if (!kref_get_not_zero(&a->kref, 1)) {
+                                               a_i = 0;        /* lost the race, will error out later */
+                                       }
+                                       break;
+                               }
+                       }
+                       spin_unlock(&p->alarmset.lock);
+                       if (!a_i)
+                               error("Unable to open alarm, concurrent closing");
+                       break;
+       }
+       c->mode = openmode(omode);
+       /* Assumes c is unique (can't be closed concurrently */
+       c->flag |= COPEN;
+       c->offset = 0;
+       return c;
+}
+
+static void alarmcreate(struct chan *c, char *name, int omode, int perm)
+{
+       error(Eperm);
+}
+
+static void alarmremove(struct chan *c)
+{
+       error(Eperm);
+}
+
+static long alarmwstat(struct chan *c, uint8_t *dp, long n)
+{
+       error("No alarmwstat");
+       return 0;
+}
+
+static void alarmclose(struct chan *c)
+{
+       /* There are more closes than opens.  For instance, sysstat doesn't open,
+        * but it will close the chan it got from namec.  We only want to clean
+        * up/decref chans that were actually open. */
+       if (!(c->flag & COPEN))
+               return;
+       switch (TYPE(c->qid)) {
+               case Qctl:
+               case Qtimer:
+                       kref_put(&QID2A(c->qid)->kref);
+                       break;
+       }
+}
+
+static long alarmread(struct chan *c, void *ubuf, long n, int64_t offset)
+{
+       struct proc_alarm *p_alarm;
+       switch (TYPE(c->qid)) {
+               case Qtopdir:
+               case Qalarmdir:
+                       return devdirread(c, ubuf, n, 0, 0, alarmgen);
+               case Qctl:
+                       p_alarm = QID2A(c->qid);
+                       return readnum(offset, ubuf, n, p_alarm->id, NUMSIZE32);
+               case Qtimer:
+                       p_alarm = QID2A(c->qid);
+                       return readnum(offset, ubuf, n, p_alarm->a_waiter.wake_up_time,
+                                      NUMSIZE64);
+               default:
+                       panic("Bad QID %p in devalarm", c->qid.path);
+       }
+       return 0;
+}
+
+/* Note that in read and write we have an open chan, which means we have an
+ * active kref on the p_alarm.  Also note that we make no assumptions about
+ * current here - we find the proc (and the tchain) via the ref stored in the
+ * proc_alarm. */
+static long alarmwrite(struct chan *c, void *ubuf, long n, int64_t unused)
+{
+       ERRSTACK(1);
+       char buf[32];
+       struct cmdbuf *cb;
+       struct proc_alarm *p_alarm;
+       uint64_t hexval;
+
+       switch (TYPE(c->qid)) {
+               case Qtopdir:
+               case Qalarmdir:
+                       error(Eperm);
+               case Qctl:
+                       p_alarm = QID2A(c->qid);
+                       cb = parsecmd(ubuf, n);
+                       if (waserror()) {
+                               kfree(cb);
+                               nexterror();
+                       }
+                       if (!strcmp(cb->f[0], "evq")) {
+                               if (cb->nf < 2)
+                                       error("evq needs a pointer");
+                               /* i think it's safe to do a stroul on a parsecmd.  it's kernel
+                                * memory, and space or 0 terminated */
+                               hexval = strtoul(cb->f[1], 0, 16);
+                               /* This is just to help userspace - event code can handle it */
+                               if (!is_user_rwaddr((void*)hexval, sizeof(struct event_queue)))
+                                       error("Non-user ev_q pointer");
+                               p_alarm->ev_q = (struct event_queue*)hexval;
+                       } else if (!strcmp(cb->f[0], "cancel")) {
+                               unset_alarm(p_alarm->proc->alarmset.tchain, &p_alarm->a_waiter);
+                       } else {
+                               error("%s: not implemented", cb->f[0]);
+                       }
+                       kfree(cb);
+                       poperror();
+                       break;
+               case Qtimer:
+                       /* want to give strtoul a null-terminated buf (can't handle random
+                        * user strings) */
+                       if (n >= sizeof(buf))
+                               error(Egreg);
+                       memcpy(buf, ubuf, n);
+                       buf[n] = 0;
+                       hexval = strtoul(buf, 0, 16);
+                       p_alarm = QID2A(c->qid);
+                       /* if you don't know if it was running or not, resetting will turn
+                        * it on regardless. */
+                       reset_alarm_abs(p_alarm->proc->alarmset.tchain, &p_alarm->a_waiter,
+                                       hexval);
+                       break;
+               default:
+                       panic("Bad QID %p in devalarm", c->qid.path);
+       }
+       return n;
+}
+
+struct dev alarmdevtab = {
+       'A',
+       "alarm",
+
+       devreset,
+       alarminit,
+       devshutdown,
+       alarmattach,
+       alarmwalk,
+       alarmstat,
+       alarmopen,
+       alarmcreate,
+       alarmclose,
+       alarmread,
+       devbread,
+       alarmwrite,
+       devbwrite,
+       alarmremove,
+       alarmwstat,
+       devpower,
+       devconfig,
+       devchaninfo,
+};
diff --git a/kern/include/devalarm.h b/kern/include/devalarm.h
new file mode 100644 (file)
index 0000000..33acf62
--- /dev/null
@@ -0,0 +1,35 @@
+/* Copyright (c) 2013 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Alarm device includes (needed for the linkage to struct proc) */
+
+#ifndef ROS_KERN_DEVALARM_H
+#define ROS_KERN_DEVALARM_H
+
+#include <sys/queue.h>
+#include <kref.h>
+#include <alarm.h>
+#include <event.h>
+#include <atomic.h>
+
+struct proc_alarm {
+       TAILQ_ENTRY(proc_alarm)         link;
+       int                                                     id;
+       struct kref                                     kref;
+       struct alarm_waiter                     a_waiter;
+       struct proc                                     *proc;
+       struct event_queue                      *ev_q;
+};
+TAILQ_HEAD(proc_alarm_list, proc_alarm);
+
+struct proc_alarm_set {
+       struct proc_alarm_list          list;
+       spinlock_t                                      lock;
+       struct timer_chain                      *tchain;
+       int                                                     id_counter;
+};
+
+void devalarm_init(struct proc *p);
+
+#endif /* ROS_KERN_DEVALARM_H */
index 30dae93..2833b3d 100644 (file)
@@ -20,6 +20,7 @@
 #include <mm.h>
 #include <vfs.h>
 #include <schedule.h>
+#include <devalarm.h>
 
 TAILQ_HEAD(vcore_tailq, vcore);
 /* 'struct proc_list' declared in sched.h (not ideal...) */
@@ -86,6 +87,8 @@ struct proc {
        /* UCQ hashlocks */
        struct hashlock                         *ucq_hashlock;
        struct small_hashlock           ucq_hl_noref;   /* don't reference directly */
+       /* For devalarm */
+       struct proc_alarm_set           alarmset;
 };
 
 /* Til we remove all Env references */
index eee96e7..32715f7 100644 (file)
@@ -310,6 +310,8 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
 
        atomic_inc(&num_envs);
        frontend_proc_init(p);
+       //plan9setup(p, parent);
+       //devalarm_init(p);
        printd("[%08x] new process %08x\n", current ? current->pid : 0, p->pid);
        } // INIT_STRUCT
        *pp = p;
@@ -359,6 +361,7 @@ static void __proc_free(struct kref *kref)
        printd("[PID %d] freeing proc: %d\n", current ? current->pid : 0, p->pid);
        // All parts of the kernel should have decref'd before __proc_free is called
        assert(kref_refcnt(&p->p_kref) == 0);
+       assert(TAILQ_EMPTY(&p->alarmset.list));
 
        /* close plan9 dot and slash and free fgrp fd and fgrp */
        kref_put(&p->fs_env.root->d_kref);
diff --git a/tests/alarm.c b/tests/alarm.c
new file mode 100644 (file)
index 0000000..cf168ae
--- /dev/null
@@ -0,0 +1,125 @@
+/* Copyright (c) 2013 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * alarm: basic functionality test for the #A device */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <parlib.h>
+#include <unistd.h>
+#include <event.h>
+#include <measure.h>
+#include <uthread.h>
+
+/* Am I the only one annoyed at how open has different includes than
+ * close/read/write? */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+static void handle_alarm(struct event_msg *ev_msg, unsigned int ev_type)
+{
+       assert(ev_type == EV_ALARM);
+       printf("\tAlarm fired!\n");
+}
+
+int main(int argc, char **argv)
+{
+       int ctlfd, timerfd, alarm_nr, ret, srvfd;
+       char buf[20];
+       char path[32];
+       struct event_queue *ev_q;
+
+       printf("Starting alarm test\n");
+       /* standard 9ns stuff: clone and read it to get our path, ending up with the
+        * ctlfd and timerfd for #A/aN/{ctl,timer}.  if you plan to fork, you can
+        * open CLOEXEC. */
+       ctlfd = open("#A/clone", O_RDWR | O_CLOEXEC);
+       if (ctlfd < 0) {
+               perror("Can't clone an alarm");
+               exit(-1);
+       }
+       ret = read(ctlfd, buf, sizeof(buf) - 1);
+       if (ret <= 0) {
+               if (!ret)
+                       printf("Got early EOF from ctl\n");
+               else
+                       perror("Can't read ctl");
+               exit(-1);
+       }
+       buf[ret] = 0;
+       snprintf(path, sizeof(path), "#A/a%s/timer", buf);
+       /* Don't open CLOEXEC if you want to post it to srv later */
+       timerfd = open(path, O_RDWR);
+       if (timerfd < 0) {
+               perror("Can't open timer");
+               exit(-1);
+       }
+       /* Since we're doing SPAM_PUBLIC later, we actually don't need a big ev_q.
+        * But someone might copy/paste this and change a flag. */
+       ev_handlers[EV_ALARM] = handle_alarm;
+       if (!(ev_q = get_big_event_q())) {
+               perror("Failed ev_q");  /* it'll actually PF if malloc fails */
+               exit(-1);
+       }
+       ev_q->ev_vcore = 0;
+       /* I think this is all the flags we need; gotta write that dissertation
+        * chapter (and event how-to)! */
+       ev_q->ev_flags = EVENT_IPI | EVENT_NOMSG | EVENT_SPAM_PUBLIC;
+       /* Register the ev_q for our alarm */
+       ret = snprintf(path, sizeof(path), "evq %llx", ev_q);
+       ret = write(ctlfd, path, ret);
+       if (ret <= 0) {
+               perror("Failed to write ev_q");
+               exit(-1);
+       }
+       /* Try to set, then cancel before it should go off */
+       ret = snprintf(buf, sizeof(buf), "%llx", read_tsc() + sec2tsc(1));
+       ret = write(timerfd, buf, ret);
+       if (ret <= 0) {
+               perror("Failed to set timer");
+               exit(-1);
+       }
+       ret = snprintf(buf, sizeof(buf), "cancel");
+       ret = write(ctlfd, buf, ret);
+       if (ret <= 0) {
+               perror("Failed to cancel timer");
+               exit(-1);
+       }
+       uthread_sleep(2);
+       printf("No alarm should have fired yet\n");
+       /* Try to set and receive */
+       ret = snprintf(buf, sizeof(buf), "%llx", read_tsc() + sec2tsc(1));
+       ret = write(timerfd, buf, ret);
+       if (ret <= 0) {
+               perror("Failed to set timer");
+               exit(-1);
+       }
+       uthread_sleep(2);
+       close(ctlfd);
+       /* get crazy: post the timerfd to #s, then sleep (or even try to exit), and
+        * then echo into it remotely!  A few limitations:
+        * - if the process is DYING, you won't be able to send an event to it.
+        * - the process won't leave DYING til the srv file is removed. */
+       srvfd = open("#s/alarmtest", O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (srvfd < 0) {
+               perror("Failed to open srv file");
+               exit(-1);
+       }
+       ret = snprintf(buf, sizeof(buf), "%d", timerfd);
+       ret = write(srvfd, buf, ret);
+       if (ret <= 0) {
+               perror("Failed to post timerfd");
+               exit(-1);
+       }
+       printf("Sleeping for 10 sec, try to echo 111 > '#s/alarmtest' now!\n");
+       uthread_sleep(10);
+       ret = unlink("#s/alarmtest");
+       if (ret < 0) {
+               perror("Failed to remove timerfd from #s, proc will never be freed");
+               exit(-1);
+       }
+       printf("Done\n");
+       return 0;
+}