This video presentation is the eighteenth installment of the Essentials of MQX RTOS Application Development training course. In this session, you will be introduced to Priority Inversion.
This training was created by Embedded Access Inc., a Freescale sponsored training provider and proven partner.
|Session 18 Course Line||Lab Outline|
First, watch the video for Session 18: Priority Inversion.
Then, follow through with the interactive lab assignment below.
SESSION 18: LAB ASSIGNMENT
You'll recall from the video that tasks of 3 different priority levels are required for a priority inversion scenario to occur. All of our tasks are quite short, as they should be, so to create the priority inversion we'll be artificially extending the time that a task takes to execute in order to create the symptoms of an inversion.If you aren't fully familiar with the conditions to create a priority inversion then it is suggested that you first watch the video for this session before continuing with the lab.
The objective of this lab is to create a priority inversion and to verify that it is happening. Then, the priority protection features will be added to the Mutex being used and the results will be verified.
CREATING A FUNCTION TO WASTE A REQUESTED AMOUNT OF TIME
- Open the Task Template List in main.c and observe the different priority levels used. Since we need 3 tasks at different priority levels the highest priority task, the Health Task, is a good candidate to use, as is the lowest priority task, the UI Task. We're going to use the Input Task as our middle priority task that we'll modify to spend an inordinate amount of time running without blocking.
- To get the Input Task to run for an extended period of time we'll need a utility function that takes a long time to execute. Since this is throw away code we'll put it in main.c. Create a function called 'waste_time()' that accepts a 32bit number that represents the number of milliseconds the function is to consume.
- Declare a time start and time end variable of type MQX_TICK_STRUCT, a Boolean to indicate an overflow, and a uint32_t that will store the time delta.
- The first thing our function needs to do is to get the current time using the _time_get_ticks() function and then save it as our start time.
- In a DO loop, use the _time_get_ticks() function to save the current time into the end time variable. Then use the _time_diff_milliseonds() function to compare the two times, store an overflow in the overflow variable, and save the result in the time delta variable. The DO loop should continue while the time delta is less than the passed in number of milliseconds to consume.
- Since the Input Task will need to call the waste_time() function make it an extern.
UPDATING TASKS TO CONSUME TIME
- For the purposes of our lab we'll pick on the ADC conversion code in the Input Task. When the ADC_EVENT bit is set and the internal and external ADCs are being read, add in a call to the new waste_time() function to consume 250 milliseconds.
- Also, to create the priority inversion we'll need the UI Task to consume a bit of extra time when the health logs are being read and printed. Update the Shell_log() function so that each time it goes through the while loop an extra 100 milliseconds are consumed using the waste_time() function.
OBSERVING THE RESULTS
- Compile and run your code.
- Use the shell to print out a list of logs. If you do so reasonably soon after the system starts you may not see any issues with the log, though the user interface should appear to run slower. However, if you let the system run for 30 or more seconds before requesting a printout of the logs it should create the priority inversion while the Input Task is printing out the log. Once the log has stopped printing, wait 5 seconds or so and then print the log again. Each printout has a sequence number and the Health Records are logged every second, so before the priority inversion occurs the sequence number of each entry should match the number of seconds in the time stamp for the same entry (assuming that you haven't updated the time), or the time stamp may be one second less since the first entry could have been logged when only a fraction of a second has gone by. But if the priority inversion occurred during the first printing session of the logs, the Health Task will occasionally not be able to log an entry (even though it is the highest priority task) because it cannot get access to the mutex. See the Whiteboard for a diagram. Yes it does require you to create a specific scenario to generate a priority inversion, but this is similar to diagnosing any elusive, intermittent bug. It can be difficult to pin down what sequence of events are required to create a priority inversion, and it can be equally hard to understand what is happening, so better to avoid them in the first place!
ADDING PRIORITY INVERSION PROTECTION
- To prevent this scenario from occurring, Priority Inversion Protection needs to be added and this is done in a Mutex Attribute Structure which is used to configure a Mutex when it is initialized. In the Health Task create a variable of type MUTEX_ATTR_STRUCT and a unit32_t that will store our priority level for the Mutex.
- Just before the Mutex is initialized in the Health Task, use the _mutatr_init() function to put the default attributes into the attributes structure.
- Use the _mutatr_set_sched_protocol() function to change the scheduling protocol to Priority Protection using the MUTEX_PRIO_PROTECT define.
- The priority for the Mutex needs to be set and to prevent a priority inversion from occurring the priority of the UI Task (who is locking the Mutex) needs to become higher than the priority of the Input Task (who is running for a long time and preventing the UI Task from running). In general, the priority of the Mutex should be set to the priority level of the task that you want to run (ie the Health Task), or higher. Use the _task_get_priority() function to find out the priority of the Health Task and save it into our priority level variable. Since this code is running in the Health Task, you are really wanting to get the priority of the calling task. For this you can use MQX_NULL_TASK_ID as the task id in the _task_get_priority() function to mean that you want the priority level of the calling task.
- Use the _mutatr_set_priority_ceiling() function to set the priority level in the attributes structure to the priority level returned in the previous step.
- Use the _mutatr_set_wait_protocol() function to set the queuing mechanism that this Mutex will use to MUTEX_PRIORITY_QUEUING.
- Finally, update the _mutex_init() function to use the attributes we just set in lieu of the default attributes.
CONFIRMING THE RESULTS
- Compile and run your code. After the code has been running for a good while check the logs and confirm that the log entries did not fall behind like they did before.
Need more help? The full source code for this lab can be found in the 'Lab Source Code' folder here.