[ prev | next | up ]

More Q&A for Assignment #2

[from your friendly TA]
> I want to use ThreadCreate in ThreadsInit to create the thread for main. 
> However, how do I find out what the pc and stack are for main?
You should not use ThreadCreate to create a thread for main. If you look at ThreadCreate, you will see that it might do a context switch, in which case you are assuming that you already have an active thread. If you haven't even created the thread for main yet, then you certainly don't have an active thread and thus you can't do a context switch. This is one of the reasons why you need a separate routine called ThreadsInit that will take care of the special case of creating the first thread.

Thus, "creating a thread for main" simply means taking a TD structure from the free list and making this structure the currently active thread. When you do this, you are right, you will have to fill in all the fields of the TD structure. You should easily be able to figure out what to do for the priority and the thread id field. For the sp and the pc fields, think about what will happen at the first context switch. In other words, will anybody ever use the values of sp and pc that you initialize the TD for main with? If nobody will use them, then you can initialize those values to whatever you want.

> In ThreadsInit(), should we assign thread ids to the
> descriptors in FreeTDs?
You basically have two choices, both of which are valid:

[From your instructor: The second option suggested below is preferable. Think about your threads library as if it were the process manager, responsible for allocating process IDs to newly created processes. If a process terminates and its id is reused immediately afterward, what kind of problems might you run into? (Hint: consider the case where two processes are communicating and one of them dies...)]

a) In ThreadsInit, you assign thread ids to the TDs (Thread Descriptors) in the free list (FreeTDs). In this case, in ThreadCreate, when you remove a TD from the free list, you already have a thread id in it, so you don't have to generate a new one. You will thus be recycling the same thread ids over and over. If you have MAX_THREAD TDs then you might want to make the thread ids go from 1 to MAX_THREAD.

b) The second option you have is not to assign thread ids in ThreadsInit, but generate a new thread id each time you need it, namely in ThreadCreate. You can for example generate a new thread id from the previous one by adding one. Be careful though. A thread id is just an int, so it can hold values up to 2^31-1. What happens if throughout the life of your program, you spawn more than 2^31-1 threads (unlikely for this assignment, but for servers that run for years without rebooting...)? You can wrap around, but then you have to be careful because if id 1 is still in use, you should skip it. In other words, you find your new id by incrementing the previously allocated id, but skipping ids that are already in use.

If you're curious, this method of allocating ids by incrementing and skipping the used ones is exactly the way that Linux generates pid's. If you know where to find the Linux source code, and you're curious, you can take a look at fork.c, which implements the fork (2) system call. The code to find the next pid is in get_pid().

> What is the range of thread ids?
A thread id is an int, and it can't be 0 or negative. So the theoretical range is 1 to 2^31-1. Of course, if you use the a) option that I talked about above, you might use a range of 1 to MAX_THREAD.
> In the assignment, it says that we should validate all inputs to our 
> library functions, and return errors if the inputs are not valid. Is there 
> any way to validate the pc in ThreadCreate?
When the OS creates a new thread, it can do some rudimentary validation of the pc, such as checking if the value points indeed to somewhere in the code area of the program. However, in this assignment, you don't need to validate the pc. Thus, if the user of your library sends an invalid value for the pc (e.g. something that points to data instead of code), the program will indeed crash, but we'll simply say it's the fault of the user. Keep in mind that apart from this particular case, your library should never crash, regardless of what the user input is.
> What methods should our queue structure have?  In class, Cooperstock 
> said to write CreateQueue, Enqueue, and Dequeue, where Dequeue just 
> removes and returns the first element in the queue.  But looking at
> the description of ThreadDestroy and ThreadChangePriority, it seems
> that we should have a function to remove a thread descriptor according
> to its id.  I can do that, but then the queue would be specific to
> thread descriptors.  Is that what you want?
Yes, you are absolutely right. It is ok if your queue is specific to thread descriptors. In fact, if you look at the definition that Cooperstock gave in the assignment, the TD structure has a next pointer, so the queue structure is embedded into the TD structure. Thus, it is perfectly fine if your queue has an operation that deletes a TD in a given a thread id.
> For the FreeTDs, should I use a link list of TDs as elements?  Or
> should I use a linked list of ThreadIDs only?  A list of TDs will be
> easier to implement but less memory efficient.
You should use a linked list of TDs. (See the answer to the previous question for more details). As for memory efficiency, if you take a look at the Idle() function given to you, you will see that all the TDs are stored in one array, even those that aren't used. By pre-allocating the array of TDs, or declaring it static, we avoid having to allocate and free TDs in ThreadCreate and ThreadDestroy. Thus, we spend less time in the library calls, which is important because we definitely want to minimize the time spent in library code relative to time spent in user code. However, you definitely are right in pointing out that there is a memory inefficiency. In fact, it is more of a trade off between memory and speed.
> Can you explain to me what the function:
>        FillTD( td, pc-8, stack, priority, id);
>   does? Are we suppose to implement this function ourself?
You have to implement this function yourself. FillTD simply takes a pointer to a TD, and several parameters. The parameters are used to intialize (or reset) the values in the TD structure. If you add fields to the TD structure, then you might want to add some more parameters to the FillTD function, one for each new field.