Forum Index
Task switching problem example #2
Goto page 1, 2
 
Author Message
stevech



Joined: 23 Feb 2006

Posted: 10 November 2006, 5:07 AM    Post subject: Task switching problem example #2

The code, below, runs OK as is on a ZX24.
If, in releaseTaskStackMemory(), you uncommment the sleep() and comment out the delayUntilClockTick(), it doesn't run correctly - the task sequencing stops after one second. During that second, hundreds of correct task switches with system.alloc() and system.free() happened.

I'm trying to determine the "right" method and timing to free the exiting task's memory.

Code:
option targetCPU ZX24

dim task1Stack(100) as byte
dim taskno as byte
dim switches as unsignedInteger

Sub Main()
   dim x as byte
   
   switches = 0
   console.writeline("Main START")
   sleep(1.0)
   calltask task1(0), System.Alloc(sizeof(task1stack))
   x = -1
   do
      sleep(1.0)
      console.writeline(cstr(switches))
      switches = 0
   loop
End Sub

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
sub task1(byval priorTaskSP as unsignedInteger)
   call releaseTaskStackMemory(priorTaskSP)
   taskno = 1
   calltask task2(Register.TaskCurrent), System.Alloc(sizeof(task1stack))
end sub ' implied exit task here
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
sub task2(byval priorTaskSP as unsignedInteger)
   call releaseTaskStackMemory(priorTaskSP)
   taskno = 2
   calltask task3(Register.TaskCurrent), System.Alloc(sizeof(task1stack))
end sub ' implied exit task here
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
sub task3(byval priorTaskSP as unsignedInteger)
   call releaseTaskStackMemory(priorTaskSP)
   taskno = 3
   calltask task1(Register.TaskCurrent), System.Alloc(sizeof(task1stack))
end sub ' implied exit task here
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
sub releaseTaskStackMemory(byval addr as unsignedInteger)
   if (addr <> 0) then
      'sleep(0)
      call delayUntilClockTick()
      system.free(addr)
   end if
   switches = switches + 1
   'console.write("T" & cstr(taskno))
end sub
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 10 November 2006, 5:20 AM    Post subject: Re: Task switching problem example #2

stevech wrote:
I'm trying to determine the "right" method and timing to free the exiting task's memory.

You might try the code below. The idea is that it waits until the task that owns the stack is halted before freeing the memory.

Code:
sub releaseTaskStackMemory(byval addr as unsignedInteger)
   if (addr <> 0) then
      Do While (StatusTask(CByteArray(addr)) <> TaskHalted)
         sleep(1)
      Loop
      system.free(addr)
   end if
   ...
end sub


Also, it would be advisable to check the return value of System.Alloc() rather than trusting that the call will be successful. Perhaps you omitted that as a simplification for the sake of clarity.

Code:
Dim tsAddr as UnsignedInteger
tsAddr = System.Alloc(sizeof(task1stack))
If (tsAddr <> 0) Then
  calltask task1(0), tsAddr
End If
Back to top
stevech



Joined: 23 Feb 2006

Posted: 10 November 2006, 5:35 AM    Post subject:

Thanks Don...

maybe using delayUntilClockTick() instead of sleep(1) would save a bit of time?

yes- my sample posted here omitted error checking for simplicity.
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 10 November 2006, 6:26 AM    Post subject:

stevech wrote:
maybe using delayUntilClockTick() instead of sleep(1) would save a bit of time?
The internal implementation of DelayUntilClockTick() is precisely Sleep(1). The former is slightly more efficient in time and space due to not having to push the 16-bit parameter onto the stack. Other than that, they are identical.
Back to top
stevech



Joined: 23 Feb 2006

Posted: 13 November 2006, 4:47 AM    Post subject:

I found one additional oddity. Below, note the delayUnTilClockTick() just after the do-loop. If that is omitted, the task switching shown in the code above fails, but only with the addition of one more task that is in a do-loop with no yields- task chaining quits.

I also put a debug.print whenever StatusTask() returns other than TaskHalted: it never happened.

