About
Docs
Files
Support
Index

Essentials
AutoDocs
Library functions
Cooperate
Process mastering
Debug
Own debugging kit
FGE
Smart modeid pick
Loader
Shared lib. loader
Macros
Helper macros
Structs
All the structures
Logs
Library changelogs
Tools
Progs baked inside



C
O
O
P
E
R
A
T
E

 qdev_cooperate.txt
--------------------


                                 1. INTRO
                          =======================

This file is totally devoted to  'mem_cooperate()'  function, so that those
of  you who would like to know how it works and thus when it is  OK  to use
it  can  now get  some  knowledge. First  of, let  me expain  what does the 
-cooperation- mean in the context of this magic routine. To cooperate is to
share  your  CPU time, so others can grab it. Analog to Prometheus who gave
fire to the people ;-) . By calling this function you  basically give  away
CPU time you would utilise in some other way, in a loop possibly.

Suppose that  you desperately need to check for data on an external device,
but that device does not generate any interrupts, nor notifies using signal
and you cannot help it. What would you do  to keep the OS reponsive and yet
the device I/O  as fast as possible? Here is the  example that depicts some
fictious memory mapped device communication and how to deal with checks:

/*
 * Fictious Memory Mapped Device I/O example using 'mem_cooperate()'.
*/
#include <qdev.h>

#define CDI_DEVCTRLREG   0xFA001000         /* Device control register   */
#define CDI_DEVDATAREG   0xFA002000         /* Device data register      */
#define CDI_DEVSIZEREG   0xFA003000         /* Device length register    */

#define CDI_FDATAREADY   0x80000000         /* Data received flag        */
#define CDI_FDATASAVED   0x00000008         /* Data request saved flag   */

LONG checkdevio(void)
{
  LONG *addr = (LONG *)CDI_DEVCTRLREG;


  if (*addr & CDI_DATAREADY)
  {
    *addr &= ~CDI_DATAREADY;                /* We have to clear this     */

    addr = (LONG *)CDI_DEVSIZEREG;

    return *addr;
  }

  return 0;
}

LONG senddevio(LONG ptr, LONG size)
{
  LONG *addr = (LONG *)CDI_DEVCTRLREG;


  if (!(*addr & CDI_FDATASAVED))
  {
    addr = (LONG *)CDI_DEVDATAREG;

    *addr = ptr;

    addr = (LONG *)CDI_DEVSIZEREG;

    *addr = size;

    addr = (LONG *)CDI_DEVCTRLREG;

    *addr |= CDI_FDATASAVED;                /* Device will clear this    */

    return 1;
  }

  return 0;
}

int main(void)
{
  UBYTE buf[32]
  LONG fill;


  if (senddevio((LONG)buf, sizeof(buf)))    /* Try to obtain some data   */
  {
    while (!(fill = checkdevio()))          /* Busy-loop reply check     */
    {
      mem_cooperate(0, SIGF_SINGLE);        /* Lets go to quick sleep    */
    }

    //...                                   /* Request was satisfied     */
  }

  return 0;
}


OK. So what is actually happening in this example? We want to get some data
off  the device, so we send device request that takes address of the buffer
and  its size. We  then enter busy-loop  in which we  check if the data did
appear in the buffer. Now,  if that would  be just the loop  the  OS  would
become  rather sluggish cus we would hog the  CPU  in order for that check.
But of course there is that 'mem_cooperate()' hero that grants the CPU time
to the rest of  0 prioritised tasks(happens to be most of the OS tasks), so
that  it does  not feel like  we check for  the data that often, but  we do
since  context switches  and tasks that  mostly wait  do not take  too much
time. The check interval will vary yes, but that is still like  1000 micros
in the worst case. If the device lags due to some unpredictible events then
still the OS is responsive and at the same time device check is atomic.

Concerns. I  think I know what you  may think. The priority. What about the
tasks who are negative on priority? Well, they are  locked out temporarily,
but that is what they will do anyway. They were given negative priority for
a good reason. The positively prioritised tasks  will continue  to function
normally though. You may always stuff -128 instead 0 then your process will
be totally OS friendly, but keep in mind that others may then lock you out!
I can hear: "I can replace this with 'usleep()', 'Delay()', etc and it will
still give me good results.". Of course you can,  but you cannot get rid of
the  timer  overhead ;-) . As to  signal it may be any signal you like, but
SIGF_SINGLE  is just perfect for this kind  of ops because it is considered
temporary.


                                 2. TIPS
                          =======================

