Idling in kernel & nested interrupts (x86)

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.
Post Reply
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Idling in kernel & nested interrupts (x86)

Post by hrniels »

Hi!

I'm trying to implement an idle-"thread" which doesn't need much resources and let my OS react quickly on interrupts. Therefore I thought the best way would be not to have a real thread/process for idling but implement the thread-switch as follows:

Code: Select all

void thread_switch(tTid tid) {
  if(thread_save(...))
    return;
  
  if(tid == idleThread) {
    enable_interrupts();
    while(1)
      asm("hlt");
  }

  thread_resume(...);
}
So the CPU will halt until an interrupt arrives. This interrupt would of course be nested, because we were already handling an interrupt. So the CPU doesn't switch the privilege level and continues using the current stack. Lets say we should switch to a different thread/process now. That means we call thread_switch(...) which saves the current thread (idle) and resumes the new one. The save is not really necessary since we'll never resume idle but shouldn't hurt. thread_resume() restores the registers and exchanges the page-directory so that we get the kernel-stack of the resumed task back. That means if this thread now leaves the interrupt-handling and restores the registers (including segment-registers, eflags and so on) we are *not* leaving a nested interrupt but continuing a user-process.

So far, so good. As far as I understand this, it should be working. But it doesn't. I'm trying to debug this for more than 2 days now and getting more and more frustrated because I really don't have a clue what is wrong with this approach or my implementation of it :/
I know that as soon as I'm switching from idle to a different thread/process (or later, I were unable to figured that out yet), at some point of time the user-process gets the wrong stack-pointer and segment-registers (it gets the values from kernel-mode). That means, just handling an interrupt during idle and after that continue idling, works fine.

My questions are:
Am I missing something? Does this approach even work?
Do I need a separate TSS for the idle-task (I've read this somewhere)? If yes, why? ATM I have one TSS for all because I do the task-switching in software.

Thanks!
hrniels
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re: Idling in kernel & nested interrupts (x86)

Post by Colonel Kernel »

It's not clear to me why you're avoiding having a dedicated idle thread. The system will be just as fast to respond to interrupts as with the way you've done it.

Also, I see big re-entrancy concerns with how you've done it. What if the interrupt that occurs during idle needs to switch tasks? Is your scheduler able to handle that case?

IMO it would be simpler and better just to have a dedicated idle thread. No need for a separate TSS if you're using software task switching.
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!
User avatar
Artlav
Member
Member
Posts: 178
Joined: Fri Aug 21, 2009 5:54 am
Location: Moscow, Russia
Contact:

Re: Idling in kernel & nested interrupts (x86)

Post by Artlav »

Check the stack stuff:
-The fields for different-level stacks should be valid in TSS's.
-popa does not update ESP.
-Interrupt does not push SS and ESP if called from the same CPL, but does if from different.

Also, try checking for thread-safeness of the interrupt handlers if you have re-entry possibility.
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: Idling in kernel & nested interrupts (x86)

Post by hrniels »

Colonel Kernel wrote:It's not clear to me why you're avoiding having a dedicated idle thread. The system will be just as fast to respond to interrupts as with the way you've done it.
The previous approach I used was that the init-thread, after it has finished work (start shells etc.), goes into an endless-loop which calls wait() all the time. wait() causes the kernel to block the thread. Everytime the scheduler finds no runnable thread it chooses the init-thread (no matter if it is blocked). So the init-thread does just run if there is nothing else to do because if there are runnable threads, they will be chosen first.
This way is easy and works but wastes some time because it makes syscalls all the time and they have to be finished before we can switch to another thread (interrupts are disabled in my kernel and I don't support SMP).

Of course it is possible to prevent the syscalls by e.g. telling the kernel when the init-thread is finished with starting shells so that the kernel will just choose init from that point on if there is no other runnable thread. But the problem is that the hlt-instruction is a privileged one so that the init-thread would have to run in kernel-mode, right? My OS doesn't support kernel-mode threads yet and I wanted to prevent the need of them, if possible.
Or is there another way halting the cpu when idling in user-mode?
Colonel Kernel wrote:Also, I see big re-entrancy concerns with how you've done it. What if the interrupt that occurs during idle needs to switch tasks? Is your scheduler able to handle that case?
This problem shouldn't occurr with no SMP-support and interrupts disabled in kernel, right?
On the other hand, this case is exactly the problem. Perhaps I wasn't clear enough in the last post. If I have to switch tasks when I've got an interrupt during idle something wents wrong. I am not sure if we're talking about the same thing :)
Artlav wrote:-The fields for different-level stacks should be valid in TSS's.
It is valid for CPL 0. Since I'm just using ring3 and ring0 this should be ok, AFAIK.
Artlav wrote:-popa does not update ESP.
I'm saving and restoring all required registers manually. The stack-pointer (for user-mode) is updated automatically if switching back to a (numerically) higher PL, right?
Artlav wrote:-Interrupt does not push SS and ESP if called from the same CPL, but does if from different.
That's one of the possible problems I had in mind. But if I have understood it correctly it should work. Because the CPL is determined by the segment-registers. They are stored on the kernel-stack at interrupt-begin (cs by the CPU, the other manually). That means if I switch the kernel-stack and stack- and base-pointer (from nested-interrupt to not-nested-interrupt) everything should be as if the idle-task has never existed and the CPU should restore SS and ESP because we're changing the CPL when leaving interrupt-handling.
Or is this not correct?
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: Idling in kernel & nested interrupts (x86)

Post by hrniels »

Found the problem =)
We can't idle in the current stack-frame because an interrupt will overwrite parts of it. This is a problem as soon as the kernel-stack of the thread that was used for idle should be used 'normally' again.
But it works fine if I call a function to idle. This way we get a new stack-frame (which doesn't need to be valid after the interrupt since we'll never leave it) and the old one doesn't get destroyed :)

Thanks for your help!
Post Reply