How to do proper locking

Hans Petter Selasky hselasky at c2i.net
Thu Aug 4 16:49:19 GMT 2005


On Thursday 04 August 2005 15:53, John Baldwin wrote:
> On Thursday 04 August 2005 07:40 am, Hans Petter Selasky wrote:
> > On Wednesday 03 August 2005 19:21, John Baldwin wrote:
> > > On Tuesday 02 August 2005 06:23 pm, Hans Petter Selasky wrote:
> > > > Hi,
> > > >
> > > > I am looking for a safe way to access structures that can be freed.
> > > > The solution I am looking for must not:
> > > >
> > > > - hinder scaleability
> > > > - lead to use of a single lock
> > > > - lead to lock order reversal
> >
> > There is one more thing. When the structure is freed it should not block
> > waiting for any reference counts.
> >

>
> crhold() is always called when the caller knows that it has a reference to
> cr that can't go away.  Thus, in the case of p_ucred, one holds the
> associated PROC_LOCK() while doing crhold().  After the crhold() you can
> drop the proc lock, so that lock is only held for a very short period of
> time.  You then have your own reference to the cred structure that is valid
> until you call crfree().  In the case of curthread->td_ucred, the reference
> is valid until curthread releases it on a cred update at the start of a
> syscall, so your reference is valid without needing locks for the life of a
> syscall.  The key here is that you solve this problem at a higher level,
> not within the structure itself.

Yes, this works with mbufs, crhold() and alike, where one doesn't actually 
free the object until the refcount reaches zero. But I am speaking about 
destroying an object while the refcount is non-zero. Do you see the 
difference?

There are two ways to handle this:

1) blocking: wait until the refcount reaches zero.

2) nonblocking: increment some other refcount that the
   callback checks before accessing any data.


Do you agree that method 2 is going to:

- avoid deadlock
- avoid use of sx-locks, locks that allow calls to msleep(),
  to keep synchronization while destroying objects 
  with refcounts.


When calling functions like "callout_stop()", it is likely that one is holding 
a lock to prevent other code from calling "callout_start()" on the same 
callback before "callout_stop()" has been called. Now, if "callout_stop()" 
sleeps, the lock that is held while calling this function, must allow calls 
msleep(). So this must be an sx-lock (see "man sx"). Now if one routine 
sleeps, then any callers of this routine must sleep too. So this affects the 
synchronization of the whole system.

Isn't nonblocking operation preferred over blocking operation?

>
> > > What actual problem are you trying to solve?
> >
> > Generic callbacks:
> >
> > - setting up timers. Make sure that callback is never called after that
> > timer has been stopped.
>
> See callout_stop() and callout_drain().  callout_drain() won't return until
> the callout has both been stopped and has finished executing if it was
> already in progress.

The callback can be called after that "callout_stop()" has been called!

You can use _callout_stop_safe(), but the problem is that it will sleep, 
waiting for "the other thread" to finish. The solution I have described, 
makes this process non-blocking, which means that one can hold a lock while 
calling callout_stop() which must be an advantage.

>
> > - setting up devices. Make sure that read/write/ioctl/poll callbacks are
> > never called after that the device has been freed.
>
> This is managed with refcounts on the dev_t object by devfs.  Once you call
> destroy_dev() devfs manages the rest.

No, the callback functions read/write/ioctl/poll can be called after that 
"destroy_dev()" has been called.

>
> > - setting up interrupts. Make sure that interrupt routine is never called
> > after that it is been teared down.
>
> bus_teardown_intr() already does this.

I'm sure it is the same here. If the interrupt handler doesn't have its own 
lock then, I think the interrupt callback can be called after that 
"bus_teardown_intr()" has been called.

>
> > And more. There are lots of situations in the kernel that run into the
> > same scheme, without a proper solution.
>
> Are there any other places you can think of that don't handle this already?

I am thinking about all the device drivers that create devices in /dev/ that 
can be detache, and that really never checks anything before accessing the 
softc:
int
ulptread(struct cdev *dev, struct uio *uio, int flags)
{
        struct ulpt_softc *sc;
        int error;

        USB_GET_SC(ulpt, ULPTUNIT(dev), sc);

XXX
XXX here can be an segmentation fault where sc == NULL
XXX


        if (sc->sc_dying)
                return (EIO);

        sc->sc_refcnt++;
        error = ulpt_do_read(sc, uio, flags);
        if (--sc->sc_refcnt < 0)
                usb_detach_wakeup(USBDEV(sc->sc_dev));
        return (error);
}

> The approach FreeBSD's kernel has taken is to solve this problem by
> duplicating a lot of checks all over the place for each data structure, but
> to solve it in the primitives themselves instead (hence callout_drain(),
> bus_teardown_intr(), destroy_dev(), etc.)

Yes, but why are the implementations based on blocking operation, hence this 
is not required?



What I want is that the kernel provides some routine that can do locking based 
on a structure like below. Also the kernel must provide some routines to 
allocate, free and read a refcount.

struct callback_args {
  void *sc;
  struct mtx *p_mtx;
  u_int32_t refcount_copy;
  u_int32_t *p_refcount;
};

Then I want routines like "callout_reset()", "make_dev()", "bus_setup_intr()" 
and so on, to pass these four parameters to the callback function, either 
directly, or like a pointer to this structure.


--HPS


More information about the freebsd-hackers mailing list