Pumpkin, Inc.

Pumpkin User Forums

Tasks are running twice

If you can't make Salvo do what you want it to do, post it here.

Tasks are running twice

Postby Colin Stocks » Tue Oct 23, 2001 12:31 am

The following code is a minimal version of a program (running on PICC 7.87pl2, MPLAB V5.4, for a 16F877 target) that illustrates operation I don't understand:

code:
#include "salvo.h"

#define FOREVER for(;;)
#define PrintingTaskTCB OSTCBP(1)
#define DisplayingTaskTCB OSTCBP(2)
#define PrintedCharSemaphore OSECBP(1)

_OSLabel(PrintingTaskYield)
_OSLabel(DisplayingTaskYield)

void PrintingTask(void);
void DisplayingTask(void);

void OSIdleTaskHook(void) {}

unsigned char PrintedChar = '0';

void main(void)

{
PORTA = 0b00001000; PORTB = 0b00000000; PORTC = 0b01000001; PORTD = 0b00000000; PORTE = 0b00000111;

TRISA = 0b11010000; TRISB = 0b00110100; TRISC = 0b01110000; TRISD = 0b00000000; TRISE = 0b00000000;

OSInit();

OSCreateTask(DisplayingTask, DisplayingTaskTCB, 8);
OSCreateTask(PrintingTask, PrintingTaskTCB, 8);
OSCreateSem(PrintedCharSemaphore, 0);

FOREVER {
OSSched();
}
}

void PrintingTask(void)

{
FOREVER {
OSSignalSem(PrintedCharSemaphore);
if (PrintedChar == '0') PrintedChar = '1';
else if (PrintedChar == '1') PrintedChar = '2';
else PrintedChar = '0';
OS_Yield(PrintingTaskYield);
}
}

void DisplayingTask(void)

{
static unsigned char TestChar;

FOREVER {
OS_WaitSem(PrintedCharSemaphore,DisplayingTaskYield);
TestChar = PrintedChar;
}
}


I had anticipated that PrintingTask would run once and set the semaphore, then DisplayingTask would run once and clear it, and so on. But what actually happens is that PrintingTask runs TWICE, then DisplayingTask runs TWICE, and so on. Why?

(The contents of these illustrative functions are only there so that I can see things happen when I run the code).

Regards,

Colin

[This message has been edited by Salvo Tech Support (edited October 23, 2001).]

Colin Stocks
 
Posts: 2
Joined: Mon Oct 22, 2001 11:00 pm
Location: Crowborough, East Sussex, UK

Re: Tasks are running twice

Postby aek » Tue Oct 23, 2001 3:21 am

Hi Colin.

I created a project with your application, and am running it on a 16F877 via a MPLAB-SIM, using the freeware library sfp42Cab.lib.

You have to be a bit careful as to where you put your breakpoints. For example, if I place them immediately after the OSSignalBinSem() and OS_WaitSem(), I get this sequence:

code:
PrintingTask()
PrintingTask()
DisplayingTask()
DisplayingTask()
PrintingTask()
PrintingTask()
etc.

However, if I place a single breakpoint on OSSched(), and then step after the breakpoint, I get this sequence:

code:
DisplayingTask()
PrintingTask()
PrintingTask()
DisplayingTask()
DisplayingTask()
PrintingTask()
PrintingTask()
etc.

The latter more accurately reflects how the scheduler is dispatching tasks.

Now to address your direct question. This little program is very interesting -- it took me a while to figure out the "why" of its behavior. I played around with it, changing the order in which you create tasks (that has an affect on which task runs first) and also used binary semaphores instead of semaphores.

Here's what's happening: You are, in effect, desiring a tight coupling (synchronization) between one task and another. What's upsetting this is the subtle effect of round-robin scheduling (the tasks have the same priority). If you raise DisplayingTask()'s priority, you'll find that it's now a ONCE ... ONCE ... sequence of events.

By having equal priorities, what happens (due to round-robin scheduling) is that the situation arises where PrintingTask() signals the semaphore twice before DisplayingTask() can wait the semaphore, which it will then do twice in a row. Another way to look at it is that PrintingTask() is always eligible, whereas DisplayingTask() may be eligible or waiting. It's pretty much a 50-50% split in this simple program, and so it's often the case that PrintingTask() is the only task eligible, and so it "racks up" the semaphore count. DisplayingTask() always catches up to it, however, and so we see either ONCE ... ONCE ... ONCE or TWICE ... TWICE ... TWICE, depending on task priorities.

