Task Switch

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Whitebird
Posts: 23
Joined: Wed Feb 02, 2011 12:30 pm
Location: Belo Horizonte, Minas Gerais, Brazil
Contact:

Task Switch

Post by Whitebird »

Hello every one,

First of all, sorry for my poor english.

I'm also developing an OS, using a monolithic approach. For now, I'm working on multitasking, more specifically, on a task switch function. It's better to inform previously that I have only one task that it's executing in the system (the kernel task), and only ring0 is enabled. Ok here is my theory for task switching:

1) IRQ 0 interrupt fires and loads an hardware interrupt handler hook - _clock_interrupt() - which is responsible for save general registers, load kernel data segment registers, switch to an interrupt stack and call the real handler function - do_clock_tick().

2) do_clock_tick() just decrement the current task's quantum, and increment the global variable ticks, witch only counts clock ticks since system has been powered on. When a task run out its quantum, do_clock_tick() calls schedule().

3) schedule(), just chooses a new task for execute by taking the next task in ready_task queue. Once a task was choosen, schedule() calls _switch_task(struct task_t* next_task).

4) _switch_task(struct task_t* next_task) is an assembly function, so it first saves the pointer passed to it as parameter. Next it saves ebp, esp, eip and cr3 registers, updates the global pointer curr_task to next_task, and finally loads the new task registers.

The problem is that, when system is switching to the new task for the first time all that stuff works, but, when comes the next clock tick, a page fault rises, when _switch_task(struct task_t* next_task) tries to load the new task registers.

Here it's the debug information that I have:

- When the page fault comes, I've got:

Code: Select all

[present] [fault_address : 0xFFFFFF08] [fault instruction: 0x0010152E]
The fault instruction is

Code: Select all

mov    (%edx),%eax
- If I add this before _switch_task() function, oddly, everything goes well.

Code: Select all

load db "loading...", 0


Here it's the code:

Code: Select all

/* This structure describes a task. */  
    struct task_t
    {
    	unsigned int esp;
    	unsigned int ebp;
    	unsigned int eip;
    	unsigned int cr3;
    	unsigned int kstack;
    	unsigned int ustack;
    	struct page_dir_t* dir;
    	unsigned int pid;
    	unsigned char* name;
    	unsigned char quantum;
    	struct task_t* next;
    };

Code: Select all

; Clock interrupt.
[GLOBAL _clock_interrupt] ; (void)(void)
_clock_interrupt:
	cli
	cld
	
	; Save registers.
	pushad
	push ds
    push es
    push fs
    push gs	
    
    ; Load kernel data segment register.
    mov ax, ss
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax   
    
    ; Switch to interrupt handler's stack. 
    mov ecx, esp          ; Before switching to another stack, save esp.
	mov edx, [curr_task]  ; Loads a new stack for the interrupt handler.	
	mov eax, [edx + 0x10] 
	mov esp, eax
	push ecx		      ; Save old esp into interrupt stack.
	
	call do_clock_tick
	
	; Switch to task's stack.
	pop ecx
	mov esp, ecx
	
	; Send reset signal to master.
	mov al,  0x20
	out 0x20, al
    
    ; Restore registers.
	pop gs
	pop fs
	pop es
	pop ds
	popad
	
	iretd

Code: Select all

/* Number of clock ticks since system has been powered on. */
PRIVATE unsigned int ticks = 0;

/* Handles a clock interrupt. */
PUBLIC void do_clock_tick()
{
	ticks++;
	curr_task->quantum--;
	if(curr_task->quantum == 0)
	{
		curr_task->quantum = 100;
		schedule();
	}
}

Code: Select all

/* Switch between tasks using a primitive roud robin algorithm. */
PUBLIC void schedule()
{	
	struct task_t* next_task = curr_task->next;
	if (next_task == NULL) next_task = ready_task;
	
	_switch_task(next_task);
}

Code: Select all

[EXTERN curr_task]
[EXTERN ready_task]

next_task dw 0
;load db "loading...", 0 --> IF I UNCOMMENT THIS, EVERYTHING GOES WELL.
	 
