OSDev.org

The Place to Start for Operating System Developers
It is currently Wed May 15, 2024 8:15 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 73 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next
Author Message
 Post subject: Re:tss on stack???
PostPosted: Sat Sep 10, 2005 6:13 pm 
Quote:
all schedule() does is deturmin what task gets run next, then returns


... which will always be into the IRQ handler, since schedule() is only called from there.

Right, but I'm just giving you a warning for when you make a new task. If you cause it to initially leave kernel without EOI'ing, you may end up making a post like this: http://www.mega-tokyo.com/forum/index.p ... eadid=8290

This could happen if you set the initial EIP to be somewhere other than in the IRQ handler before the EOI.

Also, you may end up switching tasks from somewhere other than an IRQ handler. Say an app makes a syscall which causes it to block. The scheduler goes ahead and schedules something else. Later, when the app is unblocked, another app's timeslice expires, and you switch back to the app, no-EOI. The app exits kernel as though from a syscall. No-EOI. IRQs stop.


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 11:04 am 
ok, i kinda get it?, my schedule() contains:
Code:
void schedule()
{
    if(!is_empty() && cur_task_time() < 1)
    {
        cur_task_time_equ(get_pri_time(cur_task_pri()));
        front_to_end();
        start_task(rrq[front]);
        printf("%s Is Running...\n", rrq[front].name);
    }
}

but what should start_task(); contain???

p.s. my task struct looks like this:
Code:
struct task_data
{
    char name[33];
    unsigned int esp;
    unsigned int ss;
    unsigned int kstack;
    unsigned int ustack;
    unsigned int time;
    unsigned int priority;
};


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 3:15 pm 
Offline
Member
Member
User avatar

Joined: Tue Oct 17, 2006 6:06 pm
Posts: 1437
Location: Vancouver, BC, Canada
I don't have the source for my OS handy, otherwise I'd post the relevant bits... I'll just have to explain it with words.

Your schedule() should return a pointer to the next task to run, from which you can get a pointer to that task's kernel stack. Then, right here:

Code:
irq:
    pusha
    push ds
    push es
    push fs
    push gs
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov eax, esp
    push eax
    mov eax, _irq_handler
    call eax
---> HERE!!
    pop eax
    pop gs
    pop fs
    pop es
    pop ds
    popa
    add esp, 8
    iret


Instead of pop eax, you do this:

Code:
    mov esp, eax
    pop gs
    pop fs
  etc...


That's where the actual task switch happens. The rest of the pops will pop the new task's registers and eventually the iret will switch back to user mode, running that task at the last point it entered the kernel.

IMO this is much easier to understand than doing a "start_task()" inside your scheduler. It also avoids the EOI problem that was mentioned earlier.

My $0.02....

_________________
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!


Top
 Profile  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 5:15 pm 
ok thx, but how do i get back to the isr without scherewing up EAX???, what redgister could i use???, EDI???


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 7:24 pm 
GLneo wrote:
ok thx, but how do i get back to the isr without scherewing up EAX???, what redgister could i use???, EDI???

eax will poped from the new stack by popa


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 7:24 pm 
Offline
Member
Member
User avatar

Joined: Tue Oct 17, 2006 6:06 pm
Posts: 1437
Location: Vancouver, BC, Canada
GLneo wrote:
ok thx, but how do i get back to the isr without scherewing up EAX???, what redgister could i

use???, EDI???


Why are you worried about eax? You saved the interrupted thread's copy of it when you did the pusha. Then you overwrote it with a few things before calling the IRQ handler. What I'm proposing is to have the IRQ handler return a pointer to the new stack to switch to. C functions always put the return value in eax. See the GCC calling conventions on the OS FAQ.

_________________
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!


Top
 Profile  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 8:01 pm 
should the switch be done on every interrupt even if its not an irq or with just the timer irq0 ???


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 8:19 pm 
If the switch isn't supposed to happen then you can just return the same ESP (you could return 0 and have the ISR check it and everything but that would be unnecessarily slower and more complex since just reloading ESP regardless is faster anyway).


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 8:22 pm 
i really didn't mean so, i meant that do i need do add the code for switching in every isr.
in general do i need to do switching in any int ???
or just add the code for switching in irq0 only ???


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 8:31 pm 
You would need it in every ISR where you will need to perform a task switch, this is probably going to mean every ISR though depending on your OS design.


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 8:34 pm 
if i just done the task switching on the irq0 only would that affect anything in the overall performace of the os ???


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 9:01 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

Guest wrote:
in general do i need to do switching in any int ???
or just add the code for switching in irq0 only ???


