GCon
GCon

Reputation: 1527

Implementing user level threads library Starting a new thread [Homework]

I have seen this: Implementing a User-Level Threads Package and it doesn't apply.

During the implementation of Thread_new(int func(void*)), that assigns a thread and creates a stack, I am unable to think of a way to set the program counter (%eip) if I am correct, so when the thread is started by the scheduler, it starts at the given function's (func) entry point.

Although I have seen many c-only (no assembly) implementations, we have been given the following code (x86):

_thrstart:
    pushl  %edi
    call *%esi
    pushl %eax
    call Thread_exit

Is there a specific reason to push %edi to the stack? I can't seem to find another use for esi/edi apart from byte copying.

I realize that the indirect call to *%esi is probably used to call the function from the context of the new thread, but apart from that, I don't seem to understand how (or what) %esi points to being a valid function address when _thrstart is called from Thread_new

NOTES:

Thread_exit is the cleanup thread, implemented in c.

This is HOMEWORK

Upvotes: -1

Views: 602

Answers (3)

GCon
GCon

Reputation: 1527

Seems that the problem wasn't as complicated as before.

Based on the answer given by @Martin James, the Stack is prepared so that the return address is the _thrstart function. Based on the assembly used to perform a context switch, the registers edi and esi are stored in specific locations on the stack (when the thread is inactive). By using edi and esi as general purpose registers, edi contains the void* argument, and esi contains the address of the function to be called from the new thread.

_thrstart:
pushl  %edi        #pushes argument for function func to the stack
call *%esi         #indirect call to func
pushl %eax         #Expect return value in eax, push to stack
call Thread_exit   #Call thread cleanup

Upvotes: 0

Martin James
Martin James

Reputation: 24857

Typically, in simple RTOessess, threads are not started by being called or jumped to - they are started by being returned or interrupt-returned to.

The trick is to assemble data at the top of the new stack so that is looks as if the thread has been running before and has either called the scheduler or entered it via an interrupt. At the bottom of this 'frame' should be the address of the thread function. You can then load the stack pointer with the address of the frame, enable interrupts and and perform a RET or IRET to start the thread function.

It's convenient to also first shove on a parameter that the new thread can retrieve and a call to the 'TerminateThread' or 'Thread_Exit', so that if the thread function returns, the scheduler can terminate it.

Upvotes: 2

Brendan
Brendan

Reputation: 37232

In general; you can break "scheduler" down into 4 parts.

The first part is the mechanics of switching from one thread to another. This mostly involves storing the previous thread's state somewhere and loading the next thread's state from somewhere. Here, "somewhere" could be some sort of thread control block, or it could be the thread's stack, or both, or something else. A thread's state may include the contents of general purpose registers, it's stack top (esp), it's instruction pointer (eip), and anything else (MMX/SSE/AVX registers). However, for co-operative scheduling a thread's state could be much less (e.g. most of a thread's state is trashed by thread switching and cooperative scheduling is used so that the thread itself knows when its state is going to be trashed and can prepare for that).

The second part is deciding when to do a thread switch and which thread to switch to. This varies widely for different schedulers.

The third part is starting a thread. This mostly involves constructing the data that would be loaded during a thread switch. However, it's possible to do this in a "lazy" way, where you only create the minimal amount of state when first creating a thread, and then finish creating the remainder of the thread's state after it has been given CPU time.

The fourth part is terminating a thread. This involves destroying/freeing the data that would be loaded during a thread switch; but can also mean cleaning up any resources that the thread failed to release (e.g. file handles, network connections, thread local storage, whatever) so that you don't end up with "resource leaks".

Upvotes: 3

Related Questions