> Date: Fri, 18 Dec 2020 13:49:32 -0600
> From: Scott Cheloha <[email protected]>
> 
> Hi,
> 
> This patch adds support for passing NULL as the ident when calling
> tsleep(9) etc.  When this happens, sleep_setup() will use the address
> of the sleep_state struct as the value for p_wchan.  This address is
> basically always a private value so the thread should never receive a
> wakeup(9) broadcast.
> 
> Why do we want this?  Sometimes there is no logical ident to sleep on.
> Sometimes there is legitimately no reason to receive a wakeup(9).
> 
> In the past, people have handrolled private channels to work around
> this situation.  The code often looks like this:
> 
> void
> foo(void)
> {
>       int chan;
> 
>       tsleep(&chan, ...);
> }
> 
> Permitting the use of NULL and letting the implementation choose a
> private channel is better than handrolling a private channel for two
> reasons:
> 
> 1. We save a bit of stack space.  tsleep(9) etc. already have a
>    sleep_state struct on the stack and it's at a private address
>    so there is no space cost to use it.
> 
> 2. The NULL clearly communicates the author's intent to the reader.
>    It indicates the author had no wakeup channel in mind when they
>    wrote the code.  The reader then doesn't need to reason about
>    whether or not the ident value is superfluous.  Poring over
>    a file (or several) to determine whether any thread ever calls
>    wakeup(9) on a given ident sucks.
> 
> FreeBSD/NetBSD have a dedicated interface for this "sleep without a
> wakeup channel" operation.  They call it "pause".  I proposed adding
> it but I got mixed feedback on the patch.  Then mpi@ proposed this
> idea.  I think this is simpler and better.
> 
> The actual implementation requires just a few small changes to
> sleep_setup().
> 
> I've added an additional KASSERT to each of tsleep(9), msleep(9), and
> rwsleep(9).  You now need at least one of (a) an ident or (b) PCATCH
> or (c) a timeout, otherwise there is no way to get the thread started
> again.  This would indicate a programmer error and we should panic if
> it ever happens.
> 
> I've documented the new NULL ident behavior in tsleep.9.
> 
> Also included here is a sample user, sys_nanosleep().  nanosleep(2)
> wakes up due to interruption by signal or timeout.  It should never be
> awoken with wakeup(9).  Up until now we had a private channel on the
> stack.  Now we can just pass NULL.  It's simpler.
> 
> There are a bunch of other potential users but they can wait until a
> later patch.
> 
> I'm running with this now so I'm pretty sure this is a sound change.
> Feel free to test it out.  nanosleep(2) gets called all the time so if
> there was an issue I imagine it'd show up pretty quickly.
> 
> Thoughts?  ok?

The downside of this approach is that we no longer catch cases where
somebody inadvertedly uses a pointer that happens to be NULL as a
sleep channel.

I still think using a single unqieue global "nowait" variable is the
best thing to do.

