Frequently Asked Questions


General

How can we tell if we're using the most recent version of the Minithreads code?
The code may be updated to fix bugs. You can tell which version you're using by looking in the CHANGES.txt file in the version you have. This file will contain details of the changes made up to the current version.

What should we do about errors (in non-C jargon, "exceptions") which occur in the functions we write?
It depends on what causes the error. If it's because of bad parameters passed by the caller, or because a data structure is empty when the caller thought it was full, or some similar condition, then you should return an error code indicating what the error is, if necessary. If the error is one which the function or the caller can't deal with gracefully (memory corruption, not enough memory for a malloc, etc), then it may be better to print out as descriptive an error message as possible, and terminate the program, since the error is likely to be unrecoverable.

What is the assert() function for? When should I use it?
Assert() is for detecting mistakes and inconsistencies when you're writing and debugging code. If the condition in the assert fails, the program prints an error and dumps core (writes the contents of its memory to a file, for non-Unix people). For instance, if at at the start of function A, the value of X should be 2, and the value of Y should be 8, you could put an "assert(X == 2 && Y == 8)" at the top. Asserts are useful for detecting when you made an incorrect assumption about a program invariant, or you violated the invariant elsewhere in your code. You should not really use them for catching any of the caller-caused errors in the previous paragraph.

I seem to be getting some kind of corruption of memory pointed to by pointers. What could be the cause?
Here's one potential problem: you are using a pointer to a local variable on the stack, which you returned from a function. For instance:
char* return_a_string(...) {
  char string[32];
  ...
  return string;
}

...

int main() {
  char* x = return_a_string(...);
  ...
}
This is a serious, but unfortunately common C mistake, which is often hard to spot. The problem is that a function allocates its local variables on the stack, which is shared by subsequent function invocations. The pointer x ends up pointing to some location on the stack, which may get used for some other data and overwritten by a later function invocation. Returning this kind of a pointer is even more dangerous because you might not notice the bug immediately. If in place of return_a_string we had
int* return_an_integer(...) {
  int integer = 32;
  ...
  return &integer;
}
then we'd have a different version of the same problem. Be aware that the problem still occurs if you store one of these pointers-to-the-stack in a data structure which gets used after the function finishes, as well. You just have to be careful.

I am getting a "guard page exception" when running my system, what is the reason?
It could be because you are declaring an array as a local variable in a function, which is too big to fit on the stack. Threads have quite small stacks (about 64k). Either increase the stack size (defined by the STACKSIZE constant in minithread_md.h), or make the array smaller.

Why is it necessary to use "make" to build the Minithreads system?
The emulation of interrupts (in project 2) requires that we specify the order object files are linked, to allow us to prevent preemptions in NT library code. Visual Studio doesn't seem to allow specifying the order of linking without a makefile.


Threads primitives

Do we have to stick to the interfaces provided in minithread.h, queue.h, synch.h, etc? Can we change files which say "You should not need to change this file" at the top?
We would prefer it if you didn't: it is possible to do the projects without having to change the interfaces. If you find yourself having to change a header file, or a "you do not need to change this file" file, please include a note in the README accompanying your submitted project explaining: (i) which files you changed, (ii) which definitions/functions/etc were changed, and (iii) for a non-obvious change (e.g. modification of an algorithm), where the change is and what the reason is. If you do this, it will make grading your project much easier for us. You should not have to change any of these files in order to implement your course projects.

How does stack allocation work?
Each new thread you create needs its own stack to store function arguments and return addresses on. You can obtain a stack by caling minithread_allocate_stack(), and initialise it by calling minithread_initialize_stack(). Initialising the stack requires two functions: the function the new thread should run, and a "final" function, which runs after the main function has returned. The final function need not be unique for each thread. Both functions have arguments, so that the order of invocation looks like: main_proc(main_arg); finally_proc(finally_arg). The exception is the first thread you create, which gets the original stack for the process, and runs inside minithread_system_initialize(). It still needs a thread control block, however, so that it can participate in context switches correctly. This first thread cannot terminate in the same way as the others: since it's running on the NT stack, when it returns from main(), it will destroy the whole process.

Why can't a thread free its own stack?
When a thread announces it wants to terminate, by calling the thread destroy routine, it is still running on its own stack. In order to transfer control to another thread in a context switch, it pushes some data onto its stack. So if it freed its stack and then context-switched, it would end up writing to an invalid stack. The solution is to have the terminating thread "notify the system" (set a flag, put itself on a queue, etc) so that a thread which runs after it will know it has to free the stack.

What are minithread_start() and minithread_stop() for?
They're really only useful for implementing semaphores, it's unlikely that your "user programs" will need to use these low-level calls.

What are minithread_self() and minithread_id() for?
A thread needs to be able to get a pointer to its TCB in case it wants to add it to some data structure, for instance: then another thread could call minithread_start() on it. Of course, this is only useful outside minithread.c. Thread IDs are really only helpful for debugging.

What do I do if all the threads terminate? What if there's no thread avaible to run when a thread does a stop or yield?
Performing a yield when there are no other threads to run is a no-op. Doing a stop with no other thread to run ought to be an error, since it will prevent the system from making any progress (e.g. a thread blocks on a semaphore when no-one will signal it) -- note that this may not be true when preemption is considered (see below). What to do when no threads are left to run is your choice, you could terminate the program, or just loop indefinitely.

Is it ok to read (not write) the value of a semaphore without executing a P on it?
No, you should stick to the interface in synch.h.


Preemption

Why does Minithreads run more slowly when preemption is turned on?
Getting user-level threads preemption to work correctly in Windows NT requires some extra kernel threads to trigger clock interrupts and make the interrupt handler run and exit cleanly, which compete for CPU time with the NT kernel thread running the minithreads. Basically, the design of timers in NT makes an efficient implementation difficult.

What do I do if no thread is available to run on a context switch (i.e. all threads are blocked)?
This situation could arise if all the threads in the system are blocked on semaphores or waiting for alarms. The clock interrupt mechanism we have given you assumes that there is always a thread running, ready to take the interrupt. This means that you have to create an "idle thread", which is ready to run if all the other threads are blocked, but does nothing (i.e. runs a function which loops infinitely, or yields infinitely). The idle thread should only run if all other threads are blocked, so it should not go on run queue with all the normal threads, and should never block!


File system

Why is the call to delete a file called "unlink" instead of "delete"?
The Unix file system allows the same file to have an entry in multiple directories, i.e. the same file can have more than one name. The system call to create a new name for the file is "link", and the reverse is "unlink", which removes a name. The file is implicitly deleted when the last link is removed.