⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tskasm.asm

📁 一个多任务操作系统CTask的源代码 用C语言编写
💻 ASM
📖 第 1 页 / 共 2 页
字号:
;
;	--- Version 2.2 93-03-09 12:05 ---
;
;	CTask - Scheduler and miscellaneous utilities
;
;	Public Domain Software written by
;		Thomas Wagner
;		Ferrari electronic Gmbh
;		Beusselstrasse 27
;		D-1000 Berlin 21
;		Germany
;
;
; NOTE: Version 2.1 enables interrupts shortly after setting the in_sched
;	flag. This greatly reduces interrupt latency, but it has its
;	pitfalls. The main culprit is modification of the current TCB
;	while the scheduler is busy processing it. However, the situations
;	where a "foreign" TCB is modified are rare, so it is possible to
;	handle those cases in the appropriate places. Note that it was
;	never a good idea to make a task waiting or, worse yet, kill the
;	current task, while the scheduler was active. This was not detected
;	in previous releases, but would still cause crashes, even though
;	the interrupt was enabled only in the eligible waiting loop.
;
;	The only legitimate action is to make a task eligible while it
;	is being scheduled out. Since this only modifies the queue head
;	and task status, no adverse effects are possible. But making a task
;	waiting that is already scheduled out (as it was possible in
;	all versions up to 2.1) simply won't work. The wait action requires
;	that the scheduler switch the context away from the running task,
;	which it can't do when it's already active and there is no real
;	"running task". So the scheduler would immediately return, and 
;	the wait would never occur, most likely causing some very 
;	strange effects. Version 2.1 now explicitly checks for this error,
;	and will terminate the tasker if this should ever happen.
;
;	Other safeguards include not calling the INT 8 timer interrupt 
;	chain for early INT8 processing while the scheduler is active.
;	If INT8_EARLY is used, the tick chain is called from within
;	the interrupt handler. Since the interrupt could have hit 
;	while the scheduler was active, and the original INT 8
;	may have TSRs attached that call DOS, dangerous conflicts are
;	possible. For the same reason, the scheduler will set the In-DOS
;	flag while it is active, to keep TSR's activated by other
;	interrupts (esp. the keyboard) from calling DOS.
;	Using the same delayed processing as is used in INT 8 in 
;	the INT 9 handler was tried, but led to lost keystrokes depending
;	on system load.
;
;
;	Interrupts also are enabled in most other places in the scheduler,
;	except when traversing the queues.
;
;	Having interrupts enabled during the save/restore code also
;	eliminates a deadlock problem with floating point exceptions
;	on the 8087, which simplifies saving and restoring the '87 registers.
;
;
;	Version 2.2 eliminates a race condition problem introduced by
;	the above changes. If a task was made waiting, only the queue head
;	reflected this. Only after the scheduler had entered the task into
;	the appropriate queue would it have been safe to enable interrupts,
;	since an interrupt handler calling (indirectly) tsk_runable would
;	never see the task as waiting if it hit before this time.
;	To keep interrupts enabled, the logic was changed in that the
;	wait action now inserts the task into the appropriate queue
;	directly, without waiting for the scheduler to do it. Naturally,
;	this means a change here so that the scheduler doesn't touch
;	the queue if the task is already enqueued. Only if the task is not
;	in any queue is the queue head used to determine the place to
;	enqueue, which now can occur only when making a running task
;	eligible.
;
;**************************  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 MS-DOS 6.00 (Beta). It
;	also has been reported to work with DR-DOS 3.41.
;
;*************************************************************************
;
	name	tskasm
;
	include	tsk.mac
	include	tskdeb.h
;
	.tsk_model
;
	public	tsk_scheduler
	public	sched_int
;
	Pubfunc	schedule
	Pubfunc	yield
	Pubfunc	c_schedule
;
	Pubfunc	tsk_callfunc
	Pubfunc	tsk_dis_int
	Pubfunc	tsk_ena_int
	Pubfunc	tsk_cli
	Pubfunc	tsk_sti
	Pubfunc	tsk_dseg
	Pubfunc	tsk_flags
	Pubfunc	tsk_outp
	Pubfunc	tsk_inp
	Pubfunc	tsk_inpw
	Pubfunc	tsk_nop
	Pubfunc	tsk_memcpy
;
	IF	DOS
	Pubfunc	tsk_get_dosswap
	ENDIF
	IF	CLOCK_MSEC
	Pubfunc	tsk_timeout
	ENDIF
;
	IF	CHECKING
	CGlbext	tsk_fatal_pmd
	ENDIF
	IFDEF	SERSNAP
	Globext	tsk_cprint_getc
	Globext	comsnapshot
	ENDIF
;
	IF	DEBUG AND DEB_TSKDIS
	extrn	tsk_debtask: word
	ENDIF
	IF	DEBUG AND DEB_FLASHERS
	Pubfunc	tsk_inccdis
	extrn	tsk_debflash: word
	ENDIF
	IF	DEBUG
	extrn	tsk_dtposn: dword
	ENDIF
;
	global_ext
;
;
LSTACK_SIZE	=	512	; 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_savoff	=	2eh	; Offset of PSP stack pointer save dword
;
	IF	FAR_STACKS
