home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frostbyte's 1980s DOS Shareware Collection
/
floppyshareware.zip
/
floppyshareware
/
DOOG
/
CTASK.ZIP
/
TSKASM.ASM
< prev
next >
Wrap
Assembly Source File
|
1989-12-22
|
14KB
|
670 lines
;
; --- 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