Mayank Gupta / Mbed OS pelion-example-frdm

Dependencies:   FXAS21002 FXOS8700Q

Embed: (wiki syntax)

« Back to documentation index

atomic-queue.h File Reference

atomic-queue.h File Reference

A linked-list queue based on atomic access. More...

Go to the source code of this file.

Functions

int aq_push_tail (struct atomic_queue *q, struct atomic_queue_element *e)
 Add an element to the tail of the queue.
struct atomic_queue_element * aq_pop_head (struct atomic_queue *q)
 Get an element from the head of the queue.
int aq_empty (struct atomic_queue *q)
 Check if there are any elements in the queue.
unsigned aq_count (struct atomic_queue *q)
 Iterates over the queue and counts the elements in the queue.
void aq_initialize_element (struct atomic_queue_element *e)
 Initialize an atomic queue element.
int aq_element_take (struct atomic_queue_element *e, void *ctx)
 Take an element (this acquires the element lock)
int aq_element_release (struct atomic_queue_element *e, void **ctx)
 Release an element (this releases the element lock)
int aq_atomic_cas_uintptr (uintptr_t *ptr, uintptr_t oldval, uintptr_t newval)
 Atomic Compare and Set.
int32_t aq_atomic_inc_int32 (int32_t *ptr, int32_t inc)
 Atomic increment.

Detailed Description

A linked-list queue based on atomic access.

This queue is designed explicitly for use in contexts where atomic access is desirable. Using an atomic queue comes with three major benefits:

1. This allows the queue to be used in contexts where some RTOS primitives (mutexes) cannot be used, such as interrupt context. 2. On many platforms, critical sections are very expensive, while atomics are not. Thus, there is a significant performance benefit to using atomic primitives wherever possible. 3. Atomic operations have the least effect on all other execution contexts on the device. Critical sections have performance and responsiveness side effects. Mutexes can disrupt the execution of other threads. Atomics do not affect the execution of other contexts and are immune to priority inversion.

In short, the atomics are the most cooperative way of building a queue.

Theory of Operation: The queue is intended to be multi-writer/multi-reader. Multi-writer semantics have been fully validated, but multi-reader semantics still require additional validation. It is recommended that the atomic queue be treated as multi-writer/single-reader until this validation is complete.

Assumptions: The queue MUST own all memory currently in the queue. Any modification to a queue element that is already enqueued can result in undefined behaviour. Because of this, the queue expects queue elements to be pool-allocated prior to insertion and freed after extraction.

To mitigate the possibility of a double-insert, the queue elements are populated with a "lock" field. This is used to indicate when the element is in use, to ensure that a parallel thread of execution cannot accidentally reuse an element that has already been inserted.

*NB:* Element locks are unnecessary when the atomic queue is used exclusively with pool allocated queue elements.

Queue Organization: The queue is a singly linked list. The list pointer is the tail pointer of the queue. The tail pointer points to the last element in the queue. To find the head of the queue, the dequeue mechanism traverses the list, starting at the tail of the queue until it finds the last list element, which is the head of the queue. Each queue element contains: * Next pointer * Lock element * Data (void* by default, custom element possible)

Element Insertion: To insert an element: Do: * Read the tail pointer (load exclusive). * Write the tail pointer into the next pointer of the new element. * Write the tail pointer with the address of the new element (store exclusive). Until the store is successful.

If a different thread of higher priority executed between the read and the last write, it will cause the new element to have the wrong next pointer. This is why the load-exclusive and store-exclusive are used. These are ARMv7 instructions that allow a given thread of execution to recognize whether it has been interrupted. If another thread has set the exclusive bit, then the store will fail.

Element Extraction: Extracting an element is much more complex than element insertion due to the need to traverse the queue.

1. Read the tail pointer 2. If the tail pointer is NULL, return NULL. 3. Set the current element to the tail pointer. 4. Read the current element's next pointer (load exclusive). 5. If the next pointer was NULL, start over (go to 1.) 6. Load the next element. 7. Check the value of the next element's next pointer 8. If it is non-NULL, set the current element pointer to the next element and go to 4. 9. Otherwise, set the current element's next pointer to NULL (store exclusive). 10. Return the next element.

There is the potential for another thread of execution to interrupt the search for the head of the queue. This should cause the dequeue mechanism to find a NULL next pointer in the current element. However, this may depend on other circumstances in the system, which might break the behaviour of the queue. Until this is fully analyzed, the queue should be treated as single- reader/multi-writer.

Definition in file atomic-queue.h.


Function Documentation

int aq_atomic_cas_uintptr ( uintptr_t *  ptr,
uintptr_t  oldval,
uintptr_t  newval 
)

Atomic Compare and Set.

Take a pointer to a uintptr_t, compare its current value to oldval. If it is as expected, try to write newval to the pointer target. Fail if interrupted.