ctask_stacks	segment word public 'CTASK_STACKS'
	ELSE
	.tsk_data
	ENDIF
;
	dw	LSTACK_SIZE dup(?)
slocal_stack	label	word
;
	IF	FAR_STACKS
ctask_stacks	ends
	.tsk_data
	ENDIF
;
	.tsk_edata
	.tsk_code
;
tsk_dgroup	dw	@CTASK_DATA
;
	IF	FAR_STACKS
stackseg	dw	SEG ctask_stacks
	ELSE
stackseg	equ	<tsk_dgroup>
	ENDIF
;
;------------------------------------------------------------------------
;
;	enq	Enqueue tcb in queue. For local use only.
;		entry:	es:di = tcb to enqueue
;		exit:	-
;		uses:	ax, cx, si
;	NOTE:	Interrupts must be disabled on entry.
;
enq	macro
;
	push	ds
	lds	cx,es:cqueue.q_next[di]	; this is NULL if not in queue
	mov	ax,ds
	or	ax,cx
	jnz	enq_end			; don't touch if enqueued
	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:
	test	q_kind[si],Q_HEAD	; at head?
	jnz	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:
	test	es:q_kind[di],Q_HEAD	; queue head?
	jnz	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
;
;-------------------------------------------------------------------------
;
	IF	DEBUG AND DEB_TSKDIS
;
fill8	macro
	mov	cx,10
	mov	ax,0720h
	rep stosw
	endm
;
@strcpy8	proc	near
	mov	cx,8
	mov	ah,7
strcpyl:
	lodsb
	or	al,al
	jz	strcpyfill
	stosw
	loop	strcpyl
;
strcpyfill:
	add	cx,2
	mov	al,' '
	rep stosw
	ret
;
@strcpy8	endp
;
	ENDIF
;
	IF	DEBUG AND DEB_FLASHERS
;
;	tsk_inccdis - increment counter on display
;
;	Entry:	AX is counter first digit offset
;		DS is CTask dgroup
;
;	Uses:	All registers preserved
;
Localfunc	tsk_inccdis
;
	push	ds
	push	si
	push	cx
	lds	si,tsk_dtposn
	add	si,ax
	mov	cx,DEBFLASH_NDIGS
	add	si,(DEBFLASH_NDIGS - 1) * 2
incdis_loop:
	inc	byte ptr [si]
	cmp	byte ptr [si],'9'
	jbe	incdis_end
	mov	byte ptr [si],'0'
	sub	si,2
	loop	incdis_loop
incdis_end:
	pop	cx
	pop	si
	pop	ds
	ret
;
tsk_inccdis	endp
;
	ENDIF
;
;-------------------------------------------------------------------------
;
;	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 we'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. Starting at version 2.0, the flag is located in the 
;	global variable block, to support ROM-based implementations, 
;	and to avoid multiple entries into the scheduler when code
;	sharing is not used in secondary invocations.
;
;	Version 2.1 sets the 'pretick' flag when the scheduler is busy.
;	This allows an immediate re-schedule if the schedule request
;	hit after loading the new task.
;
	push	ds
	push	bx
	mov	ds,cs:tsk_dgroup
	IF	SINGLE_DATA
	cmp	tsk_glob_rec.in_sched,0	; already in the scheduler?
	je	sched_ok	; continue if not
	mov	tsk_glob_rec.pretick,1	; mark schedule pending
	ELSE
	lds	bx,tsk_global
	cmp	in_sched[bx],0	; already in the scheduler?
	je	sched_ok	; continue if not
	mov	pretick[bx],1	; mark schedule pending
	ENDIF
	pop	bx
	pop	ds		; else return immediately
	iret
;
;	Not the second time into the scheduler, set the in_sched flag,
;	and load the current task's TCB.
;	If we're running under DOS, additionally set the In-DOS flag
;	for the reasons outlined above.
;
sched_ok:
	IF	SINGLE_DATA
	inc	tsk_glob_rec.in_sched
	IF	DOS
	push	ds
	lds	bx,tsk_glob_rec.dos_in_use
	inc	byte ptr ds:[bx]
	pop	ds
	ENDIF
	lds	bx,tsk_glob_rec.current_task
	ELSE
	inc	in_sched[bx]
	IF	DOS
	push	ds
	push	bx
	lds	bx,dos_in_use[bx]
	inc	byte ptr [bx]
	pop	bx
	pop	ds
	ENDIF
	lds	bx,current_task[bx]
	ENDIF
	push	ax
	mov	ax,ds
	or	ax,bx
	pop	ax
;
;	Registers are stored in the TCB to keep stack usage minimal.
;	If there is no current task (i.e. it has been killed),
;	we can't store the registers.
;
	IF	CHECKING
	jnz	chk_rsave
	jmp	no_rsave
chk_rsave:
	CHECK_TCBPTR_R	ds,bx,"scheduler: current task"
	ELSE
	jz	no_rsave
	ENDIF
	cmp	state[bx],ST_RUNNING
	jne	store_regs
	mov	state[bx],ST_ELIGIBLE
;
store_regs:
	sti
	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
