Page 1 of 2

PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:15 am
by osdever
I have a problem with my preemptive multitasking code. It switches tasks nicely, but after it PIT doesn't fire anymore. This is my multitasking .c and .h code:

multitasking.h:

Code: Select all

#ifndef MULTITASKING_H
#define MULTITASKING_H
#include <stdint.h>
enum task_priority {
	PRIORITY_IDLE    = 1,  //Only for idle process.
	PRIORITY_LOW     = 5,
	PRIORITY_NORMAL  = 10, //Recommended for use by all regular processes.
	PRIORITY_HIGH    = 20,
	PRIORITY_MAXIMAL = 40, //It will slow down all processes with other priorities, but will work maximally fast.
};
/*
 * Reserved PIDs:
 * 
 * - 0 is idle process.
 * - 1 is init process. This is every process' parent.
 */
struct task
{
	struct task *prev;
	//We need to check if the process is dead, so on next PIT tick it will be removed from process list.
	uint8_t alive;      //On new process, it's set to 1.
	uint16_t time_out;  //Time before task switch
	int8_t exit_status; //Contains 0 when the process is alive. kill_process() sets it to provided value, nulls all register data, frees stack memory and makes alive field == 1.
	enum task_priority priority;
	void *stack; //Stack needs to be separated from kernel and other processes' stack.
	uint32_t eax, ebx, ecx, edx, eip, esi, edi, ebp, cr3;
	struct task *next;
};
extern struct task *process_list;
extern struct task *running_task;
void init_multitasking();
void switch_task(struct task*);
#endif
multitasking.c:

Code: Select all

#include <arch/i686/multitasking.h>
#include <arch/i686/regs.h>
#include <memory.h>
#include <stdio.h>
#include <tty_old.h>
#include <stddef.h>
struct task *process_list=NULL;
struct task *running_task;
void insertTaskAfter(struct task* a, struct task* b)
{
	b->prev = a;
	b->next = a->next;
	a->next = b;
	b->next->prev = b;
}
//What do you want?
//Arguments. Lots of arguments.
struct task *create_task(void *page_directory,
			             void *start_address,
			             enum task_priority priority)
{
	struct task *t = malloc(sizeof(struct task));
	printf("creating task, structure address=0x%x, code address=0x%x\n", t, start_address);
	t->eax         = 0;
	t->ebx         = 0;
	t->ecx         = 0;
	t->edx         = 0;
	t->esi         = 0;
	t->edi         = 0;
	t->stack       = malloc(4096) + 4096; //4kb will be enough
	t->eip         = (uint32_t) start_address;
	t->cr3         = (uint32_t) page_directory;
	t->priority    = priority;
	t->alive       = 1;
	t->exit_status = 0;
	t->time_out    = priority;
	t->next        = t; //It will point to start of process list.
	//Now we will put it into our process list.
	if(process_list)
		insertTaskAfter(process_list->prev, t);
	else
	{
		t->prev = t;
		process_list = t;
	}
	//process_list->prev = t;
	return t;
}
static uint32_t task_eip_address; //VERY bad code, but everything crashes without this
void __attribute__((noreturn)) switch_task(struct task *task)
{
	//We will change stack there, so we want to save EIP to jump.
	task_eip_address = task->eip;
	asm volatile("mov %0, %%eax;"
		"mov %1, %%ebx;"
		"mov %2, %%ecx;"
		"mov %3, %%edx;"
		"mov %4, %%esi;"
		"mov %5, %%edi;"
		"mov %6, %%ebp;"
		"mov %7, %%esp;" :
		"=g" (task->eax),
		"=g" (task->ebx),
		"=g" (task->ecx),
		"=g" (task->edx),
		"=g" (task->esi),
		"=g" (task->edi),
		"=g" (task->ebp),
		"=g" (task->stack) : : "memory");
	asm volatile("jmp *%0" : "=g" (task_eip_address) : : "memory");
	//If something happened, halt the system.
	while(1);
}
void idle_process()
{
	while(1)
		asm("sti; hlt");
}
void test_process()
{
	while(1)
		tty_putchar('a');
}
void test2_process()
{
	while(1)
		tty_putchar('b');
}
void test3_process()
{
	while(1)
		//tty_putchar('c');
		;
}
void init_multitasking()
{
	create_task(0, (void*) idle_process, PRIORITY_IDLE);
	create_task(0, (void*) test_process, PRIORITY_NORMAL);
	create_task(0, (void*) test2_process, PRIORITY_NORMAL);
	create_task(0, (void*) test3_process, PRIORITY_NORMAL);
	struct task *t;
	for(t = process_list; t->next!=process_list; t=t->next)
		printf("struct: 0x%x, code: 0x%x, priority: %d -> ", t, t->eip, t->priority);
	printf("struct: 0x%x, code: 0x%x, priority: %d", t, t->eip, t->priority);
	running_task = process_list; //Idle process enters the scene! Woo-hoo!
}
IRQ0 handler:

