Page 1 of 1

Multitasking code question.

Posted: Wed Nov 12, 2014 10:15 am
by ExeTwezz
Hi,

I have a code based on this http://hosted.cjmovie.net/TutMultitask.htm tutorial. It doesn't cause any errors, but it doesn't work for its purpose, switching tasks.

IRQ0 handler.

Code: Select all

global irq0
irq0:
    cli
    pusha
    push ds
    push es
    push fs
    push gs

    mov eax, 0x10               ; Data segment.
    mov ds, eax
    mov es, eax
    mov fs, eax
    mov gs, eax

    push esp                    ; Pointer to the stuff we just pushed.
    extern switch_task          ; multitasking/task.c
    call switch_task            ; Call C code.

    pop eax
    mov esp, eax                ; Replace the stack with the return value of
                                ; the C function.
    ; Send End Of Interrupt signal.
    mov al, 0x20
    out 0x20, al

    pop gs
    pop fs
    pop es
    pop ds
    popa
    iret
multitasking/task.h

Code: Select all

// Basic Operating System.
// Multitasking.

#ifndef TASK_H
#define TASK_H

#include <stdint.h>

// Task descriptor.
typedef struct task_s
{
    uint32_t esp0; // Stack for the kernel.
    uint32_t esp3; // Stack for the task.

    struct task_s *next; // The next task.
} __attribute__ ((packed)) task_t;

/* multitasking/task.c */
task_t  *create_task (void *);
uint32_t switch_task (uint32_t);

#endif /* !TASK_H */
multitasking/task.c

Code: Select all

// Basic Operating System.
// Multitasking.

#include <multitasking/task.h>
#include <memory/kmalloc.h>
#include <io/screen.h>
#include <stddef.h>

// Task queue.
task_t *task_queue;

// The current task.
task_t *current_task = NULL;

// Create a task.
task_t *create_task (void (*task_func))
{
    uint32_t *stack; // Pointer to the stack of the new task.

    // Create a new task.
    task_t *new_task = (task_t *) kmalloc (sizeof (task_t));

    // Create a stack for the task.
    stack = (uint32_t *) kmalloc (4096);
    new_task->esp0 = (uint32_t) stack;

    // Are there any tasks in the queue?
    if (task_queue == NULL)
    {
        // No. The created task will be the first one.
        task_queue = new_task;
    }
    else
    {
        // Yes. Make the task `new_task' the last one.
        task_t *last = task_queue; // The last task.
        while (1)
        {
            if (last->next != NULL)
                last = last->next;
            else
                break;
        }
        last->next = new_task;
    }

    // Fill in the task descriptor.
    // Set the registers.
    // First, the segment registers.
    *--stack = 0x10; // GS
    *--stack = 0x10; // FS
    *--stack = 0x10; // ES
    *--stack = 0x10; // DS

    // Next, the registers pushed by `pusha'.
    *--stack = 0; // EDI
    *--stack = 0; // ESI
    *--stack = 0; // EBP
    *--stack = 0; // ESP
    *--stack = 0; // EBX
    *--stack = 0; // EDX
    *--stack = 0; // ECX
    *--stack = 0; // EAX

    // Now these are the registers pushed automatically.
    *--stack = (uint32_t) task_func; // EIP
    *--stack = 0x08; // CS
    *--stack = 0x0202; // EFLAGS
    *--stack = 0; // USERESP
    *--stack = 0x10; // SS

    // Update the stack pointer.
    new_task->esp0 = (uint32_t) stack;
    new_task->next = NULL;

    return new_task;
}

// Switch between tasks.
uint32_t switch_task (uint32_t task_esp)
{
    puts ("switch_task()\n");

    // Is there the current task?
    if (current_task == NULL)
    {
        // No. Make the first task in the queue the current one.
        current_task = task_queue;
    }

    // Save the stack pointer of the task.
    current_task->esp0 = task_esp;

    // Switch to the next task.
    // Is there the next task?
    if (current_task->next == NULL)
    {
        // No. Switch to the start of the queue.
        current_task = task_queue;
    }
    else
    {
        // Yes. Switch to it.
        current_task = current_task->next;
    }

    // This is reached, and it's returned well from the function to the ISR.
    return current_task->esp0;
}
Tasks' functions

