Semaphore monopolization

akuros wrote on Tuesday, February 23, 2010:

FreeRTOS V6.0.2 and V5.0.2.

It is possible to make taking semaphore impossible. It happens when tasks running on equal priorities try to take semaphore. First one (see task1 below) takes semaphore rarely for short time, the second one (see task2 below) takes semaphore for longer time but gives it back for a very short time. Takin semaphore in task1 never succeeds. Explicit taksYIELD() would walk around this problem, but it has significant impact on performance.

Simply giving semaphore doesn’t switch context to waiting task.
I think it is a BUG but I can’t make a bug request. Can anyone help?

xSemaphoreHandle sem ;
void task1(void *pvParam)
{
	while(1) {
		if (xSemaphoreTake(sem, 100)) {
			// BUG! We should be able to go into here, but it never happens
			xSemaphoreGive(sem) ;
			vTaskDelay(100) ;
		}
	}
}
void task2(void *pvParam)
{
	while (1) {
		if (xSemaphoreTake(sem, 50)) {
			// Let's do something time consuming with semaphore taken
			vTaskDelay(100) ;
			xSemaphoreGive(sem) ;
		}
	}
}
int main( void )
{
	prvSetupHardware();
	xTaskHandle thDummy ;
	sem = xSemaphoreCreateMutex();
	xTaskCreate(task1, (signed portCHAR*)"1", configMINIMAL_STACK_SIZE, NULL, 1, &thDummy) ;
	xTaskCreate(task2, (signed portCHAR*)"2", configMINIMAL_STACK_SIZE, NULL, 1, &thDummy) ;
	vTaskStartScheduler();
}

davedoors wrote on Tuesday, February 23, 2010:

xSemaphoreGive() calls xQueueGenericSend().  Then within xQueueGenericSend() (on line 474 of V6.0.2) there is a call to xTaskRemoveFromEventList() which will return true if the queue send caused a task of EQUAL OR HIGHER priority to unblock (despite what the comment says). xTaskRemoveFromEventList() returning true will cause a yield, which is what you say should happen but isn’t. It looks ok to me.

It is possible that in your example the timing is such that one task always yields to the other just at the end of the time slice, meaning that a yield is performed back again almost immediately. This would be legitimate behavior.

akuros wrote on Tuesday, February 23, 2010:

I is not about timing. Code below still shows the problem

xSemaphoreHandle sem ;
void task1(void *pvParam)
{
	while(1) {
		if (xSemaphoreTake(sem, portMAX_DELAY)) {
			// BUG! We should be able to go into here, but it never happens
			xSemaphoreGive(sem) ;
			vTaskDelay(1000) ;
		}
	}
}
void task2(void *pvParam)
{
	while (1) {
		if (xSemaphoreTake(sem, 50)) {
			// Let's do something time consuming with semaphore taken
			vTaskDelay(100) ;
			xSemaphoreGive(sem) ;
		}
	}
}

akuros wrote on Tuesday, February 23, 2010:

What is interesting, taskYIELD is called within xSemaphoreGive, but it looks like double context switch happens. Unblocked task was transferred to ready list, byt then taskYIELD cause context switch to the next one.

rawka wrote on Wednesday, February 24, 2010:

Akuros, I think your code works as designed, too.

As I understand, in your example task2 will poll the system constantly and it excludes task1 processing with the semaphore. So task1 will - most likely - never got available time-slice to run (but it could happen, if the scheduler break task2 execution after sem.give and before the sem.take, but this is very unlikely), because they are on same priority level, and task2’s sem.give will not force a context switch.

However, I’m not telling that there isn’t bug in the scheduler, but I think this is not that scenario, where the scheduling fails.

akuros wrote on Wednesday, February 24, 2010:

Probably the code works as designed, but not as intended. task1 and task2 are on the same priority to keep priorities count as low as possible, to lower memory usage for semaphore and speedup scheduling mechanism. As I suspect a semaphore (as a queue) contains priority list of tasks waiting for the semaphore to unblock them on xSemaphoreGive. If it doesn’t work as in example, semaphore could be built on simple volatile portBASE_TYPE, and could have significant lower memory usage.

However, I think it is still a bug, because in xQueueGenericSend there is an intended taskYIELD when we are giving back a semaphore which other task (on priority >= current) is waiting on.

rtel wrote on Wednesday, February 24, 2010:

I’m not really following this.

It would appear to me that when a task gives a semaphore back then a context switch will be performed if there is a task of equal or higher priority that was blocked waiting for the semaphore.  I think this is the expected and wanted behaviour.

If two tasks of equal priority then attempt to take the semaphore either could get it - there are no guarantees there.

Have I missed something?

Regards.

akuros wrote on Wednesday, February 24, 2010:

If two tasks of equal priority then attempt to take the semaphore either could get it - there are no guarantees there.

But when a task is already blocked on semaphore, and semaphore holder gives it back the context switch should be performed to the next waiting for this semaphore. In the example context switch is performed, but context remains in task giving semaphore. So the otther task has no chance to take it.

On xSemaphoreGive expected and wanted behaviour is that the context is switched to the next task waiting on the semaphore. And this task should be able to take it, because it is waiting for it. If there was such guarantee here, it would be really useful.

Regards.