Cooperative Multitasking: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (Mark as in progress, add rating)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(14 intermediate revisions by 13 users not shown)
Line 2: Line 2:
{{In_Progress}}
{{In_Progress}}


In this tutorial, we'll cover the creation of a kernel-level multitasking system. That is, kernel threads and processes. The code here is specific to the i386 architecture.
This tutorial will cover the creation of a co-operative (not pre-emptive) kernel-level multitasking system. That is, kernel threads and processes. The code here is specific to the IA-32 architecture.


== Requirements ==
== Requirements ==
Line 8: Line 8:


== ''task.h'' ==
== ''task.h'' ==
This header defines the function and types used in here. Its code is
This header defines the function and types used in here. It's code is
<source lang="c">
<syntaxhighlight lang="c">
#ifndef __TASK_H__
#ifndef __TASK_H__
#define __TASK_H__
#define __TASK_H__
Line 18: Line 18:


typedef struct {
typedef struct {
uint32_t eax, ebx, ecx, edx, esi, edi, esp, ebp, eip, eflags, cr3;
uint32_t eax, ebx, ecx, edx, esi, edi, esp, ebp, eip, eflags, cr3;
} Registers;
} Registers;


typedef struct Task {
typedef struct Task {
Registers regs;
Registers regs;
struct Task *next;
struct Task *next;
} Task;
} Task;


Line 29: Line 29:
extern void createTask(Task*, void(*)(), uint32_t, uint32_t*);
extern void createTask(Task*, void(*)(), uint32_t, uint32_t*);


extern void preempt(); // Switch task frontend
extern void yield(); // Switch task frontend
extern void switchTask(Registers *old, Registers *new); // The function which actually switches
extern void switchTask(Registers *old, Registers *new); // The function which actually switches


#endif /* __TASK_H__ */
#endif /* __TASK_H__ */
</syntaxhighlight>
</source>


== ''task.c'' ==
== ''task.c'' ==
This file defines the actual wrappers that ''switchTask()'' uses, ''createTask()'' and ''preempt()'':
This file defines the actual wrappers that ''switchTask()'' uses, ''createTask()'' and ''yield()'':
<source lang="c">
<syntaxhighlight lang="c">
#include "task.h"
#include "task.h"


Line 45: Line 45:


static void otherMain() {
static void otherMain() {
printk("Hello multitasking world!"); // Not implemented here...
printk("Hello multitasking world!"); // Not implemented here...
preempt();
yield();
}
}


void initTasking() {
void initTasking() {
// Get EFLAGS and CR3
// Get EFLAGS and CR3
asm volatile("movl %%cr3, %%eax; movl %%eax, %0;":"=m"(mainTask.regs.cr3)::"%eax");
asm volatile("movl %%cr3, %%eax; movl %%eax, %0;":"=m"(mainTask.regs.cr3)::"%eax");
asm volatile("pushfl; movl (%%esp), %%eax; movl %%eax, %0; popfl;":"=m"(mainTask.regs.eflags)::"%eax");
asm volatile("pushfl; movl (%%esp), %%eax; movl %%eax, %0; popfl;":"=m"(mainTask.regs.eflags)::"%eax");

createTask(&otherTask, otherMain, mainTask.regs.eflags, (uint32_t*)mainTask.regs.cr3);
createTask(&otherTask, otherMain, mainTask.regs.eflags, (uint32_t*)mainTask.regs.cr3);
mainTask.next = &otherTask;
mainTask.next = &otherTask;
otherTask.next = &mainTask;
otherTask.next = &mainTask;

runningTask = &mainTask;
runningTask = &mainTask;
}
}


void createTask(Task *task, void (*main)(), uint32_t flags, uint32_t *pagedir) {
void createTask(Task *task, void (*main)(), uint32_t flags, uint32_t *pagedir) {
task->regs.eax = 0;
task->regs.eax = 0;
task->regs.ebx = 0;
task->regs.ebx = 0;
task->regs.ecx = 0;
task->regs.ecx = 0;
task->regs.edx = 0;
task->regs.edx = 0;
task->regs.esi = 0;
task->regs.esi = 0;
task->regs.edi = 0;
task->regs.edi = 0;
task->regs.eflags = flags;
task->regs.eflags = flags;
task->regs.eip = (uint32_t)main;
task->regs.eip = (uint32_t) main;
task->regs.cr3 = (uint32_t)pagedir;
task->regs.cr3 = (uint32_t) pagedir;
task->regs.esp = (uint32_t)allocPage() + 0x1000; // Not implemented here
task->regs.esp = (uint32_t) allocPage() + 0x1000; // Not implemented here
task->next = 0;
task->next = 0;
}
}


void preempt() {
void yield() {
Task *last = runningTask;
Task *last = runningTask;
runningTask = runningTask->next;
runningTask = runningTask->next;
switchTask(&last->regs, &runningTask->regs);
switchTask(&last->regs, &runningTask->regs);
}
}
</syntaxhighlight>
</source>


