Project B FAQ
Last Significant Update: Mon, Dec 10, 2007 (1115 Hours)
Errata
Index
- Q1:
I'm just curious as to how you want us to do cleanup. At the moment,
I just defined a uth_destroy function that destroys the stacks of all
the threads as well as the data structures I allocate in the library.
I was wondering if you had any preference?
- Q2:
Can I assume that you always pass a single structure of type th_arg to
uth_create()? Thus, when I call makecontext() the argc parameter will
always be one? Or, do you want us to incorporate variable argument
functionality into uth_create()?
- Q3:
So,
as far as I can tell we have two types of priorities: high and low.
Then, there is the idle thread and the main thread and non-runnable
threads (sleeping threads).
I'm confused about the scheduling rules for the main thread.
What sort of priority does it have?
- Q4:
I am thinking of statically allocating an array of thread
descriptors where each thread descriptor includes a u_context.
Is that ok?
- Q5:
You said that uth_init should make the context for the main
thread, but what do I put for the function pointer and the
argument to makecontext?
- Q6:
I put my thread descriptors at the beginning of uth.c and each
descriptor has a uncontext:
struct {
int priority;
ucontext_t ucontext;
} thread_table[8];
Is that ok?
- Q7:
For mileC.c, how do I call alarm() so that I can wakeup my
thread when the uth_sleep() call should expire?
- Q8:
Can I do this for a run queue?
ucontext_t uc[8];
int running_tid;
...
Swapcontext(&uc[running_tid], &uc[next_tid);
- Q9:
I'm confused how to manage time, as in implementing the uth_sleep
and uth_usage functions. I've read the setitimer example but I'm
still confused about how to count time on a per thread basis.
- Q10:
I am confused about uth_join for milestone B.
In the description it says that a call to uth_join should
suspend the current thread until a the thread with id Tid
calls uth_exit.
However, when you suspend the main thread, both fibFiber 1 and 2
get a chance to complete. Then milestoneB calls uth_join again
for fibFiber2 and things break because fibFiber2 already finished.
Questions and Answers
- Q1:
I'm just curious as to how you want us to do cleanup. At the moment,
I just defined a uth_destroy function that destroys the stacks of all
the threads as well as the data structures I allocate in the library.
I was wondering if you had any preference?
A1:
That's all you have to do.
- Q2:
Can I assume that you always pass a single structure of type th_arg to
uth_create()? Thus, when I call makecontext() the argc parameter will
always be one? Or, do you want us to incorporate variable argument
functionality into uth_create()?
A2:
Always one.
- Q3:
So,
as far as I can tell we have two types of priorities: high and low.
Then, there is the idle thread and the main thread and non-runnable
threads (sleeping threads).
I'm confused about the scheduling rules for the main thread.
What sort of priority does it have?
A3:
All threads are low priority unless stated otherwise.
RR within each priority level.
Milestone C has 1 high priority thread (monitor) and a bunch of
low priority threads.
- Q4:
I am thinking of statically allocating an array of thread
descriptors where each thread descriptor includes a u_context.
Is that ok?
A4:
Strategically, that is OK as a starting point, but is not the
desired final solution. However, if you got things working using
this static structure and never got around to making it dynamically
allocated, it isn't the end of the world. What I mean by "it is
strategically OK" is that you can avoid dynamic storage allocation
if you are that squeamish about it or using pointers.
- Q5:
You said that uth_init should make the context for the main
thread, but what do I put for the function pointer and the
argument to makecontext?
A5:
You do not call makecontext when creating the context for the
main thread. Let's review the purpose for getcontext and makecontext:
- getcontext: You supply the address of a uncontext
structure, and it fills in that structure. If you look
at the bottom of the ucontext_basic.c file posted in
Homework 8, you will see that the struct contains the
hardware state and other stuff. But the useful
part that we need from getcontext is things like
the stack pointer and a few other things.
- makecontext: You supply the address of a
ucontext and the entry point (function
pointer) and thread (function) arguments and it updates
the ucontext structure. The point of this call is so
that when you eventually call swapcontext, the
Program Counter will be loaded with the entry point so
that you jump to the thread and that the stack is setup
properly, etc.
But since we will NOT be enterring the main thread, there is no
need to call makecontext in uth_init.
- Q6:
I put my thread descriptors at the beginning of uth.c and each
descriptor has a uncontext:
struct {
ucontext_t uc;
} thread_table[8];
Is that ok?
A6:
Yes. But here is a slight improvement.
const int DESC_TBL_SZ = 8;
typedef int uth_tid_t;
enum uth_priority_t { HI_PRI=0, LO_PRI, IDLE_PRI };
struct uth_desc {
uth_tid_t tid;
uth_priority_t priority;
ucontext_t uc;
// ... more stuff here ...
};
typedef struct uth_desc uth_desc_t;
uth_desc_t uth_desc_tbl[DESC_TBL_SZ]; // static allocation
Well, maybe the only thing this buys you is that you can now use
"uth_desc_t *p = ..." to declare a pointer to a thread descriptor
as an alternative for "struct uth_desc *p".
And it puts the tid and priority fields in the struct which you know
you will need.
- Q7:
For mileC.c, how do I call alarm() so that I can wakeup my
thread when the uth_sleep() call should expire?
A7:
You don't call alarm().
Your SIGVTALRM signal handler will be called periodically.
Every time the signal handler is called, you increment virtual time
(a counter).
The semantics of uth_sleep is that the thread should sleep
for atleast the amount specified.
So, you can just put the thread on a sleep queue along with a counter
that you decrement each time the signal handler is called.
When the counter reaches 0, you can move the thread from the
sleep queue to the appropriate run queue.
- Q8:
Can I do this for a run queue?
ucontext_t uc[8];
int running_tid; // current thread
...
Swapcontext(&uc[running_tid], &uc[next_tid);
A8:
Uhhh, well, yes, it will work, but it will
not be of much use after about the first 15-30 minutes.
You have to be careful about what is running_tid and next_tid, and
you can generalize this a little and get much further like this
if you use the structure in A8 above:
uth_desc_t uth_desc_tbl[DESC_TBL_SZ]; // static allocation
int running_tid; // current thread
old_tid = running_tid;
running_tid = ... search run queue for next_tid ...
Swapcontext( &(uth_desc_tbl[running_tid].uc),
&(uth_desc_tbl[next_tid].uc) );
If you swapcontext without changing running_tid, your
scheduler will become confused.
But, again, eventually you will want to dynamically allocate the
thread descriptor table. But you can do that when you get everything
else working.
- Q9:
I'm confused how to manage time, as in implementing the uth_sleep
and uth_usage functions. I've read the setitimer example but I'm
still confused about how to count time on a per thread basis.
A9:
- Let's suppose you set the clock interval to 10 msec.
That means that SIGVTALRM will fire every 10 msec
of virtual time.
- If someone called uth_sleep(100), they want to sleep for
100 msec of virtual time or 10 ticks.
So, you have to put that thread on the sleep queue
until its time has expired.
You can track expiration by either counting down (decrement
for every tick) or counting up (increment for every tick).
- uth_usage(tid) says to return the number of ticks that
thread tid has accumulated.
Every thread has a usage counter which is incremented
whenever it is running when a tick occurs. This is only a rough
approximation since there is no guarantee that the thread was
running during the entire tick.
- Q10:
I am confused about uth_join for milestone B.
In the description it says that a call to uth_join should
suspend the current thread until a the thread with id Tid
calls uth_exit.
However, when you suspend the main thread, both fibFiber 1 and 2
get a chance to complete. Then milestoneB calls uth_join again
for fibFiber2 and things break because fibFiber2 already finished.
A10:
As mentioned in class by Khalid (?), there are 2 cases:
- uth_join executes before the termination of the joined thread
This is the easy case because the caller just gets queued
until the termination occurs.
- uth_join executes after the termination of the joined thread
The joined thread is like a zombie process; i.e., there needs
to be enough of a foot print so that the caller can get
the exit status.
Note that you can assume that a thread will only get joined by
one thread.
There is also the error case when you call uth_join for a
non-existent thread.