Lab 1: Real-Time Programming using Ada
See the lab introduction slides.
The goal of this assignment is to gain basic understanding of the Ada programming language and some of its constructs. Some problems are related to the real-time aspects of programming in Ada, other problems should make the student familiar with the Ada programming style.
All problems address generic aspects of real-time programming.
Working in Groups
Solve this assignment in your groups. All students participating in the group shall be able to describe all parts of the solution. The lab should be done in groups of 3 people, or in exceptional cases in groups of 2 people. Submissions by a single student will normally not be accepted.
Your submission must consist of the following:
- Programs solving the individual assignments (well commented, well structured programs),
- A report with illustrative test-runs for each program.
- Pointers to source code, executables and start-up scripts necessary to run the program (if you use compilers or IDEs different than the lab). Please observe file-access rights, i.e., all files must be readable by the examiner.
Solutions have to be submitted via the student portal, deadline for submission is September 19th, 23:59. No submissions will be accepted after the 19th.
Language and Compiler
This assignment should be solved in Ada95 and compiled with GNU's Ada compiler gnat, that can be installed for free either by downloading it directly or asking your favorite package manager. GNAT can be downloaded from sourceforge.For more installation instruction, please check this webpage. The Unix machines at the IT department also have gnat installed. Note that, you can install a later version of Ada compiler but can only use Ada95 features for solving this assignment.
When using gnat all files should have the suffixes .ads or .adb, for specifications and bodies respectively. Each file should contain exactly one compilation unit. A compilation unit is (for example) a package, a procedure or a function. The "main" procedure of your program should be a procedure which accepts no arguments and resides in a file with the same name as the procedure. Use lower case characters for file names. For instance a "hello world" program can look like this:
-- File: hello_word.adb -- Purpose: Greet the world with Text_Io; procedure Hello_World is begin Text_Io.Put_Line("Hello World!"); end Hello_World;
Compilation on the UNIX Machines
To compile your program you use the command "gnatmake <main_procedure_name>". E.g., to compile and run the program above you should write:
$ gnatmake hello_world $ ./hello_world
Compilation at home
To compile your program at home you should, after a successful installation, just type
Part 1: Cyclic Scheduler
Write a cyclic scheduler which manages three procedures: F1, F2 and F3. We demand the following for the executions:
- F1 should be executed every second.
- F2 starts when F1 terminates.
- F3 should execute every other (varannan) second, starting 0.5 seconds after F1's start.
The execution times for the functions are not known. However, it can be assumed that F1 and F2 together execute for less than 0.5 seconds, and that F3 does not take more than 0.5 seconds to execute.
Let the functions print an informative message when they execute, e.g.
F1 executing, time is now: 1.00000
Make sure that the printed time has a resolution of as least 1/1000 sec.
Note: Some amount of jitter in start times for functions F1 and F3 cannot be avoided. However, the start times of the functions are not allowed to "drift" further and further away from the schedule.
- Use the package Calendar for access to the clock
- Use the package Text_Io for printing messages
- To print Duration variables you can instantiate the generic package Text_Io.Fixed_Io with a duration type: package DIO is new Text_Io.Fixed_Io(Duration); The DIO package will then export, among other things, the procedure DIO.Put(D:Duration, Fore:Field, Aft:Field) to print variable D of type Duration.
- See Ada 95 Predefined Language Environment under Text_Io.Fixed_Io (A.10.1) for more information.
- Another way of printing discrete types or subtypes is to use the Image attribute. It will return the character string that corresponds to the way we will see the type. E.g., if D is of type Duration we can print D using the package Text_Io like: Text_Io.Put(Duration'Image(D));
- There is, again, no need to create any tasks in this part.
Part 2: Cyclic Scheduler with Watch-dogs
Modify F3 from Part 1 so that it occasionally takes more than 0.5 seconds to execute.
Augment the cyclic scheduler from Part 1 with a watchdog task to monitor F3's execution time. When F3 exceeds its deadline (0.5s), the watchdog task should immediately print a warning message. I.e., 0.5s after start of F3, either F3 has finished or the watchdog has printed a message. The watchdog should let F3 finish even if it misses its deadline.
The watchdog task should be started (released) at the same time as (or just before) F3 starts executing, and from that point measure the time that F3 uses.
When F3 misses its deadline the cyclic executive should re-synchronize so that F1 is started at whole seconds.
- Implement the watchdog as a separate task.
- The select, accept and delay statements might be useful in the code for the watchdog.
- The packages Ada.Numerics.Discrete_Random and Ada.Numerics.Float_Random are useful for creating random numbers.
Part 3: Process Communication
Create three tasks:
- A task that act as a first in, first out (FIFO) buffer for integers. The buffer should block any task which makes calls which could overflow or underflow the buffer. The buffer should at least be able to buffer 10 integers.
- A task that puts integers in the range 0..25 into the buffer at irregular intervals (i.e. a producer of values).
- A task that pulls integers out of the buffer at irregular intervals (i.e. a consumer of values), and summarizes them. When the sum is above 100 the task should terminate the program. The information that the buffer and the producer should terminate should be spread using synchronization (not using global variables, producer should not count, ...). You are not allowed to use abort or terminate commands.
In your solution, the buffer should not print any messages, but the producer and consumer should print messages containing the integers that are given to/taken from the buffer.
- You should not implement semaphores nor use the Semaphore_Package. Synchronization and mutual exclusion should be implemented by the task which manages the FIFO buffer.
- You should not throw away any number produced by the producer, even if the buffer is full at the moment.
- To detect errors, make sure your test run includes situations where both the producer and the consumer get blocked on the buffer. (Not at the same time, but each one eventually.)
- The statements accept and when might be useful for the buffer task.
Part 4: Data Driven Synchronization
Re-implement the FIFO buffer in the previous part as a protected shared object.