Cooperative Multitasking: Difference between revisions
Revision as of 20:29, 8 August 2017
| Difficulty level |
|---|
| Difficulty 2.png Medium |
This page is under construction! This page or section is a work in progress and may thus be incomplete. Its content may be changed in the near future.
In this tutorial, we'll 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. Its code is
#ifndef __TASK_H__ #define __TASK_H__ #include<stdint.h> externvoidinitTasking(); typedefstruct{ uint32_teax,ebx,ecx,edx,esi,edi,esp,ebp,eip,eflags,cr3; }Registers; typedefstructTask{ Registersregs; structTask*next; }Task; externvoidinitTasking(); externvoidcreateTask(Task*,void(*)(),uint32_t,uint32_t*); externvoidyield();// Switch task frontend externvoidswitchTask(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" staticTask*runningTask; staticTaskmainTask; staticTaskotherTask; staticvoidotherMain(){ printk("Hello multitasking world!");// Not implemented here... yield(); } voidinitTasking(){ // Get EFLAGS and CR3 asmvolatile("movl %%cr3, %%eax; movl %%eax, %0;":"=m"(mainTask.regs.cr3)::"%eax"); asmvolatile("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; } voidcreateTask(Task*task,void(*main)(),uint32_tflags,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; } voidyield(){ 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
voidswitchTask(Registers*from,Registers*to);
Its code is
.section.text .globalswitchTask switchTask: pusha pushf mov%cr3,%eax#Push CR3 push%eax mov44(%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) mov36(%esp),%ebx#EAX mov40(%esp),%ecx#IP mov20(%esp),%edx#ESP add4ドル,%edx#Remove the return value ;) mov16(%esp),%esi#EBP mov4(%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 ;) mov48(%esp),%eax#Now it is the new object mov4(%eax),%ebx#EBX mov8(%eax),%ecx#ECX mov12(%eax),%edx#EDX mov16(%eax),%esi#ESI mov20(%eax),%edi#EDI mov28(%eax),%ebp#EBP push%eax mov36(%eax),%eax#EFLAGS push%eax popf pop%eax mov24(%eax),%esp#ESP push%eax mov40(%eax),%eax#CR3 mov%eax,%cr3 pop%eax push%eax mov32(%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" voiddoIt(){ printk("Switching to otherTask... \n"); yeild(); printk("Returned to mainTask!\n"); }
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.