home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 September
/
Simtel20_Sept92.cdr
/
msdos
/
ddjmag
/
ddj8712.arc
/
HOLUB.ARC
/
TASK.C
< prev
next >
Wrap
Text File
|
1987-12-21
|
55KB
|
574 lines
#include <dos.h>
#include <stdarg.h>
#include <signal.h>
#include <tools/hardware.h> /* #define for TIMR_CLK */
#include "kernel.h"
static int Speedup_factor = 0;
static long Executed, Timed_out, Did_swap;
#define max(a,b) ((a) > (b) ? (a) : (b))
/*------------------------------------------------------------
* Strings for error codes. Note that these are upside down
* to compensate for the negative indexes
*/
static char *msgs[] =
{
"Ctrl-Break or Ctrl-C" /* -10 */
"Stack overflow", /* -9 */
"Delete would have caused a deadlock", /* -8 */
"Internal error", /* -7 */
"No tasks to send message to", /* -6 */
"Queue is full", /* -5 */
"Timeout", /* -4 */
"Illegal Argument", /* -3 */
"Insufficient memory available", /* -2 */
"Maximum number of tasks (32) already exists", /* -1 */
"No error" /* 0 */
};
char **t_errlist = msgs + ((sizeof(msgs)/sizeof(*msgs)) -1);
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
t_iserr(x)
{
return( -10 <= (x) && (x) < 0 );
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
t_perror( str, errcode )
char *str;
{
if( !t_iserr(errcode) && errcode != 0 )
t_printf( "%s status %d\n", str, errcode );
else
t_printf( "%s %s\n", str, t_errlist[errcode] );
}
/*------------------------------------------------------------*/
static intr()
{
t_stop( TE_KILL );
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int t_start( speedup_factor )
{
/* Start multitasking. At least one task must have
* been created prior to this call. The speedup factor
* determines the system clock-tick rate. A value of 1
* gives the default rate of roughly 18.2 times a second
* (once every 55 milliseconds, more or less). A
* speedup_factor of 2 gives twice that speed: 36.4 ticks
* per second, one every 27 milliseconds or thereabouts.
* Speeding up the clock rate shouldn't affect the DOS
* clock. Nonetheless, it's safest if the speedup factor
* is a power of two.
*
* If the speedup factor is 0 then the system is
* nonpreemptive. You'll have to use t_yield(), t_send(),
* and t_wait() to change contexts.
*
* Control passes imediately to the highest priority task.
* Control will automatically pass back to the calling
* subroutine when all tasks have been deleted. The task
* is actually started in _t_shazam(), declared in swap.asm.
*
* Normal return values:
*
* TE_NOTASKS No tasks exist, multitasking not
* started;
*
* TE_DEADLOCK A task deleted itself and it's the
* only active task in the system. Other
* tasks exist but they're all pending
* on queues.
*
* TE_NOERR All tasks have been deleted normally,
* no tasks are waiting on queues.
*
* TE_STACK Task stack overflow.
*
* If TE_NOERR is returned, then all memory allocated to
* tasks will have been saved, otherwise, if one of the
* above errors was returned, T_active will point at the
* TCB of the offending task.
*
* Other return values are possible if a task calls t_stop()
* directly. The argument passed to t_stop() is returned by
* t_start(). The process is analogous to the value of
* exit(), which doesn't return and who's argument is
* passed back to a wait() call in the parent process. Note
* that the TCB pointed to by T_active will not be free()ed
* unless TE_NOERR (0) is returned.
*/
Speedup_factor = speedup_factor;
if( !pq_del( T_tasks, &T_active ) )
return TE_NOTASKS;
if( speedup_factor > 0 )
{
t_cli();
_t_speedup( speedup_factor );
}
signal( SIGINT, intr );
_t_shazam();
return TE_INTERNAL; /* Shouldn't ever get here */
}
/*------------------------------------------------------------*/
int t_second()
{
/* Returns the number of system clock ticks in a
* second, given the speedup factor passed to t_start().
* Returns 0 if the speedup factor was 0. In this case
* a t_wait() call will never time out.
*/
return (TIMR_CLK / 65536) * Speedup_factor ;
}
/*------------------------------------------------------------*/
TCB *t_create( subr, tag, priority, stack_size, ... )
int (*subr)(); /* Subroute that forms main module */
char *tag; /* String used to identify TCB */
unsigned priority; /* Priority */
int stack_size; /* Stack size (in 2-byte words) */
{
/* Creates a new task. Subr is a pointer to the main()
* subroutine for the task.
*
* Priorities must be in the range 0-255. 255 is the
* highest. If more than one task has the same priority,
* they are executed in a round-robin
* fashion. Forces a reschedule if tasking is active.
*
* Arguments may be passed to the subroutine at startup.
* That is, a NULL-terminated list of pointer-sized
* arguments follow stack_size in the t_create() call.
* These are passed to the subroutine in the normal way.
* For example:
*
* foo( a, b, c ) int a, b, c; {}
* t_create( foo, "foo" 10, 128, doo, wha, ditty, NULL);
*
* starts up foo() as a task at priority 10 with a
* 128-byte stack. Doo, wha, and ditty are passed
* to foo as arguments a, b, and c. Note that the
* arguments use 6 of the 128 bytes in the stack
* (two for each argument). The "foo" tag is just
* used for identification purposes in debugging.
* It can be any string.
*
* Note that a few Microsoft functions (like printf)
* use up inordinate amounts of stack. If you're going
* to call Microsoft library routines, you'll need
* at least 1K bytes of stack (stack_size==512) per
* task.
*
* A pointer to the created TCB is returned normally.
* Error return values are:
*
* TE_TOOMANY Maximum number of tasks already exists
* TE_NOMEM Insufficient memory available
*/
TCB *t;
struct SREGS segs;
int pq_cmp();
int pq_swap();
va_list argptr;
void *arg;
void *malloc();
va_start( argptr, stack_size );
if( ++T_numtasks > T_MAXTASK )
return (TCB *) TE_TOOMANY;
t_block();
/* Allocate the stack, converting stack_size to bytes.
* I'm requesting one more cell than specified in order
* to make room for the error return address, below
* [stack[0] is included in the sizeof(TCB)]. The minimum
* stack size is 20 words, this gives us enough for a
* context swap plus a little slop.
*/
stack_size = max( stack_size, 20 );
if( !(t = (TCB *) malloc(sizeof(TCB)+(stack_size*sizeof(void*)) )))
{
t_release();
return (TCB *) TE_NOMEM;
}
/* Create the active queue if necessary, then initialize
* the TCB. The stack pointer is initialized to point
* just past the end of the stack (rather than to the
* last cell) because a push uses a predecrement. The
* PC points at the subroutine. Uninitiailzed registers
* are unimportant, but will contain 0. The stack area
* is initialized to the pattern a5a5a5a5.... so that
* we can look it with a debugger and see what's been
* used. 0 is no good for this purpose because 0 is
* a likely thing to be pushed on the stack.
*/
if( !T_tasks )
T_tasks = pq_create( T_MAXTASK, sizeof(TCB *),
pq_cmp, pq_swap, NULL);
segread( &segs );
memset( t, 0x0, sizeof(TCB) - sizeof(void*) );
memset( t->stack, 0xa5, stack_size * sizeof(void*) );
t->sp = t->stack + ++stack_size;
t->ss = segs.ds ; /* Stacks are in data seg */
t->initial_sp = t->sp ;
t->priority = priority ;
t->timestamp = T_clock ;
t->tag = tag ;
/* Initialize the stack, First pretend that we've
* already called the initial subroutine by pushing
* the arguments, and t_stop as a dummy return address.
* The task shouldn't return, but if it does, t_stop
* seems like a reasonable thing to call, even though
* it will return garbage.
* The last 11 things are the initial context.
* They'll be popped as part of the context swap.
*/
while( arg = va_arg(argptr, void*) )
*--(t->sp) = arg;
*--(t->sp) = t_stop; /* Vector to t_stop */
*--(t->sp) = 0; /* flags*/
*--(t->sp) = segs.cs; /* cs */
*--(t->sp) = subr; /* ip */
*--(t->sp) = 0; /* ax */
*--(t->sp) = 0; /* bx */
*--(t->sp) = 0; /* cx */
*--(t->sp) = 0; /* dx */
*--(t->sp) = 0; /* si */
*--(t->sp) = 0; /* di */
*--(t->sp) = 0; /* bp */
*--(t->sp) = segs.ds; /* ds */
*--(t->sp) = segs.es; /* es */
if( T_active && T_PRIORITY( t, T_active ) <= 0 )
{
pq_ins( T_tasks, &T_active );
_t_swap_in( t );
}
else
{
pq_ins( T_tasks, &t );
t_release();
}
return t;
}
/*------------------------------------------------------------*/
static TCB *del_fm_queues( task )
TCB *task;
{
/* Traverse the queue list and if the task is waiting
* for a message, delete it from the queue
* and return a pointer to it, otherwise return NULL.
*/
T_QUEUE *q;
TCB *t, **prev ;
for( q = T_queues; q ; q = q->next )
{
prev = &q->task_h;
for( t = q->task_h; t; prev = &t->next, t = t->next)
{
if( t == task )
{
*prev = t->next ;
return t;
}
}
}
return NULL;
}
/*------------------------------------------------------------*/
int t_chg_priority( tp, new_priority )
TCB *tp;
int new_priority;
{
/* Change priority for indicated task. Forces a reschedule.
* If the task was waiting on a message, it is immediately
* timed out and put back on the active list. A task may
* change it's own priority.
*
* Return values:
*
* TE_NOERR
* TE_BADARG Task doesn't exist;
*/
int rval = TE_NOERR;
TCB *deleted;
int pq_rm_cmp();
if( new_priority > 255 )
return TE_BADARG;
t_block();
if( tp == T_active )
{
T_active->priority = new_priority;
t_yield();
}
else
{
if( !pq_remove( T_tasks, &deleted, pq_rm_cmp, tp ) )
if( deleted = del_fm_queues( tp ) )
{
deleted->status = TS_TIMEOUT;
deleted->msg = NULL;
}
else
return TE_BADARG;
deleted->priority = new_priority;
pq_ins( T_tasks, &deleted );
t_yield();
}
}
/*------------------------------------------------------------*/
int t_delete( task )
TCB *task;
{
/* Delete task created with previous t_create() call and
* free all memory associated with task. Note that
* malloced() memory is not freed, only the memory that
* t_create() allocated to begin with. A task may delete
* itself. Forces a reschedule.
*
* Return values:
*
* TE_BADARG Task doesn't exist
* TE_NOERR
*
* T_stop is called automatically when the only active
* task deletes itself. See t_start() for an explanation
* of the return stati.
*
* For convenience, a task may delete itself with a
* t_delete(NULL) call.
*/
TCB *deleted;
static TCB garbage;
int pq_rm_cmp();
t_block();
if( T_active == task || task == NULL )
{
/* Delete the current task
* Note that the t_install() call below will not
* return. It replaces the current task with the new
* one, a pointer to which is in "deleted."
*/
if( !pq_del( T_tasks, &deleted) )
t_stop( T_numtasks <= 1 ? TE_NOERR : TE_DEADLOCK );
else
{
--T_numtasks;
_t_install( deleted );
}
}
else
{
/* Delete a task that's not active. pq_remove tries
* to get it from the active list. If that's not
* successful, del_fm_queues() scans the queues looking
* for it. If that's not successfule, TE_BADARG is
* returned.
*/
if( pq_remove( T_tasks, &deleted, pq_rm_cmp, task ) )
free( deleted );
else if( deleted = del_fm_queues(task) )
free( deleted );
else
{
t_release();
return TE_BADARG ;
}
if( --T_numtasks <= 0 ) /* Deleted the only task */
t_stop( TE_NOERR );
}
t_release();
return TE_NOERR;
}
/*------------------------------------------------------------*/
static pq_cmp( task1, task2 )
TCB **task1, **task2;
{
return T_PRIORITY( *task1, *task2 );
}
static pq_swap( task1, task2 )
TCB **task1, **task2;
{
TCB *tmp;
tmp = *task1;
*task1 = *task2;
*task2 = tmp;
}
static pq_rm_cmp( task1, item )
TCB **task1;
TCB *item;
{
return !( *task1 == item );
}
/*------------------------------------------------------------*/
static outc(c)
{
if( c == '\n' )
putch('\r');
putch(c);
}
t_printf( fmt )
char *fmt;
{
/* The doprnt() function used here is from: Allen Holub,
* The C Companion (Englewood Cliffs: Prentice-Hall,
* 1987). You can also use the ANSI vprintf(). Note
* that the Microsoft version of vprintf() uses A LOT
* of stack. If you're using vprintf, your tasks should
* have at least 1K bytes of stack.
*/
va_list args;
va_start(args, fmt);
t_block();
doprnt(outc, 0, fmt, args); /* vprintf( fmt, args ); */
t_release();
}
/*------------------------------------------------------------*/
t_sstats()
{
/* Print various scheduler related statistics. This routine
* should not be called from a task (because it uses printf).
*/
printf("\nScheduler called %ld times: %ld Tasks timed_out, "
"%ld context swaps\n",
Executed, Timed_out, Did_swap );
}
/*------------------------------------------------------------*/
#pragma check_stack-
TCB *_t_reschedule()
{
static T_QUEUE *q;
static TCB *t, **prev ;
/* Workhorse function called by the schedular
* (timer-interrupt service routine).
*
* Scan all the queues, checking for timed-out tasks. If
* you find one, remove it from the queue and add it to
* the active list.
*
* Modify T_active to point at the next task to activate.
* (the original T_active if no change).
*/
++Executed;
_t_sus_chkstk(); /* Stack probes off for the nonce */
for( q = T_queues; q ; q = q->next )
{
prev = &(q->task_h);
for( t = q->task_h ; t ; )
{
if( --(t->wait) <= 0 )
{
++Timed_out;
*prev = t->next ;
t->msg = NULL ;
t->status = TE_TIMEOUT;
pq_ins( T_tasks, &t );
}
prev = &(t->next);
t = t->next ;
}
}
/* Check the highest-priority element of the queue.
* If it's not higher than the current task, do nothing.
* Otherwise do a context swap. pq_replace will
* extract the highest priority object from the active
* list and put it into t, simultaneously putting
* T_active into the list.
*/
T_active->timestamp = ++T_clock ;
if( t = *( (TCB **) pq_look(T_tasks)) )
{
if( T_PRIORITY(t, T_active) >= 0 )
{
++Did_swap;
pq_replace( T_tasks, &t, &T_active );
T_active = t ;
}
}
_t_rst_chkstk(); /* Stack probes on again */
}
#pragma check_stack+