|
|
Mailboxes
Detailed Description
Mailboxes are used to pass information between tasks.
Mailboxes contain a pointer to the mailbox data, and a version number. Tasks can both read from and write to mailboxes. The rules governing mailboxes are
- A task can only read from one mailbox at a time - the task must finish reading a mailbox before it can start reading another one
- A task cannot write to a mailbox while there are other tasks reading from it
- A mailbox can contain only one "message" at a time
Mailboxes must be initialised with initialise_mbox(). Tasks can write to mailboxes with write_mbox() or write_mbox_now(). Tasks can read from mailboxes with read_mbox() or read_mbox_min_version(), and must subsequently call release_mbox_read() when they are done reading from the mailbox. Other functions governing the use of mailboxes are get_current_mbox_version() and wait_for_receiver().
Mailboxes can be synchronous or asynchronous. When task A writes to a mailbox, its version number is incremented. If task B has been waiting for that version of the mailbox, it is immediately marked as reading the mailbox, hence other tasks are prevented from writing to the mailbox until task B has finished reading from it. Of course, if there wasn't a task B waiting for the data, then a task C could over-write what task A wrote to the mailbox without any task ever seeing what task A wrote. To mitigate this problem, task A is able to request that a certain minimum number of tasks be waiting for the mailbox to be updated before the data is written to the mailbox - hence task A is guaranteed that the data it writes is seen.
It is the responsibility of the task that writes to the mailbox to allocate memory for the mailbox data. To prevent problems arising from the memory allocation going out of scope before all the reading tasks have finished reading the data, a writing task is able to request that all reading tasks finish reading before control is passed back to the writing task. Not only that, but the writing task is also able to request that the mailbox data be "nullified" (i.e. that a new version of the mailbox with a null data pointer be published) as soon as all the reading tasks have finished reading, thereby ensuring that no subsequent reading tasks will attempt to read the writing tasks' data.
See using mailboxes for information on how to use mailboxes
Function Documentation
| int16_t get_current_mbox_version |
( |
mailbox_t * |
mbox |
) |
|
Get the current version of a mailbox.
This function can be called by an ISR, by any task, or even before the RTOS starts
| void initialise_mbox |
( |
mailbox_t * |
mbox, |
|
|
void * |
data, |
|
|
const int16_t |
version | |
|
) |
| | |
Initialise a mailbox - this must be called on every mailbox before it is used.
The arguments are
mbox - the mailbox to initialise
data - the pointer to the mailbox data. This is the value that will be returned by read_mbox() and read_mbox_min_version(). It is acceptable to set this value to zero - though callers to read_mbox() and read_mbox_min_version() would need to be aware that the return value could be zero.
version - the initial version of the mailbox
| void* read_mbox |
( |
mailbox_t * |
mbox, |
|
|
int16_t * |
version | |
|
) |
| | |
Read a mailbox.
This function reads a mailbox regardless of the version of the mailbox.
- Attention:
- It is vital that release_mbox_read() be called to release the mailbox for other tasks to write to it when the calling task has finished reading the mailbox data
The arguments are
mbox - the mailbox to read
version - if not null, the version of the mailbox will be written to this address
The return value is a pointer to the mailbox data. Note that a zero return value does not mean that the mailbox read failed - it means that the mailbox was empty - and hence release_mbox_read() must still be called.
Since this function can cause a suspension (i.e. if the mailbox hasn't reached the specified version), it can only be called from a task with a non-zero priority
| void* read_mbox_min_version |
( |
mailbox_t * |
mbox, |
|
|
int16_t * |
version | |
|
) |
| | |
Wait for a mailbox to reach at least a certain version, and then start reading from it.
- Attention:
- It is vital that release_mbox_read() be called to release the mailbox for other tasks to write to it when the calling task has finished reading the mailbox data
The arguments are
mbox - the mailbox to read from
version - the minimum version of the mailbox that we require. Note that the version of the mailbox actually read will be written to this address
The return value is a pointer to the mailbox data. Note that a zero return value does not mean that the mailbox read failed - it means that the mailbox was empty - and hence release_mbox_read() must still be called.
Since this function can cause a suspension (i.e. if the mailbox hasn't reached the specified version), it can only be called from a task with a non-zero priority
Function to call when finished reading from a mailbox.
Since tasks are not allowed to write to a mailbox while there are other tasks reading from it, this function must be called when a task has finished reading from a mailbox - i.e. it must be called after every call to read_mbox() or read_mbox_min_version()
Since a task can only read one mailbox at a time, there are no arguments to this function. The return value is the mailbox that the task was reading from, or zero if it was not reading from any mailbox.
Note that calling this function may cause a higher-prioroty task that is waiting to write to the mailbox to be scheduled.
Wait for a task to be suspended while trying to read from a mailbox.
This function will suspend the caller until another task is suspended while calling read_mbox_min_version(). This could be used to give a task that needs to read the mailbox a chance to initialise.
| void write_mbox |
( |
mailbox_t * |
mbox, |
|
|
void * |
data, |
|
|
uint8_t |
wait_for_receivers, |
|
|
uint8_t |
wait_for_empty_nullify | |
|
) |
| | |
Write to a mailbox.
A write to a mailbox will
- wait for all tasks that are reading the mailbox to call release_mbox_read()
- if
wait_for_receivers is not zero, it will wait until there are at least that many tasks that have suspended themselves on read_mbox_min_version() while waiting for fresh data to be put into this mailbox. Otherwise, the data that is put into the mailbox might end up not being picked up by any task (e.g. if another write is made to the mailbox before any task attempts to read from it, that second write will over-write what is put into the mailbox by this function call)
- then, the mailbox data will be updated, and the version will be incremented by one
- if there are any higher-priority tasks suspended on read_mbox_min_version(), they will start executing
- if
wait_for_empty_nullify is not zero, the function will not return until all tasks that had been suspended on read_mbox_min_version() have called release_mbox_read().
- if
wait_for_empty_nullify is greater than one, a new version of the mailbox data, containing a null pointer, will be published
If the task that writes to the mailbox can be stopped with stop_task(), then you could end up in a situation where the memory location of the task data goes out of scope while the mailbox is being read. If this is a possibility, then it is recommended that a mutex be created for the mailbox, that it be locked on to while the mailbox is being written to, that the task only ever be stopped with the wait_for_mutexes parameter of stop_task() set, and that wait_for_empty_nullify be set to at least one (it would need to be set at two if there are more than one tasks that could read from the mailbox).
The arguments are
-
mbox - the mailbox to write to
-
data - the pointer to the mailbox data. This is the value that will be returned by read_mbox() and read_mbox_min_version(), so it is vital that it points to valid data for the lifetime of this particular version of the mailbox.
-
wait_for_receivers - if non-zero, the write to the mailbox will not occur until there are at least wait_for_receivers task(s) waiting to read data from the mailbox. This could be used if it is vital that at least a specified number of receiving task(s) receive the information - it would give the receiving task(s) a chance to initialise
-
wait_for_empty_nullify
-
if greater than zero, the function will not return until all tasks that were waiting to read the mailbox have done so. This could be used if it is vital that the sender knows that all messages have been received by the receiving task(s).
-
if greater than one, the mailbox data will be set to null when all the tasks that were waiting to read the mailbox have done so (which means that subsequent reads of the mailbox will return a null pointer), so the data in the mailbox data buffer can safely be changed
Since this function can cause a task suspension, it can only be called from a task with a non-zero priority
| int8_t write_mbox_now |
( |
mailbox_t * |
mbox, |
|
|
void * |
data | |
|
) |
| | |
Attempt to write to a mailbox.
This function will attempt to write to a mailbox - if the mailbox is being read, and is therefore unavailable for writing to as per the rules of mailboxes, this function will return immediately without having written anything to the mailbox.
The arguments are -
The return value is zero on success
Since this function will never cause a task suspension, it can be called from an ISR, from the idle task, or from anywhere else in the application. Also, if this function is called from within a task, and if the task is potentially stoppable by another task using stop_task() on it, then it is important that the memory location pointed to by data doesn't go out of scope when the task is stopped.
|