For this method, you'd have to add the code to every IRQ handler that could cause a task switch, and anything used as a kernel API (software interrupt call gate SYSCALL and/or SYSENTER - whichever you're using), and any exception handler that might cause a task switch (e.g. page fault exception handler which may need to wait for disk IO, or any of the others that can terminate a faulty task).

IMHO one of the main problems with C is that you can only return one value from a function without doing ugly hacks. For your kernel API, using the only return value for task switching will mean that you'll be doing ugly hacks for everything that's actually returned to the caller. This means for the kernel API you'll have an ugly hack caused by C's inability to handle interrupts, followed by ugly hacks to return values to the caller.

This complicates things more, because to allow more return values the caller must supply an address to store each return value. These addresses will need to be checked. For example, if an application accidentally asks for some return values to be stored where the kernel's code is, then it's better to return an error (without the checking an application could easily make the kernel overwrite itself). Of course, in order to return an error you'll need another return value, so you'll need a third ugly hack.

Because the kernel API usually uses one software interrupt and/or one call gate and/or one SYSENTER entry point, and/or one SYSCALL entry point, even simple kernel API functions will suffer from the collection of ugly hacks.

I was going to provide an example of just how ugly things can get, and ask other posters if I've missed something, but I'm out of time - I'll try to squeeze out the example when I get to where I should already be....


Cheers,

Brendan

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 10:22 pm 
Why exactly do you need more than one return value? I usually use "void" as the return value for the C function to the interrupt stub, I don't see what exactly you're referring to.

This concept is quite simple:
Code:
asmentry:
pusha
push %ds
mov %esp, %eax

push %eax
.extern CHandler
call CHandler
# add $4, %esp
mov %eax, %esp

pop %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss

popa
iret
Code:
uintptr_t CHandler(InterruptEntry_t *IntData)
{
     /* ... */
     return (uintptr_t) IntData;
}
To change the values of the return registers you simply alter the struct which will then be popa-ed, sub functions can be given pointers/references into the struct or just the struct pointer itself.

I believe the one return value is built on the mathematical principles of an algebraic equation. eg: m = f(b), m = a + 3b + 2. In maths there is always only one result from an equation, all languages that I know which are higher than assembly seem to be built on this principle which basically means that any and all algorithms can be broken down to components that will only require 1 return value. Of course in computers there are more than just numbers (strings, images) but the instances where multiple return values are absolutely necessary don't come around that often.


Top
  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 11:34 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi again,

OK, time for the example. I'm going to use the word "task" everywhere. For your OS design this might mean "thread" or "process" or something else.

I should point out that I'm going to assume a whole pile of things. Hopefully readers will be able to adjust for the differences between my assumptions and their OS design, and my assumptions themselves won't effect any decisions made.

The first of these assumptions is that the task switching code itself consists of more than just swapping stacks. This isn't a bad idea because in reality, extra code for swapping address spaces, sorting out FPU/MMX/SSE/SSE2 state and updating the TSS is very likely, and doing other things (like working out how much CPU time each task has used) are possible.

Related to the first assumption, I'll also assume that there's a single function responsible for doing the actual task switch, rather than a seperate version of it for each "kernel exit point", as this would be much easier to implement and maintain.

I will also assume that, for the kernel API, parameters are passed in registers through a software interrupt, which includes a "function number" that is used with a table to find the address of the kernel function to be executed. I'm making this assumption for a few reasons - it's almost exactly what Linux and my own OS does, and it's one of the cleanest, most efficient methods. I will also assume that all kernel functions return a 32 bit status code, such as "OK", "function not defined", "out of memory" or "time-out error". This is just my personal preference.

Please note: The kernel API calling interface used in this example is very similar to my own OS, and does not create problems for programs written in C (the library "hides" the actual kernel API calling interface, the same as it does on Linux and most other OSs).

I guess I should also make it clear that the point of this post is to compare the "task switches at kernel exit points only with as much as possible written in C" approach suggested by others to the "handle task switches at any time with as much as sensible written in assembly" approach that I use. Specifically, I'll be looking at the added complexity the former method causes for a simple kernel API function that does not cause a task switch, in an attempt to either highlight the code I called "ugly hacks" in my earlier post, or alternatively to highlight any mis-understanding of mine.

For this purpose I've decided to examine a kernel function that returns the current system timer tick. I will assume that this function returns a 32 bit value called "system_timer_tick" for the sake of simplicity. Even though I've selected a simple function the difference in complexity for both methods should be the same for more complex kernel functions.

