serializing token
Matthew Dillon
dillon at apollo.backplane.com
Fri Apr 23 23:02:30 PDT 2004
:I am looking at the serializing token work some more
:and I have a few questions. I hope it is not a bother
:for y'all to answer.
:
:As I understand it, multiple threads can hold tokens
:but only one of the threads holding a particular token
:can be running at any time. As I looked at the lwkt
:token implementation, I believe obtaining a token
:reference is a blocking point, is this correct? What I
Right.
:mean is if thread A is running and holding token T,
:and thread B attempts to grab a reference to token T,
:thread B blocks (and the scheduler gets invoked to run
:another thread) since A is already running. Is this
:correct?
Right.
:If a call to get a token reference is a blocking
:point, then what happens when you need to hold more
:than one token? Specifically, I am concerned with this
:case: Token T1 protects list L1 and token T2 protects
:list L2. If the code looks like this:
The LWKT scheduler will not schedule a thread until the scheduler
can acquire all tokens currently held by that thread. So if the
second gettoken blocks, the first token can be lost but the call
to lwkt_gettoken(T2) will not return until both tokens can be
acquired again by the scheduler.
:lwkt_gettoken(T1);
:foo = L1->head;
:...
:...
:lwkt_gettoken(T2);
:bar = L2->head;
:...
:...
:dosomething(foo);
:
:Is this valid/correct code? Is foo still valid after
:the second lwkt_gettoken call? I ask this because the
:second call to lwkt_gettoken(T2) can be a blocking
:point, so some other thread can get scheduled at that
:point. If another thread that is holding T1 runs, then
No, foo will NOT still be valid if it depends on T1. You would
have to use this sequence to guarentee foo, or you would have to
perform some sort of recheck after gaining T2 before using foo:
lwkt_gettoken(T1);
lwkt_gettoken(T2);
foo = L1->head;
bar = L2->head;
:If my understanding is correct, then is the code for
:sysctl_vnode() in src/sys/kern/vfs_subr.c incorrect?
:In this function (which I see is #if 0'd out), it
:takes the token for mountlist_token, which I presume
:protects the mountlist. Then it takes the token for
:mnbtvnode_token. However, the head of the mountlist,
:obtained before it takes the second token, is used
:after the second token acquisition. So, this code is
:wrong since the head of the mountlist could have
:changed, right???
Correct. Scanning the vnode list is not safe if the code within
the for() loop does anything that might block, and obtaining another
token counts as potentially blocking.
In fact, as an example of how tokens are used properly to scan the
mount vnode list, take a look at vmntvnodescan() line 1806
or so in kern/vfs_subr.c
:Apologies in advance if this seems like a stupid
:question.
:
:-J
Not at all, your questions show a very good understanding of the
token code.
Basically you have hit upon a fundamental difference between tokens
and mutexes. While a mutex acts sort of like a lock, a token
acts nothing like a mutex or a lock. Whereas getting multiple mutexes
guarentees the atomicy of earlier acquired mutexes, getting multiple
tokens does not. This also means that mutexes have deadlock issues
while tokens cannot deadlock.
In fact, the fact that tokens do not deadlock coupled with the fact
that there is no expectation of atomicy for earlier acquired tokens
when later operations block leads to a great deal of code simplification.
If you look at FreeBSD-5, you will notice that FreeBSD-5 passes held
mutexes down the subroutine stack quite often, in order to allow some
very deep procedural level to temporarily release a mutex in order to
switch or block or deal with a deadlock. There is a great deal of
code pollution in FreeBSD-5 because of this (where some procedures
must be given knowledge of the mutexes held by other unrelated procedures
in order to function properly).
You don't have any of that mess with the token abstration but there is a
cost and that cost is that you lose atomicy across blocking ops.
Another way to think of it is to compare the token abstraction with the
SPL mechanism. Tokens and SPLs work almost identically, except a token
works across cpus while SPLs only work within a single cpu's domain.
For example, if you are holding an SPL and you tsleep(), the system
'loses' the SPL until your thread resumes from the tsleep(). The
same thing happens with tokens you hold through a tsleep().
All a token guarentees is that all the tokens you are holding will be
acquired while your thread is running. If your thread blocks or switches
away for any reason (with one exception which I will describe below),
the tokens you are holding may be lost for the duration. The scheduler
will not schedule your thread again until it is able to reacquire all
the tokens you are holding.
The one exception to this rule occurs in how DragonFly handles interrupt
preemption. Since interrupts are in fact their own threads interrupt
preemption in fact switches to the interrupt thread, then switches back
to the original thread. However, in the preemption case the tokens held
by the original thread are left acquired and if the interrupt thread
blocks for any reason the system switches back to the original thread,
leaving the token abstraction intact from the point of view of the
original thread regardless of how many interrupt preemptions might occur.
-Matt
Matthew Dillon
<dillon at xxxxxxxxxxxxx>
More information about the Kernel
mailing list