-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RTOS: The new wide priority range leads to implementation inefficiencies #1
Comments
The range of priorities can be reduced to more or less the actual number of named priorities in the enum. The approach with configurable number of priorities sounds interesting. However configuring the actual priority values at the project level is a problem when using library binaries which also use RTOS but with different priority definitions. Possible solution is to define only the configurable number of priorities (sub priority bits) at the project level where the actual priority values are defined for all available priorities. Proposal for osPriority:
Each priority available to user code (Low, BelowNormal, Normal, AboveNormal, High, Realtime) is extended with a 3-bit sub priority. Maximum number of priority levels for user code is 48. When RTOS implementation is sensible to the number of priority levels it can implement the actual number of different levels according to user configurable number of sub priorities (1,2,4 or 8). |
in this case why not make the default range the widest available on 1 byte, and let the implementation scale it down if necessary. using the configurable number of priorities algorithm, that would translate to:
which will result in the following:
to me this seems like a fairly uniform distribution. I can also make this the default in CMSIS++, however allowing the user to reduce the scale if no binary libraries are used. |
as for defining all these intermediate levels, I would not do it; instead I would suggest the user to define specific priorities for each specific thread:
|
for completeness, here are the CMSIS++ files that define the priorities:
(I already updated the CMSIS++ priorities to use the full byte range by default) |
while thinking more about this, I discovered another weakness of the current CMSIS RTOS specifications: it can be safely used only to build applications from source code; distributing binary libraries is possible only if the libraries and the final application are built using exactly the same <cmsis_os.h>, otherwise behaviour is undefined. the reason for this is the heavy reliance on macros, to define RTOS implementation. in other words, if you build a library with the RTX customised <cmsis_os.h>, I have little chance to link it with an application built with my own customised <cmsis_os.h>, because I have different definitions for the osXyzDef() macros. so your concern about priorities, although correct in principle, no longer matters since building binary libraries is problematic. |
Would be nice however mixing enums with ints causes compiler and MISRA warnings.
This is the reason for defining all possible levels.
This goes towards having a large number of priority levels which we wanted to avoid in the first place. It also defeats the named levels concept. 6 group priorities with 3-bit sub priority provides 48 levels which seems to be an acceptable compromise. We could consider a 4-bit sub priority with 96 levels (all defined in the enum). Going over 100 levels doesn't make sense.
Correct. There is only one definition for osPriority values (with all levels defined). RTOS implementation however can be configured to use less sub levels by accepting all values but scaling them internally. For example when RTOS is configured to use only 2-bit sub priority than priorities Normal and Normal+1 would be effectively the same. The user needs to know how many sub levels the library requires and also the application in order to configure the number of sub levels for RTOS implementation which supports this. This should help specific RTOS implementations which have configurable number of priorities. Typically the number of priorities is fixed. The recommendation for the user would be to still use group priorities and the sub priorities only if really needed. Libraries typically won't use threads or only a few and the expectation would anyway be that they only use group priorities. Note: osPriorityError is not a real thread priority and the value can be outside the real priority range. It is only used as an error indicator for GetPriority function. |
yes, I know, but in CMSIS++ I solved the problem differently, I define for reference, the definitions are in cmsis-plus/rtos/os-c-decls.h. as for using priorities in libraries, if you want it to be portable, you need to pass the value via a run-time parameter.
I'm not sure you got the point related to the problems of distributing binaries. hard-coding priorities is a minor nuisance compared to hardcoding the content of the osThreadDef(), osMutexDef(), etc definition structures, which, according to the documentation, "CAN BE CHANGED" in different CMSIS RTOS ports. in other words, if you use a MessageQueue in your library, and you build it with the RTX specific <cmsis_os.h>, you expect the RTX to dynamically allocate the queue. If I have my own <cmsis_os.h> where I want to use statically allocated queues, I have different macros to define the queues, and my queue creation function expects exactly my definitions, if you call it with the RTX definitions it'll probably crash. the conclusion is that any function that creates objects (osThreadCreate(), osTimerCreate(), osMutexCreate(), osSemaphoreCreate(), etc) cannot be used in a library, because it depends on a non-portable, implementation dependent macro. |
Yes, that is an issue and we will look into it, also in the light of dynamic objects.
Not completely correct. "osObjectCreate" functions can be used since they only use a pointer to the definition structure. However the "osObjectDef" macros cannot be used in a binary library but rather in source module. |
yes, sure, they can be used, but the result is unpredictable. otherwise what would be the point of passing a pointer to a structure that has a different definition, coming from a different header? |
Not if the library consists of a binary (with RTOS functions) and a source module (with osObjectDef's).
Application and the library source is compiled with the same definitions coming from the same header. |
well, in this case all discussions of priorities definitions (and macro definitions, and generally |
Let me clarify my previous post by an example: Library consist of: A. Modules which use CMSIS RTOS functions but no osObjectDef macros B. Module with osObjectDef macros Library is shipped with a prebuilt binary and a source module with osObjectDef macros. User still decides which RTOS implementation to choose. |
this should probably work, although I would not consider it very elegant. personally I would move everything non-portable out of the library and into the source file (object creation, use of priorities, etc) as for the C++ API, the modern trend being towards constexpr, inlines and inline templates, to give the compiler the best chance to optimise, I would be very cautious to recommend binary libraries. but I can understand that there is life outside the open source world too. |
Initial CMSIS-RTOS API Documentation
Existing priorities available to user (Low, BelowNormal, Normal, AboveNormal, High, Realtime) are extended with a 3-bit sub priority. Priorities are encoded in a way that can be scaled down according to user configurable number of sub priorities (1,2,4 or 8). |
In CMSIS RTOS 1.x, priorities ranged from -3 to +3, which sometimes was a bit to little.
In the new proposal, the range was extended to -48 to +48, which is a bit too much.
The problem is not the size of the variable required to store the value, but the implications in the scheduler implementation.
Although the specifications of the CMSIS RTOS should not be made with specific implementations in mind, they should also not prevent common implementations.
The problem is related to the implementation of the READY list.
For very small systems, with only 2-3 threads, it is not a problem to keep a single list, with all threads ordered by priority.
But for medium to large size applications iterating through this list starts to matter, and a common solution is to use a two layer structure, with threads grouped by priorities.
For fast access, the first layer is usually implemented as an array, with each element pointing to a list of threads.
Mandating for almost 100 priorities means this array should have 100 elements, each with two pointers and a counter, which means 4*3 = 12 bytes, with a total of more than 1 KB, spent only on this array.
The alternative to the array is a list of priorities, but if there are many priorities used at the same time, iterating throughout this list is again expensive in time.
So, mandating for a large number of priorities is either expensive in memory or in time.
My approach to this problem was a flexible one, allowing the user to decide how many levels are really needed for each application, and to decide on memory size vs speed compromises.
As such, the definitions use a custom scaler value, defined in the project configuration.
As the comment says, the basic range has 16 priorities, with 2-14 available to the user, quite enough for the majority of applications.
Increasing the scaler to 1 extends the range to 32 priorities, 2 gives 64 priorities, 3 gives 128 priorities, and so on.
The scheduler has access to the scaler definition and can statically or dynamically allocate the priorities array or any other memory structures it needs.
My suggestion is to analyse such a scheme for CMSIS RTOS v2.x.
Regards,
Liviu
The text was updated successfully, but these errors were encountered: