Update markdown and tabs and stuff
This commit is contained in:
@@ -39,95 +39,96 @@ Since the return address is stored on the stack, if you were to switch
|
||||
stacks inside a function, when you return, you'll be somewhere else.
|
||||
This is a common way of making usermode threads. Ponder the following:
|
||||
|
||||
void switch_thread()
|
||||
{
|
||||
push_all_registers();
|
||||
switch_stack_pointer();
|
||||
pop_all_registers();
|
||||
return;
|
||||
}
|
||||
|
||||
void a()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
:::c
|
||||
void switch_thread()
|
||||
{
|
||||
push_all_registers();
|
||||
switch_stack_pointer();
|
||||
pop_all_registers();
|
||||
return;
|
||||
}
|
||||
|
||||
void b()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something_else();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
void a()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
|
||||
void b()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
do_something_else();
|
||||
switch_thread();
|
||||
}
|
||||
}
|
||||
|
||||
Imagine two threads - __A__ and __B__ running, __A__ runs `a()` and __B__
|
||||
runs `b()`. Each has a stack somewhere in memory, and __A__ is currently
|
||||
running. The top of the stacks looks like:
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
+----------ESP----------+ |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
+----------ESP----------+ |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
where `RA` means Return Address and `ESP` is where the stack pointer is
|
||||
currently pointing.
|
||||
As execution of __A__ continues, the processor will `do_something()` and
|
||||
then call `switch_thread()`...
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
+----------ESP----------+ |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
+----------ESP----------+ |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
`switch_thread()` pushes all registers to the stack and calls
|
||||
`switch_stack_pointer()`
|
||||
|
||||
+----------ESP----------+ +-----------------------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+----------ESP----------+ +-----------------------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
`switch_stack_pointer()` performs some scheduling to find out which
|
||||
thread is to run next, and then switches the stack pointer over to the
|
||||
top of __B__'s stack.
|
||||
|
||||
+-----------------------+ +----------ESP----------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+-----------------------+ +----------ESP----------+
|
||||
|switch_stack_pointer RA| |switch_stack_pointer RA|
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
The processor keeps on executing code, and `switch_stack_pointer()` soon
|
||||
returns
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA| +----------ESP----------+
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA| +----------ESP----------+
|
||||
|all registers | |all registers |
|
||||
|switch_thread RA | |switch_thread RA |
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
`switch_thread()` pops all registers and returns...
|
||||
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
|switch_thread RA | +----------ESP----------+
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|switch_stack_pointer RA|
|
||||
|all registers |
|
||||
|switch_thread RA | +----------ESP----------+
|
||||
|a RA | |b RA |
|
||||
| ... | | ... |
|
||||
|
||||
... and we're now in `b()` with all registers of __B__ loaded.
|
||||
|
||||
###Stacks in the kernel
|
||||
@@ -144,38 +145,41 @@ Molloys](http://www.jamesmolloy.co.uk/tutorial_html/) or [Brandon
|
||||
Friesens](http://www.osdever.net/bkerndev/Docs/title.htm)) you probably
|
||||
have something like this to handle interrupts:
|
||||
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
call int_handler
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
{: .lang-nasm}
|
||||
:::nasm
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
void int_handler(registers_t r)
|
||||
{
|
||||
do_stuff();
|
||||
}
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax, 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
call int_handler
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
|
||||
|
||||
|
||||
:::c
|
||||
void int_handler(registers_t r)
|
||||
{
|
||||
do_stuff();
|
||||
}
|
||||
|
||||
In fact, if you've been following one of those tutorials, you probably
|
||||
have the above code twice, for some reason...
|
||||
@@ -184,42 +188,45 @@ Anyway. This would take care of both pushing and poping all registers,
|
||||
and with only a small modification, it becomes very easy to switch the
|
||||
stacks too...
|
||||
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
push esp ;Pass stack pointer to int_handler
|
||||
call int_handler
|
||||
mov esp, eax ;int_handler returns a new stack pointer
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
{: .lang-nasm }
|
||||
:::nasm
|
||||
int_stub:
|
||||
pusha
|
||||
|
||||
registers_t *int_handler(registers_t *r)
|
||||
{
|
||||
do_stuff();
|
||||
r = get_next_thread(r);
|
||||
return r;
|
||||
}
|
||||
xor eax, eax
|
||||
mov ax, ds
|
||||
push eax
|
||||
|
||||
mov eax 0x10
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
push esp ;Pass stack pointer to int_handler
|
||||
call int_handler
|
||||
mov esp, eax ;int_handler returns a new stack pointer
|
||||
|
||||
pop eax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
|
||||
popa
|
||||
|
||||
add esp, 8
|
||||
|
||||
iret
|
||||
|
||||
|
||||
|
||||
:::c
|
||||
registers_t *int_handler(registers_t *r)
|
||||
{
|
||||
do_stuff();
|
||||
r = get_next_thread(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
This gives a pointer to the threads registers as input to the ISR and
|
||||
expect a pointer to some registers in return. They may or may not be the
|
||||
@@ -246,56 +253,55 @@ single data structure. So let's think about it for a while.
|
||||
While the thread is running we want some information stored somewhere in
|
||||
kernel space about it.
|
||||
|
||||
+-----------------------+
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
|
||||
Then, when an interrupt or syscall happens, a new stack is loaded
|
||||
and some stuff is pushed onto it. If we want this near our thread
|
||||
information it will have to go right before it, since the stack grows
|
||||
backwards.
|
||||
|
||||
+-----------------------+
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
|
||||
Finally, we want the kernel mode stack. Well... the stack pointer is
|
||||
right at the start of the registers now, so why not just continue the
|
||||
stack from there?
|
||||
|
||||
+-----------------------+
|
||||
| ... |
|
||||
|kernel mode stack |
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
{: .nopretty}
|
||||
+-----------------------+
|
||||
| ... |
|
||||
|kernel mode stack |
|
||||
|thread registers |
|
||||
|thread information |
|
||||
+-----------------------+
|
||||
|
||||
###Setting this up
|
||||
To set this up, the thread information structure has to be set up
|
||||
something like:
|
||||
|
||||
struct thread_info_struct
|
||||
{
|
||||
uint8_t stack_space[KERNEL_STACK_SIZE];
|
||||
registers_t r;
|
||||
struct thread_data_struct thread_data;
|
||||
} my_thread_info;
|
||||
:::c
|
||||
struct thread_info_struct
|
||||
{
|
||||
uint8_t stack_space[KERNEL_STACK_SIZE];
|
||||
registers_t r;
|
||||
struct thread_data_struct thread_data;
|
||||
} my_thread_info;
|
||||
|
||||
When the thread is running in user mode, the TSS should be set up in
|
||||
such a way that the stack pointer loaded at an interrupt points to the
|
||||
end of the registers, i.e. the beginning of the thread data.
|
||||
|
||||
TSS.esp0 = &my_thread_info.thread_data;
|
||||
:::c
|
||||
TSS.esp0 = &my_thread_info.thread_data;
|
||||
|
||||
And that's really all there is to it. Unbelievable, really, how many
|
||||
years it took for me to figure this out.
|
||||
|
||||
In the process, I've found inspiration in [Rhombus by Nick
|
||||
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
|
||||
Johnson](https://github.com/nickbjohnson4224/rhombus/) and
|
||||
[linux](http://www.linux.org).
|
||||
|
||||
###Some considerations
|
||||
@@ -335,15 +341,16 @@ I recently learned - the hard way - that the [clang
|
||||
compiler](http://clang.llvm.org) does not use this calling convention
|
||||
for functions which do not in turn call other functions. I.e
|
||||
|
||||
int double_integer(int a)
|
||||
{
|
||||
return 2*a;
|
||||
}
|
||||
:::c
|
||||
int double_integer(int a)
|
||||
{
|
||||
return 2*a;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
double_integer(5);
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
double_integer(5);
|
||||
}
|
||||
|
||||
If this code is compiled with clang `double_integer` will (in some
|
||||
cases) not push `ebp` to stack.
|
||||
|
||||
Reference in New Issue
Block a user