Well, this seems to be working, but my confidence is waning.

Code:
sub releaseTaskStackMemory(byval addr as unsignedInteger)
   if (addr <> 0) then
      Do While (StatusTask(CByteArray(addr)) <> TaskHalted)
         call delayUntilClockTick()
      Loop
      call delayUntilClockTick()
      system.free(addr)
   end if
end sub


Don't know if this is a clue: without the added task that never yields, there are 512 task switches per second. With that task looping, there are 256 task switches per second.

(BTW: not bad for BASIC on an itty bitty micro, eh?)
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 13 November 2006, 19:56 PM    Post subject:

stevech wrote:
I found one additional oddity. Below, note the delayUnTilClockTick() just after the do-loop. If that is omitted, the task switching shown in the code above fails, but only with the addition of one more task that is in a do-loop with no yields- task chaining quits.

I have determined the cause of the problem that you described. It turns out that a halted task's TaskControlBlock does not get removed from the task list until the "next task" selector gets back around to the halted task. This circumstance requires a bit more code than I had suggested to determine when it is permissible to dispose of a task stack.

The code below first confirms that the TaskControlBlock is no longer in the task list before freeing the TaskControlBlock memory.
Code:

Private Structure TaskControlBlock
   Dim status as Byte
   Dim sleepTime as UnsignedInteger
   Dim nextTask as UnsignedInteger
   Dim IP as UnsignedInteger
   Dim BP as UnsignedInteger
   Dim SP as UnsignedInteger
   Dim flags as Byte
   Dim stackEnd as UnsignedInteger
End Structure

Sub releaseTaskStackMemory(ByVal addr as UnsignedInteger)
   If (addr <> 0) Then
      Do
         ' make sure the task stack has been removed from the task list
         Dim tcbAddr as UnsignedInteger
         Dim tcb as TaskControlBlock Based tcbAddr
         Dim isPresent as Boolean
         isPresent = False
         tcbAddr = Register.TaskCurrent
         Do
            If (tcbAddr = addr) Then
               isPresent = True
               Exit Do
            End If
            tcbAddr = tcb.nextTask
         Loop Until tcbAddr = Register.TaskCurrent
         If (isPresent) Then
            Call DelayUntilClockTick()
         Else
            Exit Do
         End If
      Loop
      System.Free(addr)
   End If
End Sub

This could be simplified if there were a function IsValidTask().
Back to top
stevech



Joined: 23 Feb 2006

Posted: 13 November 2006, 21:27 PM    Post subject:

thanks much, Don.
I get the idea - continually walk the linked list of tasks until the one of interest is absent from the list, with a delay if it is present. (As I recall, the end of the link list points to the head of the list rather than having a null pointer.)

To avoid the overhead of walking the list, would the code I have in the post, above, assuredly work in all cases? I.e., is there a way to sleep a bit so other tasks can run (ensure that "NextTask selector" runs) instead of doing this loop, since this loop doesn't do any useful work for the application?
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 13 November 2006, 22:00 PM    Post subject:

stevech wrote:
To avoid the overhead of walking the list, would the code I have in the post, above, assuredly work in all cases? I.e., is there a way to sleep a bit so other tasks can run (ensure that "NextTask selector" runs) instead of doing this loop, since this loop doesn't do any useful work for the application?

I do not think that it will reliably work in all cases. I didn't fully describe the situation. A halted task is not removed from the list until it comes up for execution in the task rotation. When this situation is detected, the halted task is removed from the list and the search for a "ready to run" task is resumed with the immediately following task.

The technique that you suggested might work reliably if a call to DelayUntilClockTick() were guaranteed to cause every task on the list to be given an opportunity to run before control returned to the "freeing" task. As it is, however, the task list is not always visited sequentially. WaitForInterrupt() and WaitForInterval() may cause non-sequential task execution. There may be other circumstances that cause non-sequential execution that don't immediately come to mind.
Back to top
stevech



Joined: 23 Feb 2006

Posted: 13 November 2006, 23:03 PM    Post subject:

