Page 1 of 4

Multitasking Issues

Posted: Tue Apr 17, 2007 11:39 pm
by XCHG
Can anybody point me in the right direction for supporting multitasking in my kernel? I've been reading Intel manuals and searching a lot but unfortunately, have not found a tutorial that tells you what and why. I'd really appreciate it if somebody could give a link to a tutorial or something. Thanks in advance.

P.S: I've read wiki's pages but I'm still confused about TSS and etc.

Posted: Wed Apr 18, 2007 4:52 am
by digo_rp
first of all, sorry for my poor english.

to create a multitasking system, you have only two " 2 " choices

software and hardware taskswitching, software you do by change the

stack pointer, first you setup a stack layout like when a exception occurs.

and on that kinda of multitasking to use protection you have only one TSS

loaded be ltr(desc_in_GDT);

on each taskswitching on your scheduler you have to change the
TSS.SS0, TSS.ESP0 from your next_task_to_be_switched.

the other one kinda of multitasking is hardware taskswitching, but isn´t
used anymore in that new 64 processor in long mode.

but if you want to use it in 32 bits mode, go ahead, it is much slow then
software taskswitching.

on that kinda of taskswitching you have to do a jump_to_tss_in_GDT

in case you want, I have my kernel right now in software taskswitching

much used in other computer archtecture, RISC, MACINTOSH, SPARC,
ETC.

using software taskswitching is much faster.

just email me, then I can send to you my sources.

[email protected]

Posted: Wed Apr 18, 2007 7:37 am
by hakware
Right. Software taskswitching just requires swapping out all the registers (including IP and stack) with that of another task every so often (often done using a timer interrupt, though in cooperative multitasking there is a yield function that does that). Hardware multitasking does roughly the same thing, and requires no less work -- about the only positive I can think of is that it provides a structure for multitasking that's rather standardized among ia32 processors, but on the other hand it's much harder to work with and understand, and has the tendency to kill the whole system on a little fudge, whereas with software multithreading you can build in support for special cases and such.

Software task switching is really quite easy though. I'd be willing to write example code for it, but probably loads exists on the web anyway ;-)

Posted: Wed Apr 18, 2007 10:41 am
by frank

Posted: Wed Apr 18, 2007 11:18 am
by XCHG
Thank you so much everyone. I'm starting to get quite a better picture of what I should do and what I shouldn't. Alright let's say that I want to implement preemptive multitasking and I am going to do it by means of software. Now every process should have its own Code, Data and stack segment but let say IRQ0 is fired and my scheduler is trying to pass the control to a process's thread. Now if I change the CS to another segment selector that I will create for that process on the fly, the IRQ0 will not be able to be handled by my kernel anymore because CS is changed. What should I do then? should I just use CS that I am using for my kernel? That would be neither rational nor safe, right? Thank you again everyone.

Posted: Wed Apr 18, 2007 1:01 pm
by jnc100
What you need to do is to have the interrupt handler code available at the same address in each processes address space. Then, by switching the value of ESP and EIP stored on the stack (ESP is only stored if the privilege level changes) you effect the task switch. Most people use paging and map the kernel's pages (which presumably include the task switch interrupt handler) at the same location in every process. You then just change the page directory base register for each task switch and the interrupt handler continues as it was, because the handler is still in the same location.

Regards,
John.

Posted: Thu Apr 19, 2007 2:57 am
by XCHG
Alright, let me see if I got it right. suppose I am not using Paging and all addresses are physical. Now what I should do is to create the scheduler in the kernel. A process can be created with a procedure or a function that I should code. A process has 1 main thread. Each of the processes have to have their own segments and etc. When a process is created, the scheduler should check for its priority and other settings and then transfer the control to that process while having saved kernel's segments in process's stack.

There are certain points that I really can't figure out about this scheme:

1) How am I supposed to handle IRQs in processes while I should not have control over what code is written for them?

2) Suppose a process is running at the privilege level of 3. How is the kernel's scheduler supposed to control it while all segment registers are changed?

I'd really appreciate it if somebody could clear these points for me. By the way, than you everyone for your responses.

Posted: Thu Apr 19, 2007 3:03 am
by pcmattman
You generally don't change it during the IRQ, but at the end. For instance, if your IRQ handler gets passed a pointer to a structure of registers (taken from the IRQ stack) then you can change the values there and then the structure's new data will be popped off the stack. That's what I do, though it doesn't really work and I've opted to remove multitasking from my OS.

