Context switching between threads crashes and burns

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
Barrucadu
Posts: 9
Joined: Sun Jan 24, 2010 10:14 am
Location: York, England
Contact:

Context switching between threads crashes and burns

Post 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.
Last edited by Barrucadu on Sat Dec 17, 2011 7:54 am, edited 1 time in total.
User avatar
eryjus
Member
Member
Posts: 286
Joined: Fri Oct 21, 2011 9:47 pm
Libera.chat IRC: eryjus
Location: Tustin, CA USA

Re: Context switching between threads crashes and burns

Post 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
Adam

The name is fitting: Century Hobby OS -- At this rate, it's gonna take me that long!
Read about my mistakes and missteps with this iteration: Journal

"Sometimes things just don't make sense until you figure them out." -- Phil Stahlheber
cyr1x
Member
Member
Posts: 207
Joined: Tue Aug 21, 2007 1:41 am
Location: Germany

Re: Context switching between threads crashes and burns

Post 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);
Barrucadu
Posts: 9
Joined: Sun Jan 24, 2010 10:14 am
Location: York, England
Contact:

Re: Context switching between threads crashes and burns

Post 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)"
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Re: Context switching between threads crashes and burns

Post 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
cyr1x
Member
Member
Posts: 207
Joined: Tue Aug 21, 2007 1:41 am
Location: Germany

Re: Context switching between threads crashes and burns

Post 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
...
User avatar
turdus
Member
Member
Posts: 496
Joined: Tue Feb 08, 2011 1:58 pm

Re: Context switching between threads crashes and burns

Post 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.
Barrucadu
Posts: 9
Joined: Sun Jan 24, 2010 10:14 am
Location: York, England
Contact:

Re: Context switching between threads crashes and burns

Post 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
Post Reply