This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit a5e714a71e5a693957a6a4f0f151d8842c844588 Author: Ludovic Vanasse <[email protected]> AuthorDate: Sun Oct 27 14:29:08 2024 -0400 Doc: Migrate Signaling Events from Interrupt Handlers doc Migrate https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Events+from+Interrupt+Handlers to official wiki Signed-off-by: Ludovic Vanasse <[email protected]> --- Documentation/guides/index.rst | 1 + .../guides/signal_events_interrupt_handlers.rst | 273 +++++++++++++++++++++ 2 files changed, 274 insertions(+) diff --git a/Documentation/guides/index.rst b/Documentation/guides/index.rst index 9ea60fcd65..5286df48b3 100644 --- a/Documentation/guides/index.rst +++ b/Documentation/guides/index.rst @@ -52,3 +52,4 @@ Guides port_drivers_to_stm32f7.rst semihosting.rst renode.rst + signal_events_interrupt_handlers.rst diff --git a/Documentation/guides/signal_events_interrupt_handlers.rst b/Documentation/guides/signal_events_interrupt_handlers.rst new file mode 100644 index 0000000000..77e5d40f69 --- /dev/null +++ b/Documentation/guides/signal_events_interrupt_handlers.rst @@ -0,0 +1,273 @@ +======================================== +Signaling Events from Interrupt Handlers +======================================== + +.. warning:: Migrated from + https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Events+from+Interrupt+Handlers + +Best way to wake multiple threads from interrupt? +================================================= + + I want to make a character device driver that passes the same data to + all tasks that are reading it. It is not so important whether the data + is queued or if just latest sample is retrieved. Problem is just how to + wake up the waiting threads. + +At the most primitive level, a thread can be waiting for a semaphore, a signal, +or a message queue (not empty or not full). Then there are higher +level wrappers around these like mutexes, semaphores, poll waits, +etc. But under the hood those are the three fundamental wait +mechanisms. Any could be used to accomplish what you want. + +In NuttX, some additional effort was put into the design of the signalling +side of each of the IPCs so that they could be easily used by interrupts +handlers. This behavior is unique to NuttX; POSIX says nothing about +interrupt handlers. As a result, we will be talking about primarily +non-portable OS interfaces. + + So far I've considered the following options: + +And you basically have gone through the list of wait mechanisms: + +Message Queues +============== + + 1) Open a message queue when the device is opened (a new queue for each + task) and keep them in a list. Post to a non-blocking endpoint of these + queues in the ISR. Read from a blocking endpoint in the device ``read()``. + I would need to generate names for the message queues, as there doesn't + seem to be anonymous message queues? + +When you start a project. It is a good idea to decide upon a common IPC +mechanism to base your design on. POSIX message queues are one good +choice to do that: Assign each thread a message queue and the ``main()`` +of each thread simply waits on the message queue. It is a good +architecture and used frequently. + +However, I would probably avoid creating a lot of message queues just +to support the interrupt level signaling. There are other ways to do +that that do not use so much memory. So, if you have message queues, +use them. If not, keep it simple. + +In this case, your waiting task will block on a call to ``mq_receive()`` +until a message is received. It will then wake up and can process +the message. In the interrupt handler, it will call ``mq_send()`` when +an event of interest occurs which will, in turn, wake up the waiting +task. + +Advantages of the use of message queues in this case are that 1) you +can pass quite a lot of data in the message, and 2) it integrates +well in a message-based application architecture. A disadvantage +is that there is a limitation on the number of messages that can be +sent from an interrupt handler so it is possible to get data overrun +conditions, that is, more interrupt events may be received than can +be reported with the available messages. + +This limitation is due to the fact that you cannot allocate memory +dynamically from an interrupt handler. Instead, interrupt handlers +are limited to the use of pre-allocated messages. The number of +pre-allocated messages is given by ``CONFIG_PREALLOC_MQ_MSGS`` + 8. +The ``CONFIG_PREALLOC_MQ_MSGS`` can be used either by normal tasking +logic or by interrupt level logic. The extra eight are an emergency +pool for interrupt handling logic only (that value is not currently +configurable). + +If the task logic consumes all of the ``CONFIG_PREALLOC_MQ_MSGS`` messages, it +will fall back to dynamically allocating messages at some cost to +performance and deterministic behavior. + +If the interrupt level consumes all of the ``CONFIG_PREALLOC_MQ_MSGS`` +messages, it will fall back and use the emergency pool of 8 +pre-allocated messages. If those are also exhausted, then the message +will not be sent and an interrupt is effectively lost. + +Semaphores +========== + + 2) Allocate a semaphore per each device open and keep them in a list. + Post the semaphores when new data is available in a shared buffer. + Read the data inside ``sched_lock()``. + +If you don't have an architecture that uses message queues, and all of +these threads are waiting only for the interrupt event and nothing else, +then signaling semaphores would work fine too. You are basically using +semaphores as condition variables in this case so you do have to be careful. + +NOTE: You do not need multiple semaphores. You can do this with a single +semaphore. If the semaphore is used for this purpose then you initialize +it to zero: + +.. code-block:: c + + sem_init(&sem, 0, 0); + sem_setprotocol(&sem, SEM_PRIO_NONE); + +``sem_setprotocol()`` is a non-standard NuttX function that should be called +immediately after the ``sem_init()``. The effect of this function call is to +disable priority inheritance for that specific semaphore. There should +then be no priority inheritance operations on this semaphore that is +used for signaling. See `Signaling Semaphores and Priority Inheritance +<https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Semaphores+and+Priority+Inheritance>`_ +for further information. + +Since the semaphore is initialized to zero, each time that a thread joins +the group of waiting threads, the count is decremented. So a simple loop +like this would wake up all waiting threads: + +.. code-block:: c + + int svalue; + int ret; + + for (; ; ) + { + ret = sem_getvalue(&sem, &svalue); + if (svalue < 0) + { + sem_post(&sem); + } + else + { + break; + } + } + +NOTE: This use of ``sem_getvalue()`` is not portable. In many environments, +``sem_getvalue()`` will not return negative values if there are waiters on +the semaphore. + +The above code snippet is essentially what the NuttX +``pthread_cond_broadcast()`` does (see `nuttx/sched/pthread_condbroadcast.c <https://github.com/apache/nuttx/blob/master/sched/pthread/pthread_condbroadcast.c>`_). +In NuttX condition variables are really just wrappers around semaphores +that give them a few new properties. You could even call +``pthread_cond_broadcast()`` from an interrupt handler: See +http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cond_signal.html +for usage information. + +Neither of the above mechanisms are portable uses of these interfaces. +However, there is no portable interface for communicating directly with +interrupt handlers. + +If you want to signal a single waiting thread, there are simpler things +you an do. In the waiting task: + +.. code-block:: c + + semt_t g_mysemaphore; + volatile bool g_waiting; + ... + + sem_init(&g_mysemaphore); + sem_setprotocol(&g_mysemaphore, SEM_PRIO_NONE); + ... + + flags = enter_critical_section(); + g_waiting = true; + while (g_waiting) + { + ret = sem_wait(&g_mysemaphore); + ... handler errors ... + } + + leave_critical_section(flags); + +In the above code snippet, interrupts are disabled to set and test +``g_waiting``. Interrupts will, of course, be re-enabled automatically +and atomically while the task is waiting for the interrupt event. + +Then in the interrupt handler + +.. code-block:: c + + extern semt_t g_mysemaphore; + extern volatile bool g_waiting; + ... + + if (g_waiting) + { + g_waiting = false; + sem_post(&g_mysemaphore); + } + +An integer type counter could also be used instead of a type bool to +support multiple waitings. In that case, this is equivalent to the +case above using ``sem_getvalue()`` but does not depend on non-portable +properties of ``sem_getvalue()``. + +NOTE: There is possibility of improper interactions between the +semaphore when it is used for signaling and priority inheritance. +In this case, you should disable priority inheritance on the +signaling semaphore using ``sem_setprotocol(SEM_PRIO_NONE)``. See `Signaling Semaphores and Priority Inheritance +<https://cwiki.apache.org/confluence/display/NUTTX/Signaling+Semaphores+and+Priority+Inheritance>`_ +for further information. + +Signals +======= + + 3) Store the thread id's in a list when ``read()`` is called. Wake up the + threads using ``sigqueue()``. Read the data from a shared buffer + inside ``sched_lock()``. + +Signals would work fine too. Signals have a side-effect that is sometimes +helpful and sometimes a pain in the butt: They cause almost all kinds of +waits (``read()``, ``sem_wait()``, etc.) to wake up and return an error with +``errno=EINTR``. + +That is sometimes helpful because you can wake up a ``recv()`` or a ``read()`` +etc., detect the event that generated the signal, and do something +about it. It is sometimes a pain because you have to remember to +handle the ``EINTR`` return value even when you don't care about it. + +The POSIX signal definition includes some support that would make this +easier for you. This support is not currently implemented in NuttX. +The ``kill()`` interface for example +(http://pubs.opengroup.org/onlinepubs/009695399/functions/kill.html) +supports this behavior: + +"If pid is 0, sig will be sent to all processes (excluding an unspecified +set of system processes) whose process group ID is equal to the process +group ID of the sender, and for which the process has permission to send +a signal. + +"If pid is -1, sig will be sent to all processes (excluding an unspecified +set of system processes) for which the process has permission to send that +signal." + +"If pid is negative, but not -1, sig will be sent to all processes (excluding +an unspecified set of system processes) whose process group ID is equal to +the absolute value of pid, and for which the process has permission to send +a signal." + +NuttX does not currently support process groups. But that might be a good +RTOS extension. If you and others think that would be useful I could +probably add the basics of such a feature in a day or so. + +poll() +====== + + Is there some better way that I haven't discovered? + +The obvious thing that you did not mention is ``poll()``. See +http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html . +Since you are writing a device driver, support for the ``poll()`` method +in your driver seems to be the natural solution. See the ``drivers/`` +directory for many examples, ``drivers/pipes/pipe_common.c`` for one. +Each thread could simply wait on ``poll()``; when the event occurs the +driver could then wake up the set of waiters. Under the hood, this +is again just a set of ``sem_post``'s. But it is also a very standard +mechanism. + +In your case, the semantics of ``poll()`` might have to be bent just a +little. You might have to bend the meaning of some of the event +flags since they are all focused on data I/O events. + +Another creative use of ``poll()`` for use in cases like this: + + That would be something great! PX4 project has that implemented somehow + (in C++), so maybe - if license permits - it could be ported to NuttX in + no time? + + https://pixhawk.ethz.ch/px4/dev/shared_object_communication + +I don't know a lot about this, but it might be worth looking into +if it matches your need. \ No newline at end of file
