OSDev.org

The Place to Start for Operating System Developers
It is currently Sun Apr 28, 2024 1:41 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 79 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next
Author Message
 Post subject: Confused about context switch
PostPosted: Tue Oct 03, 2023 2:32 pm 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
Hello, forum!
I were trying to start with my first device driver but firstly, of course, I have to implement scheduler. I have scheduling algorithm which works as it should (I'm not completely sure but...). The only problem for me is Context switching.
I was following the [wiki]Brendan's Multi-tasking Tutorial[/wiki] and I cant make context switch work properly. Now my code looks like this
Code:
section .text
global switch_context

switch_context:
    push ebx
    push esi
    push edi
    push ebp

    mov edi,[now_executed]    ;edi = address of the previous task's "thread control block"
    mov [edi + TCB.esp],esp         ;Save ESP for previous task's kernel stack in the thread's TCB

    ;Load next task's state

    mov esi,[esp+(4+1)*4]         ;esi = address of the next task's "thread control block" (parameter passed on stack)
    mov [now_executed],esi    ;Current task's TCB is the next task TCB

    mov esp,[esi + TCB.esp]         ;Load ESP for next task's kernel stack from the thread's TCB
    mov eax,[esi + TCB.pgd]         ;eax = address of page directory for next task
    mov ebx,[esi + TCB.esp0]        ;ebx = address for the top of the next task's kernel stack
    mov [TSS + TSS_struc.esp0],ebx   ;Adjust the ESP0 field in the TSS (used by CPU for for CPL=3 -> CPL=0 privilege level changes)
    mov ecx,cr3                   ;ecx = previous task's virtual address space

    cmp eax,ecx                   ;Does the virtual address space need to being changed?
    je .doneVAS                   ; no, virtual address space is the same, so don't reload it and cause TLB flushes
    mov cr3,eax                   ; yes, load the next task's virtual address space
.doneVAS:
    pop ebp
    pop edi
    pop esi
    pop ebx

    ret                           ;Load next task's EIP from its kernel stack

TSS and now_executed are pointers to TSS and task structures. This is how I call this function
Code:
switch_context(&(task));
, where task is TaskStructure. But this context switch function throws exception "Invalid opcode". Where is the problem? My idea is that I prepare stack for task wrong. Here is the code:
Code:
void prepare_task(task_struct_t task, uint32_t eip) {
    uint32_t esp;
    asm volatile (
        "movl %%esp, %0"
        : "=r"(esp)
    );

    asm volatile (
        "mov %0, %%esp\n"
        "pushl %1\n"
        "push %%ebp\n"
        "push %%edi\n"
        "push %%esi\n"
        "push %%ebx\n"
        "movl %2, %%esp\n"
        :
        : "r"(task.esp), "r"(eip), "r"(esp)
    );
}

So I'm totally confused about this. Thank you for reply.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Tue Oct 03, 2023 6:43 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
The problem is indeed your prepare_task() function. You don't need inline assembly. You do need to modify the task's stack pointer. If you want this function to update the stack pointer, you need to pass a pointer to the task structure.

Something like this?
Code:
void prepare_task(task_struct_t * task, uint32_t eip) {
    uint32_t * stack = (uint32_t *)task->esp;
    stack[-1] = eip;
    task->esp -= 5 * sizeof( uint32_t );
}


Also, this isn't a problem, but you could simplify switch_context() a bit. It only needs to push the four callee-saved registers, save ESP, load ESP, pop the four registers, and return. The other things like updating CR3 and TSS.ESP0 can be done before you call it.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Wed Oct 04, 2023 1:27 pm 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
Octocontrabass wrote:
The problem is indeed your prepare_task() function. You don't need inline assembly. You do need to modify the task's stack pointer. If you want this function to update the stack pointer, you need to pass a pointer to the task structure.

Something like this?
Code:
void prepare_task(task_struct_t * task, uint32_t eip) {
    uint32_t * stack = (uint32_t *)task->esp;
    stack[-1] = eip;
    task->esp -= 5 * sizeof( uint32_t );
}


Also, this isn't a problem, but you could simplify switch_context() a bit. It only needs to push the four callee-saved registers, save ESP, load ESP, pop the four registers, and return. The other things like updating CR3 and TSS.ESP0 can be done before you call it.

Still have the same problem. Maybe I miss some important thing? If this can help, here is my code for preparing kernel task:
Code:
task_struct_t create_kernel_task() {
    uint32_t esp, eip;
    asm volatile (
        "movl %%esp, %0\n"
        "call get_eip\n"
        "get_eip:\n"
        "pop %1\n"
        : "=r"(esp), "=r"(eip)
    );

    task_struct_t task = create_task(esp, TASK_EXEC_KERNEL);
    task.memory_map.pgd = page_directory;
    task.state = TASK_STATE_WAITING;
    task.exec_mode = TASK_EXEC_KERNEL;

    prepare_task(task, eip);
    return task;
}


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Wed Oct 04, 2023 4:56 pm 
Offline
Member
Member

Joined: Mon Feb 02, 2015 7:11 pm
Posts: 898
It looks like you are setting the eip of the new task to the current task's one. Are you trying to implement fork()? You will need to also copy the stack and a bunch of other things, it might be better to start with a simpler approach (i.e. don't try to fork).

