home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / drdobbs / 1989 / 02 / green.asc < prev    next >
Text File  |  1989-01-04  |  18KB  |  630 lines

  1. _A C++ Multitasking Kernel_
  2. by Tom Green
  3.  
  4. [LISTING ONE]
  5.  
  6.  
  7. /********************************************/
  8. /*        TASK.HPP                    */
  9. /*        Tom Green                   */
  10. /********************************************/
  11. /* this file contains classes needed to use multi-tasking kernel */
  12. /* include this file in your source code and then link with */
  13. /* task.cpp and timer.asm */
  14.  
  15. /* this is used when a task is initialized */
  16. /* this is a pointer to a function */
  17. typedef void (*func_ptr)(void);
  18. /* this is used for interrupt handler to call old interrupt handler */
  19. /* this is a far pointer to a function */
  20. typedef void (far *far_func_ptr)(void);
  21.  
  22. /* this is how the registers will look on the stack for a task */
  23. /* after they have been saved for a task switch. the sp and ss */
  24. /* registers will point to this when a task is started from the */
  25. /* interrupt handler or save_image */
  26.  
  27. typedef struct task_image{
  28.     unsigned int bp;
  29.     unsigned int di;
  30.     unsigned int si;
  31.     unsigned int ds;
  32.     unsigned int es;
  33.     unsigned int dx;
  34.     unsigned int cx;
  35.     unsigned int bx;
  36.     unsigned int ax;
  37.     unsigned int ip;
  38.     unsigned int cs;
  39.     unsigned int flags;
  40. }task_image;
  41.  
  42. /* a task object. contains information needed by task_control object */
  43. /* to do task switching and a pointer to the task's workspace (stack) */
  44.  
  45. class task{
  46.     private:
  47.         friend class task_control;  // task_control object needs access
  48.         friend class signal;        // signal needs access to next_task
  49.         task_image far *stack_ptr;  // task stack ("image") pointer
  50.         unsigned char task_state;   // task state flag
  51.         unsigned char *workspace;   // address of allocated task stack
  52.         task *next_task;            // pointer to next task in queue
  53.     public:
  54.         task(func_ptr func,unsigned int workspace_size); // constructor
  55.         ~task();                                            // destructor
  56. };
  57.  
  58.  
  59. /* this is a queue for tasks */
  60. /* it is called signal so user can define a signal for task communication */
  61.  
  62. class signal{
  63.     private:
  64.         friend class task_control;   // task_control needs access
  65.         task *head;
  66.         task *tail;
  67.         task *get_task_q(void);         // get next task off of queue
  68.         void put_task_q(task *tptr); // append task to queue
  69.     public:
  70.         signal(void){head=tail=0;};  // constructor
  71. };
  72.  
  73.  
  74.  
  75.  
  76. /* task_control object */
  77. /* routines and methods to interface with and control tasks */
  78. /* this object will initialize and restore interrupt vectors, */
  79. /* keep track of timer ticks, and switch execution between the */
  80. /* task objects */
  81.  
  82. class task_control{
  83.     private:
  84.         signal ready_q;            // queue of tasks ready to run
  85.         task *current_task;        // current active task
  86.         task_image far *old_stack_ptr;    // return to this stack when done
  87.         unsigned int task_running;    // task switching enabled flag
  88.         unsigned long timer_ticks;    // 18.2 ticks/second
  89.         unsigned int task_lock;        // lock out task switching
  90.         task_image far *task_switch(task_image far *stk_ptr,
  91.                                         unsigned int flag,
  92.                                         signal *sig);
  93.     public:
  94.         task_control(void);                // constructor
  95.         void add_new_task(task *new_task); // add new task object to ready q
  96.         void start_tasks(void);               // start switching tasks on ready_q
  97.         void stop_tasks(void){task_running=0;};
  98.         unsigned long get_timer_ticks(void){return(timer_ticks);};
  99.         void lock(void){task_lock=1;};        // current task can not be switched
  100.         void unlock(void){task_lock=0;};    // allow task switching
  101.         void send(signal *sig);             // put task from sig q on ready q
  102.         void wait(signal *sig);             // put task on sig q
  103.         void block(void);                   // task allows next to run
  104. };
  105.  
  106. [LISTING TWO]
  107.  
  108. /********************************************/
  109. /*        TASK.CPP                    */
  110. /*        by Tom Green                */                    
  111. /********************************************/
  112.  
  113. /* this file implements the methods used by task_control and task */
  114. /* objects */
  115.  
  116. #include <stdio.h>
  117. #include <stdlib.h>
  118. #include <dos.h>
  119. #include <int.h>
  120. #include "task.hpp"
  121.  
  122. /* task states */
  123. #define    TASK_INACTIVE    0
  124. #define    TASK_ACTIVE     1
  125. #define TASK_READY    2
  126. #define    TASK_WAITING    3
  127. #define TASK_ERROR    0xff
  128.  
  129. /* flags for interface routines */
  130. #define    TASK_TIMER_INTR    0
  131. #define    TASK_SEND        1
  132. #define    TASK_WAIT        2
  133. #define TASK_BLOCK        3
  134.  
  135. /* system timer interrupt or "timer tick" */
  136. #define TIMER_INT        8
  137.  
  138. /* routines we need from timer.asm */
  139. unsigned int getCS(void);
  140. extern void timer_handler(void);
  141. extern void save_image(unsigned int flag,signal *sig);
  142.  
  143. /* global for timer_handler to call old interrupt routine */
  144. far_func_ptr old_timer_handler;
  145.  
  146. /* this is really ugly. */
  147. /* when constructor for task_control object is called we save the */
  148. /* this pointer for task switch routine to call our task_control object */
  149. /* task_switch. this means we can only have 1 task_control object. sorry */
  150. task_control *gl_tptr;
  151.  
  152. /* constructor for a new task. workspace will be the stack space for */
  153. /* the task. when the timer interrupt happens the tasks "image" */
  154. /* is saved on the stack for use later and the task_image *stack_ptr */
  155. /* will point to this image */
  156.  
  157. task::task(func_ptr func,unsigned int workspace_size)
  158. {
  159.     task_image *ptr;
  160.     
  161.     /* get stack or "workspace" for task */
  162.     if((workspace=(unsigned char *)malloc(workspace_size))==NULL){
  163.         task_state=TASK_ERROR;    // do not let this one run
  164.         return;
  165.     }
  166.  
  167.     /* now we must set up the starting "image" of the task registers */
  168.     /* ptr will point to the register image to begin task */
  169.     ptr=(task_image *)(workspace+workspace_size-sizeof(task_image));
  170.  
  171.     /* now save the pointer to the register image */
  172.     stack_ptr=MK_FP(getDS(),(unsigned int)ptr);
  173.  
  174.     ptr->ip=(unsigned int)func; // offset of pointer to task code
  175.     ptr->cs=getCS();            // segment of pointer to task, compiler bug
  176.     ptr->ds=getDS();
  177.     ptr->flags=0x200;        // flags, interrupts on
  178.     task_state=TASK_INACTIVE;   // task is inactive
  179.     next_task=0;
  180.  
  181. /* destructor for a task object */
  182.  
  183. task::~task(void)
  184. {
  185.     free(workspace);
  186. }
  187.  
  188. /* get the next task off of a task queue */
  189.  
  190. task *signal::get_task_q(void)
  191. {
  192.     task *temp;
  193.     
  194.     temp=head;
  195.     if(head)
  196.         head=head->next_task;
  197.     return(temp);
  198. }
  199.  
  200. /* append a task to the end of a task queue */
  201.  
  202. void signal::put_task_q(task *tptr)
  203. {
  204.     if(head)
  205.         tail->next_task=tptr;
  206.     else
  207.         head=tptr;
  208.     tail=tptr;
  209.     tptr->next_task=0;
  210. }
  211.  
  212. /* constructor for task_control */
  213. /* inits private stuff for task control */
  214.  
  215. task_control::task_control(void)
  216. {
  217.     gl_tptr=this;
  218.     task_running=0;
  219.     current_task=0;
  220.     timer_ticks=0L;
  221.     task_lock=0;
  222. }
  223.  
  224. /* adds a task to the task ready_q */
  225. /* call this routine after creating a task object */
  226.  
  227. void task_control::add_new_task(task *new_task)
  228. {
  229.     if(new_task->task_state!=TASK_ERROR){
  230.         new_task->task_state=TASK_READY;
  231.         ready_q.put_task_q(new_task);
  232.     }
  233. }
  234.  
  235. /* call to start up tasks after you have created some */
  236. /* and added them to the ready_q */
  237.  
  238. void task_control::start_tasks(void)
  239. {
  240.     unsigned int offset,segment;
  241.  
  242.     task_running=1;
  243.     /* get address of old timer interrupt handler */
  244.     int_getvector(TIMER_INT,&offset,&segment);
  245.     old_timer_handler=(far_func_ptr)(MK_FP(segment,offset));
  246.     /* set up our new timer interrupt handler */
  247.     int_setvector(TIMER_INT,(unsigned int)timer_handler,getCS());
  248.     /* tasks will now start running */
  249.     while(task_running)
  250.         ; // do nothing, trick to wait for tasks to start up
  251.     /* falls through to here when multi-tasking is turned off */
  252. }
  253.  
  254. /* gets the next task off of sig queue and puts it */
  255. /* on the ready_q. this suspends operation of the calling */
  256. /* task which is also put on the ready queue */
  257.  
  258. void task_control::send(signal *sig)
  259. {
  260.     save_image(TASK_SEND,sig);
  261. }
  262.  
  263. /* puts the calling task on the sig queue to wait for a signal */
  264.  
  265. void task_control::wait(signal *sig)
  266. {
  267.     save_image(TASK_WAIT,sig);
  268. }
  269.  
  270. /* this causes the current task to be placed on the ready queue */
  271. /* and a switch to the next task on the ready_q */
  272.  
  273. void task_control::block(void)
  274. {
  275.     save_image(TASK_BLOCK,(signal *)0);
  276. }
  277.  
  278. /* this routine is called to do a task switch. it is */
  279. /* passed a task_image far * to the current stack or task "image". */
  280. /* also pass a flag (described above) and a signal pointer if needed. */
  281. /* a task_image * to the "image" of the next task is returned */
  282.  
  283. task_image far *task_control::task_switch(task_image far *stk_ptr,
  284.                                                 signal *sig)
  285. {
  286.     task_image far *temp;
  287.     task *tptr;
  288.  
  289.     if(flag==TASK_TIMER_INTR)  // increment clock if it is a timer interrupt
  290.         timer_ticks++;
  291.  
  292.     /* this saves a pointer to stack when we first start multi-tasking */
  293.     /* allows us to return to where start_tasks was called */
  294.     if(!current_task){  // no current task so save state for restoring
  295.         old_stack_ptr=stk_ptr;    // save stack pointer
  296.         current_task=ready_q.get_task_q(); // set up a current task
  297.     }
  298.  
  299.     /* we have an active task, so do task switch if we can */
  300.     if(current_task->task_state==TASK_ACTIVE){
  301.         current_task->stack_ptr=stk_ptr;  // save stack pointer
  302.         current_task->task_state=TASK_READY; // task is ready to go
  303.         /* do not allow task switching if tasks are locked and */
  304.         /* it is timer interrupt */
  305.         if(!task_lock || flag!=TASK_TIMER_INTR){
  306.             /* check and see what caused task_switch to be called */
  307.             switch(flag){
  308.                 case TASK_WAIT:
  309.                     current_task->task_state=TASK_WAITING;
  310.                     sig->put_task_q(current_task);
  311.                     break;
  312.                 case TASK_SEND:
  313.                     if((tptr=sig->get_task_q())!=0)
  314.                         ready_q.put_task_q(tptr);
  315.                     // fall through
  316.                 case TASK_BLOCK:
  317.                 case TASK_TIMER_INTR:
  318.                     current_task->task_state=TASK_READY;
  319.                     /* put old task on ready queue */
  320.                     ready_q.put_task_q(current_task);
  321.                     break;
  322.             }
  323.             /* get next task to go */
  324.             current_task=ready_q.get_task_q();
  325.         }
  326.     }
  327.  
  328.     /* if we are still multi-tasking, get task ready to run */
  329.     if(task_running){
  330.         current_task->task_state=TASK_ACTIVE;
  331.         temp=current_task->stack_ptr;    // get stack pointer of task
  332.     }
  333.     /* multi-tasking has stopped, get ready to return where we started */
  334.     else{                    // someone called stop_tasks
  335.         int_setvector(TIMER_INT,FP_OFF(old_timer_handler),
  336.                         FP_SEG(old_timer_handler));
  337.         temp=old_stack_ptr;    // get back original stack
  338.     }
  339.     /* return far pointer to stack_image to do task switch */
  340.     return(temp);
  341. }
  342.  
  343. [LISTING THREE]
  344.  
  345. ;*****************************************************************************
  346. ;    TIMER.ASM
  347. ;    by Tom Green
  348. ;    Timer interrupt handler
  349. ;    Timer interrupt handler calls original handler first and then calls the
  350. ;    task_control object task switcher. a pointer to the stack "image"
  351. ;    of the new task is returned by the task switcher.
  352. ;    getCS
  353. ;    returns current code segment
  354. ;    save_image
  355. ;    saves "image" of task as if interrupt had happened and then calls the
  356. ;    task_control object task switcher. a pointer to the stack "image"
  357. ;    of the new task is returned by the task switcher.
  358. ;*****************************************************************************
  359.  
  360.     .MODEL SMALL
  361.     .8086
  362.     
  363.     .DATA
  364.  
  365. extrn _old_timer_handler:dword
  366. extrn _gl_tptr:word
  367. extrn __task_control_task_switch:near
  368.  
  369.     .CODE
  370.  
  371. ;*****************************************************************************
  372. ;    unsigned int getCS(void) - returns current code segment.
  373. ;    this is needed because of compiler bug. when a function is cast
  374. ;    to a far function, and you try to get the segment, DS is returned
  375. ;    instead of CS.
  376. ;*****************************************************************************
  377. _getCS    proc    near
  378. public    _getCS
  379.         mov        ax,cs
  380.         ret
  381. _getCS    endp
  382.         
  383. ;*****************************************************************************
  384. ;    timer_handler - this replaces the MS-DOS timer tick interrupt (8H).
  385. ;    this routine saves everything on stack, calls original timer interrupt
  386. ;    handler, and then calls task_control object task switcher.
  387. ;*****************************************************************************
  388. _timer_handler    proc    near
  389. public    _timer_handler
  390.         push    ax                ;save everything
  391.         push    bx
  392.         push    cx
  393.         push    dx
  394.         push    es
  395.         push    ds
  396.         push    si
  397.         push    di
  398.         push    bp
  399.         mov        bp,dgroup
  400.         mov    ds,bp            ;get our data segment back
  401.         pushf
  402.         call    dword ptr dgroup:_old_timer_handler  ;call original handler
  403.         sti
  404.         xor        dx,dx
  405.         mov        ax,ss
  406.         mov        bx,sp
  407.         push    dx            ;push 0 for last 2 parameters
  408.         push    dx        ;meaning timer interrupt
  409.         push    ax
  410.         push    bx
  411.         mov        dx,_gl_tptr   ;push hidden pointer for C++ object
  412.         push    dx
  413. ;stack is now set up for call to task_control object task_switch
  414.         cli                   ;turn off interrupts for task switch
  415.         call    __task_control_task_switch
  416. ;no need to clean up the stack because it will change
  417.         sti
  418.         mov        ss,dx    ;new ss returned in dx
  419.         mov        sp,ax    ;new sp returned in ax
  420.         pop        bp    ;restore registers
  421.         pop        di
  422.         pop        si
  423.         pop        ds
  424.         pop        es
  425.         pop        dx
  426.         pop        cx
  427.         pop        bx
  428.         pop        ax
  429.         iret
  430. _timer_handler    endp
  431.  
  432. ;*****************************************************************************
  433. ;    void save_image(unsigned int flag,signal *sig) - send, wait, block
  434. ;    etc. all call through here to save the "image" of the task. this
  435. ;    code simulates what will happen with an interrupt by saving the task
  436. ;    image on the stack. the flag passed is passed on to the task_control
  437. ;    object task switcher so it knows if it was called by the timer
  438. ;    interrupt handler, send, wait, block, etc. the second parameter
  439. ;    is a signal pointer which is used by send and wait and is passed
  440. ;    through to the task switcher.
  441. ;*****************************************************************************
  442. _save_image        proc    near
  443. public    _save_image
  444. ;since this is a C call we can destroy some registers (ax bx cx dx),
  445. ;so now we will set up the stack as if an interrupt call had happened.
  446. ;leave parameters on stack, because calling routine will adjust on
  447. ;return. bx and cx will have the parameters that were passed.
  448.         pop        ax    ;get return address offset on stack
  449.         pop        bx    ;get first parameter off stack
  450.         pop        cx    ;get second parameter off stack
  451.         push    cx        ;put them back on stack
  452.         push    bx
  453.         pushf            ;save flags for iret
  454.         mov        dx,cs    ;get code segment
  455.         push    dx        ;save code segment for return address
  456.         push    ax        ;push saved return address offset
  457.         push    ax        ;save everything
  458.         push    bx
  459.         push    cx
  460.         push    dx
  461.         push    es
  462.         push    ds
  463.         push    si
  464.         push    di
  465.         push    bp
  466.         sti
  467.         mov        ax,sp    ;stack pointer parameter
  468.         push    cx        ;second parameter passed
  469.         push    bx        ;first parameter passed
  470.         mov        bx,ss
  471.         push    bx        ;far pointer to stack, parameter passed
  472.         push    ax
  473.         mov        ax,_gl_tptr  ;push hidden pointer for C++ object
  474.         push    ax
  475. ;stack is now set up for call to task_control object task_switch
  476.         cli              ;turn off interrupts for task switch
  477.         call    __task_control_task_switch
  478. ;no need to clean up the stack because it will change
  479.         sti
  480.         mov        ss,dx        ;new ss returned in dx
  481.         mov        sp,ax        ;new sp returned in ax
  482.         pop        bp        ;restore registers
  483.         pop        di
  484.         pop        si
  485.         pop        ds
  486.         pop        es
  487.         pop        dx
  488.         pop        cx
  489.         pop        bx
  490.         pop        ax
  491.         iret
  492. _save_image        endp
  493.  
  494.         end
  495.  
  496. [LISTING FOUR]
  497.  
  498. /********************************************/
  499. /*        TASKDEMO.HPP                */
  500. /*        by Tom Green                */
  501. /********************************************/
  502.  
  503. /* this file is a demonstration of how to use the C++ multi-tasking */
  504. /* kernel. 5 tasks are run and the various means of task switching */
  505. /* and communication are shown */
  506.  
  507. /* you must have the Zortech C++ compiler version 1.5 and linker and */
  508. /* Microsoft MASM 5.xx to compile this code. */
  509. /* type "ztc taskdemo task timer" and the ztc.com will take */
  510. /* care of compiling, assembling, and linking */
  511.  
  512. #include <stdio.h>
  513. #include <disp.h>
  514. #include "task.hpp"
  515.  
  516. void task0(void);
  517. void task1(void);
  518. void task2(void);
  519. void task3(void);
  520. void task4(void);
  521.  
  522. /* our task_control object (just 1 please) */
  523. task_control tasker;
  524.  
  525. void main(void)
  526. {
  527.     /* task objects */
  528.     task t0((func_ptr)task0,1024);
  529.     task t1((func_ptr)task1,1024);
  530.     task t2((func_ptr)task2,1024);
  531.     task t3((func_ptr)task3,1024);
  532.     task t4((func_ptr)task4,1024);
  533.  
  534.     /* add task objects to our task_control object ready q */
  535.     tasker.add_new_task(&t0);
  536.     tasker.add_new_task(&t1);
  537.     tasker.add_new_task(&t2);
  538.     tasker.add_new_task(&t3);
  539.     tasker.add_new_task(&t4);
  540.  
  541.     /* use zortech display package */
  542.     disp_open();    
  543.     disp_move(0,0);
  544.     disp_eeop();
  545.  
  546.     /* start tasks up and wait for them to finish */
  547.     tasker.start_tasks();
  548.  
  549.     disp_move(0,0);
  550.     disp_eeop();
  551.     disp_close();
  552. }
  553.  
  554. static unsigned long counter[]={0L,0L,0L,0L,0L};
  555. static signal sig;
  556.  
  557. /* task 0 prints the values of the counters for the other 4 tasks. */
  558. /* lock is used to prevent task switching while the screen is being */
  559. /* updated. when the task is finished, block is called to transfer */
  560. /* control to the next task on the ready q */
  561.  
  562. void task0(void)
  563. {
  564.     while(1){
  565.         /* disable task switching */
  566.         tasker.lock();
  567.         disp_move(5,10);
  568.         disp_printf("Task 1  %lx",counter[1]);
  569.         disp_move(5,50);
  570.         disp_printf("Task 2  %lx",counter[2]);
  571.         disp_move(15,10);
  572.         disp_printf("Task 3  %lx",counter[3]);
  573.         disp_move(15,50);
  574.         disp_printf("Task 4  %lx",counter[4]);
  575.         /* if key pressed then stop the kernel and return */
  576.         if(kbhit())
  577.             tasker.stop_tasks();
  578.         /* re-enable task switching */
  579.         tasker.unlock();
  580.         /* let next task run */
  581.         tasker.block();
  582.     }
  583. }
  584.  
  585. /* tasks 1 and 2 just update counters. these tasks will run until */
  586. /* a timer interrupt occurs, so they get a very large chunk of time */
  587. /* to run, so the counters increase rapidly */
  588.  
  589. void task1(void)
  590. {
  591.     while(1){
  592.         counter[1]++;
  593.     }
  594. }
  595.  
  596. void task2(void)
  597. {
  598.     while(1){
  599.         counter[2]++;
  600.     }
  601. }
  602.  
  603. /* task 3 waits for a signal from task 4 each time the counter is */
  604. /* incremented. when a task waits, it is put on a signal q and the */
  605. /* next task on the ready q is run. this means task 3 and 4 counters */
  606. /* will increment very slowly. in task 4 when a signal is sent, the */
  607. /* task signal q is checked for a task to put on the ready q. the task */
  608. /* sending the signal is then placed on the ready q */
  609.  
  610. void task3(void)
  611. {
  612.     while(1){
  613.         counter[3]++;
  614.         /* wait for a signal from task 4 */
  615.         tasker.wait(&sig);
  616.     }
  617. }
  618.  
  619. void task4(void)
  620. {
  621.     while(1){
  622.         counter[4]++;
  623.         /* send signal to task 3 */
  624.         tasker.send(&sig);
  625.     }
  626. }
  627.  
  628.  
  629.  
  630.