L Computer Science & Electrical Engineering

Real-time Systems – SMD138


Lab 3

A re-implementation of lab 1 using threads


In this assignment we will explore the benefits of threaded concurrency by revisiting the problem studied in lab 1. That is, the task is to implement a program that (1) blinks an LCD segment of your choice once a second, (2) computes an ascending chain of prime numbers and displays these on the LCD, and (3) reacts to joystick button presses, all at the same time.

To this end you will need the to incorporate most of your code developed in lab 1. To make the assignment slightly more interesting, we will limit prime number printouts to LCD positions 0 and 1, and require the accumulated key press count to be displayed at LCD at positions 3 and 4. Function printAt() from lab 2 can preferably be used for this purpose (restored to its original definition, of course, involving no global variables). The concurrency support will be the multi-threading kernel you developed in the second lab assignment, which provides the ability to spawn new threads and to lock and unlock mutex variables.

How to use these features for best results is a design issue, though, and we will actually explore three alternative solutions in this assignment. The task is to implement them all and then draw some conclusions. Note: Although we will play with both the tinythreads kernel and the tasks that use its services in this assignment, a clear separation between the kernel and its clients is still utterly important. This means that the kernel should be kept in the file tinythreads.c, and that client code that use the kernel services reside in other files that only access the services exported in tinythreads.h.

Alternative 1

Here you will rely on the time-sharing features of your final tinythreads kernel from lab 2 and literally reuse the code for the three different tasks you developed in parts 1-3 of lab 1. Spawn two of the tasks as separate threads and confirm that all three tasks appear to run concurrently. Compare this solution to the one you created in lab 1, part 4. Also experiment with different period settings for the kernel timer. What happens to the button response time when the timer period is high (1 second or above)? Why?

Alternative 2

We will now abandon time-sharing in favor of interrupt-driven concurrency. You must therefore start by removing the time-sharing features from your kernel, which means deleting the timer interrupt handler and all timer register manipulation. It will also be beneficial to change the scheduling policy slightly, so that newly arrived tasks are given more attention than old ones. Do this by modifying function enqueue() to insert a new thread in the first position of the given queue instead of the last.

Now make the following changes to your client code:
  1. For each of the busy-waiting tasks, create a global mutex variable and replace the busy-waiting loop with a lock operation on this mutex.
  2. Configure Timer/Counter1 to generate an interrupt twice a second, and let the interrupt handler perform an unlock operation on the blinking task mutex. This "asymmetric" use of the mutex operations is more commonly associated with the semaphore datatype, but it works for our implementation of mutexes just as well. Also note that even though you are likely to reuse most of your previously developed code for accessing the timer, that code is now part of the client application, and not the kernel.
  3. Configure the joystick port to generate an interrupt when the joystick button is pressed in the downward direction, and create a handler for this interrupt that unlocks the button count mutex.
Verify that the concurrent application still works as expected. Can you think of a program setting (like the timer interval in alternative 1 above) that would make the blinking jitter and button count response times deteriorate?

Alternative 3

Going one step further in the event-driven direction, we will now replace the blocking event-loops of alternative 2 with threads that just react to one event and then terminate. Implement the following changes:
  1. Remove all loops as well as the mutexes and the calls to lock in your button count and blinking code from the previous solution. Package what remains as functions of the type expected by the spawn primitive. If your functions need to refer to the state of a previous reaction, use global variables.
  2. Modify the two interrupt handlers so that each call to unlock is replaced by a call to the spawn instead.
Verify that this formulation of the solution works just as well as before.



When all three alternative solutions have been implemented and verified, draw your own conclusions regarding the different programming styles exploited here and in lab assignment 1. Which one do you prefer? What pros and cons do you identify? Note that there are no absolute right or wrong answers here, just express your educated opinion.

As a final exercise, try to remove the prime number computing task from one of the last two alternative solutions. What happens and why? Can you suggest a generically applicable fix to this problem?