Page 1 of 2

Some questions - tasks, the stack

Posted: Mon Jul 25, 2011 10:07 am
by Luns
First question - in my assembly code which takes control from GRUB and passes it to my C kernel, I reserve 16 Kb of space, and then set my stack to be that, like this

Code: Select all

    mov esp, stack+0x4000
    push ebx
    call _kmain

section .bss
    stack: resb 0x4000
Since I've reserved 16 Kb of space, does that mean that now my stack is not "undefined", as it was when GRUB first gave up control? As I understand it, this means that within my kernel binary there is 16 Kb of space which is reserved from my stack - is this right?

Then, tasks - I'd like to implement a basic software task switcher. I've been reading about how to do this, and I think I understand the general idea but I want to be sure this is correct:

Each task has a structure, containing its esp, eip, ebp, task's id, etc. The PIC generates interrupts, and after a few go by a task switch is called. The next task structure pointed to by the current task structure is selected. Then somehow, I need to replace the old task's esp and eip values with the new task's values on the stack - these are then pop'd off by the iret command after returning to the interrupt service routine. This way, instead of returning to the original task that was running when the interrupt occurred, the ISR returns to the new task, at the instruction pointed to by the new eip. Does that all sound correct, or am I missing something?

One thing I'm not clear on is the stack - if I only have one kernel stack, do I need to change the value of esp that iret pops off when I change tasks? If I don't, how does a new task not trample the data the old task had stored on the stack?

Any clarification would be much appreciated.

Re: Some questions - tasks, the stack

Posted: Mon Jul 25, 2011 10:28 am
by Combuster
If you want to restore a task's registers you have to do it somewhere. Userland's ESP can only be sensibly set using IRET, so you have to put it somewhere for that IRET to find it.

Re: Some questions - tasks, the stack

Posted: Mon Jul 25, 2011 12:07 pm
by egos
>>Since I've reserved 16 Kb of space, does that mean that now my stack is not "undefined", as it was when GRUB first gave up control?
Yes.

>>As I understand it, this means that within my kernel binary there is 16 Kb of space which is reserved from my stack - is this right?
Not necessary. It could be reserved in memory, occupying no space within file.

>>Each task has a structure, containing its esp, eip, ebp, task's id, etc. The PIC generates interrupts, and after a few go by a task switch is called. The next task structure pointed to by the current task structure is selected. Then somehow, I need to replace the old task's esp and eip values with the new task's values on the stack - these are then pop'd off by the iret command after returning to the interrupt service routine. This way, instead of returning to the original task that was running when the interrupt occurred, the ISR returns to the new task, at the instruction pointed to by the new eip. Does that all sound correct, or am I missing something?
In general that's right. But you can do task switching by calling a "near" routine in kernel space, not by correcting "far" return address in the stack.

>>One thing I'm not clear on is the stack - if I only have one kernel stack, do I need to change the value of esp that iret pops off when I change tasks? If I don't, how does a new task not trample the data the old task had stored on the stack?
If you want to have a single kernel stack architecture you should move user space return address and stack pointer from the (kernel) stack to thread structure with disabled interrupts (in non-interruptible mode), check kernel on busy, put thread into wait queue if it's busy (wait while it's working), make useful work and then restore user space return address and stack pointer in the stack with disabled interrupts. Or you can use small kernel stack for every thread (while entering into kernel mode occurs) and then switch to common kernel stack. But I prefer a multiple kernel stacks architecture in reality.

Re: Some questions - tasks, the stack

Posted: Mon Jul 25, 2011 12:58 pm
by Luns
Ok, I think I'm beginning to understand this better - thank you both :)

I've made an alteration to my task-switching method - now, when the ISR called, I grab the eip value on the stack (which was pushed there by the CPU when it called the ISR), and pass that to my task-switching method. I store that value as the eip in the current task's structure, move to the next structure in my task structure list, load the eip value in that structure into a general register and jmp to it. This way, the next time I switch tasks I jump to where the old task was interrupted - where the ISR would have returned had I not jmp'd away before it had a chance to.

