User:Johnburger/Demo/Exec/Alloc/TSS
This module provides two routines to Allocate a new TSS.
Allocating the memory for a new TSS is easy, but the code assumes that the TSS will need some tailoring done to it before it will become a true TSS. For that reason Exec.Alloc.TSS
returns a Data Segment, and expects Exec.Alloc.TSS.Enable
to be CALL
ed when finished so that it can convert it to a true TSS.
You'll note that the code specifies that it initialises three significant fields: EFlags
, IOMap
and LDT
. The last two are obvious - so why is EFlags
important?
The two bits that are initialised are the Always-1
bit (which isn't strictly necessary: even if it was 0, it would be set to 1 by the CPU) - and the Interrupt Flag (IF
) bit. That means that this Task starts with interrupts enabled. What would happen if it started with them disabled? Since a User Task can't use the CLI
or STI
instructions, it would effectively mean that the User Task would be running with interrupts permanently disabled: not a good thing for an OS! Try it!
- If the User Code uses
User.Halt
to wait for its timeslice to end, then the CPU will be HALTed with interrupts disabled - effectively zombied. VMWare detects this and stops the emulation. VirtualBox just locks up.- To protect against this scenario, the
User.Halt
system implementation should ensure that interrupts are enabled (STI
) beforeHLT
is called. After all, that's what System Code is supposed to do - ensure that User Code can't get the system into strife!
- To protect against this scenario, the
- If the User Code uses
User.Yield
to end its timeslice prematurely, then the next Task will get its turn - but with no IRQs, neither the Timer nor the Keyboard can interrupt the system. Runaway! - If the User Code simply does nothing to yield, or just twiddles its thumbs between bouncing updates, then it will be the only task that ever runs - very, very fast!
Another thing to try here is to add | x86.EFlags.TF
That will start the Task with Single Step (Trace Flag (TF
)) on, and nothing will happen in the task until <Space>
is pressed - which will only execute one instruction and then pause again. Pressing <Enter>
will remove the Trace Flag and allow the Task to run normally.
Demo/Exec/Alloc/TSS.inc
;
; Exec/Alloc/TSS.inc
;
; This module provides functions to allocate and configure Task State Segments.
;-------------------------------------------------------------------------------
; This function allocates sufficient RAM for a TSS, and fills in the most
; important entries: EFlags, IOMap and LDT.
;
; It then returns a Data descriptor, ready for more customisation. To actually
; turn it into a TSS, call Exec.Alloc.TSS.Enable.
;
; Input: AX = LDT to use for the TSS
; Output: AX = GDT Descriptor for (future) TSS, or zero on error
; ES = GDT Descriptor unless AX is zero
; EAX = Memory Base
; ECX = Memory Size
Exec.Alloc.TSS:
Exec.TSS.EFlags EQU x86.EFlags.IF | x86.EFlags.1 ; Enable interrupts
%push Alloc.TSS ; Let's not leave these %defines just lying around...
%define %$LDT EBP - 4
%define %$Base EBP - 8
%define %$Size EBP - 12
ENTER 12, 0
MOV [%$LDT],EAX
MOV ECX,x86.TSS_size ; Size to allocate
CALL Exec.Alloc.RAM
TEST EAX,EAX ; Any left?
JZ .End ; Pity that!
MOV [%$Base],EAX
MOV [%$Size],ECX
MOV DL,Type.Mem(Data, DPL0, RW)
MOV DH,Gran.Mem(Byte, Small)
CALL Exec.Alloc.GDT.Mem ; Get Descriptor to use
TEST EAX,EAX ; Any left?
JZ .End ; Pity that! **** Memory leak! ****
MOV ES,AX ; Fresh Descriptor!
MOV ECX,[%$Size] ; Number bytes to Zero
XOR EDI,EDI ; Zero everything
CALL Exec.Alloc.RAM.Zero
MOV AX,[%$LDT]
MOV ECX,[%$Size]
MOV [ES:x86.TSS.LDT],AX
MOV [ES:x86.TSS.IOMap],CX
MOV DWORD [ES:x86.TSS.EFlags],Exec.TSS.EFlags
MOV EAX,[%$Base]
.End:
LEAVE
%pop
RET
;-------------------------------------------------------------------------------
; This function converts a Segment previously allocated with Alloc.TSS (above)
; into a true TSS. Note that as soon as this happens, the TSS is a candidate for
; switching.
;
; Input: ES = Descriptor to modify
; AH = System.TSS if TSS is to be a System TSS. Zero otherwise.
; Output: EBX = TSS Descriptor
; ES modified
Exec.Alloc.TSS.Enable:
; Note that I could use GS: overrides to access the GDT, but ES points to the
; soon-to-be TSS - a Bad Thing. This fixes that!
MOV EBX, ES ; Get Descriptor to modify
PUSH GS ; Get GDT into ES
POP ES
MOV [ES:EBX+x86.Desc.Type],BYTE Type.Sys(TSS, DPL0, 386)
OR [ES:EBX+x86.Desc.Mem.Gran],AH ; OR in System.TSS
RET