Multitasking Issues

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Aali,

I am now setting the RPL of the segment selector for both the CS and DS/SS/ES/etc of the process to 0x03 so that it will become the user. Now when I issue the IRET instruction in order to just change the CS to the process's CS, I get this error:

Code: Select all

00003082754e[CPU0 ] iret: SS.rpl != CS.rpl
I then tried changing the kernel's stack to the process's stack before I switch to the task and I get this error:

Code: Select all

00003082750e[CPU0 ] load_seg_reg(SS): rpl != CPL
So the RPL of SS should be set to the CPL at the same time that the CPL is changed to 0x03? That is ridiculous or I might be doing something wrong!
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Post by Combuster »

When you push an IRET stack frame in protected mode, you need 5 items: SS ESP CS EIP and EFLAGS (in a certain order). The processor will not automatically load SS.rpl, but will take it from the GDT. You will have to make sure the correct SS is on the stack and that its RPL is what is expected.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Combuster,

Thank you for your response. I understand that other information should be pushed onto the kernel’s stack before switching to the user’s process but the situation for me is more complicated here. I’d really appreciate it if you or somebody else could read the below things that I have done and tell me what I should do next.

1. I do have a GDT and an LDT.
2. I have allocated one TSS (104 bytes long) statically. I have also added this TSS to the GDT and issued the LTR instruction. There are no exceptions generated by the CPU up to this point.
3. I have coded a [__CreateProcess] function that takes the base address of a process and then sets all of its information into a structure that I create for each process. This structure is 128 bytes long and is similar to the TSS in that it holds the process’s CS, DS, etc and the state of its general purpose registers. The process will then be added to the Process Queue.
4. In the [__CreateProcess] function, I set all the RPL and DPL of segment selectors and segment descriptors to 3 as for user’s process.
5. I have coded my scheduler in my IRQ0Handler procedure that is called every time PIT interrupts my kernel’s code.
6. As soon as IRQ0 is fired, my kernel will look into the Process Queue and then picks up a process from the queue. Now I have access to the picked process’s structure (128 byte structure that was created for each process in the __CreateProcess function). Therefore, I know what the process’s CS/DS/ES/FS/GS/EIP/EFLAGS and other information are.

The problem starts here. I simply am confused on how I should switch to a process with DPL/CPL/RPL of 3 while my kernel’s DPL/CPL/RPL is zero. Please note that I have not set any information in my TSS. I really don’t know if I have to build one TSS for each process or not. Please tell me exactly what needs to be done because I have read enough of “switch this and switch that”. What I need to know is “how to switch this and how to switch that”. What are the steps that you would take in order to enable context switching if this was the kernel that you were working on?

P.S: I am using software multitasking and not hardware.

Thank you guys in advance. I really appreciate your concern.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

Does it work for kernel-level (ie. kernel thread) switches - which are DPL0->DPL0 (and RPL0->RPL0)?
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

pcmattman,

Yes (un)fortunately it does.

P.S: I just found out that the code segment created for user processes with RPL and DPL of 3 should be Conforming or else a GPF will be raised when switching from CPL0 to CPL3. The problems are still there anyway. So if anyone can help me by reading my previous post, I would really appreciate it.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
urxae
Member
Member
Posts: 149
Joined: Sun Jul 30, 2006 8:16 am
Location: The Netherlands

Post by urxae »

(Warning: this has not been extensively tested)

I only switch between threads running in ring 0.
To switch to a ring 3 process, I switch to the ring 0 stack associated with it, which then at the top contains a filled-in version of the following struct:

Code: Select all

struct IretData {
    void* eip;
    uint cs, eflags;
    uint esp, ss;
}
When that's the next thing on the stack, you can just execute an iretd instruction to jump to your ring 3 process, simultaneously loading above fields into the registers of the same name.
The cs and ss in that structure should be for ring 3 (i.e. lower 2 bits should be set).
Note that the segment registers occupy 4 bytes, even though only 2 are used.
Also note that iretd works differently when switching between privilege levels (the "esp" and "ss" fields are only used when the "cs" field indicates a switch to a less-privileged ring).
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

urxae wrote: Also note that iretd works differently when switching between privilege levels (the "esp" and "ss" fields are only used when the "cs" field indicates a switch to a less-privileged ring).

That was exactly what I needed to know. I don't remember reading that anywhere in the Intel Manuals even in their Instruction Manuals!

So what is the SS and ESP for? Should that be set to kernel's stack? Do we have to put anything in the kernel's stack before switching to the user's stack? Could you be more specific?

Thank you in advance.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
urxae
Member
Member
Posts: 149
Joined: Sun Jul 30, 2006 8:16 am
Location: The Netherlands

Post by urxae »

XCHG wrote:
urxae wrote: Also note that iretd works differently when switching between privilege levels (the "esp" and "ss" fields are only used when the "cs" field indicates a switch to a less-privileged ring).
That was exactly what I needed to know. I don't remember reading that anywhere in the Intel Manuals even in their Instruction Manuals!
It's in there. In my copy it's in 5.12.1 (Interrupt and Exception Handling/Exception and Interrupt Handling/Exception- or Interrupt-Handler procedures), both in the text and figure 5-4.
So what is the SS and ESP for? Should that be set to kernel's stack? Do we have to put anything in the kernel's stack before switching to the user's stack? Could you be more specific?

Thank you in advance.
No, those fields of the structure should contain the user-mode stack pointer and segment. Like I said,
urxae wrote:When that's the next thing on the stack, you can just execute an iretd instruction to jump to your ring 3 process, simultaneously loading above fields into the registers of the same name.
so the values of those fields will be the values of the respective registers after the iretd instruction. The code will be executing at cs:eip, with stack at ss:esp and flags set to eflags

For example if you have the desired values of those registers in some variables, one way to switch to ring 3 code would be:

Code: Select all

push [user_ss]
push [user_esp]
push [user_eflags]
push [user_cs]
push [user_eip]
iretd
This technique basically "fakes" the structures set up by an inter-privilege level interrupt and then "returns" from it.

Above code is not exactly how I do it, by the way.
I only use something akin to the above for creating new processes. And even then, I use regular memory accesses to set it up, and also recreate the stack layout of my stack switching function below that, with the return address set to a stub that clears some registers and performs the iretd.
That way I can add it to my scheduler queue and later use my regular context-switch function to switch to it.

After that, the only way that thread ever gets to a switchable state is by switching to another thread from an interrupt or exception handler, using the same context-switch function.
That context-switch function just pushes a lot of registers, switches to another kernel stack, pops the same registers (in reverse order, of course) and returns (using the return address on the new stack).
That return address will be whatever function last called the context-switch function to go to another thread (for "old" threads) or above-mentioned iretd-ing stub function.
("old" threads then typically continue returning down to the handler for whatever interrupt last occured in that thread and return from it, getting back to whatever code that thread was executing)
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

urxae,

I'm thankful beyond words for your help. Now I am able to switch to the picked process. Now there are two things that I don't understand and I'd really appreciate if you could, please, answer these questions for me:

1) Now the process is running in CPL 3. How is my scheduler in CPL 0 is supposed to take control?

2) What does the TSS do in software switching? Can you please give me an example?

Thank you so much. I really appreciate your help.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
Aali
Member
Member
Posts: 58
Joined: Sat Apr 14, 2007 12:13 pm

Post by Aali »

most schedulers use a timer interrupt to preempt userland processes

there are lots of great articles on both the PIT timer and the APIC, where the PIT is probably the easier one to program (you need the APIC if you're doing SMP though and its overall a more capable device)

this interrupt thing is also where the TSS comes into the picture, the TSS is needed to specify the kernel stack you want to load when the processor switches from CPL 3 to CPL 0

when the processor recieves an interrupt in userland (assuming you have the IDT setup correctly) it can't use the userland stack because esp may not even point to a valid memory location so instead it loads ESP0 and SS0 from the TSS and saves the "IretData" stuff from the user process on the new (kernel) stack
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Aali,

Thank you for your response. About the PIC and PIT, I do have both of them working over here.

About the SS0:ESP0 in the TSS: suppose I have one single process running. The control is given to this process using my scheduler. The process is running in CPL/DPL/RPL 3. The CS and the DS created for the task have the base address in their segment descriptors equal to the task's starting memory address and the EIP of the task is set to zero the first time it is executed.

Now an IRQ is fired. What the processor will do is that it will access the TSS that I have created in my kernel. It will then load the SS0:ESP0 from the TSS and then WHAT WILL HAPPEN? lol. I just seem to be very frustrated about this whole TSS thing. How is the SS0:ESP0 going to help in order for my scheduler and kernel in general to take control again from the process? Thank you for your help and time.
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Post by Combuster »

When an interrupt occurs:
- the processor loads CS and EIP from the IDT
- the processor looks up CS in the GDT
- it will see that it transfers control to a higher-privilege segment
- because of that, it will store SS and ESP, and load the new pair from the TSS
- it will store EFLAGS, the original CS and EIP

The idea behind SS/ESP's in the TSS is that the kernel gets a trusted stack segment. Otherwise, i could set ESP to some location in the kernel, and upon interrupt the processor writes 3 dwords over kernel code. Voila: security hole.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

Combuster wrote: The idea behind SS/ESP's in the TSS is that the kernel gets a trusted stack segment. Otherwise, i could set ESP to some location in the kernel, and upon interrupt the processor writes 3 dwords over kernel code. Voila: security hole.
Actually there might not be any stack at all, you'd get a tripple-fault, and reboot.
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
XCHG
Member
Member
Posts: 416
Joined: Sat Nov 25, 2006 3:55 am
Location: Wisconsin
Contact:

Post by XCHG »

Combuster,
That information really helped. Thank you so much.

Alrighty! I created a Call Gate in my GDT for the first time and actually demonstrated the fact that the control is given to my process. What I am confused about is this: suppose I am now in the kernel and IRQ0 is fired:


1) IRQ0 is fired and my IRQ0Handler procedure takes control.

2) In my IRQ0Handler, at the beginning, I am creating the stack frame by pushing all the registers that I know I am going to be destroying, into the stack. Therefore, the kernel's stack is now altered.

3) Now my scheduler, which resides in IRQ0Handler, picks one process up from the Process Queue and has now access to its structure that contains the process's CS/DS/ES/FS/GS/EIP/EFLAGS/Base Address and etc.

4) Now my scheduler in IRQ0Handler, pushes the process's SS, ESP, CS, EIP and EFLAGS respectively into the kernel's stack and issues an IRET. This IRET is issued before my IRQ0handler issues its main IRET that returns to the kernel. You see, I am issuing one IRET in order to switch to the process and one IRET is supposed to be issued at the end of the IRQ0Handler.

My problem and the confusion is that the IRET at the end of my IRQ0Handler will never be issued unless there are no proesses in the Process Queue to be given control to. I think this will be clearer when you look at my IRQ0Handler procedure:

Code: Select all

; ——————————————————————————————————————————————————
  __IRQ0Handler:
    PUSH    EAX
    PUSH    EBX
    PUSH    ECX
    PUSH    EDX
    PUSH    ESI
    PUSH    EDI
    PUSHFD
    INVOKE  __DisableIRQPulse , IRQ_PULSE_IRQ0


    ; See if there are any processes left to be processed
    MOV     EAX , DWORD PTR [ProcessesCount]
    TEST    EAX , EAX
    JNZ     .Continue1
    ; Send End Of Interrupt if no process is in the Queue
    JMP     .SendEOI




    .Continue1:
    ; Set the Timer Cycles and adjust it
    LEA     ESI , [TimerCycles]
    MOV     EAX , DWORD PTR [ESI]
    MOV     EDX , DWORD PTR [PITFrequency]
    ; EAX = TimerCycles
    ; EDX = PIT Frequency
    INC     EAX
    CMP     EAX , EDX
    JBE     .NoCyclcesAdjustmentNeeded
    MOV     EAX , 0x00000001
    .NoCyclcesAdjustmentNeeded:
      MOV     DWORD PTR [ESI] , EAX




    INVOKE  __FindHighestPriorityProcess
    ; See if a procss could be found (EAX <> 0)
    TEST    EAX , EAX
    JNZ     .Continue2
    JMP     .SendEOI




    .Continue2:
      ; EDI = Picked Process's structure
      MOV     EDI , EAX




      ; Set the SS0:ESP0 in the TSS Structure
      MOV     DWORD PTR [TSS_STRUCT + 4] , ESP
      MOV     WORD PTR [TSS_STRUCT + 8] , SS



      ; Push the SS, ESP, EFLAGS, CS and EIP of the picked process
      MOV     EAX , DWORD PTR [EDI + 20] ; Process's SS
      AND     EAX , 0x0000FFFF
      PUSH    EAX
      MOV     EAX , DWORD PTR [EDI + 60] ; Process's ESP
      PUSH    EAX
      MOV     EAX , DWORD PTR [EDI + 64] ; Process's EFLAGS
      PUSH    EAX
      MOV     EAX , DWORD PTR [EDI + 16] ; Process's CS
      AND     EAX , 0x0000FFFF
      PUSH    EAX
      MOV     EAX , DWORD PTR [EDI + 28] ; Process's EIP
      PUSH    EAX
      MOV     EAX , PIC_EOI
      OUT     PIC1_COMMAND , AL
      IRET                               ; Start executing the process






    .SendEOI:
      MOV     EAX , PIC_EOI
      OUT     PIC1_COMMAND , AL
      INVOKE  __EnableIRQPulse , IRQ_PULSE_IRQ0
      POPFD
      POP     EDI
      POP     ESI
      POP     EDX
      POP     ECX
      POP     EBX
      POP     EAX
    IRET
In this case, You can see that if a process is picked to be given control to, the IRQ0Handler will never reach the .SendEOI label in order to pop the regisers it has already pushed into the kernel's stack. I am really confused. Is it just me or is multitasking complicated?
On the field with sword and shield amidst the din of dying of men's wails. War is waged and the battle will rage until only the righteous prevails.
User avatar
os64dev
Member
Member
Posts: 553
Joined: Sat Jan 27, 2007 3:21 pm
Location: Best, Netherlands

Post by os64dev »

In this case, You can see that if a process is picked to be given control to, the IRQ0Handler will never reach the .SendEOI label in order to pop the regisers it has already pushed into the kernel's stack. I am really confused. Is it just me or is multitasking complicated?
No multitasking isn't complicated the x86 architecture is. My OS uses 64-bit long mode and then you only have 1 kind of stack frame which simplifies everything.
Author of COBOS
Post Reply