;
;	This is the entry when restarting the scheduler.
;	Note that registers don't have to be saved again.
;
sched_restart:
no_rsave:
	cli
	mov	ss,cs:stackseg		; switch to local stack
	mov	sp,offset slocal_stack
	sti
	cld
	mov	ds,cs:tsk_dgroup	; establish addressing of our vars
	IF	DEBUG AND DEB_FLASHERS
	cmp	tsk_debflash,0
	je	debdd0
	mov	ax,DEBP_CNTSCHED
	call	tsk_inccdis
debdd0:
	ENDIF
	IF	DEBUG AND DEB_TSKDIS
	cmp	tsk_debtask,0
	je	debdd1
	les	di,tsk_dtposn
	mov	byte ptr es:[di],'*'
debdd1:
	ENDIF
	IF	SINGLE_DATA
	mov	bx,offset tsk_glob_rec
	ELSE
	lds	bx,tsk_global
	ENDIF
;
	cmp	var_prior[bx],0
	je	no_var_pri
;
	cli
	upd_prior			; update priorities in eligible queue
	sti
;
no_var_pri:
;
	and	preempt[bx],1		; Turn off temp preempt flag
;
	les	di,current_task[bx]	; get current tcb again
	mov	ax,es			; check if NULL (current task killed)
	or	ax,di
	IF	NOT CHECKING
	jz	no_current
	ELSE
	jnz	do_chkstk
	jmp	no_current
;
do_chkstk:
	push	ds
	lds	si,es:stkbot[di]
	mov	cx,8
	xor	ah,ah
chkstk:
	lodsb
	cmp	ah,al
	jne	stkoflo
	inc	ah
	loop	chkstk
	jmp	short chkstend
;
stkmsg	db	"Stack Overflow",0
;
stkoflo:
	callp	tsk_fatal_pmd,<<cs,#stkmsg>>
;
chkstend:
	pop	ds
	ENDIF
;
;	If there is a scheduler-entry routine in the current task's
;	TCB, call it. (This is used by the DOS handler module)
;	NOTE: Interrupts are still enabled.
;
	IF	DOS
	mov	ax,word ptr es:sched_ent_func[di]
	or	ax,word ptr es:sched_ent_func[di]+2
	jz	no_schentcall
;
	push	es
	push	di
	push	ds
	push	bx
	call	es:sched_ent_func[di]
	pop	bx
	pop	ds
	pop	di
	pop	es
;
no_schentcall:
	ENDIF
;
	cli
	enq				; Enqueue current task
	sti
;
no_current:
	lea	si,eligible_queue[bx]
;
;	Now we enter an enabled loop if there is no task in the
;	eligible queue.
;
wait_elig:
	IFDEF	SERSNAP
	callp	tsk_cprint_getc
	jz	nosnap
	or	al,20h
	cmp	al,'s'
	jne	nosnap
	push	bx
	callp	comsnapshot
	pop	bx
nosnap:
	ENDIF
	IF	DEBUG AND DEB_TSKDIS
	push	ds
	mov	ds,cs:tsk_dgroup
	cmp	tsk_debtask,0
	je	debdd2
	les	di,tsk_dtposn
	add	di,DEBP_ELIGTSK
	pop	ds
	push	ds
	push	si
	lds	si,q_first[si]
	test	q_kind[si],Q_HEAD
	jnz	deb101
	push	si
	lea	si,tname.nname[si]
	call	@strcpy8
	pop	si
	lds	si,q_next[si]
	test	q_kind[si],Q_HEAD
	jnz	deb102
	lea	si,tname.nname[si]
	call	@strcpy8
	jmp	short deb105
;
deb101:
	fill8
deb102:
	fill8
deb105:
	pop	si
debdd2:
	pop	ds
	ENDIF
	jmp	$+2			; allow ints
	cli
	les	di,q_first[si]
	test	es:q_kind[di],Q_HEAD
	jz	not_empty		; jump if not
;
	sti			       	; enable interrupts
	IF	DEBUG AND DEB_FLASHERS
	cmp	tsk_debflash,0
	je	debdidle
	mov	ax,DEBP_CNTIDLE
	call	tsk_inccdis
debdidle:
	ENDIF
	jmp	$+2
	jmp	wait_elig
;
;	Eligible queue not empty, activate first eligible task.
;	First, take it off the queue.
;	The preemption tick flag is cleared here. If it should be
;	set by an interrupt handler trying to schedule after this point,
;	we immediately re-schedule when hitting the end.
;
not_empty:
	CHECK_TCBPTR_R	es,di,"scheduler: eligible task"
	mov	pretick[bx],0		; No preemption tick
	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
;
;	Again, interrupts can be enabled here.
;
	sti
;
;	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	sched_complete		; don't save/restore if same task
;
chk_sav:
	or	ax,di
	jz	set_new_task		; 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	es			; push TCB address
	push	es			; push segment again
	pop	ds			; and put into DS
	push	di
;	
	call	es:save_func[di]	; call function
        add     sp,4
;
        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
	mov	es:t_new[di],0		; task is no longer new
;
	push	ds
	push	di
	mov	ax,ds
	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

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -