Another multitasking question
Another multitasking question
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.
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.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
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...
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.
Re: Another multitasking question
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.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...
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.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
I think you are getting a little confused here...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)
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()
Re: Another multitasking question
Thanks. But won't that kill kernel stack after a while?JAAman wrote:I think you are getting a little confused here...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)
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()
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: Select all
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 how I initialize tasking and switch to first task:
Code: Select all
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.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
And then you proceed to return to that second task. And pop out the register values in the process.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.
If something looks overcomplicated, most likely it is.
Re: Another multitasking question
Ah, I couldn't understand what did you mean.Velko wrote:And then you proceed to return to that second task. And pop out the register values in the process.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.
Can you explain a bit more?
Keyboard not found!
Press F1 to run setup.
Press F2 to continue.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
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):
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).
- 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).
If something looks overcomplicated, most likely it is.
Re: Another multitasking question
Actually I couldn't explain what did I mean very good.
Then I prepared that image to explain everything fully:
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.
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:
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.
Then I prepared that image to explain everything fully:
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: Select all
...
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: Select all
int sys_exit(uint32_t code)
{
...
remove_task_fint(current_task->pid, regs);
...
}
Syscall handler doesn't put it as an argument while calling sys_exit and other syscalls.
I can change it to:
Code: Select all
...
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: Select all
int sys_exit(registers_t* r, uint32_t code)
{
...
remove_task_fint(current_task->pid, regs);
...
}
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.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
Okay, I took a paper and started writing, tested every way in paper.
Result? Nothing.
I'm fully out of ideas now.
Result? Nothing.
I'm fully out of ideas now.
Keyboard not found!
Press F1 to run setup.
Press F2 to continue.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
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:
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.
If something looks overcomplicated, most likely it is.
Re: Another multitasking question
I've implemented yield, sleep and partially implemented write on pipe to another process.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: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.
- 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.
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.
Press F1 to run setup.
Press F2 to continue.
Re: Another multitasking question
That's your main problem. IRQ0 (timer) should be only one of many ways to initiate task switch.My scheduler is completely a part of IRQ0, that makes calling scheduler harder when I want.
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: Select all
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);
Code: Select all
int sys_yield(int *needs_reschedule)
{
*needs_reschedule = 1;
return 0;
}
If something looks overcomplicated, most likely it is.