Electronics > Microcontrollers

FreeRTOS - pre-emptive or not?

(1/13) > >>

I've been writing a lot of code for this for last 10 months. All working fine. I use the base priority (zero) for all threads, and always yield to RTOS with osDelay(1) when hanging around. It all works great.

Someone else working on another specific part of the project (replacing the buggy as hell STM-supplied PolarSSL with MbedTLS) has spent a lot of time trying to make that work also (again, buggy as hell and as usual loads of posts all over the place from frustrated coders trying to make it work - this seems to be a pattern with most STM libs and especially SSL stuff) and it appears that this code relies on some threads having a higher priority than others, otherwise the thing just hangs. Is this a weird way to write software? If one thread is generating data and another is consuming it, should they not be same priority but using some appropriate mechanism to communicate? I struggle to think of a scenario where different priorities are the only option, and there is a price to pay because the more levels you define the more RAM it uses.

I am not totally new to this. I wrote my own simple RTOS, in assembler, for Z180 and Z280, yonks ago, and it was absolutely solid and was used in loads of fair volume comms products. It facilitated easy development of complicated protocol converters. But I never implemented priority, because all critical stuff was running off interrupts, and if all the code is written properly and yields when hanging around, everything will still run. And comms products should have buffers on I/O which are interrupt driven. Of course I see that sometimes you want different priorities but often this is not needed, and could be "dangerous" because some tasks may never run at all.

So I bought that book on FreeRTOS recommended here recently and been reading through it. It looks like this RTOS does time-slicing so even a hanging thread gets chopped eventually (not sure where the time delay is configured) but only by a same or higher priority thread.

This is the config

--- Code: ---#define configUSE_PREEMPTION                     1
#define configSUPPORT_STATIC_ALLOCATION          1
#define configSUPPORT_DYNAMIC_ALLOCATION         1
#define configUSE_IDLE_HOOK                      0
#define configUSE_TICK_HOOK                      0
#define configCPU_CLOCK_HZ                       ( SystemCoreClock )
#define configTICK_RATE_HZ                       ((TickType_t)1000)
#define configMAX_PRIORITIES                     ( 56 )
#define configMINIMAL_STACK_SIZE                 ((uint16_t)512)
#define configTOTAL_HEAP_SIZE                    ((size_t)48*1024)
#define configMAX_TASK_NAME_LEN                  ( 16 )
#define configUSE_TRACE_FACILITY                 1
#define configUSE_16_BIT_TICKS                   0
#define configUSE_MUTEXES                        1
#define configQUEUE_REGISTRY_SIZE                8
#define configUSE_RECURSIVE_MUTEXES              1
#define configUSE_COUNTING_SEMAPHORES            1
#define configUSE_TRACE_FACILITY 1
#define configAPPLICATION_ALLOCATED_HEAP 1 // to use external RTOS buffer

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES                    0
#define configMAX_CO_ROUTINE_PRIORITIES          ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS                         1
#define configTIMER_TASK_PRIORITY                ( 2 )
#define configTIMER_QUEUE_LENGTH                 10
#define configTIMER_TASK_STACK_DEPTH             256

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet            1
#define INCLUDE_uxTaskPriorityGet           1
#define INCLUDE_vTaskDelete                 1
#define INCLUDE_vTaskCleanUpResources       0
#define INCLUDE_vTaskSuspend                1
#define INCLUDE_vTaskDelayUntil             1
#define INCLUDE_vTaskDelay                  1
#define INCLUDE_xTaskGetSchedulerState      1
#define INCLUDE_xTimerPendFunctionCall      1
#define INCLUDE_xQueueGetMutexHolder        1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_eTaskGetState               1

--- End code ---

capt bullshot:
Looks like we've got some experiences in common, since I've written my own simple (cooperative) embedded OS too, and used it in some commercial products, too.

The way you've done it (yielding to the OS if you've got nothing to do) is the way this just works fine without priorities. With preemptive tasking and priorities, something more flexible and more complicated and more prone to failures (due to the complexity and required knowledge) rose. It also works, if things are done right, but IMO there's more ways to do things wrong with priorities and preemptive tasking.

And yes, tasks don't have to yield to the OS at all if they're idle as they can be preempted by same or higher priority tasks. Anyway, I wouldn't recommend to do so (to not yield)  as it'll always fully load the CPU and have other negative side effects (most important: other tasks could have more time). So yielding and using proper OS supplied inter-task communication (like queues, semaphores, ...) as these will yield implicitely is the correct way to use any RTOS.

Not sure what your question is here, if there is any. FreeRTOS sure is preemptive.

Now properly using priority with preemptive scheduling isn't trivial, but as far as I've seen, it's not specific to FreeRTOS. The latter implements pretty basic priority-based scheduling.

A few questions in my post e.g.

- why is pre-emption by lower-priority threads inhibited (and not configurable afaik)
- what is the time slice interval for threads which don't yield
- is it smart to make use of priorities to get code work at all, and likely scenarios where somebody would do that (it seems to produce impenetrable software)

I know reams have been written on multiuser etc OSs over decades (I remember debates re Windows gradually moving to pre-emptive; it wasn't in 3.1 afaik) but this is more specific.

capt bullshot:

--- Quote from: peter-h on October 08, 2021, 07:26:21 pm ---- why is pre-emption by lower-priority threads inhibited (and not configurable afaik)

--- End quote ---
This is the whole point of assigning priorities to threads. Higher priority tasks cannot be preempted by lower priority tasks. Or the other way round: A higher priority task gets executed as soon as it is required and while it is running, all lower priority tasks must wait.

--- Quote ---- what is the time slice interval for threads which don't yield

--- End quote ---
Depends on the configuration, could be e.g. 1ms. Often generated from a ticker interrupt. With each ticker interrupt the scheduler gets invoked and gives control to another task, depending on priorities. Within the same priority, tasks are scheduled round robin. There are tickless variants of RTOSes, too. These usually calculate the next task switch from all pending tasks and priorities and dynamically program a timer for that next scheduling event.

--- Code: ---configTICK_RATE_HZ                       ((TickType_t)1000)

--- End code ---

--- Quote ---- is it smart to make use of priorities to get code work at all, and likely scenarios where somebody would do that (it seems to produce impenetrable software)

--- End quote ---
No (IMHO). Any task that has to wait for something should yield, either by sleeping or waiting for an event.
You could have a task that does some independent computation all the time, but isn't critical in terms of how long it takes to calculate whatever. This task would never finish or yield at all. You'd assign this task a low priority, so other higher priority tasks that e.g. have to do some data shuffling on I/O events can execute when they're needed. These tasks would wake up on some event and go to sleep or wait for an event when their job is done.
A common example of priorities would be the idle task: It has the lowest priority at all and only gets executed when all other tasks are waiting for something.


[0] Message Index

[#] Next page

There was an error while thanking
Go to full version