Göm meny

TSEA81 - Datorteknik och realtidssystem

Notera att dessa sidor enbart finns i engelsk version

TSEA81 - Computer Engineering and Real-time Systems

Assignment 3 : Lift with Monitors

Introduction

In this assignment, a real-time program for simulation of an elevator system is developed. A monitor is used for representation of the elevator system. The monitor contains information about a lift, which moves between floors in a building, but also information about persons waiting for the lift, and passengers travelling with the lift.

To do this assignment you need to download the archive assignment3_20201109.tar.gz and unpack it.

The tar-file holds a folder named images with images for the lift system. That folder, containing the images, should be placed in the java_gui-folder just above the java-folder from where the java-GUI is started.

The monitor implements mutual exclusion and conditional critical regions.

A graphical user interface is used for presentation of lift movements between different floors in the building, and for visualisation of persons waiting for the lift, and persons travelling with the lift.

The 2019 version of the assignment has a slightly new look, representing persons/travellers with large numbers depicting the destination floor. The lift itself also shifts between red (when the lift is moving) and green (when the lift is not moving). The old style/behaviour is still available (though not recommended) by putting a comment for the #define NEWGUI 1 line in the file lift.h.

The purpose of the assignment is to provide skills and knowledge for implementation of a real-time program with monitors, using the C programming language and a real-time operating system.

Requirements to pass

The requirements to pass this assignment are:

  • The developed software as required in Section Assignment tasks is demonstrated.
  • The source code of the developed software is described verbally. The source code shall be well structured, and commented.

Preparations

As a preparation for the assignment, it is recommended to study the lecture notes 2 - Shared Resources, 3 - Task synchronization, and 4 - Monitors, Message passing, up to and including Section Assignment 3 - Lift with Monitors.

Required software

The assignment uses C and pthreads.

The assignment will be evaluated during a lesson in the course, using computers with Linux. The actual assignment tasks can be performed using Linux (or possibly OS X or Windows with Cygwin, although this has not been tested).

Assignment tasks

A real-time program implementing a lift shall be developed.

Real-time program requirements

The program shall fulfill the following requirements:

  • A lift shall move between floors in a building. The building contains floors, numbered from 0 (the top floor) to N_FLOORS-1 (the bottom floor).
  • The lift shall move, repeatedly, from one floor to the next floor. The next floor is determined by the current direction of the lift.
  • The lift shall change direction when it has arrived at the top floor, and when it has arrived at the bottom floor.
  • The lift shall stop at every floor, here denoted the current floor. If one or more persons are standing at the current floor, or if one or more lift passengers would like to leave the lift at the current floor, the lift will shall wait. While none of these conditions are fulfilled, the lift shall continue its movement to the next floor (after the time period used in the lift_move function).
  • There shall be functionality which allows a user to create a person, which then shall be placed on a randomly chosen floor. When the lift arrives at this floor, the person shall, if there is room in the lift, enter the lift, thereby starting a journey to another floor, here denoted the destination floor, which also is chosen randomly. When the lift arrives at the destination floor, the person shall leave the lift, and disappear. After a specified amount of time, e.g. 5 seconds, the person shall reappear, and start a new journey, again from a randomly chosen floor to a randomly chosen destination floor.
  • It shall be possible to create at most MAX_N_PERSONS persons.
  • There may be at most MAX_N_PASSENGERS passengers in the lift at a given time instant.
  • A unique identity shall be assigned to every person. The identity shall be an integer number, between 0 and MAX_N_PERSONS-1.
  • When a person is waiting for the lift, the identity of the person shall be displayed.
  • When a person is travelling with the lift, the identity of the person and the destination floor for the person's lift journey, shall be displayed.
  • It shall be easy to setup a test scenario where all persons in the system are moved to specific floors in order to test the applications ability to handle various corner cases. See Debug mode further down.
  • Observe, that all waiting periods caused by sleep functions (e.g. usleep) is only there for us humans to be able to see what is happening. Meaning, if all sleep functions were to be removed (as shall be done in assignment 5 Performance analysis) everything shall still work (passengers being able to enter/exit lift without any problems).

Implementation requirements

The program shall be written in C, using pthreads for handling of parallel activities.

  • There shall be one thread, called lift_thread, for the lift.
  • There shall be one thread for each person. All person threads shall be implemented by the same C function, called passenger_thread.
  • There shall be one thread, called user_thread, which receives commands from the graphical user interface. This thread shall create new person threads. A new person thread shall be created when the text string new is received. The thread user_thread shall also contain functionality for termination of the program. The program shall be terminated when the text string exit is received.
  • In this assignment the lift data type should contain only one mutex and one condition variable.