Parameters:
[in,out]ptrA pointer to the target of the atomic compare and set
[in]oldvalA value to compare with the target of ptr.
[in]newvalA new value to store to the target of ptr.
Return values:
1if newval was stored
0if oldval did not match *ptr, or if the store was interrupted.

Definition at line 66 of file atomic.c.

int32_t aq_atomic_inc_int32 ( int32_t *  ptr,
int32_t  inc 
)

Atomic increment.

Increment the value pointed to by ptr and increment it by inc atomically This is just a passthrough to __sync_add_and_fetch on platforms where it is supported.

Parameters:
[in,out]ptrA pointer to the target of the increment
[in]incA value by which to increment *ptr
Returns:
the new value of *ptr

Definition at line 79 of file atomic.c.

unsigned aq_count ( struct atomic_queue *  q )

Iterates over the queue and counts the elements in the queue.

The value returned by this function may be invalid by the time it returns. Do not depend on this value except in a critical section.

Returns:
the number of elements in the queue

Definition at line 109 of file atomic-queue.c.

int aq_element_release ( struct atomic_queue_element *  e,
void **  ctx 
)

Release an element (this releases the element lock)

Parameters:
[in]elementElement to release
[out]ctxThe element context will be written to this pointer
Return values:
ATOMIC_QUEUE_SUCCESSfor success
ATOMIC_QUEUE_NULL_ELEMENTif the element is NULL
ATOMIC_QUEUE_INVALID_CONTEXTif the pointer to the context is NULL

Definition at line 45 of file atomic-queue.c.

int aq_element_take ( struct atomic_queue_element *  e,
void *  ctx 
)

Take an element (this acquires the element lock)

Parameters:
[in]elementElement to take
[in]ctxThe element context. If a context is not needed, use ATOMIC_QUEUE_NO_CONTEXT. If a context is needed, pass a non-NULL pointer.
Return values:
ATOMIC_QUEUE_SUCCESSfor success
ATOMIC_QUEUE_NULL_ELEMENTif the element is NULL
ATOMIC_QUEUE_INVALID_CONTEXTif the context is NULL

Definition at line 29 of file atomic-queue.c.

int aq_empty ( struct atomic_queue *  q )

Check if there are any elements in the queue.

Note that there is no guarantee that a queue which is not empty when this API is called will not be become empty before aq_pop_head is called

Return values:
non-zerowhen the queue is empty
0when the queue is not empty

Definition at line 104 of file atomic-queue.c.

void aq_initialize_element ( struct atomic_queue_element *  e )

Initialize an atomic queue element.

WARNING: Only call this function one time per element, or it may result in undefined behaviour.

Parameters:
[in]elementElement to initialize

Definition at line 125 of file atomic-queue.c.

struct atomic_queue_element* aq_pop_head ( struct atomic_queue *  q ) [read]

Get an element from the head of the queue.

This function iterates over the queue and removes an element from the head when it finds the head. This is slower than maintaining a head pointer, but it is necessary to ensure that a pop is completely atomic.

Element Extraction: Extracting an element is much more complex than element insertion due to the need to traverse the queue.

1. Read the tail pointer 2. If the tail pointer is NULL, return NULL. 3. Set the current element to the tail pointer. 4. Read the current element's next pointer (load exclusive). 5. If the next pointer was NULL, start over (go to 1.) 6. Load the next element. 7. Check the value of the next element's next pointer 8. If it is non-NULL, set the current element pointer to the next element and go to 4. 9. Otherwise, set the current element's next pointer to NULL (store exclusive). 10. Return the next element.

There is the potential for another thread of execution to interrupt the search for the head of the queue. This should cause the dequeue mechanism to find a NULL next pointer in the current element. However, this may depend on other circumstances in the system, which might break the behaviour of the queue. Until this is fully analyzed, the queue should be treated as single- reader/multi-writer.

Parameters:
[in,out]qThe queue to pop from
Returns:
The popped element or NULL if the queue was empty

Definition at line 69 of file atomic-queue.c.

int aq_push_tail ( struct atomic_queue *  q,
struct atomic_queue_element *  e 
)

Add an element to the tail of the queue.

Since the queue only maintains a tail pointer, this simply inserts the new element before the tail pointer

Element Insertion: To insert an element: Do: * Read the tail pointer (load exclusive). * Write the tail pointer into the next pointer of the new element. * Write the tail pointer with the address of the new element (store exclusive). Until the store is successful.

If a different thread of higher priority executed between the read and the last write, it will cause the new element to have the wrong next pointer. This is why the load-exclusive and store-exclusive are used. These are ARMv7 instructions that allow a given thread of execution to recognize whether it has been interrupted. If another thread has set the exclusive bit, then the store will fail.

Parameters:
[in,out]qthe queue structure to operate on
[in]eThe element to add to the queue

Definition at line 54 of file atomic-queue.c.