Does all of that make sense? Or is my logic flawed?

Re: Some questions - tasks, the stack

Posted: Mon Jul 25, 2011 2:41 pm
by egos
When I call task switching routine I actually save old eip value in the kernel stack of current thread. Inside the routine a stack switching occurs and "ret" will restore eip value from the kernel stack of new thread.

Re: Some questions - tasks, the stack

Posted: Tue Jul 26, 2011 2:40 pm
by Luns
Alright, I'm making progress :D . Switching tasks now correctly jumps back and forth between two running tasks. I'm still having problems with my stack, though - if I try to call a function which passes arguments or returns a value in one of my tasks, I crash.

I've decided to create a separate stack for each task, since that seems simpler than managing one stack between them all. My question is this - when I load a new value into esp upon a task switch, I also need to load that value into TSS.ESP0, correct? And after I do that, does the processor automatically recognize the change in the TSS, or do I need to do a 'ltl' command to force it to update?

Also, I was only going to have one TSS, for multiple tasks (everything will be running in ring 0 for now). After reading topics here, I believe that's ok, but the Intel manuals seem to assume each task will have it's own TSS. Is it ok to only have one TSS for all my tasks?

Re: Some questions - tasks, the stack

Posted: Tue Jul 26, 2011 3:21 pm
by Combuster
TSS.ESP0 is the ESP that is used when userland enters kernel mode. It should therefore point to the end of the stack, not to the current stackpointer otherwise you'll reduce the amount of stack space on each context switch.

The multiple TSSes are only needed for hardware task switches, which are pretty much overruled by their software equivalents everybody uses.

Re: Some questions - tasks, the stack

Posted: Tue Jul 26, 2011 6:40 pm
by Luns
Ok then, so it seems that I don't need to change anything with the TSS if I'm doing software task switching all within kernel mode.

In order to copy the stack, is this what I need to do - first allocate enough space for the new stack, and then set the new task's esp and ebp to the start of the allocated memory, plus the size of the stack. Then memcpy from original esp-stacksize to new esp-stacksize, for stacksize bytes.

Then, something needs to be done to correct the ebp values in the stack. Correct?

Re: Some questions - tasks, the stack

Posted: Tue Jul 26, 2011 11:54 pm
by egos
Luns wrote:Ok then, so it seems that I don't need to change anything with the TSS if I'm doing software task switching all within kernel mode.
No, if you use one kernel stack per thread (you wrote: "I've decided to create a separate stack for each task...") you should replace TSS.SP0 every time when task switching occurs (or you should remap kernel stack at same location :shock:). Look at my post here.
In order to copy the stack, is this what I need to do - first allocate enough space for the new stack, and then set the new task's esp and ebp to the start of the allocated memory, plus the size of the stack. Then memcpy from original esp-stacksize to new esp-stacksize, for stacksize bytes.

Then, something needs to be done to correct the ebp values in the stack. Correct?
I didn't understand what for you want to copy the stack. And usually kernel stacks have fixed size.

Re: Some questions - tasks, the stack

Posted: Wed Jul 27, 2011 7:22 am
by Luns
Well if I allocate space for a new child stack, won't it need all the local variables and other stuff from the parent stack? That's the only reason I thought I would have to copy the stack.

e: Suppose the stack was in a separate page from the kernel. Then, all I would have to do would be copy the entire stack page, but then map it to the same virtual address. I.e. -

Original stack is at physical address 0x00200000, and virtual 0x00200000. I copy that page, so now the new stack is at physical 0x00300000, but map it to virtual 0x00200000. So if there's a local variable on the stack at virtual address 0x00200010, both tasks can access it by accessing the same virtual address, but they'll actually be accessing two different variables, at two different (physical) memory locations. Correct?

Re: Some questions - tasks, the stack

