Page 1 of 1

an example realmode "multitasker" (context-switcher) routine

Posted: Sun Dec 12, 2010 1:55 am
by miker00lz
i just thought i'd post the context/task/whateveryoucallit switching routine from my 16-bit realmode OS. this works really well for me, and despite lots of info being available on the wiki i see a number of people who aren't sure how to go about it. doubt many of you are writing 16-bit OSes. it's quite a bit different from the hardware context switching you'll find in a 32-bit CPU, but this is more or less the same way they go about doing it. afaik, anyway. haven't done any 32-bit multitasking programming, but thats what i took away from what i've read.

this routine hooks interrupt 8h, which can't be pre-empted by any other interrupts. it is the highest priority level interrupt. read that as: DO NOT TRY TO USE INT 1Ch instead! ;) that will kinda work at first, but it's very unrealiable. and it WILL inevitably cause problems. this code has proven very solid for me so far. no problems yet. it took some trial and error to get it right. hopefully this can be of use to somebody. before somebody freaks out over my individual pushes instead of pusha/popa, keep in mind i'd like this to work even on an 8086 or 8088.

Code: Select all

;this is the heart of the RetrOS task/context switching. this routine hooks interrupt 8h
;and when it's done, performs a far jmp to the old handler.
jmp entry

curproc dw 0
maxproc dw 100
hookbusy db 0
count dw 0

entry:       
;  the FIRST thing we do is make sure no other interrupts are already active that we've pre-empted.
;  if there are, and we try to swap registers around in the middle of them your system  is going to
;  crash and burn hard. this involves reading the in-service register on the i8259 chip.
pushf
push ax
mov al, 0bh ;talk to the 8259 on port 20h
out 20h, al ;ask it what interrupts are in service 
in al, 20h ;get our answer
test al, 11111110b ; check to make sure that interrupts 1-7 aren't active
pop ax
jnz reallyfinishedandpopf ; are they? if so, just jump to the end and then we'll far jmp to the old 8h handler
popf

cmp cs:hookbusy, 1 ;has the kernel set the busy flag? for some reason it doesnt want us to taskswitch now?
jz reallyfinished ;then let's bail out because that would be bad
mov cs:hookbusy, 1 ;no, so lets set the busy flag. probably dont need to do this since we can't be re-entrant
inc cs:count          ; if we're on int 8h, but meh..

push bp
push di
push si
push ds
push es
push dx
push cx
push bx
push ax

mov ax, 0100h ;set ES to RAM area for the process array
mov es, ax
mov ax, cs:curproc
mov bl, 8
mul bl
mov si, ax
add si, 2  ;we need to save the task's SS:SP
mov es:[si], sp
add si, 2
mov es:[si], ss

findnext:
mov ax, cs:curproc
cmp ax, cs:maxproc
jz resetloop
inc ax
jmp continue

resetloop:
mov ax, 0

continue:
mov cs:curproc, ax
mov bl, 8
mul bl
mov si, ax
mov ax, es:[si]
cmp ax, 1 ;is the process curproc points to runnable?
jnz findnext ;if not, check go back and increment curproc

add si, 2  ;we need to retrieve the task's SS:SP
mov ax, es:[si]
mov sp, ax
add si, 2
mov ax, es:[si]
mov ss, ax

finished:
pop ax ;load registers back from the process' stack
pop bx
pop cx
pop dx
pop es
pop ds
pop si
pop di
pop bp
push ax ;next few lines clear our busy flag
mov al, 0
mov cs:hookbusy, al
pop ax
jmp reallyfinished ;skipping the popf

reallyfinishedandpopf:
popf

reallyfinished:
push ax
push bx
push es
mov ax, 0D00h ;the next few lines dump the old CS and IP of int 8h where they need to go from our vector array.
mov es, ax    ;the D00h is where the C kernel keeps an array of vectors. 8h is stored before it hooks this new routine.
mov ax, es:32 ;8 * 4 = 32
mov bx, es:34 ;8 * 4 + 2 = 34
mov cs:jmpip, ax
mov cs:jmpcs, bx
pop es
pop bx
pop ax
db 0EAh ;going to far jump to the original 8h handler
jmpip dw 0
jmpcs dw 0

and this is the array structure my C kernel sets up at segment 100h:

Code: Select all

struct structpid {
    unsigned isvalid; //so the task-switcher ISR knows if it should switch into a process, this will be 1 if okay.
    unsigned sp;
    unsigned ss;
    unsigned proctty; //which vtty does this PID use for stdin/stdout?
} far *proc = (void far *)MK_FP(0x100, 0x0);
unsigned far *curproc = (void far *)MK_FP(0xE00, 0x2); //my int 8h routine is at E00:0h, and we will keep track of it's curproc so the kernel can always know what the current running PID is when a system call is made.
i'm open to improvement suggestions. in fact, i'd love some if anybody has any good ideas! i'm pretty new to OS programming.

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Sun Dec 12, 2010 2:14 am
by tom9876543
Hello

Why not replace:

Code: Select all

push ax ;next few lines clear our busy flag
mov al, 0
mov cs:hookbusy, al
pop ax
with:

Code: Select all

mov cs:hookbusy, byte 0

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Sun Dec 12, 2010 2:16 am
by miker00lz
tom9876543 wrote:Hello

Why not replace:

Code: Select all

push ax ;next few lines clear our busy flag
mov al, 0
mov cs:hookbusy, al
pop ax
with:

Code: Select all

mov cs:hookbusy, byte 0
thanks for pointing that out, not sure that i was thinking. #-o

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Sun Dec 12, 2010 4:18 am
by tom9876543
Another question for you:

Have you considered changing the frequency of the PIT?

For example change divisor to 8192 so it fires approx 145 times per second. Then, only jump to the legacy BIOS code every 8th time.

I think the default of 18.21 ticks per second is a bit slow??

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Sun Dec 12, 2010 2:43 pm
by FlashBurn
I think the default of 18.21 ticks per second is a bit slow??
If he wants it to run also on 8068/88 then it should be ok.

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Sun Dec 12, 2010 3:00 pm
by miker00lz
yeah. although, 36.4 might still be a good number even on an 8088.

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Mon Dec 13, 2010 3:00 am
by Brendan
Hi,
tom9876543 wrote:I think the default of 18.21 ticks per second is a bit slow??
If the BIOS functions are being used then you can't avoid huge lag spikes. For example, someone decides to read a few sectors from floppy or from CD, and the CPU is unable to do anything until the BIOS function returns, which can takes ages (waiting for the motor to start, waiting for seek, etc). You also can't easily avoid "unfortunate timing" - for example, if a task keeps calling BIOS functions at the wrong time, then "hookbusy" can always be set during IRQ8 (causing task switching to be repeatedly skipped), and the task can hog all CPU time.

Unless these problems are fixed there isn't much point worrying about the frequency of IRQ8. However, to fix these problems you'd have to write native drivers for everything (so that there's no reason to use any BIOS functions). In my opinion, using real mode is a complete waste of time if you're going to write native drivers for everything anyway.


Cheers,

Brendan

Re: an example realmode "multitasker" (context-switcher) rou

Posted: Mon Dec 13, 2010 5:04 am
by tom9876543
I wonder how far the real mode OS will go.

I think from memory, FreeDOS has DMA hard disk drivers. The DMA drivers would somehow replace the standard BIOS calls I guess.

Also I guess it is possible to directly program the VGA skipping the BIOS (or use VESA).

So for the worst 2 aspects of BIOS performance, I guess they can be bypassed.