Multitasking hell

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.
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Multitasking hell

Post by matthias »

Hi,

My kernel is now in the stage of building in multitasking. Well I thought that's not hard to do I'll just port the scheduling code of my old OS and build new functions for creating processes/thread etc. Well I've cheked all over my kernel, debugged every file, but still didn't get an answer on why my kernel get's a general protection file every time I try to do a task switch.

In the current situation I have only one running thread, which is the current kernel thread. I've set up a TSS for my thread and loaded it with ltr, all goes fine here. I set up my execution queue which is ok as well. This is done following this code which is called from the kernel's main() function.

Code: Select all

extern unsigned long* page_directory;

/* tss structure */
typedef struct tss_s
{
   unsigned short previous, empty1;
   unsigned long esp0;
   unsigned short ss0, empty2;   
   unsigned long esp1;
   unsigned short ss1, empty3;
   unsigned long esp2;
   unsigned short ss2, empty4;
   unsigned long cr3;
   unsigned long eip;
   unsigned long eflags;
   unsigned long eax;
   unsigned long ecx;
   unsigned long edx;
   unsigned long ebx;
   unsigned long esp;
   unsigned long ebp;
   unsigned long esi;
   unsigned long edi;
   unsigned short es, empty5;
   unsigned short cs, empty6;
   unsigned short ss, empty7;
   unsigned short ds, empty8;
   unsigned short fs, empty9;
   unsigned short gs, empty10;
   unsigned short ldt, empty11;   
  
   unsigned short trapflag;
   unsigned short iomapbase;

} tss_t;

struct _thread;
struct _process;

typedef struct _thread
{
	id_t tid; // identifier of the thread

	unsigned long gdt; // entry of tss within gdt

	tss_t* tss; // this should be clear

	struct _process* parent;

	struct _thread* next; // support for the scheduler
	struct _thread* prev; // the same

} thread;

typedef struct _process
{
	char name[16]; // this should be clear

	thread* core; // This is the first thread of a process that can create other threads
				  // all properties of other thread of process are inherited by this one.

	dpl_t dpl; // dpl level

	id_t pid; // the identifier of the process
	id_t uid; // which user is running this process
	id_t gid; // which group is owner of this process

	size_t number; // number of threads

	struct _process* next; // for linked list
	struct _process* prev; // for linked list

} process;

typedef struct ___process_head_node
{
	/* points to next node */
	process* next;

	/* holds number of nodes in list */
	size_t number;

} __process_head_node;

typedef struct ___schedule_list_head_node
{
	/* points to next node */
	thread* next;

	size_t number;

} __schedule_list_head_node;

__process_head_node* process_head_node = 0;
__schedule_list_head_node* schedule_list_head_node = 0;

/* current scheduler node */
thread* node = 0;

/* proto */
void IdleTask();

/* initialize nodes */
void tasks_install()
{
	/* allocate head node */
	process_head_node = malloc(sizeof(__process_head_node));
	schedule_list_head_node = malloc(sizeof(__schedule_list_head_node));

	/* zero structure */
	memset(process_head_node, 0, sizeof(__process_head_node));
	memset(schedule_list_head_node, 0, sizeof(__schedule_list_head_node));

	CreateProcess("idle", 0, 0, (addr_t)&IdleTask);

	/* set first execution task */
	node = process_head_node->next->core;

	/* load task register */
	__asm__("ltr %%ax"::"a" (schedule_list_head_node->next->gdt));
}
This looks OK right?? Now we go to the CreateProcess() function which initializes a process structure put it in a list, create a main thread, fill it with stuff. Now I only implemented stuff for dpl0.

Code: Select all