A monitor data type for the lift shall be defined, as follows:

/* definition of monitor data type for lift */

typedef struct
{
    /* the floor where the lift is positioned */ 
    int floor; 

    /* a flag to indicate if the lift is moving */ 
    int moving; 

    /* variable to indicate if the lift is travelling in the up 
       direction, which is defined as the direction where the 
       floor number is increasing */
    int up;

    /* persons on each floor waiting to enter */ 
    person_data_type persons_to_enter[N_FLOORS][MAX_N_PERSONS];

    /* passengers in the lift */
    person_data_type passengers_in_lift[MAX_N_PASSENGERS];

    /* mutex for mutual exclusion */
    pthread_mutex_t mutex; 

    /* condition variable, to indicate that something has happend */ 
    pthread_cond_t change; 

} lift_data_type;

typedef lift_data_type* lift_type;

The data type person_data_type, which is used in the above data structure, defines the identity of a person, and the number of the floor to which the person shall travel, according to

/* data structure for person information */ 
typedef struct
{
    /* identity */ 
    int id; 
    /* destination floor */ 
    int to_floor; 
} person_data_type; 

The following monitor functions shall be associated with the monitor data type for the lift:

/* MONITOR function lift_next_floor: computes the floor to which 
   the lift shall travel. The parameter *change_direction 
   indicates if the direction shall be changed */
void lift_next_floor(
    lift_type lift, int *next_floor, int *change_direction); 

/* MONITOR function lift_move: makes the lift move from its current 
   floor to next_floor. The parameter change_direction indicates if 
   the move includes a change of direction. This function shall be 
   called by the lift process when the lift shall move */ 
void lift_move(
    lift_type lift, int next_floor, int change_direction); 

/* MONITOR function lift_has_arrived: shall be called by the lift 
   process when the lift has arrived at the next floor. This function 
   indicates to other processes that the lift has arrived, and then waits 
   until the lift shall move again. */
void lift_has_arrived(lift_type lift); 

/* MONITOR function lift_travel: makes the person with id id perform 
   a journey with the lift, starting at from_floor and ending 
   at to_floor */ 
void lift_travel(
    lift_type lift, int id, int from_floor, int to_floor);

The functions lift_next_floor, lift_move and lift_has_arrived shall be called by the lift thread. The function lift_travel shall be called by the person threads.

The monitor data type for the lift, and the associated functions, shall be implemented in two files, lift.h and lift.c. The remaining functionality of the program, e.g. the threads, shall be implemented in one or more additional files. In the lab skeleton, the file main.c contains the threads, the actual lift (declared as a variable of type lift_type), and the main-function of the program, and which includes the file lift.h. The file main.c can also contain other functions, which are used by the threads e.g. for generation of a randomly selected floor number.

The monitor data type for the lift shall be used as an abstract data type. This means that the data which belong to the monitor data type are read and written only via monitor functions, with function prototypes placed in the file lift.h. The monitor functions can use additional functions, declared in lift.c but not visible via lift.h. These functions shall be defined using the reserved word static, in order to ensure that they are not visible outside the compilation unit lift.c.

The monitor data type for the lift, and a subset of the functions that shall be implemented, are available in two files, lift.h and lift.c.

The file lift.h contains the definitions

/* size of building */ 
#define N_FLOORS 5

/* maximum number of persons in the lift system */ 
#define MAX_N_PERSONS 10

/* maximum number of passengers in lift */ 
#define MAX_N_PASSENGERS 5

which define the size of the building, the maximum number of persons that can be created, and the maximum number of passengers allowed in the lift.

Communication with a graphical user interface

Messages from the graphical user interface to the real-time program shall be sent as the text strings new and exit, according to the specification in Section Implementation requirements.

Messages from the real-time program to the graphical user interface shall be sent by calling a function which draws the lift, the persons that are waiting, and the passengers in the lift. This function, which is called draw_lift, has a function prototype according to

/* draw_lift: draws the lift, including the lift building, 
   waiting persons, and travelling passengers. It also prints 
   the person id for each waiting person, and prints id and 
   destination floor for each passenger */ 
void draw_lift(lift_type lift);

The function draw_lift is available in a program module draw, which consists of the files draw.h and draw.c.

The graphical user interface uses image files which are loaded into the graphical user interface program, for the purpose of displaying the lift, the building in which the lift is moving, and the persons.

