Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 19 Dec 1998 01:53:06 -0800 (PST)
From:      Matthew Dillon <dillon@apollo.backplane.com>
To:        Don Lewis <Don.Lewis@tsc.tdk.com>
Cc:        freebsd-current@FreeBSD.ORG
Subject:   Re: asleep()/await(), M_AWAIT, etc...
Message-ID:  <199812190953.BAA07138@apollo.backplane.com>
References:   <199812190844.AAA11936@salsa.gv.tsc.tdk.com>

next in thread | previous in thread | raw e-mail | index | archive | help

:On Dec 17, 12:05am, Matthew Dillon wrote:
:} Subject: asleep()/await(), M_AWAIT, etc...
:
:}     We add an await() kernel function.  This function initiates any timeout
:}     and puts the process to sleep, but only if it is still on a sleep queue.
:}     If someone (i.e. an interrupt) wakes up the sleep address after the
:}     process calls asleep() but before it calls await(), the slpque is
:}     cleared and the await() winds up being a NOP.
:
:How likely is this to happen if the process doesn't go to sleep for some
:other reason inbetween the asleep() and the await()?  The CPU can execute
:a *lot* of code in the time it takes for physical I/O to happen.

    Well, the idea is for asleep() to not interfere with a normal sleep.
    If a process does an asleep() and then, for some reason, does a
    normal sleep or another asleep() without waiting for the prior event
    to occur, the original asleep() condition is lost and an await() later
    on that, code-wise, was expecting to wait for the condition earmarked
    by the original asleep() will not wait for it, instead causing an 
    immediate return and thus an immediate retry.  This shouldn't cause
    a problem, though.

    The chance of a condition being signalled after an asleep() but before
    the associated await(), assuming no blocking inbetween, is not very
    high but I expect it would happen under normal operating conditions 
    maybe 1 out of every 5000 or so uses.

    The situation becomes more interesting when you get into SMP situations,
    especially once we start allowing all N processors to enter into supervisor
    mode and run mainstream supervisor code simultaniously.  It should be
    noted that event interlocks can be done very easily with asleep()/await()
    without having to mess with the ipl mask.  Since the ipl mask doesn't
    work when SMP supervisor operation is allowed on > 1 cpu at a time,
    it is just as well that another mechanism exists.

:}     The purpose of the new routines is to allow blocking conditions to
:}     propogate up a subroutine chain and get handled at a higher level rather
:}     then at a lower level in those areas of code that cannot afford to 
:}     leave exclusive locks sitting around.  For example, if bread() blocks
:}     waiting for a low level disk I/O on a block device, the vnode remains
:}     locked throughout which badly mars potential parallelism when multiple
:}     programs are accessing the same file.  There is no reason to leave the
:}     high level vnode locked while bringing a page into the VM buffer cache!
:
:What happens if some other process decides to truncate the file while
:another process is in the middle of paging in a piece of it?  If there
:is no reason to care about this sort of thing, then there is no reason
:to hold the lock across the bread(), which would probably be a simple

    Well, in this particular case we don't care because it isn't the pagein
    into the process's VM space that we are waiting on, it's the bringing 
    of the page from the underlying block device into the filesystem cache,
    which is independant of the overlayed filesystem structure and was
    queued to the disk device on the original attempt.  

    In the case of a truncate, this higher level operation will not effect
    the lower level I/O in progress (or, if it does abort it, will wakeup
    anybody waiting for that page anyway).  The wakeup occurs and the
    original requesting task retries its vm fault.  On this attempt it
    notices the fact that the file has been truncated and does the right 
    thing.  Effectively we are retrying an operation 'from scratch', so
    the fact that the truncate occured is handled properly.

    Another indirect use for asleep() would be to unwind locks when an inner
    lock cannot be obtained and to then retry the entire sequence later when 
    the inner lock 'might' become attainable.  You do this by asleep()ing on
    the event of the inner lock getting unlocked, then popping back through
    the call stack and unwinding the locks you were able to get, then
    sleeping (calling await()) at the top level (holding no locks) and retrying
    when you wake up again.  This wouldn't work very well for complex locking 
    (4 or more levels), but I would guess that it would work quite nicely
    for the 2-layer locking that we typically do in the kernel.

:}     allocation fails would be able to unwind the lock(s), await(), and retry.
:}     This is something the current code cannot do at all.
:
:Most things that allocate memory want to scribble on it right after they
:allocate it.  Using M_AWAIT would take a fair amount of rewriting.  You
:can already do something similar without M_AWAIT by using M_NOWAIT.  If
:that fails, unwind the lock, use M_WAITOK, and relock the object.  However,
:it would probably be cleaner to just do do MALLOC(..., M_WAITOK) before
:grabbing the lock, if possible.

    The point here is that if you cannot afford to block in the procedure
    that is doing the memory allocation, you may be able to block in a 
    higher level procedure.  M_NOWAIT and M_WAITOK cannot cover that
    situation at all.  M_AWAIT (which is like M_NOWAIT but it calls 
    asleep() as well as returns NULL) *can*.  The only implementation
    requirement is that the procedure call chain being implemented with
    asleep() understand a temporary failure condition and do the right
    thing with it (eventually await() and retry from the top level).

:There may be cases where this is not possible.  For example, the amount of
:the memory you need to allocate depends on the object that you have locked.

    Oh, certainly, but asleep/await do not have to be implemented everywhere,
    only in those places where it makes sense to.  We aren't removing any
    of the prior functionality, we are adding new functionality to allow
    us to solve deadlock situations that occur with the old functionality.

:If you have the object unlocked while the memory is being allocated, another
:process may touch the object while it is unlocked and you'll end up allocating
:the wrong amount of memory.  The only scheme that works in this case is
:locking the object first and leaving it locked across MALLOC(..., M_WAITOK).
:
:NOTE: some of the softupdates panics before 3.0-RELEASE were caused by

    I think you missed the primary point of asleep()/await().  The idea
    is that you pop back through subroutine levels, undoing the entire
    operation (or a good portion of it), the 'retry later'.  What you
    describe is precisely the already-existant situation that asleep() and 
    await() can be used to fix.  This might sound expensive, but most
    of the places where we would need to use asleep()/await() would not
    actually have to pop back more then a few subroutine levels to be
    effective.

						-Matt

:vnodes inadvertently being unlocked and then relocked in some low level
:routines, which allowed files to be fiddled with by one process while
:another process thought it had exclusive access.

    Matthew Dillon  Engineering, HiWay Technologies, Inc. & BEST Internet 
                    Communications & God knows what else.
    <dillon@backplane.com> (Please include original email in any response)    

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199812190953.BAA07138>