OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 3:39 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 13 posts ] 
Author Message
 Post subject: Another multitasking question
PostPosted: Thu Mar 16, 2017 8:27 am 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
I'm really sorry for asking very much questions about multitasking, but I'm out of ideas again, so I couldn't write even two lines of code.

I remove tasks with a queue. When I call remove_task(pid), it adds the task to remove queue, and when switching tasks, it removes all tasks in the queue, cleans the data and frees memory location used by task.

But, then I noticed that causes tasks to stroll out its address space.

For example:

User task calls exit(0);
Exit interrupt
System adds task to remove queue
Interrupt return
Task continues executing, strolls out its address space.
IRQ0 (task switch)
Task gets removed, system switches to next task.
Interrupt return
Next task's code...

Yes, the task gets removed after a while but it strolls out its address space until IRQ0, so it can do something bad.

My solution I thought is putting task in a kind of infinite loop, but it is an ugly solution as system call exit doesn't get the stack address (interrupt_registers_t*) as a parameter, that allows me to get / set eip.
(I *tried* to say int nn instruction pushes cs, eip and another data to stack, but I'm not a native speaker :|)

I'm out of ideas, what's the best way to do that?

Thanks in advance.

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 8:56 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 03, 2008 4:13 am
Posts: 153
Location: Ogre, Latvia, EU
What about not returning from exit syscall? Switch away from task immediately and never return to it.

Something like:
User task#1 calls exit(0);
Exit interrupt
System adds task#1 to remove queue
System switches to task#2.
Interrupt return (into task#2)
Task's#2 code...

IRQ0 (another task switch)
Task#1 gets removed, system switches to task#3.
Interrupt return
Task's#3 code...

_________________
If something looks overcomplicated, most likely it is.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 9:03 am 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
Velko wrote:
What about not returning from exit syscall? Switch away from task immediately and never return to it.

Something like:
User task#1 calls exit(0);
Exit interrupt
System adds task#1 to remove queue
System switches to task#2.
Interrupt return (into task#2)
Task's#2 code...

IRQ0 (another task switch)
Task#1 gets removed, system switches to task#3.
Interrupt return
Task's#3 code...


This is a good way to exit also, but again the systemcall handler doesn't pass the interrupt frame to exit, so I can't edit it from syscall_exit.
Should I build the interrupt frame myself and do an iret? (Same way with switching to user mode)

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 9:23 am 
Offline
Member
Member
User avatar

Joined: Wed Oct 27, 2004 11:00 pm
Posts: 874
Location: WA
Agola wrote:
This is a good way to exit also, but again the systemcall handler doesn't pass the interrupt frame to exit, so I can't edit it from syscall_exit.
Should I build the interrupt frame myself and do an iret? (Same way with switching to user mode)


I think you are getting a little confused here...

all your exit syscall should do is:

add task to your remove_task queue
JMP to the scheduler to schedule the next task (this, of course, never returns, since the task is never re-scheduled)

the code that changes tasks here is:
the same code that changes tasks on irq0
the same code that changes tasks whenever the code calls a blocking syscall
the same code that changes tasks when you get an interrupt from the HDD
the same code that changes tasks when... any time you change tasks

therefore, there is nothing special you have to do, because it happens automatically -- the only difference between the exit syscall calling the scheduler and the irq0 handler calling the scheduler is that the task is never rescheduled after the exit syscall, and therefore the code never continues

edit:
actually, I would make a function that moves the current task to the remove queue and then calls SwitchThread() without returning... so that function (which I would consider part of the scheduler) would never return at all and all your exit function has to do to terminate the task is call SchedulerTerminateTask()

_________________
## ---- ----- ------ Intel Manuals
OSdev wiki


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 10:34 am 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
JAAman wrote:
Agola wrote:
This is a good way to exit also, but again the systemcall handler doesn't pass the interrupt frame to exit, so I can't edit it from syscall_exit.
Should I build the interrupt frame myself and do an iret? (Same way with switching to user mode)


I think you are getting a little confused here...

all your exit syscall should do is:

add task to your remove_task queue
JMP to the scheduler to schedule the next task (this, of course, never returns, since the task is never re-scheduled)

the code that changes tasks here is:
the same code that changes tasks on irq0
the same code that changes tasks whenever the code calls a blocking syscall
the same code that changes tasks when you get an interrupt from the HDD
the same code that changes tasks when... any time you change tasks

therefore, there is nothing special you have to do, because it happens automatically -- the only difference between the exit syscall calling the scheduler and the irq0 handler calling the scheduler is that the task is never rescheduled after the exit syscall, and therefore the code never continues

edit:
actually, I would make a function that moves the current task to the remove queue and then calls SwitchThread() without returning... so that function (which I would consider part of the scheduler) would never return at all and all your exit function has to do to terminate the task is call SchedulerTerminateTask()


Thanks. But won't that kill kernel stack after a while?

Syscall is an interrupt, so processor pushes ss, esp, eflags, cs and eip, and my interrupt handler pushes all registers with segment registers.
Then I switch another task in half of interrupt handler, without clearing them.

It should kill the kernel stack as these values never gets popped off.

Also my task switching mechanism is a bit different.
When IRQ0 fires, it calls the task handler.
Task handler saves current register state to task's register state, then restores next task's state.
After iret, all register state of next task gets restored.

Code:
void task_handler(registers_t* regs)
{
    memcpy(current_task->regs, regs, sizeof(registers_t));

    if (!current_task->next || remove_queue) current_task = first_task;
    else current_task = current_task->next;

    while (remove_queue)
    {
        rq_task_t* rq_task = remove_queue;
        remove_queue = remove_queue->next;

         task_t* current = first_task;
         while (current->next != rq_task->task) current = current->next;

         current->next = rq_task->task->next;

         clean_task(rq_task->task);
    }

    memcpy(regs, current_task->regs, sizeof(registers_t));

    // asm volatile ("xchgw %bx, %bx"); (Bochs' magic break)
}


That is my task switch handler. CR3 (Page directory) changes in another part of code.

That is how I initialize tasking and switch to first task:

Code:
void tasking_install()
{
    first_task = create_task(&kernel_idle, 0);

    first_task->pid = 0;
    first_task->name = "KERNEL_IDLE";
    first_task->priority = PRIORITY_MID;

    current_task = first_task;

    set_kernel_stack((uintptr_t)memalign(0x10, 0x1000) + 0x1000);

    // asm volatile ("xchgw %bx, %bx"); (Bochs debugger breakpoint)

    asm volatile ("  \
        cli; \
        movl %%bx, %%ax; \
        movl %%ax, %%ds; \
        movl %%ax, %%es; \
        movl %%ax, %%fs; \
        movl %%ax, %%gs; \
    " : : "b"(first_task->regs->ds));

    asm volatile ("  \
        pushl %%eax; \
        pushl %%ebx; \
        pushl %%ecx; \
        pushl %%edx; \
        pushl %%edi; \
        iretl; \
    " : : "a"(first_task->regs->ss), "b"(first_task->regs->esp), "c"(first_task->regs->eflags), "d"(first_task->regs->cs), "D"(first_task->regs->eip));
}

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 12:22 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 03, 2008 4:13 am
Posts: 153
Location: Ogre, Latvia, EU
Quote:
Syscall is an interrupt, so processor pushes ss, esp, eflags, cs and eip.
Then I switch another task in half of interrupt handler, without clearing them.

And then you proceed to return to that second task. And pop out the register values in the process.

_________________
If something looks overcomplicated, most likely it is.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 12:44 pm 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
Velko wrote:
Quote:
Syscall is an interrupt, so processor pushes ss, esp, eflags, cs and eip.
Then I switch another task in half of interrupt handler, without clearing them.

And then you proceed to return to that second task. And pop out the register values in the process.


Ah, I couldn't understand what did you mean.
Can you explain a bit more?

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Thu Mar 16, 2017 2:24 pm 
Offline
Member
Member
User avatar

Joined: Fri Oct 03, 2008 4:13 am
Posts: 153
Location: Ogre, Latvia, EU
Imagine that task_2 was suspended some time ago. Its register values are stored in task_2->regs. Task_1 is currently running.

Now, task_1 is interrupted (it could be timer int, keyboard int, your syscall, whatever), you:

Scenario 1 (no switching):
  • save current register frame (processor does part of it, you may want to push more registers "manually")
  • do some stuff in kernel
  • return to userspace (pop off what you pushed "manually", iret takes care of the rest).

Scenario 2 (switch tasks):
  • save current register frame (processor does part of it, you may want to push more registers "manually")
  • do some stuff
  • decide that you need to switch to task_2
  • store current register frame in task_1->regs
  • load current register frame from task_2->regs
  • do some housekeeping (mark task_2 as currently active), maybe switch address spaces
  • return to userspace (pop off what you pushed "manually", iret takes care of the rest).

The last step does not care if register frame was created by the same task (scenario 1), saved and restored (scenario 2) or created artificially (initialize task that have never been ran). It just uses whats in there.

_________________
If something looks overcomplicated, most likely it is.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Fri Mar 17, 2017 12:54 pm 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
Actually I couldn't explain what did I mean very good.
Then I prepared that image to explain everything fully:

Image


That's the problem. What's the best task removing method for my scheduler and interrupt handler?

I have a new idea also. How about having two remove task functions, one for normal removes, called remove_task, one for removes in interrupt handlers, that causes the kernel stack overflow problem, remove_task_fint. The remove_task_fint gets two arguments, pid and interrupt frame address, while remove_task gets just pid.

remove_task_fint does the same method of task switching, copies the next task's regs to interrupt frame.

That's good but then my code gets a bit... erm... what was its name... ah yes, messy.

Code:
   ...
   uintptr_t syscall = (uintptr_t) syscalls[regs->eax];
   if (!syscall) return;

   syscall_t call = (syscall_t) syscall;
   uint32_t ret = call(regs->ebx, regs->ecx, regs->edx, regs->esi, regs->edi);
   ...

Code:
int sys_exit(uint32_t code)
{
     ...
     remove_task_fint(current_task->pid, regs);
     ...
}


But where is regs?
Syscall handler doesn't put it as an argument while calling sys_exit and other syscalls.

I can change it to:

Code:
   ...
   uintptr_t syscall = (uintptr_t) syscalls[regs->eax];
   if (!syscall) return;

   syscall_t call = (syscall_t) syscall;
   uint32_t ret = call(regs, regs->ebx, regs->ecx, regs->edx, regs->esi, regs->edi);
   ...

Code:
int sys_exit(registers_t* r, uint32_t code)
{
     ...
     remove_task_fint(current_task->pid, regs);
     ...
}


but it gets messy and it's a bad solution. I want do it in the best way.

Maybe I should have a rest as I can't think anymore, my brain got caught in a while loop.

Thanks in advance.

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Sat Mar 18, 2017 12:52 am 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
Okay, I took a paper and started writing, tested every way in paper.
Result? Nothing.

I'm fully out of ideas now. :?

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Sat Mar 18, 2017 1:51 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 03, 2008 4:13 am
Posts: 153
Location: Ogre, Latvia, EU
For a moment forget about exit() syscall. Once you get everything else right, it becomes a simple switch away and never return (and clean up at some point).

Correct implementation of many syscalls requires them to switch tasks. Some examples:
  • yield - a syscall where thread voluntarly gives up its timeslice. Simple, great as an exercise, but won't be used much once your OS matures;
  • sleep - thread wants to be suspended for a period of time. What should processor do? Also sleep or run code in another task?
  • write() on pipe to another process. Again, you block current task until read() is completed at the other end of that pipe.

I see, your main objection is about passing pointer to registers_t into syscall handlers. I can understand that - it messes up code clarity a bit. You will need to figure out another way how to initiate task reschedule then.

_________________
If something looks overcomplicated, most likely it is.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Sat Mar 18, 2017 2:24 pm 
Offline
Member
Member
User avatar

Joined: Sun Nov 20, 2016 7:26 am
Posts: 155
Location: Somewhere
Velko wrote:
For a moment forget about exit() syscall. Once you get everything else right, it becomes a simple switch away and never return (and clean up at some point).

Correct implementation of many syscalls requires them to switch tasks. Some examples:
  • yield - a syscall where thread voluntarly gives up its timeslice. Simple, great as an exercise, but won't be used much once your OS matures;
  • sleep - thread wants to be suspended for a period of time. What should processor do? Also sleep or run code in another task?
  • write() on pipe to another process. Again, you block current task until read() is completed at the other end of that pipe.

I see, your main objection is about passing pointer to registers_t into syscall handlers. I can understand that - it messes up code clarity a bit. You will need to figure out another way how to initiate task reschedule then.


I've implemented yield, sleep and partially implemented write on pipe to another process.
But still I couldn't find a good way to remove task and not return from exit or task_remove without killing stack.

Then I looked multitasking code of some OSes like ToaruOS to get an idea, and I noticed ToaruOS uses different kernel stack for every task.
Then kernel stack doesn't get screwed, because there isn't a static kernel stack like my OS, that causes stack gets increased after every task_remove. So in ToaruOS when a task switch occurs, kernel stack changes also. Then ToaruOS only screws the stack of task to remove. But it's going to be removed, so it's not a problem...

But that didn't work in my OS, I just get a bunch of #GPs, I'm still debugging it in Bochs and trying get it to work. Looks like stack address causes that. I'm probably mishandling something.

The bad thing is I can't think anything for that problem. When I try to think I get confused and feel like my brain is burning :|.
It has the same feeling of trying to understand and solving quantum field theory.
In other words, that's overcomplicated for me.

My scheduler is completely a part of IRQ0, that makes calling scheduler harder when I want.

Everything works good if I pass registers_t* to every syscall function, but it really messes the code.

Thanks in advance.

_________________
Keyboard not found!

Press F1 to run setup.
Press F2 to continue.


Top
 Profile  
 
 Post subject: Re: Another multitasking question
PostPosted: Mon Mar 20, 2017 7:24 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 03, 2008 4:13 am
Posts: 153
Location: Ogre, Latvia, EU
Quote:
My scheduler is completely a part of IRQ0, that makes calling scheduler harder when I want.

That's your main problem. IRQ0 (timer) should be only one of many ways to initiate task switch.

Many OSes uses different kernel stack per thread. Main advantage of such approach is ability to do task switch without having to pass a pointer to registers_t structure. Register frame is always at the bottom of the stack, and if you need not to change anything there, you need no pointer to it. You just switch stacks (store current, load new ESP) and instantly everything runs in context of new task.

You could come up with some scheme for your syscall to signal that it needs a task switch, for example:
Code:
uintptr_t syscall = (uintptr_t) syscalls[regs->eax];
if (!syscall) return;

syscall_t call = (syscall_t) syscall;
int needs_reschedule = 0;
uint32_t ret = call(&needs_reschedule, regs->ebx, regs->ecx, regs->edx, regs->esi, regs->edi);
if (needs_reschedule) call_scheduler(regs);
if you want to keep your current kernel stack model. Then your yield syscall becomes:
Code:
int sys_yield(int *needs_reschedule)
{
    *needs_reschedule = 1;
    return 0;
}

_________________
If something looks overcomplicated, most likely it is.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC - 6 hours


Who is online

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