home *** CD-ROM | disk | FTP | other *** search
- ;
- ; --- Version 2.0 89-12-14 12:24 ---
- ;
- ; CTask - Scheduler and miscellaneous utilities
- ;
- ; Public Domain Software written by
- ; Thomas Wagner
- ; Patschkauer Weg 31
- ; D-1000 Berlin 33
- ; West Germany
- ;
- ;
- ;************************** CAUTION: ***********************************
- ;
- ; If the DOS flag is set, the scheduler uses an undocumented feature
- ; of DOS versions >= 3.10 to save and restore certain variables
- ; local to the DOS kernel. This allows for fast, secure task switching
- ; with tasks owning different PSP's. To save and restore the complete
- ; state of DOS on every task switch would not be feasible with any
- ; other documented method.
- ;
- ; NOTE that this method relies on certain inner workings of DOS
- ; that may not be compatible with future releases or "compatible"
- ; operating systems. It has been tested with all DOS versions
- ; from 3.10 through 3.20, 3.30, up to PC-DOS 4.00. It
- ; has not been tested with any DOS-clones like those from DRI.
- ;
- ;*************************************************************************
- ;
- name tskasm
- .model large,c
- ;
- public tsk_scheduler
- public schedule
- public yield
- public c_schedule
- public sched_int
- ;
- public tsk_dis_int
- public tsk_ena_int
- public tsk_cli
- public tsk_sti
- public tsk_dseg
- public tsk_flags
- public tsk_outp
- public tsk_inp
- public tsk_inpw
- public tsk_nop
- ;
- public tsk_dgroup
- ;
- include tsk.mac
- ;
- LSTACK_SIZE = 256 ; local stack size in words
- ;
- INT_FLAG = 2h ; Int enable flag in upper byte of flag reg
- ;
- sr_flags = 8 ; Offset of flag register on task stack
- sr_ds = 2 ; Offset of DS on task stack
- ;
- psp_offset = 10h ; Offset of current PSP in DOS save area
- psp_savoff = 2eh ; Offset of PSP stack pointer save dword
- ;
- .tsk_data
- ;
- extrn tsk_global: dword
- ;
- dw LSTACK_SIZE dup(?)
- slocal_stack label word
- ;
- .tsk_edata
- .tsk_code
- ;
- tsk_dgroup dw @CTASK_DATA
- ;
- ;------------------------------------------------------------------------
- ;
- ; enq Enqueue tcb in queue. For local use only.
- ; entry: es:di = tcb to enqueue
- ; exit: -
- ; uses: ax, cx, si
- ;
- enq macro
- ;
- push ds
- lds si,es:qhead[di] ; queue head pointer
- mov ax,ds
- or ax,si
- jz enq_end ; nothing left to do if queue null
- ;
- mov cx,es:cqueue.q_el.q_prior[di]
- mov ax,es:cqueue.q_el.q_ini_prior[di]
- mov es:cqueue.q_el.q_prior[di],ax
- lds si,q_last[si] ; last queue element
- ;
- enq_loop:
- cmp q_kind[si],0 ; at head?
- je enq_found ; then insert
- cmp q_el.q_prior[si],cx ; else check priority
- jae enq_found ; if above or equal, insert
- ;
- lds si,q_prev[si] ; backup one element
- jmp enq_loop ; and try again
- ;
- enq_found:
- mov word ptr es:q_prev[di],si ; elem->prev = curr
- mov word ptr es:q_prev+2[di],ds
- mov ax,word ptr q_next[si] ; elem->next = curr->next;
- mov word ptr es:q_next[di],ax
- mov cx,word ptr q_next+2[si]
- mov word ptr es:q_next+2[di],cx
- mov word ptr q_next[si],di ; curr->next = elem;
- mov word ptr q_next+2[si],es
- mov si,ax
- mov ds,cx
- mov word ptr q_prev[si],di ; elem->next->prev = elem
- mov word ptr q_prev+2[si],es
- ;
- enq_end:
- pop ds
- ;
- endm
- ;
- ; upd_prior: Update priority of tasks in eligible queue.
- ; Only activated if tsk_var_prior is nonzero.
- ;
- ; entry: ds:bx = global variable block
- ; exit: -
- ; uses: ax,di,es
- ;
- ; NOTE: Contrary to what earlier versions said, this loop
- ; must be protected by interrupt disable.
- ; Since it steps through a pointer chain, and this
- ; pointer chain might be modified by external events
- ; (a task becoming eligible to run), there is indeed
- ; a danger of race conditions.
- ;
- upd_prior macro
- ;
- les di,eligible_queue.q_first[bx]
- ;
- pinc_loop:
- cmp es:q_kind[di],0 ; queue head?
- je updp_end
- inc es:q_el.q_prior[di]
- jnz pinc_nxt
- dec es:q_el.q_prior[di]
- pinc_nxt:
- les di,es:q_next[di]
- jmp pinc_loop
- ;
- updp_end:
- ;
- endm
- ;
- ;-------------------------------------------------------------------------
- ;
- ; The scheduler. Note that this routine is entered with the stack
- ; set up as for an interrupt handler.
- ;
- tsk_scheduler proc far
- ;
- cli ; better safe than sorry
- ;
- ; First, check if wer're already in the scheduler. This might
- ; happen if an interrupt handler schedules, and it would have
- ; catastrophic results if the scheduler would be re-entered.
- ; Previous versions used a code-segment based variable to store
- ; this flag. This version stores the flag in the global variable
- ; block, to support ROM-based implementations.
- ;
- push ds
- push bx
- mov ds,cs:tsk_dgroup
- lds bx,tsk_global
- cmp in_sched[bx],0 ; already in the scheduler?
- je sched_ok ; continue if not
- pop bx
- pop ds ; else return immediately
- iret
- ;
- ; store registers in TCB to keep stack usage minimal
- ;
- sched_ok:
- inc in_sched[bx]
- push ax
- lds bx,current_task[bx]
- mov ax,ds
- or ax,bx
- pop ax
- ;
- ; if there is no current task (i.e. it has been killed),
- ; we can't store the registers.
- ;
- jz no_rsave
- mov t_sp[bx],sp
- mov t_ss[bx],ss
- mov t_ax[bx],ax
- mov t_cx[bx],cx
- mov t_dx[bx],dx
- mov t_bp[bx],bp
- mov t_si[bx],si
- mov t_di[bx],di
- mov t_es[bx],es
- mov bp,sp
- or byte ptr sr_flags+1[bp],INT_FLAG ; enable interrupts on return
- mov ax,sr_ds[bp]
- mov t_ds[bx],ax
- ;
- no_rsave:
- mov ss,cs:tsk_dgroup ; switch to local stack
- mov sp,offset slocal_stack
- cld
- mov ds,cs:tsk_dgroup ; establish addressing of our vars
- lds bx,tsk_global
- ;
- cmp var_prior[bx],0
- je no_var_pri
- ;
- upd_prior ; update priorities in eligible queue
- ;
- no_var_pri:
- ;
- les di,current_task[bx] ; get current tcb
- mov ax,es ; check if NULL (current task killed)
- or ax,di
- jz no_current
- ;
- cmp es:state[di],ST_RUNNING
- jne not_eligible
- mov es:state[di],ST_ELIGIBLE
- ;
- not_eligible:
- ;
- enq ; Enqueue current task
- ;
- no_current:
- mov pretick[bx],0 ; No preemption tick
- and preempt[bx],1 ; Turn off temp preempt flag
- ;
- lea si,eligible_queue[bx]
- ;
- ; Now we enter an enabled loop if there is no task in the
- ; eligible queue.
- ;
- wait_elig:
- les di,q_first[si]
- cmp es:q_kind[di],0
- jne not_empty ; jump if not
- ;
- sti ; enable interrupts
- nop
- nop
- cli
- jmp wait_elig
- ;
- ; Eligible queue not empty, activate first eligible task.
- ;
- not_empty:
- push di
- push es
- mov bp,sp ; address new pointer thru BP
- ;
- mov ax,word ptr es:cqueue.q_next[di] ; next in eligible queue
- mov cx,word ptr es:cqueue.q_next+2[di]
- mov word ptr es:cqueue.q_next[di],0 ; mark not in queue
- mov word ptr es:cqueue.q_next+2[di],0
- mov di,ax
- mov es,cx
- mov word ptr q_first[si],di
- mov word ptr q_first+2[si],es ; eligible_queue.first = next
- mov word ptr es:q_prev[di],si
- mov word ptr es:q_prev+2[di],ds ; next->prev = eligible_queue
- ;
- ; Now check if the new task is the same as the old one.
- ; If that's the case, we skip the save/restore function calls,
- ; and the DOS variable copy.
- ;
- les di,current_task[bx] ; the previously running task
- mov ax,es
- cmp ax,[bp] ; same as the new one ?
- jne chk_sav ; jump if not same segment
- cmp di,2[bp]
- jne chk_sav ; jump if not same offset
- jmp no_setdos ; don't save/restore if same task
- chk_sav:
- or ax,di
- jz no_savedos ; don't save if no previous
- ;
- ; First, call the save function if one is defined in the old TCB.
- ;
- mov ax,word ptr es:save_func[di]
- or ax,word ptr es:save_func+2[di]
- jz no_savfcall ; jump if no save function
- ;
- push ds ; save our registers
- push bx
- push es
- push di
- push word ptr es:save_func+2[di] ; push address to call
- push word ptr es:save_func[di]
- mov bp,sp
- push es ; push TCB address
- push di
- mov ds,es:t_ds[di] ; load current DS
- mov es,es:t_es[di]
- call dword ptr [bp] ; call function
- add sp,8
- pop di ; restore registers
- pop es
- pop bx
- pop ds
- ;
- ; Save the DOS variables, and the DOS-related interrupt vectors
- ; in the old TCB.
- ;
- no_savfcall:
- IF DOS
- push ds
- mov ax,ds
- mov es:t_new[di],0 ; task is no longer new
- mov ds,es:base_psp[di] ; get base PSP address
- mov si,psp_savoff ; offset to save (caller's SS:SP)
- add di,psp_sssp ; destination
- movsw ; save two words
- movsw
- mov ds,ax
- mov cx,l_swap[bx] ; swap area addr & size
- jcxz no_swap_sav
- lds si,dos_vars[bx]
- rep movsb
- ;
- no_swap_sav:
- xor cx,cx ; copy int21-24 interrupt vectors
- mov ds,cx
- mov si,21h*4
- mov cx,8
- rep movsw
- ;
- pop ds
- ENDIF
- ;
- ; Save complete. Now check for a restore function in the new TCB.
- ;
- no_savedos:
- pop es
- pop di
- mov word ptr current_task[bx],di ; set tcb into current
- mov word ptr current_task+2[bx],es
- ;
- mov ax,word ptr es:rest_func[di]
- or ax,word ptr es:rest_func+2[di]
- jz no_resfcall ; jump if no restore function
- ;
- push bx ; save our regs
- push ds
- push di
- push es
- push word ptr es:rest_func+2[di] ; push addr to call
- push word ptr es:rest_func[di]
- mov bp,sp
- push es ; push TCB addr
- push di
- mov ds,es:t_ds[di] ; load current DS
- mov es,es:t_es[di]
- call dword ptr [bp] ; call restore function
- add sp,8
- pop es
- pop di
- pop ds
- pop bx
- ;
- ; Restore DOS variables and int vectors from new TCB.
- ;
- no_resfcall:
- IF DOS
- cmp es:t_new[di],0 ; is this TCB a fresh one?
- jne no_setdos ; then the DOS info isn't valid.
- ;
- push di
- push es
- push ds
- mov dx,ds
- mov ax,es
- mov ds,ax
- mov si,di
- add si,base_psp
- lodsw
- mov es,ax
- mov di,psp_savoff ; offset to restore (caller's SS:SP)
- movsw ; restore two words
- movsw
- ;
- mov ax,ds
- mov ds,dx
- mov cx,l_swap[bx]
- jcxz no_swap_rest
- les di,dos_vars[bx]
- mov ds,ax
- rep movsb
- ;
- no_swap_rest:
- mov ds,ax
- mov di,21h*4 ; restore int21-24
- xor cx,cx
- mov es,cx
- mov cx,8
- rep movsw
- ;
- pop ds
- pop es
- pop di
- ENDIF
- ;
- ; And that's it. Wrap it up by resetting the priority, resetting
- ; the in_sched flag, restoring the registers, and returning to
- ; the task.
- ;
- no_setdos:
- mov ax,es:q_el.q_ini_prior[di] ; reset current tasks priority
- mov es:q_el.q_prior[di],ax
- mov es:state[di],ST_RUNNING ; set task state
- ;
- mov in_sched[bx],0 ; reset scheduler active flag
- ;
- push es
- push di
- pop bx
- pop ds
- mov es,t_es[bx] ; restore all registers
- mov di,t_di[bx]
- mov si,t_si[bx]
- mov bp,t_bp[bx]
- mov dx,t_dx[bx]
- mov cx,t_cx[bx]
- mov ax,t_ax[bx]
- mov ss,t_ss[bx]
- mov sp,t_sp[bx]
- pop bx
- pop ds
- iret
- ;
- tsk_scheduler endp
- ;
- ;
- ;--------------------------------------------------------------------------
- ;
- ;
- ; sched_int
- ;
- ; Is the scheduler entry for interrupt handlers.
- ; It checks if preemption is allowed, returning if not.
- ; The stack is assumed to be set up as on interrupt entry.
- ;
- sched_int proc far
- ;
- push ds
- push bx
- mov ds,cs:tsk_dgroup
- lds bx,tsk_global
- cmp preempt[bx],0 ; preempt flags 0?
- jne no_sched1 ; no scheduling if set
- lds bx,current_task[bx] ; current running task
- test flags[bx],F_CRIT ; preemption allowed for this task?
- jnz no_sched ; no scheduling if flag set
- IF DOS
- cmp t_indos[bx],OWN_UPPER or SPAWNING
- je no_sched ; no scheduling if spawning
- ENDIF
- pop bx ; else go schedule
- pop ds
- jmp tsk_scheduler
- ;
- no_sched:
- mov ds,cs:tsk_dgroup
- lds bx,tsk_global
- no_sched1:
- mov pretick[bx],1 ; Mark preemption pending
- pop bx
- pop ds
- iret
- ;
- sched_int endp
- ;
- ;
- ; void far schedule (void)
- ;
- ; Entry for calling the scheduler. Rearranges the stack to
- ; contain flags.
- ; NOTE: Uses ax,bx.
- ;
- schedule proc far
- ;
- pop ax
- pop bx
- pushf
- push bx
- push ax
- cli
- jmp tsk_scheduler
- ;
- schedule endp
- ;
- ;
- ; void far yield (void)
- ;
- ; Entry for calling the scheduler with priority temporarily
- ; set to zero. Rearranges the stack to contain flags.
- ; NOTE: Uses ax,bx.
- ;
- yield proc far
- ;
- pop ax
- pop bx
- pushf
- push bx
- push ax
- push es
- mov es,cs:tsk_dgroup
- les bx,es:tsk_global
- les bx,es:current_task[bx]
- cli
- mov es:q_el.q_prior[bx],0
- pop es
- jmp tsk_scheduler
- ;
- yield endp
- ;
- ;
- ; void far c_schedule (void)
- ;
- ; Entry for conditionally calling the scheduler. Rearranges
- ; the stack to contain flags, then jumps to _sched_int.
- ; NOTE: Uses ax,bx.
- ;
- c_schedule proc far
- ;
- pop ax
- pop bx
- pushf
- push bx
- push ax
- cli
- jmp sched_int
- ;
- c_schedule endp
- ;
- ;--------------------------------------------------------------------------
- ;
- ; word tsk_dseg (void)
- ;
- ; Returns current contents of DS register.
- ;
- tsk_dseg proc near
- mov ax,ds
- ret
- tsk_dseg endp
- ;
- ;
- ; word tsk_flags (void)
- ;
- ; Returns current contents of Flag register.
- ;
- tsk_flags proc near
- pushf
- pop ax
- ret
- tsk_flags endp
- ;
- ;
- ; int tsk_dis_int (void)
- ;
- ; Returns current state of the interrupt flag (1 if ints were
- ; enabled), then disables interrupts.
- ;
- tsk_dis_int proc
- ;
- pushf
- pop ax
- mov al,ah
- shr al,1
- and ax,1
- cli
- ret
- ;
- tsk_dis_int endp
- ;
- ;
- ; void far tsk_ena_int (int state)
- ;
- ; Enables interrupts if 'state' is nonzero.
- ;
- tsk_ena_int proc istate: word
- ;
- cmp istate,0
- je teiend
- sti
- teiend:
- ret
- ;
- tsk_ena_int endp
- ;
- ;
- ; tsk_cli/tsk_sti: disable/enable int
- ; NOTE: These routines are normally replaced by intrinsics.
- ;
- tsk_cli proc
- cli
- ret
- tsk_cli endp
- ;
- ;
- tsk_sti proc
- sti
- ret
- tsk_sti endp
- ;
- ;
- ; tsk_inp/tsk_outp: input/output from/to port
- ; NOTE: These routines are normally replaced by intrinsics,
- ; except for Turbo C tsk_inpw.
- ;
- tsk_inp proc port: word
- ;
- mov dx,port
- in al,dx
- xor ah,ah
- ret
- ;
- tsk_inp endp
- ;
- ;
- tsk_inpw proc port: word
- ;
- mov dx,port
- in ax,dx
- ret
- ;
- tsk_inpw endp
- ;
- ;
- tsk_outp proc port: word, val: word
- ;
- mov dx,port
- mov al,byte ptr(val)
- out dx,al
- ret
- ;
- tsk_outp endp
- ;
- ;
- ; void tsk_nop (void)
- ;
- ; Do nothing. Used for very short delays.
- ;
- tsk_nop proc
- ;
- jmp short tnop1
- tnop1:
- jmp short tnop2
- tnop2:
- ret
- ;
- tsk_nop endp
- ;
- .tsk_ecode
- end
-
-
-