Code: Select all

void timer_handler(regs* r)
{
    tty_wrstr("Timer interrupt!\n");
    timer_ticks++; //Just increment timer_ticks.
    if(running_task->time_out) //Yes, it can be zero.
        running_task->time_out--;
    if(running_task->time_out == 0) //Our task's time is out, so we need to switch task.
    {
        running_task->time_out = running_task->priority;
        //Save current task state and switch to next task.
        running_task->eax    = r->eax        ;
        running_task->ebx    = r->ebx        ;
        running_task->ecx    = r->ecx        ;
        running_task->edx    = r->edx        ;
        running_task->esi    = r->esi        ;
        running_task->edi    = r->edi        ;
        running_task->eip    = r->eip        ;
        running_task = running_task->next    ;
        //Current task state saved. We will call switch_task to actually switch.
        printf("Interrupting current task! New EIP is 0x%x\n", running_task->eip);
        switch_task(running_task);
    }
}
I see that it runs idle task one time, then it switches to second and then PIT doesn't fire anymore, so nothing can interrupt second task and it displays 'a' all time.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:17 am
by BrightLight
Are you sending EOI? Is EFLAGS.IF set?
TIP: QEMU's monitor commands "info pic" and "info registers" are useful here. ;)

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:21 am
by osdever
omarrx024 wrote:Are you sending EOI? Is EFLAGS.IF set?
TIP: QEMU's monitor commands "info pic" and "info registers" are useful here. ;)
1) Oops, I'll add it now :)
2) Ok, wait, I'll use them.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:29 am
by osdever
Uhm...

Code: Select all

EFL=00200002 [-------]
I didn't set/save it, it cannot change. ](*,)

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:31 am
by BrightLight
catnikita255 wrote:Uhm...

Code: Select all

EFL=00200002 [-------]
I didn't set/save it, it cannot change. ](*,)
Whenever you run a task, always make sure EFLAGS.IF is set. Whenever loading the new register values from a task structure, do something like:

Code: Select all

task->eflags |= 0x200    // set IF

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 3:46 am
by osdever
omarrx024 wrote:
catnikita255 wrote:Uhm...

Code: Select all

EFL=00200002 [-------]
I didn't set/save it, it cannot change. ](*,)
Whenever you run a task, always make sure EFLAGS.IF is set. Whenever loading the new register values from a task structure, do something like:

Code: Select all

task->eflags |= 0x200    // set IF
I don't save EFLAGS for now, but wait, I'll add it.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 4:08 am
by osdever
No effect. Current code is:

Code: Select all

void __attribute__((noreturn)) switch_task(struct task *task)
{
	//We will change stack there, so we want to save EIP to jump.
	task_eip_address = task->eip;
	task->eflags |= 0x200;
	asm volatile("mov %0, %%eax;"
		"mov %1, %%ebx;"
		"mov %2, %%ecx;"
		"mov %3, %%edx;"
		"mov %4, %%esi;"
		"mov %5, %%edi;"
		"push %6;"
		"popf;"
		"mov %7, %%ebp;"
		"mov %8, %%esp;" :
		"=g" (task->eax),
		"=g" (task->ebx),
		"=g" (task->ecx),
		"=g" (task->edx),
		"=g" (task->esi),
		"=g" (task->edi),
		"=g" (task->eflags),
		"=g" (task->ebp),
		"=g" (task->stack) : : "memory");
	asm volatile("jmp *%0" : "=g" (task_eip_address) : : "memory");
	//If something happened, halt the system.
	while(1);
}

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 4:10 am
by BrightLight
Check QEMU's monitor again. What does EFLAGS contain? You return from the PIT IRQ handler with an IRET; the modified EFLAGS should be on the stack, not PUSH xx; POPF;.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 4:28 am
by osdever
omarrx024 wrote:Check QEMU's monitor again. What does EFLAGS contain? You return from the PIT IRQ handler with an IRET; the modified EFLAGS should be on the stack, not PUSH xx; POPF;.
EFLAGS contains [----ZAP-].
>You return from the PIT IRQ handler with an IRET
Maybe this is a mistake, but I call switch_task in PIT handler (or, more concretely, in function that PIT handler calls), and it never returns to IRET.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 4:30 am
by BrightLight
catnikita255 wrote:EFLAGS contains [----ZAP-].
>You return from the PIT IRQ handler with an IRET
Maybe this is a mistake, but I call switch_task in PIT handler (or, more concretely, in function that PIT handler calls), and it never returns to IRET.
lol, this is why I love assembly language. I know every instruction the CPU is executing and I never run into these problems. You should find a way to set the interrupt flag on the copy of EFLAGS on the stack.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 5:04 am
by osdever
Maybe I shouldn't use EFLAGS at all?

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 6:01 am
by BrightLight
catnikita255 wrote:Maybe I shouldn't use EFLAGS at all?
One way or another you will have to use it. What if a userspace application does something like this:

Code: Select all

cmp eax, 0x1234
jne some_code
And the PIT IRQ comes in after the CMP and before the JNE. When control eventually comes back to this task, the state of EFLAGS will be lost, and even when the program should jump to "some_code", it won't. EFLAGS is part of the machine state required to be saved by a scheduler.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 8:42 am
by Ch4ozz
All this data is pushed by CPU in reverse order when you enter an interrupt:

Code: Select all

uint32_t eip;
uint32_t cs;
uint32_t eflags;
uint32_t esp;
uint32_t ss;
when you leave the interrupt using IRET all this stuff will get popped again, which means you can change and save it before.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 9:44 am
by max
catnikita255 wrote:No effect. Current code is:

Code: Select all

void __attribute__((noreturn)) switch_task(struct task *task)
{
	//We will change stack there, so we want to save EIP to jump.
	task_eip_address = task->eip;
	task->eflags |= 0x200;
	asm volatile("mov %0, %%eax;"
		"mov %1, %%ebx;"
		"mov %2, %%ecx;"
		"mov %3, %%edx;"
		"mov %4, %%esi;"
		"mov %5, %%edi;"
		"push %6;"
		"popf;"
		"mov %7, %%ebp;"
		"mov %8, %%esp;" :
		"=g" (task->eax),
		"=g" (task->ebx),
		"=g" (task->ecx),
		"=g" (task->edx),
		"=g" (task->esi),
		"=g" (task->edi),
		"=g" (task->eflags),
		"=g" (task->ebp),
		"=g" (task->stack) : : "memory");
	asm volatile("jmp *%0" : "=g" (task_eip_address) : : "memory");
	//If something happened, halt the system.
	while(1);
}
This will not work. Your code will already have modified the registers before you even manage to save them using your inline assembly.

Conveniently, as Ch4ozz pointed out, a bunch of registers are automatically pushed on the current stack by the CPU itself when the interrupt handler is called. What you want to do is write a plain assembly routine that pushes all the remaining registers and then calls an interrupt handler routine. This will avoid that the values of any registers will be modified because you have full control. Usually, the task structure only has a pointer to the head of the stack, of a type which resembles a structure on which all the saved values lay on the stack.

Re: PIT stops after taskswitch

Posted: Wed Aug 31, 2016 12:01 pm
by Boris
Hello!
You should try to have one unique function that has for signature

Code: Select all

void  switch_task(struct task *taskFrom,struct task*taskTo)
Notice that:
* I removed noreturn. Why ? because you'll return in your task, when the task you got into will be preempted/terminated. Noreturn can make your compiler do nasty optimisations, like not saving any registers, or messing up the stack.
* You'll want the method to save the currentregisters , and load the next registers in one go (making the current task paused after the call to switch_task

I suggest using iret instead of ret (or instead of jmp like you are doing.) because IRET is able to restore EIP/EFLAGS/ESP/CS, and more.. The trick is to have in your "taskTo" structure, the classics registers (EAX...), and then, the registers needed by IRET.
You'll have to make ESP point into your taskTo structure, and then, just pop the registers to restore them, then do IRET