• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: waiting queue
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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
References: 
 >waiting queue (From: devmaillists <email@hidden>)
 >Re: waiting queue (From: Michael Smith <email@hidden>)
 >Re: waiting queue (From: devmaillists <email@hidden>)

  • Prev by Date: Re: waiting queue
  • Next by Date: Re: kdebug_chudhook: need finer grained kdebug
  • Previous by thread: Re: waiting queue
  • Next by thread: Call function on all processors
  • Index(es):
    • Date
    • Thread