For the "handle task switches at any time with as much as sensible written in assembly" method, the kernel API code becomes:

Code:
kernel_API_entry_point:
    cmp eax,MAX_FUNCTION_NUMBER
    ja .function_not_defined_error
    call [kernel_API_function_table + eax * 4]
    iretd

.function_not_defined_error:
    mov eax,ERROR_FUNCTION_NOT_DEFINED
    iretd


And the actual "get system timer tick" function would be:

Code:
get_system_timer_tick:
    mov ebx,[SYSTEM_TIMER_TICK]
    mov eax,STATUS_OK
    ret


The application would need this code to use the function:

Code:
   mov eax,GET_SYSTEM_TIMER_TICK
   int KERNEL_API_INTERRUPT
   test eax,eax
   jne .error


[continued]

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
 Post subject: Re:tss on stack???
PostPosted: Sun Sep 11, 2005 11:35 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
[continued]

For the "task switches at kernel exit points only with as much as possible written in C" method, the kernel API code would need to be something like:


Code:
kernel_API_entry_point:
    cmp ebx,MAX_VALID_CPL3_address   ;Is the address for the status code sane?
    ja .not_sure_how_to_handle_this  ; no
    cmp ebx,MIN_VALID_CPL3_address   ;Is the address for the status code sane?
    jb .not_sure_how_to_handle_this  ; no

    cmp eax,MAX_FUNCTION_NUMBER
    ja .function_not_defined_error

    pusha

    push edi
    push esi
    push edx
    push ecx
    push ebx
    call [kernel_API_function_table + eax * 4]
    add esp,20

    cmp eax,[CURRENT_TASK]
    jne .noTaskSwitch
    call do_task_switch
.noTaskSwitch:
    popa
    iretd

.function_not_defined_error:
    mov dword [ebx],ERROR_FUNCTION_NOT_DEFINED
    iretd


The C code for the "get system timer tick" function would be:

Code:
TASK *get_system_timer_tick(unsigned int *status, unsigned int *timer_tick) {
   if( (timer_tick > MAX_VALID_CPL3_ADDRESS) || (timer_tick < MIN_VALID_CPL3_ADDRESS) {
      *status = ERROR_BAD_INPUT_ADDRESS;
   } else {
      *timer_tick = system_timer_tick;
      *status = STATUS_OK;
   }
   return current_task;
}



And the actual "get system timer tick" function would be (assuming the compiler's optimizer is very good):

Code:
get_system_timer_tick:
    mov eax,[esp+8]                          ;Get address to store status code

    mov ebx,[esp+12]                         ;Get address to store return value
    cmp eax,MAX_VALID_CPL3_ADDRESS           ;Is this address sane?
    ja .badInputAddress                      ; no
    cmp eax,MIN_VALID_CPL3_ADDRESS           ;Is this address sane?
    ja .badInputAddress                      ; no

    mov ecx,[SYSTEM_TIMER_TICK]
    mov [ebx],ecx
    mov dword [eax],STATUS_OK
    mov eax,[current_task]
    ret

.badInputAddress:
    mov dword [eax],ERROR_BAD_INPUT_ADDRESS
    mov eax,[current_task]
    ret


The application would need this code to use the function:

Code:
   mov eax,GET_SYSTEM_TIMER_TICK
   mov ebx,ADDRESS_TO_STORE_STATUS
   mov ecx,ADDRESS_TO_STORE_TIMER_TICK
   int KERNEL_API_INTERRUPT
   cmp dword [ADDRESS_TO_STORE_STATUS],STATUS_OK
   jne .error
   mov eax,[ADDRESS_TO_STORE_STATUS]


In comparing these methods, the latter results in some 25 additional instructions (or more, depending on the compiler's optimizer - I don't have a C compiler here to make it more realistic), or has roughly 5 times the kernel API overhead, and the chance of branch mis-prediction (and instruction pipeline flush) within the kernel API return code (the "jne .noTaskSwitch" instruction). The extra complexity is obvious, and would effect almost all kernel entry/exit points.

So far the only benefit mentioned for this method is that it allows system code to be written without caring where in the code any task switches might be done (by forcing the task switch to be postponed until kernel exit). This itself can cause problems with some kernel functions (e.g. terminate a task), and can make a mess of interrupt latency - if an IRQ handler interrupts a kernel function then both can't cause a task switch with any sanity, and you end up disabling interrupts while any kernel code is being run (the kernel itself can't be interruptable or pre-emptable).


Cheers,

Brendan

_________________
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 73 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], Google [Bot], Kaius, MichaelPetch, nullplan and 21 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group