2 Copyright © 1995-2001, The AROS Development Team. All rights reserved.
6 #include <exec/lists.h>
7 #include <exec/tasks.h>
8 #include <exec/execbase.h>
14 #define timeval sys_timeval
18 #include <proto/arossupport.h>
22 void Enqueue (struct List
* list
, struct Node
* node
);
23 void AddTail (struct List
* list
, struct Node
* node
);
26 /* This is used to count the number of interrupts. */
29 /* Let the sigcore do it's magic */
32 /* This flag means that a task switch is pending */
37 struct ExecBase _SysBase
, * SysBase
= &_SysBase
;
40 struct Task Task1
, Task2
, Task3
, Task4
, TaskMain
;
43 void AddTail (struct List
* list
, struct Node
* node
)
48 void AddHead (struct List
* list
, struct Node
* node
)
53 void Remove (struct Node
* node
)
58 /* Enter a node into a sorted list */
59 void Enqueue (struct List
* list
, struct Node
* node
)
63 ForeachNode (list
, next
)
65 /* Look for the first node with a lower priority */
66 if (node
->ln_Pri
> next
->ln_Pri
)
70 /* Insert "node" before "next" */
71 node
->ln_Pred
= next
->ln_Pred
;
72 next
->ln_Pred
->ln_Succ
= node
;
77 #define STR(x) (x && x->ln_Name ? (char *)x->ln_Name : "NULL")
79 /* Print a list with nodes with names. */
80 void PrintList (struct List
* list
)
85 printf ("list %p { head=%s, tail=%s } = ", list
,
86 STR(list
->lh_Head
), STR(list
->lh_TailPred
));
88 for (node
=GetHead(list
); node
; node
=GetSucc(node
))
90 printf ("%s (%p { succ=%s pred=%s pri=%d}), ", node
->ln_Name
, node
,
91 STR(GetSucc(node
)), STR(GetPred(node
)),
103 /* Macro to get a pointer to the current running task */
104 #define THISTASK (SysBase->ThisTask)
107 Disable and enable signals. Don't use these in the signal handler
108 because then some signal might break the signal handler and then
111 #define DISABLE ({sigset_t set; sigfillset(&set); sigprocmask (SIG_BLOCK, &set, NULL);})
112 #define ENABLE ({sigset_t set; sigfillset(&set); sigprocmask (SIG_UNBLOCK, &set, NULL);})
114 /* Enable/Disable interrupts */
118 Disable signals only if they are not already. The initial value of
121 if (SysBase
->IDNestCnt
++ < 0)
130 Enable signals only if the number of calls of Enable() matches the
133 if (--SysBase
->IDNestCnt
< 0)
139 /* Allow/forbid task switches */
143 Task switches are only allowed if TDNestCnt is < 0. The initial
144 value of TDNestCnt is -1.
146 ++ SysBase
->TDNestCnt
;
151 /* Count calls and check if interrupts are allowed. */
152 if (--SysBase
->TDNestCnt
< 0
153 && SysBase
->IDNestCnt
< 0
157 Task switches are allowed again. If a switch is pending
160 if (SysBase
->SysFlags
& SF_SAR
)
163 SysBase
->SysFlags
&= ~SF_SAR
;
171 /* Main routine: Insert a task into the list of tasks to run. */
172 void Reschedule (struct Task
* task
)
175 The code in here defines how "good" the task switching is.
176 There are seveal things which should be taken into account:
178 1. No task should block the CPU forever even if it is an
181 2. Tasks with a higher priority should get the CPU more often.
183 3. Tasks with a low priority should get the CPU every now and then.
185 Enqueue() fulfills 2 but not 1 and 3. AddTail() fulfills 1 and 3.
187 A better way would be to "deteriorate" a task, ie. decrease the
188 priority and use Enqueue() but at this time, I can't do this
189 because I have no good way to extend the task structure (I
190 need a variable to store the original prio).
192 AddTail (&SysBase
->TaskReady
, (struct Node
*)task
);
195 /* Switch to a new task if the current task is not running and no
196 exception has been raised. */
197 ULONG
Wait (ULONG sigmask
);
200 struct Task
* task
= THISTASK
;
202 /* Check that the task is not running and no exception is pending */
203 if (task
->tc_State
!= TS_RUN
&& !(task
->tc_Flags
& TF_EXCEPT
) )
209 Make sure there is a signal. This is somewhat tricky: The
210 current task (which is excuting this funcion) will loose the
211 CPU (ie. some code of another task will be executed). Then
212 at some time in the future, the current task (ie. the one
213 that has executed the kill()) will get the CPU back and
214 continue with the code after the kill().
216 kill (getpid(), SIGALRM
);
221 This waits for a "signal". That's not a Unix signal but a flag set
222 by some other task (eg. if you send a command to a device, the
223 device will call you back when it has processes the command.
224 When this happens such a "signal" will be set). The task will be
225 suspended until any of the bits given to Wait() are set in the
226 tasks' signal mask. Again, this signal mask has nothing to do
227 with the Unix signal mask.
229 It's a dummy right now. All it does is switch to another task.
231 ULONG
Wait (ULONG sigmask
)
233 struct Task
* task
= THISTASK
;
236 Task is no longer running. If we didn't do this, Switch() would do
239 task
->tc_State
= TS_READY
;
241 /* Let another task run. */
244 /* When I get the CPU back, this code is executed */
248 /* Simple main for a task: Print a message and wait for a signal. */
251 struct Task
* task
= THISTASK
;
252 STRPTR name
= task
->tc_Node
.ln_Name
;
256 printf ("Main1: %s\n", name
);
262 /* Another method of waiting (but an inferior one). */
263 void busy_wait (void)
267 for (t
=cnt
; t
==cnt
; );
270 /* Same as Main1 but wait by polling */
273 struct Task
* task
= THISTASK
;
274 STRPTR name
= task
->tc_Node
.ln_Name
;
278 printf ("Main2: %s\n", name
);
281 Kids, don't do this at home. I'm a professional.
283 This is to make sure even endless loops don't harm the
284 system. Even if this task has a higher priority than any
285 other task in the system, the other tasks will get the
286 CPU every now and then.
292 #define DEBUG_STACK 0
293 #define STACKOFFSET 0
296 The signal handler. It will store the current tasks context and
297 switch to another task if this is allowed right now.
299 static void sighandler (int sig
, sigcontext_t
* sc
)
303 /* Are task switches allowed ? */
304 if (SysBase
->TDNestCnt
< 0)
306 /* Save all registers and the stack pointer */
308 SAVEREGS(THISTASK
,sc
);
315 /* Find a new task to run */
317 /* Restore signal mask */
318 if (SysBase
->IDNestCnt
< 0)
328 RESTOREREGS(THISTASK
,sc
);
333 /* Set flag: switch tasks as soon as switches are allowed again */
334 SysBase
->SysFlags
|= SF_SAR
;
338 /* Find another task which is allowed to run and modify SysBase accordingly */
341 struct Task
* this = THISTASK
;
344 /* Check the stack */
345 if (this->tc_SPReg
<= this->tc_SPLower
346 || this->tc_SPReg
>= this->tc_SPUpper
349 printf ("illegal stack\n");
352 /* Try to find a task which is ready to run */
353 if ((task
= GetHead (&SysBase
->TaskReady
)))
356 printf ("Dispatch: Old = %s (Stack = %x), new = %s\n",
357 this->tc_Node
.ln_Name
,
358 (IPTR
)this->tc_SPUpper
- (IPTR
)this->tc_SPReg
,
359 task
->tc_Node
.ln_Name
);
362 /* Remove new task from the list */
363 Remove ((struct Node
*)task
);
365 /* Sort the old task into the list of tasks which want to run */
368 /* Save disable counters */
369 this->tc_TDNestCnt
= SysBase
->TDNestCnt
;
370 this->tc_IDNestCnt
= SysBase
->IDNestCnt
;
372 /* Set new counters */
373 SysBase
->TDNestCnt
= task
->tc_TDNestCnt
;
374 SysBase
->IDNestCnt
= task
->tc_IDNestCnt
;
379 /* Set new states of the tasks */
380 this->tc_State
= TS_READY
;
381 task
->tc_State
= TS_RUN
;
383 printf("leaving dispatch!\n");
387 Initialize the system: Install an interrupt handler and make sure
393 struct itimerval interval
;
395 /* Install a handler for the ALARM signal */
396 sa
.sa_handler
= (SIGHANDLER_T
)SIGHANDLER
;
397 sa
.sa_flags
= SA_RESTART
;
399 sa
.sa_restorer
= NULL
;
400 #endif /* __linux__ */
401 sigfillset (&sa
.sa_mask
);
403 sigaction (SIGALRM
, &sa
, NULL
);
405 /* Set 50Hz intervall for ALARM signal */
406 interval
.it_interval
.tv_sec
= interval
.it_value
.tv_sec
= 1;
407 interval
.it_interval
.tv_usec
= interval
.it_value
.tv_usec
= 1000000/50;
409 setitimer (ITIMER_REAL
, &interval
, NULL
);
412 #define STACK_SIZE 4096
414 /* Create a new task */
415 void AddTask (struct Task
* task
, STRPTR name
, BYTE pri
, APTR pc
)
419 /* Init task structure */
420 memset (task
, 0, sizeof (struct Task
));
422 /* Init fields with real values */
423 task
->tc_Node
.ln_Pri
= pri
;
424 task
->tc_Node
.ln_Name
= name
;
425 task
->tc_State
= TS_READY
;
427 /* Allow task switches and signals */
428 task
->tc_TDNestCnt
= -1;
429 task
->tc_IDNestCnt
= -1;
431 /* Allocate a stack */
432 sp
= malloc (STACK_SIZE
* sizeof (SP_TYPE
));
435 Copy bounds of stack in task structure. Note that the stack
436 grows to small addresses (ie. storing something on the stack
437 decreases the stack pointer).
439 task
->tc_SPLower
= sp
;
441 task
->tc_SPUpper
= sp
;
444 Let the sigcore do it's magic. Create a frame from which an
445 initial task context can be restored from.
448 GetIntETask(task
) = malloc(sizeof(struct IntETask
));
449 task
->tc_Flags
|= TF_ETASK
;
450 GetCpuContext(task
) = malloc(SIZEOF_ALL_REGISTERS
);
452 PREPARE_INITIAL_FRAME(sp
,pc
);
453 PREPARE_INITIAL_CONTEXT(task
,pc
);
455 /* Save new stack pointer */
458 /* Add task to queue by priority */
459 Enqueue (&SysBase
->TaskReady
, (struct Node
*)task
);
463 Main routine: Create four tasks (three with the Mains above and one
464 for main(). Wait for some task switches then terminate cleanly.
466 int main (int argc
, char ** argv
)
469 NEWLIST (&SysBase
->TaskReady
);
470 NEWLIST (&SysBase
->TaskWait
);
472 /* Signals and task switches are not allowed right now */
473 SysBase
->IDNestCnt
= 0;
474 SysBase
->TDNestCnt
= 0;
476 /* Add three tasks */
477 AddTask (&Task1
, "Task 1", 0, Main1
);
478 AddTask (&Task2
, "Task 2", 5, Main2
);
479 AddTask (&Task3
, "Task 3", 0, Main2
);
480 PrintList (&SysBase
->TaskReady
);
483 Add main task. Make sure the stack check is ok. This task is *not*
484 added to the list. It is stored in THISTASK and will be added to
485 the list at the next call to Dispatch().
487 Also a trick with the stack: This is the stack of the Unix process.
488 We don't know where it lies in memory nor how big it is (it can
489 grow), so we use "reasonable" defaults. The upper bounds is the
490 first argument (or the last local variable but we don't have any
491 right now). If the stack ever passes by this, we begin to trash
492 data on our stack. The lower bounds is 0 (well, we could restrict
493 the stack to, say, STACK_SIZE from SPUpper, but Unix is responsible
494 for this stack, so I don't really care).
496 TaskMain
.tc_Node
.ln_Pri
= 0;
497 TaskMain
.tc_Node
.ln_Name
= "Main";
498 TaskMain
.tc_State
= TS_READY
;
499 TaskMain
.tc_SPLower
= 0;
500 TaskMain
.tc_SPUpper
= &argc
;
502 TaskMain
.tc_UnionETask
.tc_ETask
= malloc(sizeof(struct IntETask
));
503 TaskMain
.tc_Flags
|= TF_ETASK
;
504 ((struct IntETask
*)TaskMain
.tc_UnionETask
.tc_ETask
)->iet_Context
= malloc(SIZEOF_ALL_REGISTERS
);
506 /* The currently running task is me, myself and I */
507 THISTASK
= &TaskMain
;
509 /* Start interrupts and allow them. */
514 /* Wait for 10000 signals */
517 printf ("%6ld\n", cnt
);
519 /* Wait for a "signal" from another task. */
523 /* Make sure we don't get disturbed in the cleanup */
526 /* Show how many signals have been processed */
527 printf ("Exit after %ld signals\n", cnt
);