Page 1 of 1

16bit stack switching (multitasking)

Posted: Thu Sep 13, 2007 9:51 am
by kubeos
I am having a problem with the stack switching.. I'm almost certain its something in my timer ISR. When an IRQ fires, what is pushed onto the stack before the ISR? Is it just the ip and the cs? This is 16bit real mode by the way..

Code: Select all

$timer:
	$push bp
	$push es
	$push ds
	$push di
	$push si
	$push ax
	$push bx
	$push cx
	$push dx
	$push fs
	$push gs

	$mov ax,0x1000
	$mov es,ax
	$mov ds,ax
        //get current task sp
        $mov ax,sp
        $mov [tasksp],ax
        //give the timer a stack
        $mov ax,0xd000
        $mov sp,ax

	if lockkernel==0
		lockkernel=1
		taskcnt=taskcnt+1
             	if taskcnt==10
			taskcnt=1
			lockkernel=0
			$jmp notasks
		endif
		temp=getarray(*task.used|taskcnt|2)
		if temp==1
                       //store this tasks sp
			putarray(*task.sp|curtask|2|tasksp)
			curtask=taskcnt
                      //get sp for next task
			tasksp=getarray(*task.sp|taskcnt|2)
		endif
		lockkernel=0
	endif

	$notasks:
        //make sure timer is firing by printing T
	$mov al,"T"
	$mov ah,0x0e
	$xor bx,bx
	$int 10h
	$mov ax,[tasksp]
	$mov sp,ax
	$mov al,0x20
	$out 0x20,al
	$pop gs
	$pop fs
	$pop dx
	$pop cx
	$pop bx
	$pop ax
	$pop si
	$pop di
	$pop ds
	$pop es
	$pop bp
	$iret

Any ideas or pointers to a good tutorial would be appreciated. Thanks.

Posted: Thu Sep 13, 2007 10:05 am
by Combuster
in 16-bit modes, an interrupt pushes CS, IP and FLAGS. Why you interleaved c and assembly code is beyond me, as this is probably not how your exact code looks like.

One bug I see right now is that you should make sure that ds, es and ss contain sane values before accessing memory. Right now they can be anything. You forgot to save and restore SS as well.

my compiler

Posted: Thu Sep 13, 2007 10:17 am
by kubeos
I'm using my own compiler and that's exactly how my isr looks, i just cut and pasted it.

Anyway, I didn't realize that the flags were pushed... also, es and ds are changed to 0x1000 for the duration of the timer routine.

As for ss, i don't change it at all anywhere (other than the start of the program)


I'll try compensating for the flags in my starttask() routine and see if that works... When iret is issued, it pops off IP then CS, then FLAGS? Or are the flags popped first?

EDIT: Okay, it's ip then cs, then flags... now just need to see what the initial flags should be at for starting a new task.

Posted: Thu Sep 13, 2007 11:44 am
by Dex
This may help:

Code: Select all

;
; ==============
;  Nano OS v2.1    copyright by Viktor Peter Kovacs (KVP)
; ==============  for the fasm 512 byte os contest of 2004
;
; This is a demo os, written to show some os design
; methods, and to proove that it's possible to put
; a simple multitasking os into 512 bytes...
;

; ---- constants ----

TIMER_IRQN equ (8)                                     ; hw. timer interrupt number
IRQ_BIT    equ (512)                                   ; 'irq enable' bit in the flags register

TS_FREE    equ (0)                                     ; task states
TS_READY   equ (1)
TS_SEND    equ (2)
TS_RECV    equ (3)
TS_PUTC    equ (4)
TS_GETC    equ (5)

E_TIDLIMIT equ (0ffffh)                                ; error code for task resource limit error

TID_NUM    equ (8)                                     ; maximal number of tasks allowed
TID_STACK  equ (512)                                   ; default task stack size

; ---- startup code ----

start0:                                                ; needed for compatibility
    jmp    start1
    nop
start1:
    jmp    07c0h:start2                                ; code segment alignement
start2:
    cli
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, stack_top
    cld
    mov    di, _bss_start                              ; zero the bss
    mov    cx, _bss_end-_bss_start
    xor    ax, ax
    rep    stosb

    push   es                                          ; change the irq vector table
    xor    bx, bx
    mov    es, bx
    mov    ax, [es:TIMER_IRQN*4]
    mov    [timer0l], ax
    mov    ax, [es:TIMER_IRQN*4+2]
    mov    [timer0h], ax
    mov    ax, timer_irq
    mov    [es:TIMER_IRQN*4], ax
    mov    ax, cs
    mov    [es:TIMER_IRQN*4+2], ax
    pop    es

    mov    cx, idle_task                               ; start idle task
    call   sys_exec
    mov    cx, prg1                                    ; start program 1 (rpc demo - data source)
    call   sys_exec
    mov    cx, prg3                                    ; start program 3 (hello world)
    call   sys_exec

    mov    ax, (TID_NUM-1)*2                           ; setup and enable the scheduler
    mov    [ctid], ax
    sti
