GPF when task switching

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
codyd51
Member
Member
Posts: 77
Joined: Fri May 20, 2016 2:29 pm
Location: London, UK
GitHub: https://github.com/codyd51
Contact:

GPF when task switching

Post 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!
heat
Member
Member
Posts: 103
Joined: Sat Mar 28, 2015 11:23 am
Libera.chat IRC: heat

Re: GPF when task switching

Post 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).
If some of you people keep insisting on having backwards compatibitity with the stone age, we'll have stone tools forever.
My Hobby OS: https://github.com/heatd/Onyx
codyd51
Member
Member
Posts: 77
Joined: Fri May 20, 2016 2:29 pm
Location: London, UK
GitHub: https://github.com/codyd51
Contact:

Re: GPF when task switching

Post 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.
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: GPF when task switching

Post 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. :)
You know your OS is advanced when you stop using the Intel programming guide as a reference.
LtG
Member
Member
Posts: 384
Joined: Thu Aug 13, 2015 4:57 pm

Re: GPF when task switching

Post 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..
Post Reply