Posted: Wed Jul 27, 2011 7:32 am
by serviper
In order to copy the stack, is this what I need to do - first allocate enough space for the new stack, and then set the new task's esp and ebp to the start of the allocated memory, plus the size of the stack. Then memcpy from original esp-stacksize to new esp-stacksize, for stacksize bytes.

Then, something needs to be done to correct the ebp values in the stack. Correct?
I don't think it's a good solution to correct all the possible EBPs in your stack. There may be local variables which may be trashed...and a bit 'noisy'.
Perhaps you can move the initial kernel stack to a higher address instead of keeping the stack in the same page table or sth else as the kernel image.

Feel free to correct any of my mistakes.

Re: Some questions - tasks, the stack

Posted: Wed Jul 27, 2011 7:57 am
by Luns
Yes, that's what I was thinking I would do. I'm gonna try that now, thanks for the advice everybody :)

Re: Some questions - tasks, the stack

Posted: Wed Jul 27, 2011 11:54 pm
by serviper
Luns wrote:Well if I allocate space for a new child stack, won't it need all the local variables and other stuff from the parent stack? That's the only reason I thought I would have to copy the stack.

e: Suppose the stack was in a separate page from the kernel. Then, all I would have to do would be copy the entire stack page, but then map it to the same virtual address. I.e. -

Original stack is at physical address 0x00200000, and virtual 0x00200000. I copy that page, so now the new stack is at physical 0x00300000, but map it to virtual 0x00200000. So if there's a local variable on the stack at virtual address 0x00200010, both tasks can access it by accessing the same virtual address, but they'll actually be accessing two different variables, at two different (physical) memory locations. Correct?
Your 'virtual address' should be (partly) interpreted as indices of page directories/tables.
Perhaps you are talking about remapping virtual 0x200000 (physical 0x200000 at first) to physical 0x300000?

Re: Some questions - tasks, the stack

Posted: Thu Jul 28, 2011 10:27 pm
by Luns
I map the physical address of the stack to virtual 0xE0000000. That way it doesn't matter where the current task's stack is located, if I need to access it I just access 0xE0000000.

My current problem is that I get a page fault eventually - but not always after the same amount of time. My tasks run like this -

Code: Select all

task a:
while (1)
    asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('A'))

task b:
while (1) {
    printC ()
    asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('B'))
}

printC () {
  asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('C'))
  return
}
The page fault always occurs on the same instruction, within the function printC. The error code is always 0 - so the page wasn't present. But I don't know what page it's trying to access, as the value in CR2 is always different. I halt right after I enter my page fault ISR and I clear interrupts, so I get the error code and CR2 values from QEMU's monitor.

Does anyone have any advice as to how I should debug this, or what I can do to try and get more information about the problem and what's happening?

Re: Some questions - tasks, the stack

Posted: Fri Jul 29, 2011 5:55 am
by serviper
Luns wrote:I map the physical address of the stack to virtual 0xE0000000. That way it doesn't matter where the current task's stack is located, if I need to access it I just access 0xE0000000.

My current problem is that I get a page fault eventually - but not always after the same amount of time. My tasks run like this -

Code: Select all

task a:
while (1)
    asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('A'))

task b:
while (1) {
    printC ()
    asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('B'))
}

printC () {
  asm volatile ("mov edi, %0; mov eax, %1; stosb" :: "r" (0xb8000), "r" ('C'))
  return
}
The page fault always occurs on the same instruction, within the function printC. The error code is always 0 - so the page wasn't present. But I don't know what page it's trying to access, as the value in CR2 is always different. I halt right after I enter my page fault ISR and I clear interrupts, so I get the error code and CR2 values from QEMU's monitor.

Does anyone have any advice as to how I should debug this, or what I can do to try and get more information about the problem and what's happening?
It seems your printC failed to return because of the bad return address in your kernel stack? If you simply map the stack, maybe some return addresses should be modified. (for example, perhaps there's an old return address in lower memory and you forgot to modify it?)
Also, make sure that your kernel stack is copied.