Project #9: Thread API

Submit: Turn in your entire xinu-hw9 directory source files using the turnin command on morbius.mscsnet.mu.edu or one of the other Systems Lab machines. Please run "make clean" in the compile/ subdirectory just before submission to reduce unnecessary space consumption.

Work should be completed in pairs. Be certain to include both names in the comment block at the top of all source code files, with a TA-BOT:MAILTO comment line including any addresses that should automatically receive results. It would be courteous to confirm with your partner when submitting the assignment.

Threads

Preparation

Make a fresh copy of your work thus far.
      cp -R xinu-hw8 xinu-hw9

Then, untar the new project files on top of it:
      tar xvzf ~brylow/os/Projects/xinu-hw9.tgz

Be certain to make clean before compiling for the first time.

Threads vs. Processes

The distinction between the concepts of "threads" and "processes" can be an important one. Our textbook provides the definition that processes execute in their own memory space, whereas threads share the same memory space within a process. But beyond that, most of the aspects of processes we have already studied seem to repeat for threads -- you need a thread control block, each thread gets its own stack of activation records, we need to worry about a scheduling policy for deciding which thread we will context switch to next, etc.

So which do we have in our Embedded Xinu operating system that we've been building? Processes or threads? What we've been calling processes do not have their own independent address space -- there is only one page table that is being shared across all of the processes. In that sense, we could view our entire operating system as just one lone process scheduling multiple "threads" across the cores. (And we could certainly go through and replace the word "process" everywhere in the source code with the word "thread" to enforce that definition.) On the other hand, we've implemented a simple system call interface so that processes need to go through a trap handler to access protected kernel functions, so aspects of our system already behave as though each process exists in a lower hardware privilege level, and only the kernel has full run of the system in supervisor mode.

If we had time in a more advanced version of this course, we could invest our efforts in building an independent page table for each process that would provided it a more restricted, virtual memory layout, and then it would be much clearer that what we've been calling a process is indeed a process.

In truth, in the context of a simplified, embedded, educational operating system like ours, the distinction between processes and threads is not very relevant. We could label it either way, and build out the system in more concrete ways to reinforce that interpretation. In this assignment, we muddy the waters slightly further by implementing a crucial subset of the standard PThreads API using what the lightweight processes we've been working with all along.

PThreads API

The new tarball provides an include/pthreads.h header that defines the crucial new structs, #defines and function prototypes to mimic the standard POSIX PThreads interface. Chapter 27 in our textbook explains how this API works, and gives excellent examples.

The mapping of these PThread functions to existing Embedded Xinu functions is the primary content of this assignment.

Last Call for System Calls

Each of the five PThread API functions above should be implemented as system calls by extending the trap handlers we built in Project 5: Trap Handlers.

Begin be adding trap handlers for getmem() and freemem() from our previous assignment. (Good question for the final exam: "Why should getmem() and freemem() be implemented as system calls, but not malloc() and free()?)

First, add new trap numbers to include/syscall.h:

#define SYSCALL_GETMEM      17 /**< Allocate heap memory */
#define SYSCALL_FREEMEM     18 /**< Deallocate heap memory */

Second, add new entries to the global syscall_table in syscall_dispatch.c. Note that the row in the table must correspond to the correct syscall numbers defined above. The first column in the table is the number of arguments the syscall expects.

Finally, add the new front-end and backend calls to connect things up. For getmem() and freemem(), we can define the user-facing as getmem() and freemem(), and rename the behind-the-scenes implementations as sc_getmem() and sc_freemem().

Repeat this process for our new PThreads interface:

#define SYSCALL_PTCREATE    19 /**< PThread create */
#define SYSCALL_PTJOIN      20 /**< PThread join */
#define SYSCALL_PTLOCK      21 /**< PThread mutex lock */
#define SYSCALL_PTUNLOCK    22 /**< PThread mutex unlock */
#define SYSCALL_PTTRYLOCK   23 /**< PThread mutex trylock */

When all is said and done, you should be able to compile and execute our lab demo PThread code correctly on your Embedded Xinu kernel.

Note: If you lack confidence in prior stages of your kernel, it is OK to comment out the calls to unparkcore() in initialize.c and work with only Core 0 for this project. With preemption, this PThreads API should still work correctly on just a single core.


[back]

[Revised 2020 Nov 5 11:31 DWB]