Hello, I am posting here to potentially spur some conversation and or comments about the functionality present in FreeRTOS.
Some background, I work for a certain professional company. We will call it X. Recently my project lead decided that the best way to get into a preemptive scheduler would be to use FreeRTOS. There was a feature set that I set out with to see if FreeRTOS would be sufficient for our uses. As of now, I have patched the FreeRTOS source for our purposes, and we are currently trying to figure out the licensing so that we can adhere to the license (what source we have to share, versus buying a license and not sharing the source).
We are transitioning from a cooperative scheduler that has no stack context that was designed to run on PIC10’s (perhaps you can guess which RTOS I am referring to) to a preemptive scheduler.
Preemptive to us is not so much the ability to preempt tasks of higher priority. The main feature that we strive for was stack context. In other words, each task having its own stack and therefore could be preempted easily. The scheduler we are transitioning from required any local variables to be static (thus globals) because context was lost every task context switch.
Here are some notes that ‘would have’ made FreeRTOS perfect for us as a company, but required me to patch the source, which in itself is not desirable, because when new versions of FreeRTOS come out, I would have liked to just do a nice ‘copy & paste’.
We require static allocation. The way FreeRTOS is set up, there are no …init functions. There is a generic create for each primitive that one wants to use which handles both memory allocation and initializing the object. In order to both facilitate static and dynamic allocation schemes, you generally implement a create function which allocates memory and then an init function which just initializes the object from a pointer. The init function does not care where the memory comes from.
The “tickless” implementation in FreeRTOS is not truly tickless. It is in fact along the lines of “when i’m in the idle task, I will disable & enable the tick dynamically”. What about the case where there is literally no tick - an on demand system for example? So in other words, the functions that take timeouts would always take either a 0 or a portMAX_DELAY. In this case, all of the timeout code is completely unneeded and just using up space. Our system is a truly tickless system. We once had a tick based system; however, in light of getting even lower power, we moved to an on demand timer engine. Basically our “software timers” are implemented such that we have a list of timers created by apps. These timers are sorted by timeout. Using a hardware timer, we load it with the next timeout for the first timer on the list. Once we get the interrupt for that timer, we remove the timer from the list and get the timeout for the next timer on the list. (This is an overly simplified example, but essentially it means we implement an ‘on demand’ software timer mechanism whereas most systems implement a tick based software timer mechanism). This allows us for example to set a timer timeout for an entire minute and go to sleep for that entire minute without waking for a tick. What I am getting at is since our system is used this way, it would be very nice if there was a way to disable all timer type tick processing in FreeRTOS. For us, that code is dead code and just using precious space. To get around this for now, I am only using timeout delays of 0 and portMAX_DELAY so that either my call to the function returns immediately or my task simply suspends until we get an event. Every kilobyte matters to us as we want this system to potentially. run on extremely ROM limited chips (32, 64, 128K).
I realize that I think the idle task implements termination list functionality is because vTaskDelete also de allocates dynamic memory. The de allocation function could be non deterministic. Therefore in the idle task you do the de allocation instead of directly in the call to vTaskDelete. If this is the case, this works in systems where in fact the pvPortFree or whatever is non deterministic. Our system implements its own heap based on memory pools. Our allocation function (malloc) is O(log2(n)) where our de allocation function (free) is O(1). Since our “free” is O(1), it makes no sense to not de allocate in vTaskDelete since we know exactly how much time is being consumed. I have commented out all of the code that adds a deleted task to a termination list and the code now simply removes the active task from the ready list.
I have seen many other RTOSs we have looked at do this and it annoys the heck out of me. Will somebody please inform me why when you can return from a task, you do not just return to vTaskDelete? or implement a simple wrapper that calls vTaskDelete(NULL) to destroy the current task? I honestly do not know the reasoning for this. In the port.c file (for an ARM Cortex M), you set up the stack when a task is initialized so that it returns to a function. In FreeRTOS this function is taskError or something. Why not just return to the vTaskDelete(void) wrapper that then calls vTaskDelete(NULL)? Logically when I return from a function (which is also a task), I just want it to terminate. I don’t want it to blow up the system, which it currently does. Likewise, in all my tasks that do return I have to make a call to vTaskDelete. Why when it is a single line code change in port.c to make it return to the wrapper? Do you see a win32 console app or any console app on any system blowing up the system after IT returns?
Requiring the first include whenever I want to use any RTOS primitive (tasks, queues, whatever) to be FreeRTOS.h is not good design. In fact, it’s poor naming. If I include FreeRTOS.h, I expect that to include the headers for all the primitives in FreeRTOS. I should not have to include individual files for everything I want to use. Requiring an ordered include as a hard requirement (FreeRTOS.h has to be first) is bad by design.
This goes back to static allocation. I personally think (and many others have agreed with me on this internally) that defining structures and macros inside c files is insane. I understand that it seems like it was done so that you can “hide” the internal implementation of those structures to where they are used. However it has 2 bad side effects The first is that if I want implement static allocation mechanisms (which I have), I have to do it inside the file requiring that implementation. This means that now I have a modification to the FreeRTOS distributed source code. Even though my functions are sectioned off to the bottom of the source and I know exactly where they are, it requires many extra copy / paste steps when we go to a new version. Modifying the base source code of the distribution immediately made me think that I now have to share those modifications because of the license. The second issue is that the actual static allocation implementation becomes very hard. If these structure definitions were in a header file, then I could just include the header file and do a sizeof command to statically allocate the structure somewhere. As it is, the way we have done it, we had to actually execute code to do sizeof commands inside the FreeRTOS source to figure out the sizes of those structures. Once we have the sizes of those structures, we can just allocate buffers of that size representing the structures. My static allocation functions simply take uint8_t* buffers and cast them to the appropriate FreeRTOS structure pointer or handle.
Subsequently, I could have implemented all of my auxiliary functions in seperate source code files outside of the FreeRTOS distributed files, thus decoupling them from the distribution.
Now I realize that I think this was done for data hiding ability, but in doing so it has some bad consequences. Additionally, FreeRTOS is open source… I have access to the source code already and can dig and poke around to figure out what I need. What are you hiding? Why make it harder by doing data hiding this way? In an environment where you do not want to distribute headers that expose such structures, such as a library distribution, there are better ways to structure the code. For libraries, you distribute public API files. Then for your own state of mind, you define the structures / macros in private header files that do not get distributed. If FreeRTOS was distributed in such a way, then for the sake of your development team at least… define those structures in (private) header files. This immediately made me think of a lot of code from other companies where you see #include somefile.c in another file. Ask yourself, do you ever want to #include c files in code - multiple definition hell.
For our code base, we do not use FreeRTOS calls directly, they are wrapped under another layer. This allows us to make generic wrappers that need to be implemented by any scheduler we use. Today we use FreeRTOS, but tomorrow we could use TNKernel (or whatever). All that is needed to hook into the rest of our system is for the RTOS distribution layer to implement our wrapper functions. On that note, there is no way in heck that I am putting RTOS specific FromISR functions in any portion of our code base just because well… FreeRTOS decided to do something different than any other RTOS we have looked at. FreeRTOS is the only kernel that I have seen that requires the usage of FromISR functions. Feel free to point out other kernels that do this. After looking at how they are actually used in the FreeRTOS source, I realize ‘why’ they are there, but I also don’t understand why we cannot find FromISR functions in other kernels - they are not required. On that point, especially since we intend to use FreeRTOS in a purely event driven model (no software timers or timeouts from FreeRTOS (true tickless)), I am not sure if we really require the FromISR functions anyways.
FreeRTOS’s message queue implementation. Oh… the message queues. Where do I begin. Let me give you the thousand foot view of where I am coming from with these. I have a program in which one task sends messages to a message queue and another one reads from that same queue. Now tell me, if I ever run into a situation where I am sending more messages to the second task than my second task can process, what logically is your first train of thought? Mine is this: “Wow, so I am putting more messages in my queue than can be processed by this secondary task! I definitely have a bigger issue, and this needs to get fixed.” Let me try and paraphrase FreeRTOS’s logical train of thought: “Hmm, well, let’s hide that particular case from the user and implement 2 options to let them shoot themselves in the foot. Option number 1: Let’s make it so we can arbitrarily overwrite the last message in the queue! Option number 2: Let’s implement a timeout mechanism that allows the issue to be completely hidden and never seen again. If I just send with a timeout of 2 seconds, they should never see this issue. That will be enough time to catch up.” Well, the point is, with a properly designed system, you should never be pushing more messages to a queue than can be processed in a timely manner. FreeRTOS allows the user to shoot themselves in the foot by completely hiding this issue under the hood. My mailbox is full man! That means you can’t send me any more letters. Jeez, I wish that was the case when some of the letters that have to be sent were bills. Wouldn’t it be awesome if an important letter came in that was a bill couldn’t fit in my mailbox so what actually happened was that it “got lost”? I’m sure the bill collector would understand. But let me tell you, next time that mailbox needs its scheduler, I will tell them to use FreeRTOS.
I realize that I am being harsh here, but I am looking at FreeRTOS as a professional developer. I think there is major room for improvement in the areas that I am talking about. You might have thought that FreeRTOS covered 99% of the use cases as it was; however, we just hit the rest of the 1% of use cases it was not intended for. I apologize for not stating which company I work for; however, I do wish to try and remain ‘somewhat’ anonymous right now. I think that another “tick” no pun intended added to the companies using FreeRTOS from my company would be awesome, which is why I am striving to express these areas for improvement.
Also, if any of the things I have stated above have their reasons of why they were done rooted in the MISRA or any other sort of compliance requirement, Feel free to state that. I will publicly admit that I personally do not have much (if any) experience in compliance requirements.