The image files shall be placed in the directory java_gui/images in the Simple_OS installation directory structure.

The image files are available as gif-files, stored in a zip-file and included in the assignment 3 distribution.

Implementation recommendations

The following recommendations may be useful.

Develop the program incrementally, in a step-by-step manner. A first step can e.g. be to create a lift which moves between the floors. A next step could be to introduce a person which travels between two specific floors. Then, more persons can be introduced, and random choice of floors can be implemented.

The function draw_lift, described in Section Communication with a graphical user interface, must be called when there is a change in the lift, or on a floor. This means e.g. that the lift thread shall call draw_lift when the lift has moved to a new floor, and that the person threads shall call draw_lift when a person has been placed on a floor in order to wait for the lift, when a person has entered the lift, and when a person has left the lift.

Note that these calls to draw_lift are not done directly in the thread code. Instead they are done from a monitor function, or from a function called by a monitor function, in lift.c.

It could be useful to adjust the size of the grapical user interface window, so that it is adapted to the size of the images. This can be done using a call

    /* set size of GUI window */ 
    si_ui_set_size(670, 700); 

which can be placed in the user thread (i.e. the thread which receives commands from the graphical user interface), before the infinite while-loop of the thread.

In a typical C program random numbers are generated using the rand() function. However, this function is not thread-safe and it is better if you use the thread safe version, rand_r(). This has been prepared for you in the main.c file.

For testing purposes you may want to adjust the seed that the random number generators are initialized with in main.c rather than a seed which depends on the current time. This might simplify debugging since the passengers will always behave in the same pattern. (Although this is not a guarantee that the program will behave deterministically as the threads will inherently cause non-deterministic behavior.)

Additional information about functions in the C Standard Library is available via this link .

A unique identity can be assigned to each person thread. While this can be done in a variety of different ways, the most straightforward way to do this is to send a pointer to an integer containing the current ID as a parameter to the thread when creating it. This can be done by passing a pointer to an int containing the ID as the last argument of pthread_create():

    int id;
    pthread_t handle;
    id = 1;
    // Start a new thread with the thread_function 
    pthread_create(&handle, NULL, thread_function, (void *) &id);
    pthread_detach(handle); // Ensure resources are reclaimed appropriately
}

void *thread_function(void *arg)
{
    int *tmp = (int*) arg;	// cast the arg pointer to an int pointer
    int id = *tmp;		// assign id to what tmp is pointing to

		...
}

Note that since a pointer to the ID is passed to pthread_create() it is important to avoid changing the ID until the created thread has read the value. Hint: Asymmetric synchronization could be used here.

The file lift.c, which is available according to the description in Section Implementation requirements, contains a few functions which are ready to use. These functions can be used as a starting point during the implementation task.

Debug mode

In this assignment it can sometimes be difficult to debug the functionality and correctnes of the lift implementation. To simplify debugging we have provided two files, debug.c and debug.h, which contains a few functions that can be used to simplify testing:

  • debug_init(): Initialize debugging code
  • debug_check_override(int id, int *from_floor, int *to_floor): Call this function directly before you call the lift_travel function from the person thread. It will check whether the override mode is set for a certain person id and write a new value into the from_floor and to_floor pointers. If the pause mode is enabled it will also wait until the pause mode is disabled.
  • debug_override(int id, int from_floor, int to_floor): Enables the override mode for the specified person id
  • debug_pause(): Makes all persons wait in debug_check_override() until unpaused
  • debug_unpause(): Releases all persons that are waiting in debug_check_override()
Our suggestion is that you add three new commands to your user_thread that uses these debugging commands:
  • pause: Calls debug_pause()
  • unpause: Calls debug_unpause()
  • test: Calls debug_override() with appropriate parameters for all ten person id:s. Our suggestion is that you at least add a case that enables you to easily test a mode where the elevator is full while reaching a floor where no passengers should get off which also contains a person that is waiting to get on the elevator.
You may also be asked to add additional test cases during the assignment to test other common bugs.

Using valgrind to find synchronization errors

When creating a concurrent system it is often hard to find data races. Fortunately there are tools available that can (sometimes) be useful in finding these kind of errors. A tool called valgrind is installed on the lab system that can help you with this.

While valgrind has a wide variety of options (read the documentation if you are interested in all of the features available in valgrind), you can get by with the following command line:

valgrind --tool=helgrind ./your_program


Informationsansvarig: Kent Palmkvist
Senast uppdaterad: 2021-10-07