id_t CreateProcess(char* name, id_t uid, dpl_t dpl, addr_t eip)
{
	/* allocate */
	process* p = malloc(sizeof(process));

	/* clear node */
	memset(p, 0, sizeof(process));

	/* copy name */
	strcpy(p->name, name);

	/* fill general stuff */
	p->dpl = dpl;
	p->gid = 0; // todo
	p->uid = uid;
	p->pid = 0; // todo
	p->number++;
	
	/* create child thread */

	/* allocate memory */
	p->core = malloc(sizeof(thread));

	/* clear node */
	memset(p->core, 0, sizeof(thread));

	/* set common stuff */
	p->core->parent = p;
	p->core->next = 0;
	p->core->prev = 0;
	p->core->tid = 0; // todo
	p->core->gdt = 0;

	p->core->tss = (tss_t*)AllocPage(); /* allocate in physical memory */

	/* clear page */
	ClearPage((addr_t*)p->core->tss);

	/* fill TSS with appropriate values */
	if(p->dpl == 0)
	{
		p->core->tss->eflags = EFLAG_BASE | EFLAG_INTERRUPT;
		p->core->tss->eax = 0;
		p->core->tss->ebx = 0;
		p->core->tss->ecx = 0;
		p->core->tss->edx = 0;
		p->core->tss->esi = 0;
		p->core->tss->edi = 0;
		p->core->tss->ebp = 0;
		p->core->tss->trapflag = 0;
		p->core->tss->iomapbase = 0;
		p->core->tss->cs = KERNEL_CS;
		p->core->tss->es = KERNEL_DS;
		p->core->tss->ds = KERNEL_DS;
		p->core->tss->fs = KERNEL_DS;
		p->core->tss->gs = KERNEL_DS;
		p->core->tss->ss = KERNEL_DS;
		p->core->tss->ss0 = KERNEL_DS;
		p->core->tss->ss1 = KERNEL_DS;
		p->core->tss->ss2 = KERNEL_DS;
		p->core->tss->cr3 = (addr_t)page_directory;
		//p->core->tss->esp = (addr_t)&sys_stack;
		p->core->tss->esp = AllocPage() + PAGE_SIZE;

		p->core->tss->esp0 = 0;
		p->core->tss->esp1 = 0;
		p->core->tss->esp2 = 0;
		p->core->tss->ldt = 0;
		p->core->tss->eip = (addr_t)eip;

		/* get gdt entry */
		p->core->gdt = 8 * GDTGetFreeSegment((addr_t)&p->core->tss, sizeof(tss_t), GDT_PRESENT | GDT_DPL0 | GDT_SYS | GDT_TSS32, GDT_AVAIL);
	}
	/* todo user tasks */

	/* insert in list */
	ProcessInsertNode(p);

	/* insert core thread into execution list */
	SchedulerInsertNode(p->core);

	return p->pid;
}
Some few support functions for linked lists:

Code: Select all


inline void ProcessInsertNode(process* p)
{
	if(process_head_node->next)
	{
		/* modify next node */
		process_head_node->next->prev = p;
	}
	else
	{
		/* nothing after this node */
		p->next = 0;
	}

	/* insert node */
	process_head_node->next = p;

	/* modify node */
	p->prev = 0;

	/*increment node count */
	process_head_node->number++;
}

inline void ProcessRemoveNode(process* p)
{
	/* chek if not end of list */
	if(p->next)
	{
		/* modify next and prev nodes */
		p->next->prev = p->prev;
		p->prev->next = p->next;
	}
	else
	{
		/* set previous node's next to zero (make it end of list) */
		p->prev->next = 0;
	}

	/* common stuff */
	process_head_node->number--;
}

inline void SchedulerInsertNode(thread* p)
{
	if(schedule_list_head_node->next)
	{
		/* modify next node */
		schedule_list_head_node->next->prev = p;
	}
	else
	{
		/* nothing after this node */
		p->next = 0;
	}

	/* insert node */
	schedule_list_head_node->next = p;

	/* modify node */
	p->prev = 0;

	/*increment node count */
	schedule_list_head_node->number++;
}

inline void SchedulerRemoveNode(thread* p)
{
	/* chek if not end of list */
	if(p->next)
	{
		/* modify next and prev nodes */
		p->next->prev = p->prev;
		p->prev->next = p->next;
	}
	else
	{
		/* set previous node's next to zero (make it end of list) */
		p->prev->next = 0;
	}

	/* common stuff */
	schedule_list_head_node->number--;
}

inline void SchedulerGetNextNode()
{
	if(node->next)
	{
		node = node->next;
	}
	else
	{
		node = schedule_list_head_node->next;
	}
}
Now here comes my scheduler, it faults on the point of jumping to the other thread:

Code: Select all

extern void switch_to(unsigned long);

void schedule(struct regs* r)
{
	/* get next thread */
	SchedulerGetNextNode();

        // load next TSS
	switch_to(node->gdt);
}

asm:

.globl _switch_to

_switch_to:
	ljmp *(%esp) <--------- faults here
        ret
tasks.c