I'd vote for a VM change so that the TCB is released when the task exits, while in the VM anyway. This, so as to permit its resources (stack, and maybe other), to assuredly be released at that time, rather than being deferred to a difficult to determine future time. Perhaps the VM defers because there's work to do to reclaim the RAM for its locals.
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 14 November 2006, 0:54 AM    Post subject:

stevech wrote:
I'd vote for a VM change so that the TCB is released when the task exits, while in the VM anyway. [...] Perhaps the VM defers because there's work to do to reclaim the RAM for its locals.

I presume you're suggesting to remove the TCB from the task list (as opposed to freeing the memory). I will look into doing this. Unfortunately, it is not as simple to do as it might appear.
Back to top
stevech



Joined: 23 Feb 2006

Posted: 14 November 2006, 3:32 AM    Post subject:

Yes.
This isn't a high priority issue; I'll use the list-scanning loop for now.

I do worry about having the ZBasic TCB structure in my user program. When that structure's design changes, I have no way to know I need to recode and recompile.

steve
Back to top
stevech



Joined: 23 Feb 2006

Posted: 14 November 2006, 4:11 AM    Post subject:

Here's your code, reorganized a bit... and tested in my target program.

Code:
Sub releaseTaskStackMemory(ByVal addr as UnsignedInteger)
    Dim tcbAddr as UnsignedInteger
    Dim tcb as TaskControlBlock Based tcbAddr

   if (addr = 0) then ' might get this passed, ignore it
      exit sub
   end if
   
   tcbAddr = Register.TaskCurrent ' start in list of TCBs
   Do ' loop until the task's stack has been removed from the VM's task list
      tcbAddr = tcb.nextTask   ' visit each TCB (skipping my own task's TCB)
      If (tcbAddr = addr) Then ' is this the TCB for the exiting task?
         Call DelayUntilClockTick() ' yes, task still active, so wait, then ...
         tcbAddr = Register.TaskCurrent ' start scan of list again
      End If
   Loop Until (tcbAddr = Register.TaskCurrent) ' end of list, didn't find the task's TCB
   
   System.Free(addr) ' free the RAM
End Sub

Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 14 November 2006, 4:57 AM    Post subject:

stevech wrote:
This isn't a high priority issue; I'll use the list-scanning loop for now.

I've figured out a clean way to have the task status not change to TaskHalted until the TCB is removed from the task list. With the new VM you can code it as follows:
Code:
   If (addr <> 0) Then
      Do While StatusTask(CByteArray(addr)) <> TaskHalted
         Call DelayUntilClockTick()
      Loop
      System.Free(addr)
   End If

stevech wrote:
I do worry about having the ZBasic TCB structure in my user program. When that structure's design changes, I have no way to know I need to recode and recompile.

Since we have published the structure, our intent is to make only backward-compatible changes. Of course, it is possible, though unlikely, that a situation might arise that would require a change that isn't backward compatible.
Back to top
stevech



Joined: 23 Feb 2006

Posted: 14 November 2006, 5:15 AM    Post subject:

dkinzer wrote:
I've figured out a clean way to have the task status not change to TaskHalted until the TCB is removed from the task list. With the new VM you can code it as follows:
Code:
   If (addr <> 0) Then
      Do While StatusTask(CByteArray(addr)) <> TaskHalted
         Call DelayUntilClockTick()
      Loop
      System.Free(addr)
   End If



When you say "new VM" do you mean v1.4 (which is what I have) or a future version?

That loop, above, is what I had already used, but didn't work reliably.
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 14 November 2006, 5:27 AM    Post subject:

stevech wrote:
When you say "new VM" do you mean v1.4 (which is what I have) or a future version?

Sorry. I meant new as in an as-yet unpublished version.
Back to top
Display posts from previous:   
Page 1 of 2

 



ZBasic Microcontrollers Home
All content Copyright © 2005, 2006, 2007, 2008, 2009, 2010 Elba Corp. All Rights Reserved.
Opinions expressed in posts are those of the author and not necessarily those of Elba Corp.
Powered by phpBB © 2001, 2005 phpBB Group