Page 1 of 1

Context switching between threads crashes and burns

Posted: Fri Dec 16, 2011 11:14 am
by Barrucadu
Hi,

I'm trying to implement multithreading in my OS, but context switching causes a general protection fault.

Data structures:

Code: Select all

/**
 * Kernel thread type
 */
typedef struct
{
  uint32_t* stack;               /**< Pointer to the head of the stack */
  uint32_t* stackstart;          /**< Pointer to the base of the stack */
  uint32_t  stacksize;           /**< Size of the stack */
  void      (*threadfunc)(void); /**< Thread's main function */
  void*     threadargs;          /**< Argument of the function */
} kthread_t;

/**
 * Thread queue type
 */
typedef struct
{
  kthread_t** threads;  /**< List of threads */
  size_t      num;      /**< Number of threads */
  size_t      cur;      /**< ID of currently executing thread */
  uint8_t     inkernel; /**< Whether we're in the kernel thread or not */
  uint8_t     running;  /**< Whether we're running or not */
} schedule_t;
Scheduling code:

Code: Select all

/**
 * Switch to the next thread, if there is one
 */
uint32_t*
schedule (uint32_t* context)
{
  /* Check if we're running */
  if (!queue.running)
    return context;

  /* Check if there are any threads */
  if (queue.num == 0)
    {
      kprintf((string)"There are no threads. Halting.\n");
      halt ();
    }

  /* Check if there is a thread to switch to */
  if (queue.num == 1 && !queue.inkernel)
    return context;

  /* Save the old stack context */
  if (!queue.inkernel)
    queue.threads[queue.cur]->stack = context;

  /* Get the next thread */
  if (!queue.inkernel)
    queue.cur = (queue.cur + 1) % queue.num;

  /* Set the new stack context */
  kprintf((string)"Switching from stack context %x to %x\n", context, queue.threads[queue.cur]->stack);

  /* If we were in the kernel thread, we're not any more */
  queue.inkernel = 0;

  /* Acknowledge the interrupt */
  outport (PIC1, PIC_ACK);

  /* Return the new stack context */
  return queue.threads[queue.cur]->stack;
}
Scheduler calling code:

Code: Select all

extern schedule

;;; IRQ handler
%macro IRQ 2
global irq%1
irq%1:
        cli
        push byte 0
        push byte %2
        jmp handler
%endmacro

;;; IRQs which invoke the scheduler
IRQ 0, 32

;;; Push CPU state to the stack, call the handler, and pop the state back
handler:
        pusha
        push ds
        push es
        push fs
        push gs
        
        mov eax, 0x10
        mov ds, eax
        mov es, eax
        mov fs, eax
        mov gs, eax
        
        push esp
        call schedule
        
        mov esp, eax
        
        pop gs
        pop fs
        pop es
        pop ds
        popa

        add esp, 8
        iret
Thread creation code:

Code: Select all

/**
 * Create a thread, and add it to the scheduler queue
 */
void
kthread_create (void (*threadfunc)(void), void* threadargs)
{
  kthread_t* kthread = (kthread_t*) kcalloc (1, sizeof (kthread_t));

  /* Set up the stack */
  kthread->stackstart = (uint32_t*) kmalloc (THREAD_STACK_SIZE);
  kthread->stack      = kthread->stackstart + THREAD_STACK_SIZE / sizeof(*kthread->stack);
  kthread->stacksize  = THREAD_STACK_SIZE;

  /* Set up the initial stack */
  *(--kthread->stack) = EFLAGS_IF | EFLAGS_RESERVED; /* EFLAGS */
  *(--kthread->stack) = GDT_CODE_ID << 3;            /* CS     */
  *(--kthread->stack) = (uint32_t)threadfunc;        /* EIP    */
  *(--kthread->stack) = 0;                           /* EDI    */
  *(--kthread->stack) = 0;                           /* EDI    */
  *(--kthread->stack) = 0;                           /* EBP    */
  *(--kthread->stack) = 0;                           /* NULL   */
  *(--kthread->stack) = 0;                           /* EBX    */
  *(--kthread->stack) = 0;                           /* EDX    */
  *(--kthread->stack) = 0;                           /* ECX    */
  *(--kthread->stack) = 0;                           /* EAX    */
  *(--kthread->stack) = GDT_DATA_ID << 3;            /* DS     */
  *(--kthread->stack) = GDT_DATA_ID << 3;            /* ES     */
  *(--kthread->stack) = GDT_DATA_ID << 3;            /* FS     */
  *(--kthread->stack) = GDT_DATA_ID << 3;            /* GS     */

  /* Save the function and arguments */
  kthread->threadfunc = threadfunc;
  kthread->threadargs = threadargs;

  /* Add to the schedulers queue */
  scheduler_add_thread (kthread);
}
If you could point me on the way to fixing things, I would be grateful - I've been stuck on this for hours now.