== ''switch.S'' ==
== ''switch.S'' ==
This is the file that actually changes between tasks. It defines a function, ''switchTask()'', which does all the magic. It saves all registers to ''from'' and loads them from ''to''. It's more trickier than what you'll think. Its function prototype is
This is the file that actually changes between tasks. It defines a function, ''switchTask()'', which does all the magic. It saves all registers to ''from'' and loads them from ''to''. It is trickier than you might think. Its function prototype is
<source lang="c">
<syntaxhighlight lang="c">
void switchTask(Registers *from, Registers *to);
void switchTask(Registers *from, Registers *to);
</syntaxhighlight>
</source>


Its code is
Its code is
<source lang="asm">
<syntaxhighlight lang="asm">
.text
.section .text
.globl switchTask
.global switchTask
switchTask:
switchTask:
pusha
pusha
pushf
pushf
mov %cr3, %eax ;Push CR3
mov %cr3, %eax #Push CR3
push %eax
push %eax
mov 44(%esp), %eax ;The first argument, where to save
mov 44(%esp), %eax #The first argument, where to save
mov %ebx, 4(%eax)
mov %ebx, 4(%eax)
mov %ecx, 8(%eax)
mov %ecx, 8(%eax)
mov %edx, 12(%eax)
mov %edx, 12(%eax)
mov %esi, 16(%eax)
mov %esi, 16(%eax)
mov %edi, 20(%eax)
mov %edi, 20(%eax)
mov 36(%esp), %ebx ;EAX
mov 36(%esp), %ebx #EAX
mov 40(%esp), %ecx ;IP
mov 40(%esp), %ecx #IP
mov 20(%esp), %edx ;ESP
mov 20(%esp), %edx #ESP
add $4, %edx ;Remove the return value ;)
add $4, %edx #Remove the return value ;)
mov 16(%esp), %esi ;EBP
mov 16(%esp), %esi #EBP
mov 4(%esp), %edi ;EFLAGS
mov 4(%esp), %edi #EFLAGS
mov %ebx, (%eax)
mov %ebx, (%eax)
mov %edx, 24(%eax)
mov %edx, 24(%eax)
mov %esi, 28(%eax)
mov %esi, 28(%eax)
mov %ecx, 32(%eax)
mov %ecx, 32(%eax)
mov %edi, 36(%eax)
mov %edi, 36(%eax)
pop %ebx ;CR3
pop %ebx #CR3
mov %ebx, 40(%eax)
mov %ebx, 40(%eax)
push %ebx ;Goodbye again ;)
push %ebx #Goodbye again ;)
mov 48(%esp), %eax ;Now it is the new object
mov 48(%esp), %eax #Now it is the new object
mov 4(%eax), %ebx ;EBX
mov 4(%eax), %ebx #EBX
mov 8(%eax), %ecx ;ECX
mov 8(%eax), %ecx #ECX
mov 12(%eax), %edx ;EDX
mov 12(%eax), %edx #EDX
mov 16(%eax), %esi ;ESI
mov 16(%eax), %esi #ESI
mov 20(%eax), %edi ;EDI
mov 20(%eax), %edi #EDI
mov 28(%eax), %ebp ;EBP
mov 28(%eax), %ebp #EBP
push %eax
push %eax
mov 36(%eax), %eax ;EFLAGS
mov 36(%eax), %eax #EFLAGS
push %eax
push %eax
popf
popf
pop %eax
pop %eax
mov 24(%eax), %esp ;ESP
mov 24(%eax), %esp #ESP
push %eax
push %eax
mov 44(%eax), %eax ;CR3
mov 40(%eax), %eax #CR3
mov %eax, %cr3
mov %eax, %cr3
pop %eax
pop %eax
push %eax
push %eax
mov 32(%eax), %eax ;EIP
mov 32(%eax), %eax #EIP
xchg (%esp), %eax ;We do not have any more registers to use as tmp storage
xchg (%esp), %eax #We do not have any more registers to use as tmp storage
mov (%eax), %eax ;EAX
mov (%eax), %eax #EAX
ret ;This ends all!
ret #This ends all!
</syntaxhighlight>
</source>


== Doing it ==
== Doing it ==
Put this on some source file
Put this on some source file
<source lang="c">
<syntaxhighlight lang="c">
#include "task.h"
#include "task.h"


void doIt() {
void doIt() {
printk("Switching to otherTask... \n");
printk("Switching to otherTask... \n");
preempt();
yield();
printk("Returned to mainTask!\n");
printk("Returned to mainTask!\n");
}
}
</syntaxhighlight>
</source>


Now, from your ''kernel_main()'' call ''doIt()''!
Now, from your ''kernel_main()'' call ''doIt()''!


Congratulations! You've just implement kernel multitasking! If you want to call ''preempt()'' from your IRQ #0 handler, you should modify ''switchTask()'' to work with interrupts and ''iret''.
Congratulations! You've just implement kernel multitasking!

(Note: Article on proper kernel multi-tasking coming soon)

