Re: waiting queue
Re: waiting queue
- Subject: Re: waiting queue
- From: Michael Smith <email@hidden>
- Date: Wed, 29 Nov 2006 11:49:00 -0800
devmaillists wrote:
Hello Michael,
thank you very much. I tried to fully understand IOCommandgate /
IOCommandPool / IOCommand / IOWorkloop but I got stuck in the Apple
documentation. I used the darwin sources
the get some knowledge but it didnĀ“t explain the mechanism.
Are there some source of documentation I could try in addition?
If looking at the example I gave you, and how they are used in the
Darwin sources doesn't help, then I'm not sure that any more
documentation is going to help.
Maybe to clarify what I ultimately want:
* The driver gets calls (from a user space programm) that should wait
for an event and return when this event has occurred (1)
* The kernel driver, well, waits for this event -- is indicated by an
interrupt -- to happen.
* When the interrupt has occurred, the driver returns.
I can trigger intterupts on the card, and handlers get called when
they occur. But somehow I can not get my brain wrapped around
IOCommandGate et. al.
Perhaps working this from scratch will help. The most naive
implementation of the above looks something like this:
client_call()
{
while (flag == 0) { }
flag = 0
}
interrupt()
{
flag = 1
}
Now, this has the problem that the while loop spins and consumes 100% of
the CPU. So instead you want to put the thread to sleep, and have the
interrupt routine wake it up:
client_call()
{
if (flag == 0) // XXX race window
sleep(&flag)
flag = 0
}
interrupt()
{
flag = 1
wakeup(&flag)
}
But there is a race here between the if test and going to sleep. If the
calling thread is pre-empted by the interrupt, the flag will be set and
the wakeup issued, but it will be missed by client_call. So you need a
mutex:
client_call()
{
mutex_lock(mtx)
if (flag == 0)
mutex_sleep(&flag, mtx)
flag = 0
mutex_unlock(mtx)
}
interrupt()
{
mutex_lock(mtx)
flag = 1
wakeup(&flag)
mutex_unlock(mtx)
}
So at this point, we've just about got it. There's only one catch left;
mutex_lock() can block, and you don't ever want to block in interrupt
context. I/O Kit handles this by providing two kinds of interrupt
handler; the traditional interrupt-context handler routine referred to
as an "interrupt filter", and the default interrupt handling routine.
This separation is very similar to using a DPC under Windows or
task_queues under Linux.
The primitives that come in at this point are the WorkLoop, and the
concept of event sources. The WorkLoop can be considered to be built
from three things; all of them are managed by the implementation. They
are: a mutex lock, a thread, and an event registration list.
The event registration list provides a way for the thread to find things
that need to be done under the lock. In this regard, it can be
considered analagous to an allocatable task_queue in the Linux
vernacular. To use this functionality, you register event sources with
the WorkLoop; your ::start routine almost certainly allocates an
IOInterruptEventSource and then attaches it to your WorkLoop, and this
is how interrupts are delivered to you.
So you can see how that covers the locking for the interrupt routine;
your handler is implicitly called with the WorkLoop lock held, ensuring
mutual exclusion with any other event source. In the trivial case, you
may wonder why all this infrastructure, but when you start adding timers
to your driver, or multiple interrupt sources, power management etc. you
will quickly see how the IOEventSource model helps keep them all in line.
However, incalls from clients aren't events, and in the name of
performance you really don't want to schedule a new thread and take the
context switch hit every time just to avoid being pre-empted by your
interrupt handler. So you need a way to take the WorkLoop lock, and to
sleep safely when you hold it.
This is where the CommandGate comes in, and now we come back to my
previous example with some additions to map to the description here.
class foo : public IOService
{
...
public:
IOReturn clientCommand(..);
private:
IOWorkLoop *_workLoop;
IOCommandGate *_commandGate;
IOCommandGate::Action _commandAction;
IOInterruptEventSource *_interruptSource;
IOReturn _clientCommand(...);
void _interruptHandler(IOInterruptSource *source,
int count);
bool _busyFlag;
bool _interruptOccurred;
...
};
foo::start(IOService *provider)
{
...
_workLoop = IOWorkLoop::workLoop();
_commandGate = IOCommandGate::(this);
_workLoop->addEventSource(_commandGate);
_commandAction = OSMemberFunctionCast(IOCommandGate::Action, this,
&foo::_clientCommand);
_interruptSource = IOInterruptEventSource::interruptEventSource(this,
OSMemberFunctionCast(this, IOInterruptEventSource::Action,
_interruptHandler), provider, 0);
...
}
foo::clientCommand(...)
{
_commandGate->runAction(_commandAction, ...)
}
foo::_clientCommand(...)
{
// wait for other callers to depart
while (_busyFlag)
_commandGate->commandSleep(&busyFlag, THREAD_UNINT);
_busyFlag = true;
// wait for interrupt event
while (!_interruptOccurred /* XXX && !_timedOut */) {
_commandGate->commandSleep(&_interruptOccurred);
// XXX check for timeout here too
}
// process interrupt results
_interruptOccurred = false;
...
// unlock and return
_busyFlag = false;
_commandGate->commandWakeup(&busyFlag, true /* wakeup one only */);
}
foo::_interruptHandler(...)
{
// quiesce the hardware interrupt
_interruptOccurred = true;
_commandGate->commandWakeup(&_interruptOccurred, true /* wakeup one
only */);
}
1) There may be a timeout, but let's not make things too complicated here
For sure. However, if you want to consider the possibility, bear in
mind that you'd just add an IOTimerEventSource and have its handler do
something like this:
foo::_timeoutHandler(...)
{
_timedOut = true;
_commandGate->commandWakeup(&_interruptOccurred);
}
Hopefully this helps...
= Mike
On 16.11.2006, at 19:12, Michael Smith wrote:
devmaillists wrote:
we are to implement waiting queues for scheduling threads generated
from a UserClient interface in our kext.
As I learned from the documentation I should use wait_queue_alloc ,
wait_queue_free, wait_queue_member and so on to implement wait queues.
Unfortunately I do not find the symbols in the 10.4 SDK only in
10.39 SDK but I think this is not an option for Intel based
developments.
What is the preferred way to do wait queues with 10.4 SDK.
If by "wait queues" you mean that you want to queue incoming threads,
then you should use a CommandGate with your workloop. Something like
this:
class foo : public IOService
{
...
public:
IOReturn clientCommand(..);
private:
IOCommandGate *_commandGate;
IOCommandGate::Action _commandAction;
IOReturn _clientCommand(...);
bool _busyFlag;
...
};
foo::start()
{
...
_commandGate = IOCommandGate::(this);
getWorkLoop()->addEventSource(_commandGate);
_commandAction = OSMemberFunctionCast(IOCommandGate::Action,
this, &foo::_clientCommand);
_busyFlag = false;
...
}
foo::clientCommand(...)
{
_commandGate->runAction(_commandAction, ...) }
foo::_clientCommand(...)
{
while (_busyFlag)
_commandGate->commandSleep(&busyFlag, THREAD_UNINT);
...
_busyFlag = false;
_commandGate->commandWakeup(&busyFlag, true /* wakeup one only */);
}
The busy flag acts as the semaphore indicating that there's a thread
in the critical region. The workloop lock is used to provide
concurrency protection against your interrupt thread, and making it
possible to sleep waiting for that thread without having to deal with
other threads coming into the critical region.
Caching the result of OSMemberFunctionCast is advisable, as it is
somewhat expensive.
= Mike
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-kernel mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden