Queueing call of same function

I have a write_string_to_uart() function, which can be called from more than one task. To avoid that one write can interfere with another I’m seeking for a way to either lockout the writes from each other of
queue each write in a manner that they get executed first in first out.

How do I implement this mechanism best?

Thinking of it I may have misinterpreted the meaning of a queue. It seems it’s just a fifo buffer, nothing to do with multitasking.
Could I write a function pointer into the queue and execute the function the pointer points to? But how would I pass parameters to that function?

If you mean FreeRTOS queues these are multi-tasking aware queues/FIFOs.
But in your case using a mutex protection is probably fine/good enough.

My serial write routines basically all use the form of:

take the MUTEX defined for this serial port.

copy the data to the buffer defined for this serial port (like a queue), and start the serial port if needed

give the MUTEX defined for this serial port, so another write can occur.

so the write_string_to_uart() will use a MUTEX to serialize its possible call from multiple tasks.

Similar to what Richard wrote, the “standard” technique for this situation is as follows:

  1. Define a queue of pointers to blobs of data. The queue must be protected simultaneously by a semaphore and a mutex.
  2. Spawn a “puller” thread that is responsible for writing to the UART. It will dequeue pointers from the queue and write the associated data to the UART. It blocks, waiting on semaphore.
  3. Spawn other “pusher” threads that will take turns enqueing pointers to blobs of data into the queue.
  4. Pusher acquires the queue.
  5. Pusher enqueues pointer to data.
  6. Pusher releases the queue.
  7. Pusher raises semaphore by 1.
  8. Puller unblocks, which automatically causes lowering of the semaphore by 1.
  9. Puller acquires the queue.
  10. Puller dequeues pointer to blob of data from the queue.
  11. Puller releases the queue.
  12. Puller actually writes the data denoted by pointer to the UART.

Note that it is a bad idea for the puller to acquire the queue for the purpose of “peeking” into it to see if any data available. That is the purpose of the semaphore, to tell it when data is available. Also note that a pusher should not raise the semaphore until after it has released the queue. Were pushers to acquire/enqueue/raise/release, in that order, it might cause situation where puller comes out of wait between the raise/release; tries to acquire the queue to see what is going on; is unable, because pusher still has the lock; go back to sleep… you get the idea… a quantum allocation would be wasted by waking the puller prematurely. Better to let the pushers be 100% finished doing what they need to do before raising the semaphore.

The terminal model for this situation requires that the puller block while waiting, simultaneously, for the semaphore and some other event that tells it to exit, perhaps by a main thread. But this technique requires that OS provide the ability to wait for multiple synchronization objects simultaneously, (WFMO), which FreeRTOS lacks, so as a workaround, your puller thread should not block indefinitely, but add a delay to the wait, so that it is given the opportunity to poll some other variable to know when it is time to exit. Same goes for the pusher threads, for that matter.

I don’t get the need of the additional semaphore and mutex. When using a FreeRTOS queue I think none of them are really needed. It’s all handled by the queue.
However, when using by reference resp. by pointer queue items an additional mechanism (for instance a custom item allocator protected by a mutex) is required. It’s not needed when using a by value queue.

I think that you are right.

I just took a look at the model used by FreeRTOS [I have never programmed FreeRTOS], and it appears that the queuing mechanism integrates semaphore/mutex as you stated.

That said, my first inclination would be to bypass this integrated model, and use my model instead, as I would need to change only a small amount of my C++ code to get the behavior that I want on any OS, whereas with the FreeRTOS model…

The Mutex might be because it sounds like a “Message” might need multiple blobs to be sent and kept together. If not, you don’t need it.

I agree, the semaphore isn’t needed, as the reader can just wait on the queue when it needs another block.

This is roughly the same as @StackMaster described - amazon-freertos/iot_logging_task_dynamic_buffers.c at main · aws/amazon-freertos · GitHub

And as you can see, there is no need of semaphore or mutex.