This program illustrates how the RTOS approach is a fundamentally loosely-coupled one. DisplayingTask() will run whenever the semaphore is signaled, and PrintingTask() will run all the time. There is no mention of synchronization. If you need a tighter coupling between tasks, you can use two semaphores (or binary semaphores) where each task waits for the other to signal before proceeding (Labrosse calls this a bilateral rendevous).

Note also that Salvo is somewhat unusual in allowing tasks to have equal priorities -- this program's behavior is a direct result of this feature. In general (and perhaps we should stress this in the manual) tasks should have unique priorities in an event-driven system. Since they're round-robining at the same priority level anyway, they ought to be "folded" into a single task for savings in ROM and RAM.

------------------

[This message has been edited by aek (edited October 25, 2001).]

-------
aek
aek
 
Posts: 1888
Joined: Sat Aug 26, 2000 11:00 pm

Re: Tasks are running twice

Postby cstocks » Wed Oct 24, 2001 12:03 am

[QUOTE]Originally posted by aek:
[B]Hi Colin.

Thanks for that! It makes more sense now. One thing though. You mention that by careful location of the break point, you can observe that the operation is:

DisplayingTask()
PrintingTask()
PrintingTask()
DisplayingTask()
PrintingTask()

(ie 2:1:2:1).

But I verified task execution by tracing in the simulator, and the displaying and printing tasks were both executed twice in succession every time.

If this were a real application I would not separate the two tasks; but I had a need for a simple test program to drive a serial printer, and decided to take the opportunity to try out Salvo at the same time, so it is a little contrived. I am encouraged to try something larger now, since I had no compilation problems, and the code generated (from the freeware libraries) is quite compact.

Regards,

Colin (from work this time, hence the different login)

cstocks
 
Posts: 7
Joined: Tue Oct 23, 2001 11:00 pm
Location: Crowborough, East Sussex, UK

Re: Tasks are running twice

Postby aek » Thu Oct 25, 2001 2:28 am

Hi Colin.

There was a typo in my earlier posting (below, now corrected). I meant to write:

code:
DisplayingTask()
PrintingTask()
PrintingTask()
DisplayingTask()
DisplayingTask()
PrintingTask()
PrintingTask()
etc.

i.e. 1:2:2:2:2:...

You should also get this result -- I have one breakpoint on the label PrintingTaskYield and one on DisplayingTaskYield (both are viewable in the Program Memory window).

As an aside, and for others who may be following this thread, MPLAB does not / cannot trace program execution and/or set breakpoints well when the source-level code in question is a macro, like OS_WaitSem(). Therefore it's always best to set these sorts of breakpoints in the Program Memory window, at the assembly-language level. For other statements (e.g. "else PrintedChar = '0';") breakpoints work properly.

------------------

-------
aek
aek
 
Posts: 1888
Joined: Sat Aug 26, 2000 11:00 pm

Re: Tasks are running twice

Postby aek » Thu Oct 25, 2001 4:34 am

I've added a new configuration option to v2.3.0: OSDISABLE_FAST_RESCHEDULING (default is FALSE).

v2.2.0 uses fast rescheduling, i.e. upon its return to the scheduler, the current task is immediately placed into the eligible queue if it's still eligible. This has no deleterious effect on tasks with unique priorities, but it does affect tasks that are round-robining at the priority of the current task. Specifically, tasks that were made eligible (via OSSignalXyz()) by the current task will be enqueued after, and therefore run after, the current task. That's where the 2:2:2 sequence of Colin's program is coming from.

OSDISABLE_FAST_RESCHEDULING changes the way in which the current, eligible task is rescheduled in such a way that round-robin execution goes on like one (e.g. Colin) would expect from the test program, i.e. a task made eligible by the current task will run before the current task runs again. It costs only 8 instructions, but it increases context-switching times considerably -- like +25% -- which shows up mainly when many tasks are eligible.

------------------

-------
aek
aek
 
Posts: 1888
Joined: Sat Aug 26, 2000 11:00 pm


Return to Coding

Who is online

Users browsing this forum: No registered users and 3 guests

cron