idle_task:
    jmp    idle_task                                   ; the idle task

; ---- kernel code ----

timer_irq:
    push   bp                                          ; save task context
    push   di
    push   si
    push   dx
    push   cx
    push   bx
    push   ax

    mov    ax, [irqflg]                                ; kernel lock check
    or     ax, ax
    jz     timer_irq1
timer_irq0:
    pop    ax                                          ; restore task context
    pop    bx
    pop    cx
    pop    dx
    pop    si
    pop    di
    pop    bp
           db 0eah                                     ; jmp far to old handler (for compatibility)
timer0l    dw 00000h
timer0h    dw 00000h

timer_irq1:
    mov    ax, 1                                       ; lock kernel
    mov    [irqflg], ax

    mov    bx, [ctid]
    mov    ax, [tstate+bx]
timer_cmd1:
    cmp    ax, TS_SEND                                 ; ipc message send (blocking ipc)
    jnz    timer_cmd2
    mov    si, [msgtid+bx]
    mov    ax, [tstate+si]
    cmp    ax, TS_RECV
    jnz    timer_next
    mov    ax, [msgdat+bx]
    mov    [msgdat+si], ax
    mov    [msgtid+si], bx
    mov    ax, TS_READY
    mov    [tstate+si], ax
    jmp    timer_cmd_ok
timer_cmd2:
    cmp    ax, TS_PUTC                                 ; terminal write
    jnz    timer_cmd3
    mov    ax, [msgdat+bx]
    call   bios_putc
    jmp    timer_cmd_ok
timer_cmd3:
    cmp    ax, TS_GETC                                 ; terminal read
    jnz    timer_next
    call   bios_getc
    mov    [msgdat+bx], ax
    or     ax, ax
    jz     timer_next
timer_cmd_ok:                                          ; resume task
    mov    ax, TS_READY
    mov    [tstate+bx], ax
timer_next:

;; prints task states (see:TS_* constants; red=current task)
;; (used for testing, removed to save code space)
;    push   es
;    mov    ax, 0b800h
;    mov    es, ax
;    xor    di, di
;    mov    si, tstate
;debug_print:
;    lodsw
;    mov    ah, 15
;    add    al, '0'
;    stosw
;    cmp    si, tstate+TID_NUM*2
;    jnz    debug_print
;    mov    di, [ctid]
;    inc    di
;    mov    al, 12
;    stosb
;    pop    es

timer_next0:
    mov    bx, [ctid]                                  ; hard realtime round robin scheduler
    mov    [tstack+bx], sp
timer_next1:
    add    bx, 2
    and    bx, (TID_NUM-1)*2
    mov    [ctid], bx
    mov    ax, [tstate+bx]
    cmp    ax, TS_FREE
    jz     timer_next1
    mov    sp, [tstack+bx]

    xor    ax, ax                                      ; unlock kernel
    mov    [irqflg], ax
    jmp    timer_irq0

;
; <-
;  ax : char
;
bios_putc:                                             ; device i/o function (called internally)
    push   bp
    push   bx
    mov    ah, 00eh
    mov    bx, 00007h
    int    010h
    cmp    al, 13
    jnz    bios_putc1
    mov    al, 10
    int    010h
bios_putc1:
    pop    bx
    pop    bp
    ret

;
; ->
;  ax : char (0=no char)
;
bios_getc:                                             ; device i/o function (called internally)
    mov    ax, 00100h
    int    016h
    jnz    bios_getc1
    xor    ax, ax
    ret
bios_getc1:
    mov    ax, 00000h
    int    016h
    xor    ah, ah
    ret

; ---- standard library ----

;
; EXEC: Starts a new task
;
; <-
;  cx : code offset
; (ax, si)
; ->
;  bx : new tid (E_TIDLIMIT=out of task slots)
;
sys_exec:
    pushf                                              ; task table resource lock
    cli
    xor    bx, bx
sys_exec1:
    mov    ax, [tstate+bx]                             ; looks for a free task slot
    cmp    ax, TS_FREE
    jz     sys_exec2
    add    bx, 2
    cmp    bx, TID_NUM*2
    jnz    sys_exec1
    mov    bx, E_TIDLIMIT                              ; return error code
    popf
    ret