Posted: Sun Apr 22, 2007 2:12 am
by XCHG
Alright. I finally implemented a very basic scheduler but I still seem to have problems with context switching. The problem is that I don't understand whether I should use TSS for software context switching or not? I have not yet implemented paging yet so every memory location is physical (for now). I'm just really confused now so if any of you could, please, give me a general idea on how this should be done, it would be great. I mean should I use call gates? interrupts? who am I? who are you?

I really need a source that describes how CPL, DPL, RPL and other things should be changed, how I should move from DPL 0 to DPL 3 and vice versa and etc. I'd really appreciate it if somebody could explain these for me or could point me to a tutorial of some type.

Thanks in advance.

Posted: Sun Apr 22, 2007 5:09 am
by INF1n1t
XCHG wrote:Alright. I finally implemented a very basic scheduler but I still seem to have problems with context switching. The problem is that I don't understand whether I should use TSS for software context switching or not? I have not yet implemented paging yet so every memory location is physical (for now). I'm just really confused now so if any of you could, please, give me a general idea on how this should be done, it would be great. I mean should I use call gates? interrupts? who am I? who are you?

I really need a source that describes how CPL, DPL, RPL and other things should be changed, how I should move from DPL 0 to DPL 3 and vice versa and etc. I'd really appreciate it if somebody could explain these for me or could point me to a tutorial of some type.

Thanks in advance.
Call Gates are used if you want to jump to a code with higher priority (that is DPL < CPL). Think of the CPL-3 as a big man (very big man). He can't cross on the other side of the bridge (that is the DPL-0), because he's too big to cross it, without crashing it. The call gate is just like a bigger and stronger bridge, so the big man can cross it succesfully.

More about context-switching here: http://en.wikipedia.org/wiki/Context_switch

More about system calls here:
http://en.wikipedia.org/wiki/System_call

Posted: Sun Apr 22, 2007 7:28 am
by frank
If you are using software multitasking, then all context switches will be done using the iret instruction. You put the correct data on the stack and then iret to the next process. You will only need one TSS when you start doing CPL0 -> CP3 switches because on the return to CPL0 the proccessor will read the CPL0 stack pointer and stack segment.

these are some tutorials that helped me greatly.
http://www.osdever.net/tutorials/multitasking.php and
http://www.osdever.net/tutorials/soft_ts.php

If you have more questions, feel free to ask them. I can even give code that will successfully do CP0 -> CP3 and CP0 -> CP0 context switches.

Posted: Mon Apr 23, 2007 3:27 am
by XCHG
I created a Task State Segment. Then I started coding one simple routine that sets the GDT segment descriptor for the TSS, sets the SS0 field of the TSS and then uses the LTS instruction but I get the below error right on the LTS instruction:

Code: Select all

00002963366e[CPU0 ] LTR: doesn't point to an available TSS descriptor!
Below is the code that I have written. I have hardcoded some of the parts of the code to make it readable. I know that the 32nd byte of my GDT (for 8 bytes) is free.

Code: Select all

  XOR     EBX , EBX
  XOR     ECX , ECX
  ; ECX:EBX = GDT Descriptor Table for TSS

  ; Low Order DWORD
  MOV     EBX , OFFSET TSS_STRUCT
  SHL     EBX , 0x00000010
  OR      EBX , 0x00000067
                   
  ; High Order DWORD                 
  MOV     EAX , OFFSET TSS_STRUCT
  MOV     EDX , EAX
  SHR     EDX , 0x00000010
  AND     EDX , 0x000000FF
  AND     EAX , 0xFF000000
  OR      EAX , EDX
  OR      EAX , 0x00408900
  MOV     EBX , EAX
  
  MOV     DWORD PTR [OFFSET GDT + 32] , EBX
  MOV     DWORD PTR [OFFSET GDT + 36] , ECX

  LEA     EDI , [TSS_STRUCT]  
  MOV     WORD PTR [EDI + 8] , SS

  MOV     EAX , 32
  LTR     AX
Thank you guys in advance

Posted: Wed Apr 25, 2007 1:44 am
by XCHG
I finally created the TSS. I have also made a simple scheduler that can pick processes depending on their priorities from my process queue. The problem is that I can't really switch to PVL 3 processes. I have created a structure for each process that is 128 bytes long. I have attached the structure of each process to this thread.

I am picking processes out of process queue, in IRQ0 handler, where my scheduler resides. Now for testing, I just got the CS:EIP of one of the processes that my scheduler picks and then I did an IRET. I get the below error as soon as I do that:

Code: Select all

00003082753e[CPU0 ] check_cs: non-conforming code seg descriptor dpl != cpl
This is the code for the IRET:

Code: Select all

    PUSHFD
    MOV     EAX , DWORD PTR [EDI + 16]
    AND     EAX , 0x0000FFFF
    PUSH    EAX
    MOV     EAX , DWORD PTR [EDI + 28]
    PUSH    EAX
    IRET
EDI points to the currently picked task's structure (attached to this post). Can somebody tell me what I am doing wrong? Isn't it possible to jump from CPL 0 to CPL 3? Note that I am not changing DS/ES/SS/FS/GS for the new process.

Any help would be really appreciated.

Posted: Wed Apr 25, 2007 4:45 am
by Aali
did you set the RPL?

Posted: Wed Apr 25, 2007 11:43 pm
by XCHG
Okay let me tell you all what I’ve done up to this point:

1. I have coded a __CreateProcess function. It accepts the base address of a process and its property flags to be specified. It will then add the process to the Process Queue and creates SS, DS, ES, FS, GS and the CS for the process. Segment Descriptors for all of the above mentioned segment selectors have the limit of 0x0000FFFF and the DPL of 0x03 (Users).
2. The scheduler in IRQ0 can pick a process depending on its priority and also has the process’s base address. Now I want to switch to that process while having all of the required information but as soon as I, for example, change the SS in the kernel to SS of the process I get a general protection fault.

Can anybody tell me why I am getting the general protection fault and for the love of god please someone point me to an article or something that REALLY explains how task switching is done? I DO know that stacks should be switched and values should be pushed into and popped off of it but the question is HOW exactly is that done? Or am I doing something wrong with switching? Should the stack of the process have the DPL of 0x00 just like the kernel’s stack?

Oh and yes I have read Intel Manuals and as a matter of fact, they are the only reference that I trust but they have not been helpful in this matter as much as they are supposed to be. I’d really appreciate it if somebody could explain these to me.


P.S: This is the code that I have written so far for my __CreateProcess function:

Code: Select all