For example, I start execution at a static function named "Task::Entry()". Here is what my setup looks like:

Code:
void Task::Initialize(EntryPoint entryPoint, const void* args)
{
    const char* stack = (char*)GetStack();

    // We use an InterruptContext to "return" to the task's entry point. The reason we can't only use a CpuContext
    // is that we need to be able to set arguments for the entry point. These need to go in registers (rdi, rsi, rdx)
    // that aren't part of the CpuContext.
    constexpr auto interruptContextSize = sizeof(InterruptContext);
    stack = stack - mtl::AlignUp(interruptContextSize, 16);

    const auto interruptContext = (InterruptContext*)stack;
    interruptContext->rip = (uintptr_t)Task::Entry;                    // "Return" to Task::Entry
    interruptContext->cs = (uint64_t)Selector::KernelCode;             // "Return" to kernel code
    interruptContext->rflags = mtl::EFLAGS_RESERVED;                   // Start with interrupts disabled
    interruptContext->rsp = (uintptr_t)(stack + interruptContextSize); // Required by iretq
    interruptContext->ss = (uint64_t)Selector::KernelData;             // Required by iretq
    interruptContext->rdi = (uintptr_t)this;                           // Param 1 for Task::Entry
    interruptContext->rsi = (uintptr_t)entryPoint;                     // Param 2 for Task::Entry
    interruptContext->rdx = (uintptr_t)args;                           // Param 3 for Task::Entry

    // Setup a task switch interruptContext to simulate returning from an interrupt.
    stack = stack - sizeof(CpuContext);

    const auto cpuContext = (CpuContext*)stack;
    cpuContext->rip = (uintptr_t)InterruptExit;

    m_context = cpuContext;
}

void Task::Entry(Task* task, EntryPoint entryPoint, const void* args)
{
    task->m_state = TaskState::Running;
    entryPoint(task, args);

    // TODO: die
    for (;;)
        ;
}

_________________
https://github.com/kiznit/rainbow-os


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Wed Oct 04, 2023 6:04 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
KrotovOSdev wrote:
If this can help, here is my code for preparing kernel task:
Why do you want the new task to start in the middle of your create_kernel_task() function? Why do you want the new task to use the same stack as the current task?


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Thu Oct 05, 2023 10:18 am 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
Octocontrabass wrote:
KrotovOSdev wrote:
If this can help, here is my code for preparing kernel task:
Why do you want the new task to start in the middle of your create_kernel_task() function? Why do you want the new task to use the same stack as the current task?

Create kernel task is a function which creates task function but not starts it.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Thu Oct 05, 2023 11:20 am 
Offline
Member
Member

Joined: Tue Apr 03, 2018 2:44 am
Posts: 403
Read, and understand, PORTABLE MULTITHREADING, which is the basis of GNU PTH.

It is a user level threading library, gives details on how it creates its initial thread context, in a mostly portable manner.

Basically if using the setjmp/longjmp method, in the creating thread, you temporarily switch to the stack of the thread being created, save some state (including the stack pointer on the new stack) using setjmp (which will return 0), then switch back to the old stack.

Then, when you want to actually switch to the new thread, you save the current thread state using setjmp, then longjump using the jmp_buf setup above in the new thread, and your code will now be running on the new stack, in the bootstrap function, returning != 0 from setjmp. That is then your signal to jump to the new thread code.

Once the initial thread context is created, switching threads is then quite simple, using existing C setjmp primitives (or POSIX context primitives, in the paper). A task switch becomes:

Code:
  if (setjmp(currentthread->context)==0) {
    longjmp(nextthread->context, 1);
  }


I used this idea as the basis of my kernel threads. All kernel thread switching is implemented using setjmp/longjmp, which saves/restores the compiler visible state. That is all you need in the kernel thread, any user visible state such as address space (cr3) or the kernel stack in the TSS can be managed separately, and your interrupt handlers should already save the user level register state on the kernel stack. Kernel esp and cr3 management can be in separate code, as suggested by @Octocontrabass.