You may  use 'mem_cooperate(127, SIGF_SINGLE);' to validate  'tc_SPReg'  at
any  point in your program, so  that the  PC  will be pointing at this very
function, which makes stack hacking easy.

You should always 'mem_cooperate(0, SIGF_SINGLE);' in case of time critical
checks. This  is the  safest call. Never  go negative  nor positive  on the
priority  in this  particular case,  because either they will lock you out,
or you will lock them out.

You may  use 'mem_cooperate(-128, SIGF_SINGLE);' to forward the control  to
someone  else at any time. Just  keep in mind  that you may  not be able to
regain it quick if the OS is under heavy load.


                                 3. TECH
                          =======================

Many of you may have some theories on how it works after reading the intro,
so lets get into details to smash these theories to pieces. Briefing: Allow
a task  levelised context switch. One very  simple method  would be  to use
'timer.device',  but there is a question of excess overhead,  thus there is
no  way to control level  of that context switch. Uh, oh... Wait  a minute!
Maybe  private 'exec.library'  functions such  as 'Switch()', 'Dispatch()',
'Reschedule(), and company would do it? Yeah possibly but there is just one
minor problem, they are all priviledged  and calling them would  Guru right
away. Damn it... Oh, i know! Lets just play with 'TaskWait' and 'TaskReady'
lists. Quite an idea, but that  would lead to double task sortage and would
easily ruin the correct task switch order. No... Seems like all simple ways
are no-go. However, surprise! There is even a simplier way, check this out:

  1. Set desired cooperation priority with 'SetTaskPri()', so that schedule
     will be fixed.
  2. Disable task switches with 'Forbid()', so we can safely poke the task.
  3. Wrap  'Signal()' and old  'tc_Switch()'  in itself and/or activate it.
  4. Clear the signal and  'Wait()' for it to arrive from kernel. This will
     happen almost in no time.
  5. Fix  'tc_Switch()'  to original and/or disable it. Forbidden  state is
     active again!
  6. Allow  task switches with  'Permit()'. Can now  run  normally  though.
  7. Revert the  original priority with  'SetTaskPri()' , fix  the schedule
     again.


At first it may look all unrelated and fuzzy, so it will not work. Funnily,
it does and the  OS  takes care of everything! But  let's get to the point.
As soon as we loose the CPU  'tc_Switch()' gets called where the 'Signal()'
call is being placed,  which in  turn sends what we  do expect. But  before
that is to happen, we give away the CPU with 'Wait()' thus we loose the CPU
and  get it  immediately after  full task switch  circle on this very level
(priority). It looks more or less like this:

/---------------+ +--------------------------------------+ +--------------/
/               | | TASK = 5 / SIGF_SINGLE = s           | | TASK = n     /
/---------------+ +--------------------------------------+ +--------------/
/| tc_Switch()  | | tc_Launch() |   ...   | tc_Switch()  | | tc_Launch() |/
/|              | |             |         |              | |             |/
/|      -       | |      -      | Wait(s) | Signal(5, s) | |      -      |/
/|              | |             |         |              | |             |/
/---------------+ +--------------------------------------+ +--------------/
       :                   :      :                   : :           :
       :  +-------------+  :      :  +-------------+  : :           :
       1  | KERNEL      |  2      3  | KERNEL      |  4 5           6
       :  +-------------+  :      :  +-------------+  : :           :
       -->|             |-->      -->|             |--> :           :
          |  Interrupt  |            |   Control   |<----           :
          |  happened   |            |   granted   |---------------->
          |             |            |             |
          +-------------+            +-------------+

       -- Context switch -->      -- Context switch -->

                                                             <-- Recycle --


To better understand what is going on when  'mem_cooperate()' is in action,
follow the numbers:

  1. Task number  4 was executing its code while the interrupt has happened
     and the kernel taken away the control.
  2. Kernel  has granted  the control to task number 5. The task called the
     'mem_cooperate()', where 'Wait()' was called.
  3. Task no. 5 has granted the control back to the kernel due to 'Wait()'.
  4. Kernel went to examine  'tc_Switch()' and spotted code to be executed,
     so it jumped in and the ' Signal()'  was called, thus  'Wait()' is now
     satisfied.
  5. Kernel regained the control, the 'tc_Switch()' did return.
  6. Kernel decided to grant the control to task no.  n. The cycle repeats.


---
megacz

  


No more fear cus pure HTML is here!
Copyright (C) 2013-2014 by Burnt Chip Dominators