sys_exec2:
    mov    dx, bx
    mov    dh, dl
    xor    dl, dl
    mov    si, tstacks+TID_STACK-10*2                  ; allocate task stack
    add    si, dx
    mov    [tstack+bx], si
    mov    [si+7*2], cx                                ; set task startup context (cs:ip,flg)
    mov    ax, cs
    mov    [si+8*2], ax
    pushf
    pop    ax
    or     ax, IRQ_BIT
    mov    [si+9*2], ax
    mov    ax, TS_READY
    mov    [tstate+bx], ax                             ; activate new task
    shr    bx, 1
    popf                                               ; release task table lock
    ret

;
; EXIT: Exits a task
;
sys_exit:
    mov    bx, [ctid]
    mov    ax, TS_FREE
    mov    [tstate+bx], ax
    jmp    $

;
; SEND: Sends an ipc message
;
; <-
;  ax : msg
;  bx : dst
; (cx, bp)
;
sys_send:
    shl    bx, 1
    mov    cx, TS_SEND
sys_send0:
    mov    bp, [ctid]
    mov    [msgdat+bp], ax
    mov    [msgtid+bp], bx
    mov    [tstate+bp], cx
sys_send1:
    mov    ax, [tstate+bp]
    cmp    ax, TS_READY
    jnz    sys_send1
    ret

;
; RECV: Receives an ipc message
;
; ->
;  ax : msg
;  bx : dst
; (cx, bp)
;
sys_recv:
    mov    cx, TS_RECV
sys_recv0:
    mov    bp, [ctid]
    mov    [tstate+bp], cx
sys_recv1:
    mov    ax, [tstate+bp]
    cmp    ax, TS_READY
    jnz    sys_recv1
    mov    ax, [msgdat+bp]
    mov    bx, [msgtid+bp]
    shr    bx, 1
    ret

;
; PUTC: Outputs a character to the console
;
; <-
;  ax : chr
; (bx, cx, bp)
;
sys_putc:
    mov    cx, TS_PUTC
    jmp    sys_send0

;
; GETC: Inputs a character from the console
;
; (bx, cx, bp)
; ->
;  ax : chr (0=no char)
;
sys_getc:
    mov    cx, TS_GETC
    jmp    sys_recv0

; ---- application code ----

;; prg1 v1.0
;prg1:
;    call   sys_getc
;    or     ax, ax
;    jz     prg1
;    cmp    al, 'H'
;    je     prg1_1
;    call   sys_putc    
;    jmp    prg1
;prg1_1:
;    mov    cx, prg2
;    call   sys_exec
;    jmp    prg1

;
; prg1 v2.0
;  -on startup, starts a listerner task
;  -gets a character from the console and sends it via ipc
;  -on the 'H' key, starts a new 'hello world' process
;
prg1:
    mov    cx, prg2
    call   sys_exec
    mov    di, bx
prg1_0:
    call   sys_getc
    or     ax, ax
    jz     prg1_0
    cmp    al, 'H'
    je     prg1_1
    mov    bx, di
    call   sys_send
    jmp    prg1_0
prg1_1:
    mov    cx, prg3
    call   sys_exec
    jmp    prg1_0

;
; prg2 v1.0
;  -waits for incoming messages via ipc
;  -prints the message content on the console
;
prg2:
    call   sys_recv
    call   sys_putc
    jmp    prg2

;
; hello world v1.1
;  -prints the text 'Hello World!' on the console
;  -exits on completition
;
prg3:
     mov    si, prg3_str_hello
prg3_1:
     lodsb
     or     al, al
     jz     prg3_3
     call   sys_putc
     jmp    prg3_1
prg3_3:
     call   sys_exit

prg3_str_hello db 'Hello World!',13,0

; ---- boot flags ----
rb  510-$
dw  0aa55h

; ---- boot stack ----
rb  512
stack_top:

_bss_start:                                            ; uninitialized data area (cleared to 0 on start)

; ---- kernel data ----
irqflg rw 1                                            ; kernel lock
ctid   rw 1                                            ; current task

tstate rw TID_NUM                                      ; task states
tstack rw TID_NUM                                      ; task stack pointers
msgtid rw TID_NUM                                      ; message destination task ids
msgdat rw TID_NUM                                      ; message data

; ---- application data ----


; ---- task stacks ----

rb (512*4)-$

tstacks:
rb (TID_STACK*TID_NUM)

_bss_end:


me stupid

Posted: Thu Sep 13, 2007 2:06 pm
by kubeos
WOW i'm so DUMB.. :roll:

I didn't realize that the first task wasn't being run because when the taskcnt reached 10, it would reset taskcnt to 1... but then each time the irq fired it increases taskcnt... so it would NEVER be equal to 1..

Anyway, I got it working, and now have 16bit multitasking in Kube. I just need to work out better memory management to make room for background applications to run, and fix up my int21 functions to block the task switching until the syscall is done. <- easier than making them reentrant (i think)

Thanks for your replies.