Round Robin Scheduling Questions

toddatm wrote on Friday, April 27, 2012:

I’ve taken the IAR tools port for the Cortex M3 (specifically the STM32F103ZE) and ported it to gcc 4.6.2 (yagarto). I’m just having a look at several of the examples in the “Using FreeRtos…” document and trying to make sure things are working.

One of the thing that puzzles me is I create 2 tasks both with priority 1. All they do is check which task they are and increment 1 of 2 counters. In my debugger I stop the core and clear both counters then let it run awhile and stop it again. 1 counter is twice the other counter. Shouldn’t they be fairly close to the same? I’m just surprised that 1 task appears to be getting twice as much time as the other. My tick time is set to 0.001 (that was the default with the demo).

Another thing that puzzles me is I set a break point on both increment counters statements and it always hits one but not the other. If I set it on one or the other it does hit both, but if I set it on both, it always hits the first one (task 1) and never on task 2. Task 2, in fact, starves.

It looks basically like

if (taskNum == 1)

I am new to this Rtos and forum. I just want to verify things are working and how before I move into design/coding of my application.


richard_damon wrote on Friday, April 27, 2012:

If you code is just

while(1) {
  if(taskNum == 1) {
  } else {

Than your 2:1 difference could just be in the relative efficiencies of the true vs false path in the complied codes assembly. If th fals paths code does more branching, it could slow the program down enough to make this sort of difference.

If you add a taskYield() call in the loop (or a taskDelay(1) ), then you will find the count much closer… Without a statement like one of these, each task will spin in the loop MANY times until the next tick arrives, and then the scheduler will switch to the other, which will run many times itself (relative numbers will be based on how long each loop tasks vs the tick time). If the debugger stops the tick counter, then that would also explain always hitting the first task, it just hasn’t used up its 1 ms of execution time yet. (If it doesn’t, then stepping through a program becomes very difficult, as the next action after a breakpoint will always be go to the tick interrupt.

toddatm wrote on Friday, April 27, 2012:

The difference was actually much greater than 2:1. It’s actually thousands:1. I hadn’t looked. But you’re right. I swapped the test and the increased run time goes to the other counter. I added the taskYield and they stay in sync, and break points on both hit alternately.

I’m not sure I understand your second point, but I will look into whether or not the debugger is stopping the tick count from executing during a breakpoint. I could see that being relevant.

Thanks for the help.

richard_damon wrote on Friday, April 27, 2012:

If you have that big of a difference, I bet that the second path has something extra in it like a Yield or Delay. That would cause the second to run only once per tick, while the first runs continuously.

My point on the debugger stoping the timer is that without a Yield or delay in the loop, task1 will run its loops MANY MANY times (a full millisecond worth) before task2 will get to run. If you stop once per loop, this seem like forever. Many controller now have this sort of hardware built in, so that a debugger breakpoint can stop a lot of the hardware in the system to let you debug things, otherwise you just run into the problem of always being get hit by the interrupt and can’t step past it.

toddatm wrote on Friday, April 27, 2012:

There’s no yield or delay in the second path. Maybe it has something to do with the pipeline and the pre-fetch? This processor is executing from flash and there may be a pre-fetch also on the bus connected to the flash. It appears whichever is first (no branch) is the one that executes more. Whichever is branched executes less. I’m assuming the branch causes not only the pipeline to flush, but also flushes a prefetch buffer between the processor and the flash.

In any case, it seems the scheduler is working.

Still not sure about the breakpoints and the tick timer. I put a breakpoint both in the vApplicationTickHook() function as well as both branches of the increment counters, and oddly enough both tasks hit the break point alternately (this has the taskYIELD() call) but the tick hook never hits it. If I remove the ones in the task (have to remove both), then the breakpoint in the task hook function is reached/stopped.

It’s a good exercise to understand some of the behavior in a simple case before you get several tasks and syncrhonization events going! Best wishes.

richard_damon wrote on Saturday, April 28, 2012:

Hard to see pipeline issues causing “thousands:1” ratios in performance, but only seeing “basically like” code has the possibility of hiding the real cause of the paths being different.

My comments about the breakpoints and tick timer is that many processors now have hardware support for debugging, so that when your program hits the breakpoint and switches to the debugger, much of the hardware can “hold its state” so that time doesn’t advance in the CPU while the debugger is servicing the debug operations. If this did not happen, than after hitting the breakpoint in your code, before you have time to even see that this has happened, the timer tick will expire and set the interrupt flag to go into the timer interrupt. so when you step from the breakpoint, the first thing that happens is you go into the timer interrupt. This makes it very hard to follow the program. This is why they add the hardware support.

What happens then is your loop runs and hits the breakpoint. When you resume, the loop finishes and starts over (since you do NOT have a yield or anything like that, there is nothing that causes a task switch), and gets back to the break point with only microseconds of time having passed. Since your tick time period is a millisecond, this loop will happen on the order of a thousand times before the time finally accumulates enough time to cause a timer tick to happen and the resultant task switch. The number is probably well past your patience point, which is why you never see it happen. Adding the yield cause a task switch letting you see the alternation,.

rtel wrote on Saturday, April 28, 2012:

If you create two tasks, your system will be running at least three tasks, and possibly four depending on whether it is configured to use software timers.

While tasks of equal priority are guaranteed to execute in sequence, there is nothing to guarantee that they will get the same amount of time on the CPU.  This is partly because interrupt execution will take some time out of a time slice, but, as I suspect is happening in your case, a task will often only use part of its time slice, leaving only a partial time slice left for the next task chosen to execute.

Are your tasks running at the idle priority?  If so, then consider this scenario:

+ A tick interrupt occurs that causes a task switch to the idle task (new time slice).

+ The idle task runs, it does not have anything to do, and there are other idle priority tasks waiting to execute, so it yields before it has used up all its time slice.

+ Task 1 executes for whatever fraction of a time slice is remaining, then another tick interrupt occurs.

+ Task 2 executes for an entire time slice, then another tick interrupt occurs.

+ The idle task runs again, and we are back to the start of this execution sequence.

So, task 1 only ever executes for a fraction of a time slice, but task 2 executes for an entire time slice, and you get the behaviour you are observing.

This behaviour can be changed using the configIDLE_SHOULD_YIELD configuration parameter.  You can read about it on this page:


toddatm wrote on Monday, April 30, 2012:

Both tasks are running at priority 1.

I found the source of the problem and it’s nothing to do with the OS. It’s been years since I’ve used the gcc tools, and I’m afraid I had accidentally not placed my global variables in the BSS section. (The globals were getting placed in COMMON and I hadn’t added that section into my bss section.)

The reason there was a thousands:1 ratio was just that the variables were not getting initialized to 0. Once I fixed this, the ratio is very close to 1:1.

Sorry! I keep relearning that I should really investigate thoroughly before asking a question. On the other hand, when you’re new at something it’s hard to know what you know and what you don’t know. (Dick Cheney’s wisdom comes to mind…)

toddatm wrote on Monday, April 30, 2012:

And the issue with the breakpoints is as you pointed out, that the breakpoints get hit many, many times before the other task runs and/or the scheduler gets to run, and so what looked like breakpoints never getting hit, was just my impatience and not understanding what was going on with the debugger and scheduler and the timing of everything.

Thanks for the help.