; ——————————————————————————————————————————————————
  __CreateProcess:
    ; DWORD __CreateProcess (DWORD CreationFlags, DWORD ProcessASCII, void* BaseAddress); StdCall;
    ; Returns the Process ID of the process
    ; Returns 0x00000000 (Zero) upon failure
    PUSH    EBX
    PUSH    ECX
    PUSH    EDX
    PUSH    ESI
    PUSH    EBP
    MOV     EBP , ESP

    ; [EBP + 0x18] ; CreationFlags
    ; [EBP + 0x1C] ; ProcessASCII
    ; [EBP + 0x20] ; BaseAddress

    XOR     EAX , EAX
    MOV     EBX , OFFSET PQDTR
    MOV     ECX , DWORD PTR [EBX]

    ; See if the length of the PQDT is divisible by the length of the processes' structure -1
    TEST    ECX , PROCESS_STRUCTURE_LENGTH_IN_BYTES - 1
    JZ      .Continue1
    JMP     .EP
    
    .Continue1:
    ; See how many processes can be put into the QUEUE
    SHR     ECX , 0x00000007
    TEST    ECX , ECX
    JNZ     .Continue2
    JMP     .EP


    .Continue2:
    ; *EBX = PQDT
    MOV     EBX , DWORD PTR [EBX + 0x04]

    .FindFirstFreeEntry:
      MOV     EDX , DWORD PTR [EBX]
      TEST    EDX , EDX
      JZ      .FoundFreeSlot
      JMP     .FindNextFreeEntry


      ; DWORD#0 = ProcessID
      ; DWORD#1 = CreationFlags
      ; DWORD#2 = Original and current time slices
      ; DWORD#3 = ProcessASCII
      .FoundFreeSlot:
      MOV     DWORD PTR [EBX] , ECX
      MOV     EAX , DWORD PTR [EBP + 0x18] ; EAX = CreationFlags
      MOV     DWORD PTR [EBX + 0x04] , EAX
      
      


      ; Time Slice Formula = (PIT Frequency/16) * (Priority+1)
      ; We will only keep time slices up to 65535
      MOV     ECX , DWORD PTR [PITFrequency]
      SHR     ECX , 0x00000004
      ; EAX = [CreationFlags], Low 4 bits are for the priority
      AND     EAX , 0x0000000F
      INC     EAX
      MUL     ECX
      ; EAX  = TimeSlice, Keep the time slice that is 1 up to 65535
      AND     EAX , 0x0000FFFF
      SHL     EAX , 0x00000010
      ; Current Time Slice = Number of time slices given to the current process
      ; For a process that is just being created this number should be zero (0x0000)s
      ; EAX = DWORD#2 of the process
      MOV     DWORD PTR [EBX + 0x08] , EAX




      ; Put the process ASCII in its place
      MOV     EAX , DWORD PTR [EBP + 0x01C] ; ProcessASCII
      MOV     DWORD PTR [EBX + 0x0C] , EAX
      
      
      
      
    
      ; *EBX = PQDT
      ; [EBP + 0x18] ; CreationFlags
      ; [EBP + 0x1C] ; ProcessASCII
      ; [EBP + 0x20] ; BaseAddress
    
    

    
      ; Create the code segment for the process
      ; DWORD#0 of the Code Segment Descriptor in the Global Descriptor Table
      MOV     EAX , DWORD PTR [EBP + 0x20] ; BaseAddress
      MOV     EDX , EAX
      SHL     EAX , 0x00000010
      OR      EAX , 0x0000FFFF   ; Code segment limit = 0xFFFF
      ; EAX = DWORD#0 of the Code Segment Descriptor (CSD)
      MOV     ESI , EDX
      SHR     ESI , 0x00000010
      AND     EDX , 0xFF000000
      OR      EDX , ESI
      OR      EDX , 0x0040FA00
      ; EDX = DWORD#1 of the Data Segment Descriptor
      INVOKE  __AddGDTDescriptor , OFFSET GDTR , EAX , EDX
      MOV     WORD PTR [EBX + 16] , AX

      
      
      

      
      
      ; Create the Data Segment Descriptor for the process (DS, SS, ES, FS, GS)
      MOV     EAX , DWORD PTR [EBP + 0x20] ; BaseAddress
      MOV     EDX , EAX
      SHL     EAX , 0x00000010
      OR      EAX , 0x0000FFFF
      ; EAX = DWORD#0 of the Data Segment Descriptor of the new process
      MOV     ESI , EDX
      SHR     ESI , 0x00000010
      AND     EDX , 0xFF000000
      OR      EDX , 0x0040F200
      INVOKE  __AddGDTDescriptor , OFFSET GDTR , EAX , EDX
      ; AX = The segment selector of the DS, Get it in the upper WORD of the AX register
      ; This way we can put DS, GS, SS, ES and etc at the same time
      MOV     EDX , EAX
      SHL     EDX , 0x00000010
      OR      EAX , EDX
      MOV     WORD PTR [EBX + 18] , AX    ; DS
      MOV     DWORD PTR [EBX + 20] , EAX  ; SS, ES
      MOV     DWORD PTR [EBX + 24] , EAX  ; GS, FS
      

      
      
      ; Set the EIP of the process to zero (The base address of the segment takes care of that)
      XOR     EAX , EAX
      MOV     DWORD PTR [EBX + 28] , EAX

      
      
      
      ; Set the general purpose registers to 0x00000000
      XOR     EAX , EAX
      MOV     DWORD PTR [EBX + 32] , EAX        ; EAX
      MOV     DWORD PTR [EBX + 36] , EAX        ; EBX
      MOV     DWORD PTR [EBX + 40] , EAX        ; ECX
      MOV     DWORD PTR [EBX + 34] , EAX        ; EDX
      MOV     DWORD PTR [EBX + 48] , EAX        ; ESI
      MOV     DWORD PTR [EBX + 52] , EAX        ; EDI
      MOV     DWORD PTR [EBX + 56] , EAX        ; EBP
      MOV     DWORD PTR [EBX + 60] , 0x0000FFFF ; ESP
      PUSHFD
      POP     EAX
      MOV     DWORD PTR [EBX + 64] , EAX        ; EFLAGS
      MOV     EDX , DWORD PTR [EBP + 0x20]
      MOV     DWORD PTR [EBX + 68] , EDX        ; BaseAddress
      MOV     EAX , EDX
      INC     DWORD PTR [ProcessesCount]
      JMP     .EP
      .FindNextFreeEntry:
        ADD     EBX , PROCESS_STRUCTURE_LENGTH_IN_BYTES
        DEC     ECX
        JNZ     .FindFirstFreeEntry
    .EP:
      POP     EBP
      POP     ESI
      POP     EDX
      POP     ECX
      POP     EBX
      RET     0x0C
; ——————————————————————————————————————————————————