[[Category:C]]
[[Category:Multitasking]]
[[Category:X86 CPU]]

Latest revision as of 05:20, 9 June 2024

Difficulty level

Medium
This page is a work in progress.
This page may thus be incomplete. Its content may be changed in the near future.

This tutorial will cover the creation of a co-operative (not pre-emptive) kernel-level multitasking system. That is, kernel threads and processes. The code here is specific to the IA-32 architecture.

Requirements

task.h

This header defines the function and types used in here. It's code is

#ifndef __TASK_H__
#define __TASK_H__

#include <stdint.h>

extern void initTasking();

typedef struct {
    uint32_t eax, ebx, ecx, edx, esi, edi, esp, ebp, eip, eflags, cr3;
} Registers;

typedef struct Task {
    Registers regs;
    struct Task *next;
} Task;

extern void initTasking();
extern void createTask(Task*, void(*)(), uint32_t, uint32_t*);

extern void yield(); // Switch task frontend
extern void switchTask(Registers *old, Registers *new); // The function which actually switches

#endif /* __TASK_H__ */

task.c

This file defines the actual wrappers that switchTask() uses, createTask() and yield():

#include "task.h"

static Task *runningTask;
static Task mainTask;
static Task otherTask;

static void otherMain() {
    printk("Hello multitasking world!"); // Not implemented here...
    yield();
}

void initTasking() {
    // Get EFLAGS and CR3
    asm volatile("movl %%cr3, %%eax; movl %%eax, %0;":"=m"(mainTask.regs.cr3)::"%eax");
    asm volatile("pushfl; movl (%%esp), %%eax; movl %%eax, %0; popfl;":"=m"(mainTask.regs.eflags)::"%eax");

    createTask(&otherTask, otherMain, mainTask.regs.eflags, (uint32_t*)mainTask.regs.cr3);
    mainTask.next = &otherTask;
    otherTask.next = &mainTask;

    runningTask = &mainTask;
}

void createTask(Task *task, void (*main)(), uint32_t flags, uint32_t *pagedir) {
    task->regs.eax = 0;
    task->regs.ebx = 0;
    task->regs.ecx = 0;
    task->regs.edx = 0;
    task->regs.esi = 0;
    task->regs.edi = 0;
    task->regs.eflags = flags;
    task->regs.eip = (uint32_t) main;
    task->regs.cr3 = (uint32_t) pagedir;
    task->regs.esp = (uint32_t) allocPage() + 0x1000; // Not implemented here
    task->next = 0;
}

void yield() {
    Task *last = runningTask;
    runningTask = runningTask->next;
    switchTask(&last->regs, &runningTask->regs);
}

switch.S

This is the file that actually changes between tasks. It defines a function, switchTask(), which does all the magic. It saves all registers to from and loads them from to. It is trickier than you might think. Its function prototype is

void switchTask(Registers *from, Registers *to);

Its code is

.section .text
.global switchTask
switchTask:
    pusha
    pushf
    mov %cr3, %eax #Push CR3
    push %eax
    mov 44(%esp), %eax #The first argument, where to save
    mov %ebx, 4(%eax)
    mov %ecx, 8(%eax)
    mov %edx, 12(%eax)
    mov %esi, 16(%eax)
    mov %edi, 20(%eax)
    mov 36(%esp), %ebx #EAX
    mov 40(%esp), %ecx #IP
    mov 20(%esp), %edx #ESP
    add $4, %edx #Remove the return value ;)
    mov 16(%esp), %esi #EBP
    mov 4(%esp), %edi #EFLAGS
    mov %ebx, (%eax)
    mov %edx, 24(%eax)
    mov %esi, 28(%eax)
    mov %ecx, 32(%eax)
    mov %edi, 36(%eax)
    pop %ebx #CR3
    mov %ebx, 40(%eax)
    push %ebx #Goodbye again ;)
    mov 48(%esp), %eax #Now it is the new object
    mov 4(%eax), %ebx #EBX
    mov 8(%eax), %ecx #ECX
    mov 12(%eax), %edx #EDX
    mov 16(%eax), %esi #ESI
    mov 20(%eax), %edi #EDI
    mov 28(%eax), %ebp #EBP
    push %eax
    mov 36(%eax), %eax #EFLAGS
    push %eax
    popf
    pop %eax
    mov 24(%eax), %esp #ESP
    push %eax
    mov 40(%eax), %eax #CR3
    mov %eax, %cr3
    pop %eax
    push %eax
    mov 32(%eax), %eax #EIP
    xchg (%esp), %eax #We do not have any more registers to use as tmp storage
    mov (%eax), %eax #EAX
    ret #This ends all!

Doing it

Put this on some source file

#include "task.h"

void doIt() {
    printk("Switching to otherTask... \n");
    yield();
    printk("Returned to mainTask!\n");
}

Now, from your kernel_main() call doIt()!

Congratulations! You've just implement kernel multitasking!

(Note: Article on proper kernel multi-tasking coming soon)