; Swtiches to given task.
[GLOBAL _switch_task] ; (void)(struct task_t*)
_switch_task:
	push ebp
	mov ebp, esp
	
	mov eax, [esp + 8]
	lea ebx, [next_task]
	mov [ebx], eax
	
	; Reads previously esp, ebp and eip for later use.
	call _read_eip
	mov ebx, esp
	mov ecx, ebp   
	
	cmp eax, 0xDEADBEEF
	jne .save_and_switch
	
	mov esp, ebp
	pop ebp	
	ret	
	
	.save_and_switch:
		; Save current task registers.
		mov edx, [curr_task]
		mov [edx], ebx       ; Saves esp.
		mov [edx + 0x4], ecx ; Saves ebp.
		mov [edx + 0x8], eax ; Saves eip.
	
		; Load next task.
		mov ecx, [next_task]
		lea edx, [curr_task]
		mov [edx], ecx
		
		; Loads the current task registers.
		mov edx, [curr_task]
		mov eax, [edx]		  ; --> HER EIS THE PROBLEM.
		mov esp, eax         ; Load esp.		
		mov eax, [edx + 0x4] 
		mov ebp, eax         ; Load ebp.
		mov ecx, [edx + 0x8] ; Load eip.
		mov eax, [edx + 0xC] 
		mov cr3, eax		 ; Load cr3.
		
		; Swiches to current task.
		mov eax, 0xDEADBEEF
		jmp ecx
What am I doing wrong?

Thanks for your attention.
Pedro H. Penna.

Undergraduate student of Computer Engineering.

Current OS Project: http://nanvix.blogspot.com/
User avatar
Nessphoro
Member
Member
Posts: 308
Joined: Sat Apr 30, 2011 12:50 am

Re: Task Switch

Post by Nessphoro »

Well I wouldn't switch before you're back to your "hardware interrupt handler hook," because you can screw up lot's of thing. (Example you switch the page directory but the ESP points to a position that doesn't exist in this directory, or has wrong values.) My switch function works this way,

Code: Select all

void Switch()
{
    SwitchRequested=true;
}
Then, at the end of every interrupt handler (one for IRQ, and one for "errors"), I have a stub

Code: Select all

           MOV EAX,[_SwitchRequested]
	CMP EAX,1
	JNE CleanUp ;Somewhere in the code there was a request to switch
	MOV BYTE [_SwitchRequested],0
	CALL _GetNextThread ;This sets "Running" to the next task(thread)
	MOV EAX,[_Running] ;Get the Task struct address
	MOV EAX,[EAX+12]  ;Pointer to PageDirectory of the task
	MOV EAX,[EAX+0x2000] ; Physical address of the Tables
	MOV CR3,EAX


	;We're now in the next tasks Virtual Memory
	MOV EBX,[_Running] ;Get the pointer again
	MOV EBX,[EBX+4] ;Get ESP0

	MOV ESP,EBX ;Set ESP0
	JMP CleanUp ;Jump - unnecessary 
	
CleanUp:
    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8
    iret
egos
Member
Member
Posts: 612
Joined: Fri Nov 16, 2007 1:59 pm

Re: Task Switch

Post by egos »

Make simplified working model firstly. Look at my example here.

Code: Select all

   ; Send reset signal to master.
   mov al,  0x20
   out 0x20, al
This code should be placed before call do_clock_tick.
Last edited by egos on Sun Aug 28, 2011 11:26 am, edited 2 times in total.
If you have seen bad English in my words, tell me what's wrong, please.
Whitebird
Posts: 23
Joined: Wed Feb 02, 2011 12:30 pm
Location: Belo Horizonte, Minas Gerais, Brazil
Contact:

Re: Task Switch

Post by Whitebird »

Make simlified working model firstly. Look at my example here.

Code:
; Send reset signal to master.
mov al, 0x20
out 0x20, al

This code should be placed before call do_clock_tick.
Ok, I've changed the reset signal location's but I'm still receiving page faults. Is the something wrong with my _switch_task() function?

Thank you for helping me!
Pedro H. Penna.

Undergraduate student of Computer Engineering.

Current OS Project: http://nanvix.blogspot.com/
Post Reply