Skip to content

Commit 0c4e50a

Browse files
committed
Add ConditionVariable to mbed rtos
Add the ConditionVariable class to mbed rtos to provide easier and safer synchronization.
1 parent 6912a9d commit 0c4e50a

File tree

5 files changed

+366
-3
lines changed

5 files changed

+366
-3
lines changed

rtos/ConditionVariable.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2017-2017 ARM Limited
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
#include "rtos/ConditionVariable.h"
23+
#include "rtos/Semaphore.h"
24+
#include "rtos/Thread.h"
25+
26+
#include "mbed_error.h"
27+
#include "mbed_assert.h"
28+
29+
namespace rtos {
30+
31+
#define RESUME_SIGNAL (1 << 15)
32+
33+
struct Waiter {
34+
Waiter();
35+
Semaphore sem;
36+
Waiter *prev;
37+
Waiter *next;
38+
bool in_list;
39+
};
40+
41+
Waiter::Waiter(): sem(0), prev(NULL), next(NULL), in_list(false)
42+
{
43+
// No initialization to do
44+
}
45+
46+
ConditionVariable::ConditionVariable(Mutex &mutex): _mutex(mutex), _wait_list(NULL)
47+
{
48+
// No initialization to do
49+
}
50+
51+
void ConditionVariable::wait()
52+
{
53+
wait_for(osWaitForever);
54+
}
55+
56+
bool ConditionVariable::wait_for(uint32_t millisec)
57+
{
58+
Waiter current_thread;
59+
MBED_ASSERT(_mutex.get_owner() == Thread::gettid());
60+
MBED_ASSERT(_mutex._count == 1);
61+
_add_wait_list(&current_thread);
62+
63+
_mutex.unlock();
64+
65+
int32_t sem_count = current_thread.sem.wait(millisec);
66+
bool timeout = (sem_count > 0) ? false : true;
67+
68+
_mutex.lock();
69+
70+
if (current_thread.in_list) {
71+
_remove_wait_list(&current_thread);
72+
}
73+
74+
return timeout;
75+
}
76+
77+
void ConditionVariable::notify_one()
78+
{
79+
MBED_ASSERT(_mutex.get_owner() == Thread::gettid());
80+
if (_wait_list != NULL) {
81+
_wait_list->sem.release();
82+
_remove_wait_list(_wait_list);
83+
}
84+
}
85+
86+
void ConditionVariable::notify_all()
87+
{
88+
MBED_ASSERT(_mutex.get_owner() == Thread::gettid());
89+
while (_wait_list != NULL) {
90+
_wait_list->sem.release();
91+
_remove_wait_list(_wait_list);
92+
}
93+
}
94+
95+
void ConditionVariable::_add_wait_list(Waiter * waiter)
96+
{
97+
if (NULL == _wait_list) {
98+
// Nothing in the list so add it directly.
99+
// Update prev pointer to reference self
100+
_wait_list = waiter;
101+
waiter->prev = waiter;
102+
} else {
103+
// Add after the last element
104+
Waiter *last = _wait_list->prev;
105+
last->next = waiter;
106+
waiter->prev = last;
107+
_wait_list->prev = waiter;
108+
}
109+
waiter->in_list = true;
110+
}
111+
112+
void ConditionVariable::_remove_wait_list(Waiter * waiter)
113+
{
114+
// Remove this element from the start of the list
115+
Waiter * next = waiter->next;
116+
if (waiter == _wait_list) {
117+
_wait_list = next;
118+
}
119+
if (next != NULL) {
120+
next = waiter->prev;
121+
}
122+
Waiter * prev = waiter->prev;
123+
if (prev != NULL) {
124+
prev = waiter->next;
125+
}
126+
waiter->next = NULL;
127+
waiter->prev = NULL;
128+
waiter->in_list = false;
129+
}
130+
131+
ConditionVariable::~ConditionVariable()
132+
{
133+
MBED_ASSERT(NULL == _wait_list);
134+
}
135+
136+
}

