Skip to content

Commit 1cda7f7

Browse files
committed
Refine semaphore for correctness
This commit improves robustness with better overflow handling and error detection. The implementation maintains FIFO ordering and blocking semantics while being more efficient in the common case and more robust in error conditions.
1 parent c0fafca commit 1cda7f7

File tree

1 file changed

+55
-36
lines changed

1 file changed

+55
-36
lines changed

kernel/semaphore.c

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <sys/task.h>
1313

1414
#include "private/error.h"
15+
#include "private/utils.h"
1516

1617
/* Semaphore Control Block structure. */
1718
struct sem_t {
@@ -26,36 +27,47 @@ struct sem_t {
2627

2728
static inline bool sem_is_valid(const sem_t *s)
2829
{
29-
return (s && s->magic == SEM_MAGIC && s->wait_q);
30+
return s && s->magic == SEM_MAGIC && s->wait_q && s->max_waiters > 0 &&
31+
s->count >= 0 && s->count <= SEM_MAX_COUNT;
32+
}
33+
34+
static inline void sem_invalidate(sem_t *s)
35+
{
36+
if (s) {
37+
s->magic = 0xDEADBEEF; /* Clear magic to prevent reuse */
38+
s->count = -1;
39+
s->max_waiters = 0;
40+
}
3041
}
3142

3243
sem_t *mo_sem_create(uint16_t max_waiters, int32_t initial_count)
3344
{
3445
/* Enhanced input validation */
35-
if (!max_waiters || initial_count < 0 || initial_count > SEM_MAX_COUNT)
46+
if (unlikely(!max_waiters || initial_count < 0 ||
47+
initial_count > SEM_MAX_COUNT))
3648
return NULL;
3749

3850
sem_t *sem = malloc(sizeof(sem_t));
39-
if (!sem)
51+
if (unlikely(!sem))
4052
return NULL;
4153

42-
/* Initialize structure to known state */
54+
/* Initialize structure to known safe state */
4355
sem->wait_q = NULL;
4456
sem->count = 0;
4557
sem->max_waiters = 0;
4658
sem->magic = 0;
4759

4860
/* Create wait queue */
4961
sem->wait_q = queue_create(max_waiters);
50-
if (!sem->wait_q) {
62+
if (unlikely(!sem->wait_q)) {
5163
free(sem);
5264
return NULL;
5365
}
5466

5567
/* Initialize remaining fields atomically */
5668
sem->count = initial_count;
5769
sem->max_waiters = max_waiters;
58-
sem->magic = SEM_MAGIC; /* Mark as valid last */
70+
sem->magic = SEM_MAGIC; /* Mark as valid last to prevent races */
5971

6072
return sem;
6173
}
@@ -65,19 +77,19 @@ int32_t mo_sem_destroy(sem_t *s)
6577
if (!s)
6678
return ERR_OK; /* Destroying NULL is a no-op, not an error */
6779

68-
if (!sem_is_valid(s))
80+
if (unlikely(!sem_is_valid(s)))
6981
return ERR_FAIL;
7082

7183
NOSCHED_ENTER();
7284

7385
/* Check if any tasks are waiting - unsafe to destroy if so */
74-
if (queue_count(s->wait_q) > 0) {
86+
if (unlikely(queue_count(s->wait_q) > 0)) {
7587
NOSCHED_LEAVE();
7688
return ERR_TASK_BUSY;
7789
}
7890

79-
/* Invalidate the semaphore to prevent further use */
80-
s->magic = 0;
91+
/* Atomically invalidate the semaphore to prevent further use */
92+
sem_invalidate(s);
8193
queue_t *wait_q = s->wait_q;
8294
s->wait_q = NULL;
8395

@@ -91,23 +103,23 @@ int32_t mo_sem_destroy(sem_t *s)
91103

92104
void mo_sem_wait(sem_t *s)
93105
{
94-
if (!sem_is_valid(s)) {
106+
if (unlikely(!sem_is_valid(s))) {
95107
/* Invalid semaphore - this is a programming error */
96108
panic(ERR_SEM_OPERATION);
97109
}
98110

99111
NOSCHED_ENTER();
100112

101-
/* Fast path: resource available and no waiters (preserves FIFO) */
102-
if (s->count > 0 && queue_count(s->wait_q) == 0) {
113+
/* Fast path: resource available and no waiters (preserves FIFO ordering) */
114+
if (likely(s->count > 0 && queue_count(s->wait_q) == 0)) {
103115
s->count--;
104116
NOSCHED_LEAVE();
105117
return;
106118
}
107119

108120
/* Slow path: must wait for resource */
109-
/* Verify wait queue has capacity (should never fail for valid semaphore) */
110-
if (queue_count(s->wait_q) >= s->max_waiters) {
121+
/* Verify wait queue has capacity before attempting to block */
122+
if (unlikely(queue_count(s->wait_q) >= s->max_waiters)) {
111123
NOSCHED_LEAVE();
112124
panic(ERR_SEM_OPERATION); /* Queue overflow - system error */
113125
}
@@ -120,22 +132,22 @@ void mo_sem_wait(sem_t *s)
120132
*/
121133
_sched_block(s->wait_q);
122134

123-
/* When we return here, we have been awakened and have acquired the
124-
* semaphore. The task that signaled us did NOT increment the count - the
125-
* "token" was passed directly to us, so no further action is needed.
135+
/* When we return here, we have been awakened and acquired the semaphore.
136+
* The signaling task passed the "token" directly to us without incrementing
137+
* the count, so no further action is needed.
126138
*/
127139
}
128140

129141
int32_t mo_sem_trywait(sem_t *s)
130142
{
131-
if (!sem_is_valid(s))
143+
if (unlikely(!sem_is_valid(s)))
132144
return ERR_FAIL;
133145

134146
int32_t result = ERR_FAIL;
135147

136148
NOSCHED_ENTER();
137149

138-
/* Only succeed if resource is available AND no waiters (preserves FIFO) */
150+
/* Only succeed if resource available AND no waiters (preserves FIFO) */
139151
if (s->count > 0 && queue_count(s->wait_q) == 0) {
140152
s->count--;
141153
result = ERR_OK;
@@ -147,7 +159,7 @@ int32_t mo_sem_trywait(sem_t *s)
147159

148160
void mo_sem_signal(sem_t *s)
149161
{
150-
if (!sem_is_valid(s)) {
162+
if (unlikely(!sem_is_valid(s))) {
151163
/* Invalid semaphore - this is a programming error */
152164
panic(ERR_SEM_OPERATION);
153165
}
@@ -157,13 +169,13 @@ void mo_sem_signal(sem_t *s)
157169

158170
NOSCHED_ENTER();
159171

160-
/* Check if any tasks are waiting */
172+
/* Check if any tasks are waiting for resources */
161173
if (queue_count(s->wait_q) > 0) {
162174
/* Wake up the oldest waiting task (FIFO order) */
163175
awakened_task = queue_dequeue(s->wait_q);
164-
if (awakened_task) {
165-
/* Validate the awakened task before changing its state */
166-
if (awakened_task->state == TASK_BLOCKED) {
176+
if (likely(awakened_task)) {
177+
/* Validate awakened task state consistency */
178+
if (likely(awakened_task->state == TASK_BLOCKED)) {
167179
awakened_task->state = TASK_READY;
168180
should_yield = true;
169181
} else {
@@ -172,39 +184,46 @@ void mo_sem_signal(sem_t *s)
172184
}
173185
}
174186
/* Note: count is NOT incremented - the "token" is passed directly to
175-
* the awakened task to prevent race conditions.
187+
* the awakened task to prevent race conditions where the count could
188+
* be decremented by another task between our increment and the
189+
* awakened task's execution.
176190
*/
177191
} else {
178-
/* No waiting tasks - increment available count */
179-
if (s->count < SEM_MAX_COUNT)
192+
/* No waiting tasks - increment available resource count */
193+
if (likely(s->count < SEM_MAX_COUNT))
180194
s->count++;
181-
/* Silently ignore overflow - semaphore remains at max count */
195+
196+
/* Silently ignore overflow - semaphore remains at max count.
197+
* This prevents wraparound while maintaining system stability.
198+
*/
182199
}
183200

184201
NOSCHED_LEAVE();
185202

186-
/* Yield outside critical section to allow awakened task to run.
187-
* This improves responsiveness if the awakened task has higher priority.
203+
/* Yield outside critical section if we awakened a task.
204+
* This improves system responsiveness by allowing the awakened task to run
205+
* immediately if it has higher priority.
188206
*/
189207
if (should_yield)
190208
mo_task_yield();
191209
}
192210

193211
int32_t mo_sem_getvalue(sem_t *s)
194212
{
195-
if (!sem_is_valid(s))
213+
if (unlikely(!sem_is_valid(s)))
196214
return -1;
197215

198-
/* This is inherently racy - the value may change immediately after being
199-
* read. The volatile keyword ensures we read the current value, but does
200-
* not provide atomicity across multiple operations.
216+
/* This read is inherently racy - the value may change immediately after
217+
* being read. The volatile keyword ensures we read the current value from
218+
* memory, but does not provide atomicity across multiple operations.
219+
* Callers should not rely on this value for synchronization decisions.
201220
*/
202221
return s->count;
203222
}
204223

205224
int32_t mo_sem_waiting_count(sem_t *s)
206225
{
207-
if (!sem_is_valid(s))
226+
if (unlikely(!sem_is_valid(s)))
208227
return -1;
209228

210229
int32_t count;

0 commit comments

Comments
 (0)