Some questions - tasks, the stack

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.
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Some questions - tasks, the stack

Post 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.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Some questions - tasks, the stack

Post 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.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
egos
Member
Member
Posts: 612
Joined: Fri Nov 16, 2007 1:59 pm

Re: Some questions - tasks, the stack

Post 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.
If you have seen bad English in my words, tell me what's wrong, please.
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post 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?
egos
Member
Member
Posts: 612
Joined: Fri Nov 16, 2007 1:59 pm

Re: Some questions - tasks, the stack

Post 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.
If you have seen bad English in my words, tell me what's wrong, please.
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post 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?
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Some questions - tasks, the stack

Post 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.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post 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?
egos
Member
Member
Posts: 612
Joined: Fri Nov 16, 2007 1:59 pm

Re: Some questions - tasks, the stack

Post 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.
If you have seen bad English in my words, tell me what's wrong, please.
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post 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?
User avatar
serviper
Member
Member
Posts: 31
Joined: Sat Jul 16, 2011 6:05 am
Location: China
Contact:

Re: Some questions - tasks, the stack

Post 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.
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post by Luns »

Yes, that's what I was thinking I would do. I'm gonna try that now, thanks for the advice everybody :)
User avatar
serviper
Member
Member
Posts: 31
Joined: Sat Jul 16, 2011 6:05 am
Location: China
Contact:

Re: Some questions - tasks, the stack

Post 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?
Luns
Member
Member
Posts: 56
Joined: Sun May 01, 2011 12:15 am

Re: Some questions - tasks, the stack

Post 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?
User avatar
serviper
Member
Member
Posts: 31
Joined: Sat Jul 16, 2011 6:05 am
Location: China
Contact:

Re: Some questions - tasks, the stack

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