Re: Context switching between threads crashes and burns

Posted: Fri Dec 16, 2011 3:25 pm
by eryjus
I recently had similar issues and got lots of help. Check out this thread: http://forum.osdev.org/viewtopic.php?f=1&t=24452

Re: Context switching between threads crashes and burns

Posted: Sat Dec 17, 2011 5:18 am
by cyr1x
You are walking past your allocated stack memory.
Try this after malloc:

Code: Select all

kthread->stack += THREAD_STACK_SIZE/sizeof(*kthread->stack);

Re: Context switching between threads crashes and burns

Posted: Sat Dec 17, 2011 7:54 am
by Barrucadu
Ok, I have corrected that and updated the first post. Still get a general protection fault; the output from BOCHS is "00202969156e[CPU0 ] fetch_raw_descriptor: GDT: index (727) e4 > limit (17)"

Re: Context switching between threads crashes and burns

Posted: Sat Dec 17, 2011 9:42 am
by AJ
Hi,

Your stack pointer has become misaligned - when you pop one of the segment descriptors, you are loading an invalid value. The last instruction (which GRUB should print at the end of the register dump) should tell you which segment POP instruction has caused the fault. You then need to make sure that ESP is set correctly on the return to your assembly ISR and that you do not POP more values than you PUSH.

Cheers,
Adam

Re: Context switching between threads crashes and burns

Posted: Sat Dec 17, 2011 1:52 pm
by cyr1x
You're missing to put the error code and interrupt number on the stack.

Code: Select all

... // EIP
*(--kthread->stack) = 0; // error
*(--kthread->stack) = 0; // int
...

Re: Context switching between threads crashes and burns

Posted: Sat Dec 17, 2011 2:20 pm
by turdus
Use bochs debugger. It will tell you which instruction causes the GPF, and you'll be able to examine the stack as well. Turning cpu trace on before the crash could be very handy too (you'll see the disassembled code that leads to GPF, not just the last instruction).

If your problem causes triple fault, you can still debug it, but in that case you'll have to patch the bochs source:
in cpu/exception.cc replace shutdown() with bx_debug_break():

Code: Select all

        BX_PANIC(("exception(): 3rd (%d) exception with no resolution", vector));
        BX_ERROR(("WARNING: Any simulation after this point is completely bogus !"));
-       shutdown();
+       bx_debug_break();
Look for the string "bogus". Can't tell you the exact line number, since it varies from version to version, I do not know which one you use.

Re: Context switching between threads crashes and burns

Posted: Sun Dec 18, 2011 11:15 am
by Barrucadu
Thanks for the help guys, I have fixed it (turned out I also had a slight bug in my memory manager, too).

The working scheduler and thread implementations can be seen here if anyone is interested:
https://github.com/Barrucadu/lispos/blo ... kthreads.c
https://github.com/Barrucadu/lispos/blo ... kthreads.h
https://github.com/Barrucadu/lispos/blo ... eduler.asm
https://github.com/Barrucadu/lispos/blo ... cheduler.c
https://github.com/Barrucadu/lispos/blo ... cheduler.h