rtos/ConditionVariable.h

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2017-2017 ARM Limited
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
#ifndef CONDITIONVARIABLE_H
23+
#define CONDITIONVARIABLE_H
24+
25+
#include <stdint.h>
26+
#include "cmsis_os.h"
27+
#include "rtos/Mutex.h"
28+
29+
#include "platform/NonCopyable.h"
30+
31+
namespace rtos {
32+
/** \addtogroup rtos */
33+
/** @{*/
34+
35+
struct Waiter;
36+
37+
/** This class provides a safe way to wait for or send notifications of condition changes
38+
*
39+
* This class is used in conjunction with a mutex to safely wait for or
40+
* notify waiters of condition changes to a resource accessible by multiple
41+
* threads.
42+
*
43+
* # Defined behavior
44+
* - All threads waiting on the condition variable wake when
45+
* ConditionVariable::notify_all is called.
46+
* - If one or more threads are waiting on the condition variable at least
47+
* one of them wakes when ConditionVariable::notify is called.
48+
*
49+
* # Undefined behavior
50+
* - The thread which is unblocked on ConditionVariable::notify_one is
51+
* undefined if there are multiple waiters.
52+
* - The order which in which waiting threads acquire the condition variable's
53+
* mutex after ConditionVariable::notify_all is called is undefined.
54+
* - When ConditionVariable::notify_one or ConditionVariable::notify_all is
55+
* called and there are one or more waiters and one or more threads attempting
56+
* to acquire the condition variable's mutex the order in which the mutex is
57+
* acquired is undefined.
58+
* - The behavior of ConditionVariable::wait and ConditionVariable::wait_for
59+
* is undefined if the condition variable's mutex is locked more than once by
60+
* the calling thread.
61+
* - Spurious notifications (not triggered by the application) can occur
62+
* and it is not defined when these occur.
63+
*
64+
* @note Synchronization level: Thread safe
65+
*
66+
* Example:
67+
* @code
68+
* #include "mbed.h"
69+
*
70+
* Mutex mutex;
71+
* ConditionVariable cond(mutex);
72+
*
73+
* // These variables are protected by locking mutex
74+
* uint32_t count = 0;
75+
* bool done = false;
76+
*
77+
* void worker_thread()
78+
* {
79+
* mutex.lock();
80+
* do {
81+
* printf("Worker: Count %lu\r\n", count);
82+
*
83+
* // Wait for a condition to change
84+
* cond.wait();
85+
*
86+
* } while (!done);
87+
* printf("Worker: Exiting\r\n");
88+
* mutex.unlock();
89+
* }
90+
*
91+
* int main() {
92+
* Thread thread;
93+
* thread.start(worker_thread);
94+
*
95+
* for (int i = 0; i < 5; i++) {
96+
*
97+
* mutex.lock();
98+
* // Change count and notify waiters of this
99+
* count++;
100+
* printf("Main: Set count to %lu\r\n", count);
101+
* cond.notify_all();
102+
* mutex.unlock();
103+
*
104+
* wait(1.0);
105+
* }
106+
*
107+
* mutex.lock();
108+
* // Change done and notify waiters of this
109+
* done = true;
110+
* printf("Main: Set done\r\n");
111+
* cond.notify_all();
112+
* mutex.unlock();
113+
*
114+
* thread.join();
115+
* }
116+
* @endcode
117+
*/
118+
class ConditionVariable : private mbed::NonCopyable<ConditionVariable> {
119+
public:
120+
/** Create and Initialize a ConditionVariable object */
121+
ConditionVariable(Mutex &mutex);
122+
123+
/** Wait for a notification
124+
*
125+
* Wait until a notification occurs.
126+
*
127+
* @note - The thread calling this function must be the owner of the
128+
* ConditionVariable's mutex and it must be locked exactly once
129+
* @note - Spurious notifications can occur so the caller of this API
130+
* should check to make sure the condition they are waiting on has
131+
* been met
132+
*
133+
* Example:
134+
* @code
135+
* mutex.lock();
136+
* while (!condition_met) {
137+
* cond.wait();
138+
* }
139+
*
140+
* function_to_handle_condition();
141+
*
142+
* mutex.unlock();
143+
* @endcode
144+
*/
145+
void wait();
146+
147+
/** Wait for a notification or timeout
148+
*
149+
* @param millisec timeout value or osWaitForever in case of no time-out.
150+
* @return true if a timeout occurred, false otherwise.
151+
*
152+
* @note - The thread calling this function must be the owner of the
153+
* ConditionVariable's mutex and it must be locked exactly once
154+
* @note - Spurious notifications can occur so the caller of this API
155+
* should check to make sure the condition they are waiting on has
156+
* been met
157+
*
158+
* Example:
159+
* @code
160+
* mutex.lock();
161+
* Timer timer;
162+
* timer.start();
163+
*
164+
* bool timed_out = false;
165+
* uint32_t time_left = TIMEOUT;
166+
* while (!condition_met && !timed_out) {
167+
* timed_out = cond.wait_for(time_left);
168+
* uint32_t elapsed = timer.read_ms();
169+
* time_left = elapsed > TIMEOUT ? 0 : TIMEOUT - elapsed;
170+
* }
171+
*
172+
* if (condition_met) {
173+
* function_to_handle_condition();
174+
* }
175+
*
176+
* mutex.unlock();
177+
* @endcode
178+
*/
179+
bool wait_for(uint32_t millisec);
180+
181+
/** Notify one waiter on this condition variable that a condition changed.
182+
*
183+
* @note - The thread calling this function must be the owner of the ConditionVariable's mutex
184+
*/
185+
void notify_one();
186+
187+
/** Notify all waiters on this condition variable that a condition changed.
188+
*
189+
* @note - The thread calling this function must be the owner of the ConditionVariable's mutex
190+
*/
191+
void notify_all();
192+
193+
~ConditionVariable();
194+
195+
private:
196+
void _add_wait_list(Waiter * waiter);
197+
void _remove_wait_list(Waiter * waiter);
198+
Mutex &_mutex;
199+
Waiter *_wait_list;
200+
};
201+
202+
}
203+
#endif
204+
205+
/** @}*/

rtos/Mutex.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
namespace rtos {
2929

30-
Mutex::Mutex()
30+
Mutex::Mutex(): _count(0)
3131
{
3232
constructor();
3333
}
@@ -50,17 +50,31 @@ void Mutex::constructor(const char *name)
5050
}
5151

5252
osStatus Mutex::lock(uint32_t millisec) {
53-
return osMutexAcquire(_id, millisec);
53+
osStatus status = osMutexAcquire(_id, millisec);
54+
if (osOK == status) {
55+
_count++;
56+
}
57+
return status;
5458
}
5559

5660
bool Mutex::trylock() {
57-
return (osMutexAcquire(_id, 0) == osOK);
61+
if (osMutexAcquire(_id, 0) == osOK) {
62+
_count++;
63+
return true;
64+
} else {
65+
return false;
66+
}
5867
}
5968

6069
osStatus Mutex::unlock() {
70+
_count--;
6171
return osMutexRelease(_id);
6272
}
6373

74+
osThreadId Mutex::get_owner() {
75+
return osMutexGetOwner(_id);
76+
}
77+
6478
Mutex::~Mutex() {
6579
osMutexDelete(_id);
6680
}

0 commit comments

Comments
 (0)