So I cheked if the value pushed was ok and it is, the gdt entry excists and is valid. Is my TSS corrupted? Have been debugging for 3 days now :(

Thanks in advance!
The source of my problems is in the source.
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

Ok 1 bug is out, I have only one task now so switching to it over and over again could cause a gpf, but after editing code, It doesn't seem like the real problem, it gets killed at the first task switch.
The source of my problems is in the source.
User avatar
prajwal
Member
Member
Posts: 154
Joined: Sat Oct 23, 2004 11:00 pm
Contact:

Post by prajwal »

This is my suspect...

ljmp <tss desc> OR (__asm__ __volatile__("lcall <TSS Descriptot, 0x00"))
wil cause Switch to a Task whose info available at address knowm from TSS GDT Entry

LTR is used at the time of initalization to give a initial valid value and then on the value in TR is updated suitably by the CPU on Task Switchs by jumping to Task Gate or TSS Descriptor or by some other means....

I think removing LTR instruction might help.....
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

It does not :( I have gotten my code into a state of not getting it to fault, instead it hangs up, it just freezes after a task jump, that's possible because maybe I had forgotten to do a sti after the switch, nope, this is my handler routine:

Code: Select all

extern _schedule

; 32: IRQ0
_irq0:
    cli
    pusha
    push ds
    push es
    push fs
    push gs

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov eax, esp

    push eax
    mov eax, _schedule
    call eax
    pop eax

    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8
    sti
    iret
New scheduler code (temporary):

Code: Select all

unsigned long ticks = 0;

extern void switch_to(unsigned long);

thread* prev;

void schedule(struct regs* r)
{
	/* get next thread */
	SchedulerGetNextNode();

	if(ticks % 10000 == 0)
	{
		puts("1 second passed\n");
	}

	ticks++;

	int sw = 0;

	if(node != prev)
	{
		sw = 1;
	}

	prev = node;

	if(sw)
	{
		switch_to(node->gdt);
	}
}
It just hangs after the first switch. Even interrupts are off, this code should work right, it's just a plain copy of my old code which did work, I only changed the naming and the linked list stuff :(

Also some other changes:

Code: Select all

/* initialize nodes */
void tasks_install()
{
	/* allocate head node */
	process_head_node = malloc(sizeof(__process_head_node));
	schedule_list_head_node = malloc(sizeof(__schedule_list_head_node));

	/* zero structure */
	memset(process_head_node, 0, sizeof(__process_head_node));
	memset(schedule_list_head_node, 0, sizeof(__schedule_list_head_node));

	CreateProcess("init", 0, 0, 0);

	/* set first execution task */
	node = process_head_node->next->core;

	/* load task register */
	__asm__("ltr %%ax"::"a" (node->gdt));

	/* load in our idle taks */
	CreateProcess("idle", 0, 0, (addr_t)&IdleTask);
}
The source of my problems is in the source.
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

Anyone?? I excluded IDT, IRQ as a problems, even EFLAGS are OK, it just hangs when I jump, it does triple fault when I jump to the same task as the previous one, this is OK. The interruptable flag in each task is set, bit still it just hangs while I'm jumping to the TSS.
The source of my problems is in the source.
SpooK
Member
Member
Posts: 260
Joined: Sun Jun 18, 2006 7:21 pm

Post by SpooK »

How about a look at the GDT?
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

Then it should issue a general protection fault and it does not :(
The source of my problems is in the source.
SpooK
Member
Member
Posts: 260
Joined: Sun Jun 18, 2006 7:21 pm

Post by SpooK »

You'd be surprised how one thing can be indirectly caused by another.

Here is some corresponding generic assembly language code to help you on your way, it is a basic Ring0->Ring-3 Task Switch (NASM Syntax)...

Code: Select all

;Load the Task Register
	mov ax,GDT.TSS
	ltr ax

;Save/Set Ring-0/3 Stack Pointers
	cli
	mov DWORD[TSS.ESP_0],esp	;Ring-0 Stack
	mov eax,esp
	sub eax,4096			      ;Set Ring-3 Stack 4K behind the Ring-0 Stack
	mov DWORD[TSS.ESP],eax     ;Ring-3 Stack

;Pass execution to the Ring-3 Task
	push DWORD GDT.USER_DATA   ;SS
	push eax			          ;ESP
	push DWORD 0x00000206		;EFLAGS (Interrupt, Parity and RESERVED Flags set)
	push DWORD GDT.USER_CODE	;CS
	lea eax,[TASK_ADDRESS]     ;User Mode (Ring-3) Runtime Base
	push eax			          ;EIP
	iret				           ;Jumps to the Ring-3 Task

;Ring-3 Task Start
TASK_ADDRESS:
   mov ax,GDT.USER_DATA
   mov ds,ax
   mov es,ax
   mov fs,ax
   mov gs,ax

;Ring-3 Task Generic Runtime/Loop
TASK_LOOP:
   inc eax
   mov DWORD[0x000B8000],eax  ;Visually indicate execution of this code
   jmp TASK_LOOP
If it works (and you have Ring-3 access to the video memory), you should see a bunch of changing characters in the first 2 pixels of the screen due to EAX being incremented and copied constantly.
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

Sorry, your code is not of any help, but thanks anyway. But however I detected some strange behaviour. If I try the taskswitch on a real system, it just hangs, however in a virtual machine (MS VPC) it reboots and says it encountered a fault. What can be concluded from this?
The source of my problems is in the source.
User avatar
hailstorm
Member
Member
Posts: 110
Joined: Wed Nov 02, 2005 12:00 am
Location: The Netherlands

Post by hailstorm »

I hope this helps:

Try to clear the busy bit of the tss you are switching to, before you make the switch. It might be the problem...
User avatar
carbonBased
Member
Member
Posts: 382
Joined: Sat Nov 20, 2004 12:00 am
Location: Wellesley, Ontario, Canada
Contact:

Post by carbonBased »

Although, if this is the case, you shouldn't be switching to a busy task in the first place, I think.

--Jeff
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

hailstorm wrote:I hope this helps:

Try to clear the busy bit of the tss you are switching to, before you make the switch. It might be the problem...
Uhm if you'd taken some time to read my code, you could have seen that the task only has the interruptable flag set. So won't help
The source of my problems is in the source.
User avatar
hailstorm
Member
Member
Posts: 110
Joined: Wed Nov 02, 2005 12:00 am
Location: The Netherlands

Post by hailstorm »

Oke, I admit. I jumped in to quickly. :oops:

I have one question though about the code you've written (yes, I actually read it this time :D ).

In the code you initialize the structures for process management (I assume). So the idle process should be the first task you create, right?
Next, you assign the variable node to the next pointer of the just added idle task, which is null. If I am not misstaking, you are loading the task register with unknown value because node can't be filled with a valid pointer. But I can also be misstaking... (Als dat het geval is, dan niet slaan! :wink: )

Code: Select all

   CreateProcess("init", 0, 0, 0); 

   /* set first execution task */ 
   node = process_head_node->next->core; 

   /* load task register */ 
   __asm__("ltr %%ax"::"a" (node->gdt)); 
} 
User avatar
matthias
Member
Member
Posts: 158
Joined: Fri Oct 22, 2004 11:00 pm
Location: Vlaardingen, Holland
Contact:

Post by matthias »

hailstorm wrote:Oke, I admit. I jumped in to quickly. :oops:

I have one question though about the code you've written (yes, I actually read it this time :D ).

In the code you initialize the structures for process management (I assume). So the idle process should be the first task you create, right?
Next, you assign the variable node to the next pointer of the just added idle task, which is null. If I am not misstaking, you are loading the task register with unknown value because node can't be filled with a valid pointer. But I can also be misstaking... (Als dat het geval is, dan niet slaan! :wink: )

Code: Select all

   CreateProcess("init", 0, 0, 0); 

   /* set first execution task */ 
   node = process_head_node->next->core; 

   /* load task register */ 
   __asm__("ltr %%ax"::"a" (node->gdt)); 
} 
(slaat Hailstorm, zoals je kunt zien laadt ik een nieuwe gdt entry in CreateProcess(), die wordt toegewezen aan node->gdt, ltr laadt een tss selector, oftewel node->gdt ;))

Now in English, I updated my code to this version, which still doesn't work:

Code: Select all

/* initialize nodes */
void tasks_install()
{
	/* allocate head node */
	process_head_node = malloc(sizeof(__process_head_node));
	schedule_list_head_node = malloc(sizeof(__schedule_list_head_node));

	/* zero structure */
	memset(process_head_node, 0, sizeof(__process_head_node));
	memset(schedule_list_head_node, 0, sizeof(__schedule_list_head_node));

	CreateProcess("init", 0, 0, 0);

	/* set first execution task */
	node = process_head_node->next->core;

	/* load task register */
	__asm__("ltr %%ax"::"a" (node->gdt));

	/* load in our idle taks */
	CreateProcess("idle", 0, 0, (addr_t)&IdleTask);
}
Also could something be wrong with my interrupt handler??
The source of my problems is in the source.
User avatar
hailstorm
Member
Member
Posts: 110
Joined: Wed Nov 02, 2005 12:00 am
Location: The Netherlands

Post by hailstorm »

Matthias: Ouch. :D

DELETED TEXT...
Post Reply