This lecture covers scheduling policies of threads and processes.
Recall that a process is an abstraction which includes a program
counter, registers, and an address space which includes the code,
static data, stack and the heap. Threads are abstractions similar
to processes except that multiple threads can share the same
address space. Each thread has it's own program counter, registers
and stack. Threads can share code, static data, and heap. Threads
take significantly less space than processes because they don't require
separate address spaces.
Hand-In Procedure
You are to turn in this homework during lecture. Please write up your answers to the exercises below and hand them in to a staff member at the beginning of lecture. Mention your CSE login ID at the top of your homework submission.
$ cat | tee output.file
Turn in: While this command is running, examine the processes created:
$ cat /proc/pid-num/stackWhose stack is this?
$ cat /proc/pid-num/mapsThe
maps
file shows the address space of the process. You will
find the output divided into columns. The leftmost column is
the address space range and the rightmost column shows what is mapped into
that column. You will see the code/data from the main executable mapped in
some address ranges. You will also find other libraries that are loaded
by this executable mapped in the address space.
Can you identify where is code, static data, heap and stack
mapped?
To implement threads, the process needs to provide the abstraction of multiple control-flow (program counter), multiple register sets and multiple stacks. This can be done if after every periodic time interval, one thread can be interrupted and saved and another thread can be loaded. Saving a thread involves saving it's program counter, registers and stack pointer. Similarly, loading a thread involves loading the new thread's program counter, registers and stack pointer. Neither the save operation, nor the load operation requires any privileged operation -- we are just loading and saving registers.
So the only remaining issue is how to periodically interrupt a running thread from within a process. For an OS, this interruption is done by the hardware timer device. A process can do this using the SIGALRM signal.
Such threads implemented inside a process are called user-level threads. The OS cannot distinguish between multiple user-level threads and it can only see one process that is running which includes the thread scheduler and the different threads.
Turn in:
Read the manpage of
the signal
, alarm
, and setitimer
.
Understand how SIGALRM can
help in implementing user-level threads. Briefly describe how you
will do this (2-3 sentences and some pseudo-code).
wait()
. When the parent calls wait()
eventually,
it still expects to read the correct exitcode that the child returned.
To support this functionality, UNIX does not completely remove the process
till it's parent has called wait()
on it.
Such processes that have completed execution but still have an entry in the process table are called zombie processes. Usually, the presence of zombie processes in the system for a long time indicates a bug in the program (it is a common error).
Read about Zombie process at this wikipedia article. Also read about the SIGCHLD signal.
Turn in:
In class we discussed that the shell implements "&
" functionality
by not calling wait()
immediately. Should the shell
never call wait()
? When should it call
wait()
? Answer by providing short pseudo-code.