Page 1 of 1

GPF when task switching

Posted: Mon Aug 15, 2016 6:00 pm
by codyd51
Hi!

I'm trying to implement task switching in my OS. Creating a process and jumping to it seems fine, the problem arises when actually context switching. It seems that after the iret in task_switch, I get a GPF (error code 0), even when there's only one process switching to itself. I've included the relevant code sections below.

Code: Select all

typedef struct task {
	int id; //process id
	uint32_t esp; //stack pointer
	uint32_t stack_top; //top of stack
	uint32_t eip; //instruction pointer
	uint32_t cr3; //page directory
	struct task* next; //next task in linked list
	struct task* prev; //previous task
} task_t;

Code: Select all

void tasking_install() {
	printf_info("Initializing tasking...");

	//init first task (kernel task)
	ready_queue = (volatile task_t*)create_process(PRIO_HIGH, (uint32_t)test_1);
	current_task = (task_t*)ready_queue;
	current_task->next = current_task;
	current_task->prev = current_task;

	//add_process(create_process(PRIO_MED, (uint32_t)test_2));
	//add_process(create_process(PRIO_MED, (uint32_t)test_3));

	//create callback to switch tasks
	//add_callback((void*)switch_callback, 1, 1, 0);

	jumpstart_main_thread();
	ASSERT(1, "Failed to start tasking!");
}

static void jumpstart_main_thread() {
	asm volatile("mov %%eax, %%esp": :"a"(current_task->esp));
	asm volatile("pop %gs");
	asm volatile("pop %fs");
	asm volatile("pop %es");
	asm volatile("pop %ds");
	asm volatile("pop %ebp");
	asm volatile("pop %edi");
	asm volatile("pop %esi");
	asm volatile("pop %edx");
	asm volatile("pop %ecx");
	asm volatile("pop %ebx");
	asm volatile("pop %eax");
	asm volatile("iret");
}

task_t* create_process(int priority, uint32_t addr) {
	task_t* task = (task_t*)kmalloc(sizeof(task_t));
	memset(task, 0, sizeof(task_t));
	task->id = next_pid++;
	task->eip = addr;
	task->esp = (uint32_t)kmalloc(KERNEL_STACK_SIZE);
	asm volatile("mov %%cr3, %%eax" : "=a"(task->cr3));
	uint32_t* stack = (uint32_t*)(task->esp + KERNEL_STACK_SIZE);
	task->stack_top = task->esp;
	*--stack = 0x00000202; 		//eflags
	*--stack = 0x8;			//cs
	*--stack = (uint32_t)addr;	//eip
	*--stack = 0;			//eax
	*--stack = 0; 			//ebx
	*--stack = 0;			//ecx
	*--stack = 0;			//edx
	*--stack = 0;			//esi
	*--stack = 0;			//edi
	*--stack = task->esp + KERNEL_STACK_SIZE; //ebp
	*--stack = 0x10;		//ds
	*--stack = 0x10;		//fs
	*--stack = 0x10;		//es
	*--stack = 0x10;		//gs
	task->esp = (uint32_t)stack;
	printf_info("Created process (PID %d) with esp %x eip %x", task->id, task->esp, task->eip);
	return task;

Code: Select all

void task_switch() {
	//if we haven't initialized tasking yet, quit early
	//if (!current_task) return;

	asm volatile("push %eax");
	asm volatile("push %ebx");
	asm volatile("push %ecx");
	asm volatile("push %edx");
	asm volatile("push %esi");
	asm volatile("push %edi");
	asm volatile("push %ebp");
	asm volatile("push %ds");
	asm volatile("push %es");
	asm volatile("push %fs");
	asm volatile("push %gs");
	asm volatile("mov %%esp, %%eax":"=a"(current_task->esp));

	current_task = current_task->next;
	
	asm volatile("mov %%eax, %%cr3": :"a"(current_task->cr3));
	asm volatile("mov %%eax, %%esp": :"a"(current_task->esp));
	asm volatile("pop %gs");
	asm volatile("pop %fs");
	asm volatile("pop %es");
	asm volatile("pop %ds");
	asm volatile("pop %ebp");
	asm volatile("pop %edi");
	asm volatile("pop %esi");
	asm volatile("pop %edx");
	asm volatile("pop %ecx");
	asm volatile("pop %ebx");
	asm volatile("pop %eax");
	asm volatile("iret");
}
Does anyone know what might be causing this or how I could debug it? Thanks!

Re: GPF when task switching

Posted: Mon Aug 15, 2016 7:41 pm
by heat
Move all that inline assembly to a separate asm routine and it should work at least better(I'm on my phone right now so I won't read the tiny details on your code, probably ABI issues).

Re: GPF when task switching

Posted: Mon Aug 15, 2016 9:14 pm
by codyd51
Actually, upon further inspection, it seems to only GPF when switching back to PID 1 (main kernel task). This leads me to believe it's a problem with jumpstart_main_thread, though I'm not sure what the problem might be.

To test, I made 4 tasks, each running this code:

Code: Select all

while (1) {
    printf_info("PID: %d", getpid());
    syscall_yield();
}
Here's a screenshot of the output: https://www.dropbox.com/s/dsv2l59435rra ... 9.png?dl=0. As you can see, the first round of task switching works fine, it only throws a GPF when switching back to the main thread.

Re: GPF when task switching

Posted: Tue Aug 16, 2016 4:23 am
by BrightLight
The most common problem with multitasking has always been with the stack or the queue. After switching from PID 4 to PID 1, does it ensure there really isn't a PID 5 process running? Why don't you run in Bochs and inspect the log/registers during the time of fault?
I used to page fault when implementing my scheduler, and it was because of the stack. :)

Re: GPF when task switching

Posted: Wed Aug 17, 2016 1:25 pm
by LtG
First off, I'd recommend debugging, gdb works good with Qemu, trying to live without debugging is harder than learning it.

Based on your screenshot it looks like your DS is set to 0x1, which I assume is the null selector after which all data access (including push/pop) will cause an exception.

I'd also point out that AFAIK the compiler is free to re-order you asm statements so you should either put them in an asm file or at least keep them in the same asm block. I don't think the compiler is doing it in this case so it's likely not the cause, but better do it right than doing wrong and hoping for best.

Again, not likely the cause of your issue, but you are using a userland set segment selector, a malicious program could set SS to null selector and then when you try to do a task switch you'll reference that selector and cause an exception. With a flat model I'd be tempted to hardcode (using an enum/const) the selector into your asm since there's not much reason to store it and load it, same with the other segment registers. Though this depends on the environment you want to give to your userland processes, for me it's ok that some process gets interrupted and when it continues the segments are corrected even if they had invalid values when the interrupt happened, some other people might want to preserve the process state fully but I don't see much point..