Code: Select all

// The task №1.
void task_1 (void)
{
    halt (); // The kernel will be still running, so the function is not called.
    uint8_t *vidmem = (uint8_t *) 0xB8001;
    for (;;) *vidmem++;
}

// The task №2.
void task_2 (void)
{
    uint8_t *vidmem = (uint8_t *) 0xB8003;
    for (;;) *vidmem++;
}
Tasks creating

Code: Select all

// The C kernel main.
void kernel_main (mb_info_t *mb_info, uint32_t kernel_end)
{
    init_screen ();
    clear_screen ();

    setup_idt ();
    init_keyboard_handler ();
    init_timer_handler ();
    asm volatile ("cli"); // Disable interrupts due to multitasking.

    <...>

    puts ("[kernel_main] Creating the task #1.\n");
    task_t *task1 = create_task (task_1);
    puts ("[kernel_main] Creating the task #2.\n");
    task_t *task2 = create_task (task_2);
    puts ("[kernel_main] Enabling interrupts.\n");
    asm volatile ("sti");
    puts ("[kernel_main] End of the function.\n");
}
Why aren't tasks' functions executed? Is it because it's something wrong with POPs in the IRQ0 handler?
`switch_task()' string is displayed on the screen each short (<1 sec.) period of time, but the tasks functions are not get called.

P.S. Are there spoilers on the forum?

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 12:31 pm
by Combuster
Do you know what the C ABI states on return values?

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 1:46 pm
by ExeTwezz
Combuster wrote:Do you know what the C ABI states on return values?
I've just removed "pop eax" instruction, and General Protection Fault occurs. What need I do to fix this? I tried some things with pushing to the stack, but it didn't help.

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 2:53 pm
by Combuster
The point is that you look up the answer, not magically try things.

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 3:03 pm
by milliburn
You should also check your stack allocation procedure (in create_task), remembering that the stack grows downwards in memory (as the code following demonstrates) while the allocation function presumably returns the lowest address of an allocation, to avoid memory corruption further down the road.

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 3:13 pm
by Kazinsal
You've disabled interrupts, then expected task switching in an interrupt handler to magically work.

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 10:09 pm
by ExeTwezz
Kazinsal wrote:You've disabled interrupts, then expected task switching in an interrupt handler to magically work.
Don't you see? I'm enabling interrupts after creating tasks in `kernel_main()`. Or do you mean in the interrupt handler? If so, then popping EFLAGS from the stack with set IF (9th bit) will enable itnerrupts. Isn't so?

Re: Multitasking code question.

Posted: Wed Nov 12, 2014 10:10 pm
by ExeTwezz
milliburn wrote:You should also check your stack allocation procedure (in create_task), remembering that the stack grows downwards in memory (as the code following demonstrates) while the allocation function presumably returns the lowest address of an allocation, to avoid memory corruption further down the road.
Yep, thank you. But it didn't help.

Re: Multitasking code question.

Posted: Thu Nov 13, 2014 7:57 am
by ExeTwezz
Please, help me. I changed the order of pushing the registers to the stack in create_task() and now I don't see any faults, but the tasks' functions are not get called. What's the problem?

Source code: https://yadi.sk/d/5k_M96cHcgvSV

Re: Multitasking code question.

Posted: Thu Nov 13, 2014 8:25 am
by ExeTwezz
I've changed

Code: Select all

// Switch between tasks.
uint32_t switch_task (uint32_t task_esp)
{
    // Is there the current task?
    if (current_task == NULL)
    {
        // No. Make the first task in the queue the current one.
        current_task = task_queue;
    }

    // Save the stack pointer of the task.
    current_task->esp0 = task_esp;

    <...>
to

Code: Select all

// Switch between tasks.
uint32_t switch_task (uint32_t task_esp)
{
    // Is there the current task?
    if (current_task == NULL)
    {
        // No. Make the first task in the queue the current one.
        current_task = task_queue;
    }
    else
    {
        // Save the stack pointer of the task.
        current_task->esp0 = task_esp;
    }

    <...>
And everything works fine.