This little HowTo is intended to show, how to set up a multitasking environment for a custom OS.
If you have questions: don't hesitate to drop me a mail.
For the beginning: you will of course need several Knowledge like how to handle linked Lists or
BinaryTrees, which I won't cover in this text. I will show you the basics of a stack-based multitasking
subsystem.
Multitasking on a single processor machine: to switch between a bunch of processes in a quick manner:
each of them posesses for either a certain time or until it gives it up - the CPU.
First, lets define the process structure: it covers the essential informations about a process and
is the MAIN element an operating system uses to handle processes.
typedef struct {
uint_t prozess_esp; //actual position of esp
uint_t prozess_ss; //actual stack segment.
uint_t prozess_kstack; //stacktop of kernel stack
uint_t prozess_ustack; //stacktop of user stack
uint_t prozess_cr3;
uint_t prozess_number;
uint_t prozess_parent;
uint_t prozess_owner;
uint_t prozess_group;
uint_t prozess_timetorun;
uint_t prozess_sleep;
uint_t prozess_priority;
uint_t prozess_filehandle;
console_t *prozess_console;
memtree_t *vmm_alloc; //memory management info concerning the process
//- all its allocated virtual memory
uchar_t prozess_name[32];
} prozess_t;
typedef struct {
ushort_t backlink, __blh;
uint_t esp0;
ushort_t ss0, __ss0h;
uint_t esp1;
ushort_t ss1, __ss1h;
uint_t esp2;
ushort_t ss2, __ss2h;
uint_t cr3;
uint_t eip;
uint_t eflags;
uint_t eax, ecx, edx, ebx;
uint_t esp, ebp, esi, edi;
ushort_t es, __esh;
ushort_t cs, __csh;
ushort_t ss, __ssh;
ushort_t ds, __dsh;
ushort_t fs, __fsh;
ushort_t gs, __gsh;
ushort_t ldt, __ldth;
ushort_t trace, bitmap;
} tss_t;
%macro REG_SAVE 0
;save all registers in the kernel-level stack of the process and switch to the kernel stack
cld
pushad
push ds
push es
push fs
push gs
mov ax,0x10 ;load kernel segments
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov eax,[p] ;put the adress of the struct of CURRENT PROCESS in eax.(the CONTENT of pointer p)
mov [eax],esp ;save esp in the location of esp in the CURRENT PROCESS-STRUCT.
lea eax,[kstackend] ; switch to the kernel's own stack.
mov esp,eax
%endmacro
%macro REG_RESTORE_MASTER 0
mov eax,[p] ;put adress of struct of current process in eax.
mov esp,[eax] ;restore adress of esp.
mov ebx,[eax+8];put content of the k-stack field into ebx.
mov [sys_tss+4],ebx ;update system tss.
mov al,0x20
out 0x20,al
pop gs
pop fs
pop es
pop ds
popad
iretd
%endmacro
;and here is an example isr.
[EXTERN timer_handler]
[GLOBAL hwint00]
hwint00:
REG_SAVE
call timer_handler
REG_RESTORE_MASTER
| ring0->ring0 | ring0->ring3 | stack-position |
| - | ring3-ss | top of stack |
| - | ring3-esp | |
| eflags | eflags | |
| cs | cs | |
| eip | eip | |
| eax | eax | |
| ecx | ecx | |
| edx | edx | |
| ebx | ebx | |
| edi | edi | |
| esi | esi | |
| ebp | ebp | |
| esp | esp | |
| ds | ds | |
| es | es | |
| fs | fs | |
| gs | gs | low - address saved in p->prozess_esp |
prozess_t *task_anlegen(entry_t entry,int priority){
//various initialization stuff may be done before reaching the below described operations.
//filling in the kstack for start up:
uint_t *stacksetup; //temporary pointer - will be set to the
//kstacktop and afterwards saved in esp of proc structure.
...
stacksetup=&kstacks[d][KSTACKTOP];
*stacksetup--;
*stacksetup--=0x0202;
*stacksetup--=0x08;
*stacksetup--=(uint_t)entry; //EIP - the adress of the process' entry point (z.b. main());
*stacksetup--=0; //ebp
*stacksetup--=0; //esp
*stacksetup--=0; //edi
*stacksetup--=0; //esi
*stacksetup--=0; //edx
*stacksetup--=0; //ecx
*stacksetup--=0; //ebx
*stacksetup--=0; //eax
*stacksetup--=0x10; //ds
*stacksetup--=0x10; //es
*stacksetup--=0x10; //fs
*stacksetup= 0x10; //gs
//filling in the struct.
processes[f].prozess_esp=(uint_t)stacksetup;
processes[f].prozess_ss=0x10;
processes[f].prozess_kstack=(uint_t)&kstacks[d][KSTACKTOP];
processes[f].prozess_ustack=(uint_t)&stacks[d][USTACKTOP];
//global pointer to current task:
prozess_t *p;
//the isr:
void timer_handler(void){
if(task_to_bill->prozess_timetorun>0){
task_to_bill->prozess_timetorun--;
if(task_to_bill->prozess_timetorun<1){
schedule(0);
}
}
}
//the scheduler:
void schedule(int irq){
ELEMENT *proz;
if(!(queue_empty(&roundrobin_prozesse))&&p->prozess_timetorun<1){
p->prozess_timetorun=10;//refill timeslice.
//remove process from the head of the queue.
proz=remove_first_element_from_queue(&roundrobin_prozesse,0);
//put the element to the rear of the queue.
add_element_to_queue_rear(&roundrobin_prozesse,proz);
}
//pick the process at the head of any queue. (f. ex. round robin queue)
//and put it in p.
choose_prozess(irq);
}
typedef uchar_t unsigned char; // -->Länge: 8 bit
typedef ushort_t unsigned short; // -->Länge: 16 bit
typedef uint_t unsigned int; // -->Länge: 32 bit
typedef ulong_t unsigned long; // -->Länge: 64 bit
typedef void (*entry_t)(void);