> Index: share/man/man9/tsleep.9
> ===================================================================
> RCS file: /cvs/src/share/man/man9/tsleep.9,v
> retrieving revision 1.15
> diff -u -p -r1.15 tsleep.9
> --- share/man/man9/tsleep.9   20 Mar 2020 03:37:09 -0000      1.15
> +++ share/man/man9/tsleep.9   18 Dec 2020 19:40:04 -0000
> @@ -144,8 +144,11 @@ to the resource for which the process is
>  The same identifier must be used in a call to
>  .Fn wakeup
>  to get the process going again.
> +If the thread does not want to receive any
> +.Fn wakeup
> +broadcasts,
>  .Fa ident
> -should not be
> +should be
>  .Dv NULL .
>  .It Fa priority
>  The process priority to be used when the process is awakened and put on
> Index: sys/kern/kern_synch.c
> ===================================================================
> RCS file: /cvs/src/sys/kern/kern_synch.c,v
> retrieving revision 1.172
> diff -u -p -r1.172 kern_synch.c
> --- sys/kern/kern_synch.c     7 Dec 2020 16:55:29 -0000       1.172
> +++ sys/kern/kern_synch.c     18 Dec 2020 19:40:04 -0000
> @@ -119,6 +119,7 @@ tsleep(const volatile void *ident, int p
>  #endif
>  
>       KASSERT((priority & ~(PRIMASK | PCATCH)) == 0);
> +     KASSERT(ident != NULL || ISSET(priority, PCATCH) || timo != 0);
>  
>  #ifdef MULTIPROCESSOR
>       KASSERT(timo || _kernel_lock_held());
> @@ -214,6 +215,7 @@ msleep(const volatile void *ident, struc
>  
>       KASSERT((priority & ~(PRIMASK | PCATCH | PNORELOCK)) == 0);
>       KASSERT(mtx != NULL);
> +     KASSERT(ident != NULL || ISSET(priority, PCATCH) || timo != 0);
>  
>       if (priority & PCATCH)
>               KERNEL_ASSERT_LOCKED();
> @@ -301,6 +303,7 @@ rwsleep(const volatile void *ident, stru
>       int error, status;
>  
>       KASSERT((priority & ~(PRIMASK | PCATCH | PNORELOCK)) == 0);
> +     KASSERT(ident != NULL || ISSET(priority, PCATCH) || timo != 0);
>       rw_assert_anylock(rwl);
>       status = rw_status(rwl);
>  
> @@ -351,8 +354,6 @@ sleep_setup(struct sleep_state *sls, con
>  #ifdef DIAGNOSTIC
>       if (p->p_flag & P_CANTSLEEP)
>               panic("sleep: %s failed insomnia", p->p_p->ps_comm);
> -     if (ident == NULL)
> -             panic("tsleep: no ident");
>       if (p->p_stat != SONPROC)
>               panic("tsleep: not SONPROC");
>  #endif
> @@ -378,11 +379,23 @@ sleep_setup(struct sleep_state *sls, con
>  
>       TRACEPOINT(sched, sleep, NULL);
>  
> -     p->p_wchan = ident;
> +     /*
> +      * If ident is NULL the caller does not want to receive
> +      * wakeup(9) broadcasts.  However, p_wchan cannot actually
> +      * be NULL until the thread is awoken or the sleep is aborted.
> +      * As a workaround, we use the address of the sleep_state
> +      * struct as the wakeup channel.  It is effectively always
> +      * a private value so the thread should never receive any
> +      * broadcasts.
> +      */
> +     p->p_wchan = (ident != NULL) ? ident : sls;
> +
>       p->p_wmesg = wmesg;
>       p->p_slptime = 0;
>       p->p_slppri = prio & PRIMASK;
> -     TAILQ_INSERT_TAIL(&slpque[LOOKUP(ident)], p, p_runq);
> +
> +     /* NB: The queue is chosen based on wchan, not ident. */
> +     TAILQ_INSERT_TAIL(&slpque[LOOKUP(p->p_wchan)], p, p_runq);
>  }
>  
>  int
> Index: sys/kern/kern_time.c
> ===================================================================
> RCS file: /cvs/src/sys/kern/kern_time.c,v
> retrieving revision 1.150
> diff -u -p -r1.150 kern_time.c
> --- sys/kern/kern_time.c      10 Nov 2020 17:26:54 -0000      1.150
> +++ sys/kern/kern_time.c      18 Dec 2020 19:40:04 -0000
> @@ -269,7 +269,6 @@ sys_clock_getres(struct proc *p, void *v
>  int
>  sys_nanosleep(struct proc *p, void *v, register_t *retval)
>  {
> -     static int chan;
>       struct sys_nanosleep_args/* {
>               syscallarg(const struct timespec *) rqtp;
>               syscallarg(struct timespec *) rmtp;
> @@ -294,7 +293,7 @@ sys_nanosleep(struct proc *p, void *v, r
>       do {
>               getnanouptime(&start);
>               nsecs = MAX(1, MIN(TIMESPEC_TO_NSEC(&request), MAXTSLP));
> -             error = tsleep_nsec(&chan, PWAIT | PCATCH, "nanosleep", nsecs);
> +             error = tsleep_nsec(NULL, PWAIT | PCATCH, "nanosleep", nsecs);
>               getnanouptime(&stop);
>               timespecsub(&stop, &start, &elapsed);
>               timespecsub(&request, &elapsed, &request);
> 
> 

Reply via email to