[NSWI004] Questions

Petr Tuma petr.tuma at d3s.mff.cuni.cz
Fri Nov 6 16:53:48 CET 2020


Hi Georgii,

> 1) What happens when a thread calls thread finish in the middle of the entry function? Do we need to make the currently running thread run until it actually finishes it's entry function or do we just terminate it then and there? In case we terminate it, do just set retval to null?

There are two ways a thread can legally terminate. Either it will return from its thread entry function, or it will call `thread_finish`. The `thread_finish` function should not return back to the thread (notice the `noreturn` attribute in the header).

The `thread_finish` function accepts a single argument, and that is the return value that your thread should have (specifically, that the `thread_join` function will return to its caller as the return value of the thread).

> 2) "EBUSY Some other thread is already joining this one"
> By 'this one' do you mean the caller thread or the argument thread? If the argument thread, we don't see the issue, however there is an issue if someone is waiting for the caller thread since there might be a waiting cycle.

The argument thread. As a common convention, only one thread can safely call `thread_join (tid ...)` for a particular `tid`, because when `thread_join` returns, some internal data structures relevant to that thread might have been deallocated. In the assignment, you can actually implement things such that calling `thread_join` multiple times does not necessarily fail, but that is not common.

> 3) "EINVAL" Invalid thread
> Can you please define 'Invalid thread'? We have some thoughts about this one, but we want to know for sure.

This would normally be returned by functions where the `thread` argument is a handle, as a reaction to getting an invalid handle. When the `thread` argument is a pointer `thread_t *`, you cannot easily recognize whether it is invalid, but there are some opportunities when this could happen - for example, you might record in the `thread_t` structure whether the thread is running and return `EINVAL` when it is not, or you might add a magic number to the thread structure and check it, and so on.

The presence of `EINVAL` among the permitted return codes does _not_ mean you would have to detect calls to the functions with invalid thread pointers (this is an internal kernel API and pointers used as arguments are therefore typically trusted).

Maybe Vojtech or Lubomir will chime in with the original intent here.

> 4) I'm not sure how we can determine that an entry function has finished by some thread? I had some thoughts of maybe recording the starting value of the sp register, so once sp goes back to it, we know that the stack frame has been popped and that means that the entry function is done, but that seems kinda janky.

The best thing you can do (probably the only reasonable thing, actually) is prepare an initial context that would look exactly like some other function is calling the thread entry function, with the return address pointing to kernel code that terminates the thread. There are multiple ways of doing that, I guess this might be a good topic for discussion during the labs.

> 5) When a thread executes return from the entry function, it pops the stack frame and ordinarily it should jump to the return address that was placed by some other function on the stack, but what is even there? We didn't place anything there.

See above, it is up to you to prepare the context so that the return succeeds and essentially returns to a place in the kernel where you would clean up after the thread.

> 6)  Can you please explain the $ra register. I get the concept of the return address of a function calling another function, in which case the return address is placed on the stack for the calle to jump to. But I'm a bit confused by this register. What is its purpose?

When I talked about the stack frames of functions, I mentioned return addresses are stored on the stack. That is generally true, simply because when functions nest, we need a LIFO structure for the return addresses, and that is what the stack gives us. Processors like Intel x86, or for example SPARC, even have a single instruction, `call`, that is used to call a function and push the return address on the stack.

But MIPS, or for example ARM, decided to do things a bit differently. To call a function, you have the `jal` instruction, which also saves the return address - but not on the stack. It puts it in the `ra` register. If the called function is a "leaf" in the call tree, then it can simply leave the return address in the `ra` register and later return by executing `jr ra` (jump to the address in the `ra` register). Obviously, if the called function itself calls some other functions (it is not a "leaf"), it has to save the `ra` register somewhere (standard convention is on the stack).

Petr


More information about the NSWI004 mailing list