All you need then is the architecture specific code to execute your thread bootstrap code with some arbitrary stack pointer.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Thu Oct 05, 2023 1:31 pm 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
thewrongchristian wrote:
Read, and understand, PORTABLE MULTITHREADING, which is the basis of GNU PTH.

It is a user level threading library, gives details on how it creates its initial thread context, in a mostly portable manner.

Basically if using the setjmp/longjmp method, in the creating thread, you temporarily switch to the stack of the thread being created, save some state (including the stack pointer on the new stack) using setjmp (which will return 0), then switch back to the old stack.

Then, when you want to actually switch to the new thread, you save the current thread state using setjmp, then longjump using the jmp_buf setup above in the new thread, and your code will now be running on the new stack, in the bootstrap function, returning != 0 from setjmp. That is then your signal to jump to the new thread code.

Once the initial thread context is created, switching threads is then quite simple, using existing C setjmp primitives (or POSIX context primitives, in the paper). A task switch becomes:

Code:
  if (setjmp(currentthread->context)==0) {
    longjmp(nextthread->context, 1);
  }


I used this idea as the basis of my kernel threads. All kernel thread switching is implemented using setjmp/longjmp, which saves/restores the compiler visible state. That is all you need in the kernel thread, any user visible state such as address space (cr3) or the kernel stack in the TSS can be managed separately, and your interrupt handlers should already save the user level register state on the kernel stack. Kernel esp and cr3 management can be in separate code, as suggested by @Octocontrabass.

All you need then is the architecture specific code to execute your thread bootstrap code with some arbitrary stack pointer.


It looks like that now everything works fine even without longjump/setjump. Despithe this I'll try to understand this mechanisms.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Thu Oct 05, 2023 7:33 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
KrotovOSdev wrote:
Create kernel task is a function which creates task function but not starts it.

Yes. It sets the new task's EIP to point to the middle of create_kernel_task(), so that is the code the new task will begin executing. Why are you setting the new task's EIP that way? What code do you want the new task to execute?


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Fri Oct 06, 2023 10:43 am 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
Octocontrabass wrote:
KrotovOSdev wrote:
Create kernel task is a function which creates task function but not starts it.

Yes. It sets the new task's EIP to point to the middle of create_kernel_task(), so that is the code the new task will begin executing. Why are you setting the new task's EIP that way? What code do you want the new task to execute?

That was the first problem. I've moved label to the end of kernel initialization process but now I have another problem - kernel stack overflow. I think it might be connected with IRQ0 handler and I have to make handler function static.
In my case, kernel and scheduler use one stack. Is this a problem or not?


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Fri Oct 06, 2023 10:48 am 
Offline
Member
Member

Joined: Mon Feb 02, 2015 7:11 pm
Posts: 898
Schedulers don't use stacks. Tasks use stacks. Each task needs its own stack. When you create a new task, you need to allocate a new stack for it and initialize %esp to point to it.

_________________
https://github.com/kiznit/rainbow-os


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Fri Oct 06, 2023 11:17 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
KrotovOSdev wrote:
I've moved label to the end of kernel initialization process

That still doesn't sound right...

KrotovOSdev wrote:
In my case, kernel and scheduler use one stack. Is this a problem or not?

Each thread needs its own stack. Interrupt handlers can use the stack from whichever thread they interrupt. When your scheduler is called from an interrupt handler, it can use the same stack as the interrupt handler.

You will probably want to call your scheduler without an interrupt handler sometimes, so don't design your scheduler to require interrupts.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Fri Oct 06, 2023 1:08 pm 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
Octocontrabass wrote:
KrotovOSdev wrote:
I've moved label to the end of kernel initialization process

That still doesn't sound right...

KrotovOSdev wrote:
In my case, kernel and scheduler use one stack. Is this a problem or not?

Each thread needs its own stack. Interrupt handlers can use the stack from whichever thread they interrupt. When your scheduler is called from an interrupt handler, it can use the same stack as the interrupt handler.

You will probably want to call your scheduler without an interrupt handler sometimes, so don't design your scheduler to require interrupts.

I mean scheduler is a part of my kernel and it uses kernel stack.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Fri Oct 06, 2023 2:38 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
As long as each thread has its own kernel stack, that's fine.


Top
 Profile  
 
 Post subject: Re: Confused about context switch
PostPosted: Sat Oct 07, 2023 2:10 pm 
Offline
Member
Member

Joined: Sat Aug 12, 2023 1:48 am
Posts: 40
Location: Nizhny Novgorod, Russia
After I moved EIP to the end of initialization process, it just overflows my stack. I think the problem may be connected with IRQ0 handler which never returns. Can it be so?


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

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot] and 17 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