Vous êtes sur la page 1sur 19

Real-Time Operating Systems Laboratory Report

Salvatore Campione 145781


Vittorio Giovara 149374
Alberto Grand 149389

Academic Year 2008-2009


Contents

1 Introduction 2

2 First Steps 3

3 Using RTEMS 5
3.1 Operating System Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.2 Sample Programs and Stack Overflows . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

4 Creating Custom Programs 7


4.1 Makefile and Defines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4.2 Manual Activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4.3 Analysis of execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

A Test program – Readers and Writers 12

1
Chapter 1

Introduction

In this report we will present the workflow for configuring a simple concurrent program on an embedded
system.
The target architecture will be an ARM system, supported by the RTEMS operating systems; a cross
toolchain will be used for preparing the code and simulate it.
For test and simulation of the code we will use Skyeye program, while for compilation we will use a
cross variant of the GNU Compiler Collection.

2
Chapter 2

First Steps

The first operation was to set up the correct environment for accessing the cross toolchain, installed in
/opt/rtems-4.8; so we issued the following command:

export PATH="/opt/rtems-4.8/bin:$PATH"

After that we tried to compile with arm-rtems4.8-gcc a very simple C program that follows:

#include <stdio.h>

int main () {
int a,b,c;

a = 2;
b = 3;

c = a + b;

return 0;
}

The code seemed to compile fine, but a warning was issued claiming that the START symbol was
defaulted to 0x0008000:

/opt/rtems-4.8/lib/gcc/arm-rtems4.8/4.2.2/../../../../arm-rtems4.8/bin/ld:
warning: cannot find entry symbol _start; defaulting to 00008000

If we tried to ignore this warning and simulate it, the program would not execute.
So having a look at the assembly code, obtained by means of arm-rtems4.8-objdump -d,
we were hinted that the processor was starting to read the program from a location that contained only
garbage code:

Disassembly of section .text:

00008000 <rtems_provides_crt0>:
8000: e12fff1e bx lr

00008004 <malloc>:

3
8004: e3a00000 mov r0, #0 ; 0x0
8008: e12fff1e bx lr

[...]

In order to solve this problem we tried to force the START symbol to the location the processor was
reading from first, and thus we modified the default linker configuration with this script:

MEMORY
{
ram (W) : ORIGIN = 0x00000000, LENGTH = 0x00008000
}

SECTIONS
{
. = 0x00000000;
.text : { *(.text) } > ram
_start = 0x00000000;
}

We generated the object files only with arm-rtems4.8-gcc -o file.o file.c and sub-
sequently used arm-rtems4.8-ld ldscript.ld file.o, which read the configuration from
the ldscript.ld file. Now the compilation didn’t issue any warning, so we addressed the Skyeye
configuration. We initially set the configuration to the following:

cpu: arm7tdmi
mach: at91
mem_bank: map=M, type=RW, addr=0x00000000, size=0x00008000

We ran the compilation and tried to simulate, but Skyeye seemed stuck. Therefore, we debugged by
means of arm-rtems4.8-gdb and inferred from the following message that the system was blocked
on the first instruction:

Single stepping until exit from function rtems_provides_crt0,


which has no line number information.

By inspecting the disassembled code again, we saw that garbage code was still found at the execution
entry point. At this stage, we realised that the startup code, which should have been provided by RTEMS,
was missing: without some code for allocating memory, stacks and I/O segments the application we
developed would never have worked at all (on any system). This piece of code is either written by the
developer or is provided by the operating systems: this is why we have to configure and use RTEMS as
a base for developing our programs.

4
Chapter 3

Using RTEMS

3.1 Operating System Compilation


Once we understood that some kind of operating system was nedeed for applications to work properly,
our next target was to successfully compile RTEMS for our architecture, using an appropriate toolchain.
After setting up the environment and reading the provided documentation, we went to the RTEMS
source directory and first tried a default configuration; however, the code stopped compiling quite soon
(as expected!), so we ran the proposed configuration:

export PREFIX=./rtems-test
./configure --target=arm-rtems4.8 --disable-posix
--disable-networking --disable-cxx
--enable-rtemsbsp=rtl22x --prefix=$PREFIX

but we soon found out that the board name was wrongly spelled and that the prefix installation
location required an absolute path. So we corrected the configuration string and experienced a successful
compilation.

export PREFIX=/opt/arm-rtems4.8
./configure --target=arm-rtems4.8 --disable-posix
--disable-networking --disable-cxx
--enable-rtemsbsp=rtl22xx --prefix=$PREFIX
make && make install

We noticed that in other compilation tests it was impossible to compile on a file system that didn’t
support symbolic links and permissions (like FAT); moving the source and build directory to a file system
like ext3 or reiserfs solves this kind of problems.

3.2 Sample Programs and Stack Overflows


Having succeeded in compiling the operating system, we were then ready to test some of the sample
programs with Skyeye.
At first Skyeye refused to work, due to a bad configuration file; looking on the Skyeye website we
found the correct configuration for our board, which is reported below:

cpu: arm7tdmi
mach: lpc2210
mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000

5
mem_bank: map=M, type=RW, addr=0x81000000, size=0x08000000
mem_bank: map=M, type=RW, addr=0x40000000, size=0x00400000
mem_bank: map=I, type=RW, addr=0xE0000000, size=0xFFFFFFF
mem_bank: map=I, type=RW, addr=0xF0000000, size=0xFFFFFFF

Now we faced a very odd behavior: some of the test programs ran fine, or with minor warnings about
I/O addressing, while many others printed random error strings or just closed with unexpected results.
Fearing some misconfigurations, we tried starting over on our computers, recompiling also the cross-
toolchain from scratch, but with no avail.
So we were suggested that we should try debugging our application again with the cross GNU De-
bugger (arm-rtems-4.8-gdb) and Skyeye launched in debug mode (skyeye -d). After some
tinkering we were finally able to understand the problem: a stack overflow. Since our board is very sim-
ple, the Memory Management Unit is disconnected and so no memory protection is used. We noticed
that many programs, after the initialization of the stack, soon filled up the reserved stack size and started
overwriting other memory locations.
As a matter of fact we gathered this set of information by debugging:

stack type start address end address stack size


abort mode 0x81010250 0x810102A0 80 B
interrupt mode 0x810102A0 0x810103A0 256 B
fast interrupt mode 0x810103A0 0x810103F0 80 B
supervisor mode 0x810103F0 0x810104F0 256 B

from which we deduced that in our configuration all programs are executed in supervisor mode,
having access to all available system directives, and that of the 0x100 memory location of the supervisor
stack, 0x64 entries are protected, leaving only 0x9C of available stack space. These values were due to
the source files given for the first compilation.
So in order to see if an actual stack overflow was generated, we set up a watchpoint in the debugger
like the following:

watch ( $sp < 0x810103F0 )

where sp is the stack pointer of the segment. After initialization and some iterations of the program,
the watchpoint was activated and reported that an actual stack overflow was present; subsequently the
program began its random behavior.
So we thought that the size of the main stack was too small and tried increasing it by modifying the
linkcmds file of our board, where all startup symbols are defined. Setting a stack size of 0xF00 bytes
and recompiling from scratch provided us with a working toolchain capable of running all the sample
programs available.

6
Chapter 4

Creating Custom Programs

4.1 Makefile and Defines


We tried the Readers and Writers paradigm as our test program: studying the main scheme presented dur-
ing lessons we created our version of the problem using RTEMS directives. The Makefile we wrote was
taken from one of the sample programs and modified accordingly; exporting the necessary environment
variable

export RTEMS_MAKEFILE_PATH=/opt/rtems-4.8/arm-rtems/rtl22xx/

allowed us to skip inserting manually the included libraries and to invoke directly the make command
for quick compilation.

4.2 Manual Activation


After writing our program we compiled it with no errors or warnings. However, only the main thread (or
“task”, in RTEMS nomenclature) was executed while all the other tasks did not start.
We discovered that our programming procedure was not suited for embedded systems; as a matter of
fact, we were used to take for granted very important components like the scheduler, the counters, even
the clock, but in real time operating systems these units must be activated manually.
In order to activate these features, some proper #define’s must be set up within the code, namely

#define CONFIGURE APPLICATION NEEDS CLOCK DRIVER


activates the clock;

#define CONFIGURE APPLICATION NEEDS CONSOLE DRIVER


activates the ticker;

#define CONFIGURE RTEMS INIT TASKS TABLE


initializes the task table;

#define CONFIGURE MAXIMUM TASKS (2*(N WRITER + N READER))


sets up the number of concurrent thread.

The minimum number of tasks is overestimated, since RTEMS creates further tasks to provide times-
licing and other features.

7
4.3 Analysis of execution
The next step was to simulate the code we wrote in order to verify the correct execution of the program.
A first analysis with 3 Writers and 5 Readers, with a buffer of 50 slots, was executed. The following
output was obtained in response:

Your elf file is little endian.


uart_mod:0, desc_in:, desc_out:, converter:
start addr is set to 0x81000000 by exec file.
CCCCCCCCCCERROR:io_write a non-exsiting addr:addr = fffff140, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff144, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff148, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff14c, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff150, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff154, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff158, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff15c, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff160, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff164, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff168, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = fffff16c, data = 810012d0
ERROR:io_write a non-exsiting addr:addr = e000403c, data = 0
Writer 0 ha scritto: 0
Writer 0 ha scritto: 3
Writer 0 ha scritto: 6
Writer 0 ha scritto: 9
Writer 0 ha scritto: 12
Writer 0 ha scritto: 15
Writer 0 ha scritto: 18
Writer 0 ha scritto: 21
Writer 0 ha scritto: 24
Writer 0 ha scritto: 27
Writer 0 ha scritto: 30
Writer 0 ha scritto: 33
Writer 0 ha scritto: 36
Writer 0 ha scritto: 39
Writer 0 ha scritto: 42
Writer 0 ha scritto: 45
Writer 0 ha scritto: 48
Writer 0 ha scritto: 51
Writer 0 ha scritto: 54
Writer 0 ha scritto: 57
Writer 0 ha scritto: 60
Writer 0 ha scritto: 63
Writer 0 ha scritto: 66
Writer 0 ha scritto: 69
Writer 0 ha scritto: 72
Writer 0 ha scritto: 75
Writer 0 ha scritto: 78
Writer 0 ha scritto: 81

8
Writer 0 ha scritto: 84
Writer 0 ha scritto: 87
Writer 0 ha scritto: 90
Writer 0 ha scritto: 93
Writer 0 ha scritto: 96
Writer 0 ha scritto: 99
Writer 0 ha scritto: 102
Writer 0 ha scritto: 105
Writer 0 ha scritto: 108
Writer 0 ha scritto: 111
Writer 0 ha scritto: 114
Writer 0 ha scritto: 117
Writer 0 ha scritto: 120
Writer 0 ha scritto: 123
Writer 0 ha scritto: 126
Writer 0 ha scritto: 129
Writer 0 ha scritto: 132
Writer 0 ha scritto: 135
Writer 0 ha scritto: 138
Writer 0 ha scritto: 141
Writer 0 ha scritto: 144
Writer 0 ha scritto: 147
Reader 1 ha letto: 0
Reader 2 ha letto: 3
Reader 3 ha letto: 6
Reader 4 ha letto: 9
Reader 0 ha letto: 12
Reader 1 ha letto: 15
Reader 0 ha letto: 18
Reader 4 ha letto: 21
Reader 3 ha letto: 24
Reader 2 ha letto: 27
Writer 0 ha scritto: 150
Writer 1 ha scritto: 1
Writer 2 ha scritto: 2
Reader 1 ha letto: 30
Reader 2 ha letto: 33
Reader 3 ha letto: 36
Reader 4 ha letto: 39
Reader 0 ha letto: 42
Writer 0 ha scritto: 153
Writer 1 ha scritto: 4
Writer 2 ha scritto: 5
Reader 1 ha letto: 45
Reader 0 ha letto: 48
Reader 4 ha letto: 51
Reader 3 ha letto: 54
Reader 2 ha letto: 57
Writer 0 ha scritto: 156

9
Writer 1 ha scritto: 7
Writer 2 ha scritto: 8
Reader 1 ha letto: 60
Reader 2 ha letto: 63
Reader 3 ha letto: 66
Reader 4 ha letto: 69
Reader 0 ha letto: 72
Writer 0 ha scritto: 159
Writer 1 ha scritto: 10
Writer 2 ha scritto: 11
Reader 1 ha letto: 75
Reader 0 ha letto: 78
Reader 4 ha letto: 81
Reader 3 ha letto: 84
Reader 2 ha letto: 87
Writer 0 ha scritto: 162
Writer 1 ha scritto: 13
Writer 2 ha scritto: 14
Reader 1 ha letto: 90
Reader 2 ha letto: 93
Reader 3 ha letto: 96
Reader 4 ha letto: 99
Reader 0 ha letto: 102
Writer 0 ha scritto: 165
Writer 1 ha scritto: 16
Writer 2 ha scritto: 17
Reader 1 ha letto: 105
Reader 0 ha letto: 108
Reader 4 ha letto: 111
Reader 3 ha letto: 114
Reader 2 ha letto: 117
Writer 0 ha scritto: 168
Writer 1 ha scritto: 19
Writer 2 ha scritto: 20
Reader 1 ha letto: 120
Reader 2 ha letto: 123
Reader 3 ha letto: 126
Reader 4 ha letto: 129
Reader 0 ha letto: 132
Writer 0 ha scritto: 171
Writer 1 ha scritto: 22
Writer 2 ha scritto: 23
Reader 1 ha letto: 135
Reader 0 ha letto: 138
Reader 4 ha letto: 141
Reader 3 ha letto: 144
Reader 2 ha letto: 147

As you can see looking at the report above, the timeslice needs some time to start; this might be due
to the fact that each task has to execute its startup code, which can be somehow big, before starting the

10
job for which it was created. In fact, after some time, timeslicing starts working.
Looking at the correctness of the execution, we can observe that the written and the read sequences
in the buffer are {0 3 6 9 12 15 18 ...}, pointing out that the solution is correct; moreover,
the flow of readers and writers is not constant (i.e. W0, W1, W2, R0, R1, etc.) but it depends on the
timeslicing scheduler, which can easily be noticed looking at the report.
Although our implementation of “readers and writers” appeared to work, a sequence of errors oc-
curred at the beginning of the simulation with SkyEye. These errors pointed out that SkyEye was not able
to write to some specific address locations, which corresponded to I/O addresses. By reading the pro-
vided documentation, we found out that only 16 vectored interrupts are initialized by RTEMS. However,
the maximum number of interrupts (BSP MAX INT was set to 28 in the irq.h header file. We tried to
change this value to 16 and recompile RTEMS. To our great satisfaction, the errors now disappeared:

Your elf file is little endian.


uart_mod:0, desc_in:, desc_out:, converter:
start addr is set to 0x81000000 by exec file.
CCCCCCCCCC
Writer 0 ha scritto: 0
Writer 0 ha scritto: 3
Writer 0 ha scritto: 6
Writer 0 ha scritto: 9
Writer 0 ha scritto: 12
Writer 0 ha scritto: 15
Writer 0 ha scritto: 18
[...]

11
Appendix A

Test program – Readers and Writers

#include <bsp.h>
#include <rtems.h>
#include <stdlib.h>
#include <stdio.h>
#define MAXBUF 50
#define N_WRITER 10
#define N_READER 20

rtems_task reader(int);
rtems_task writer(int);
void enter_reader(void);
void enter_writer(void);
void exit_reader(void);
void exit_writer(void);

int count = 0;
int rc = 0;
rtems_id mutex_id /* protects rc */, db_id /* protects buffer */,
ts_id /* avoids starvation */, empty_id /* allow writer */,
full_id /* allow reader */;
int buffer[MAXBUF];
int in_ptr = 0, out_ptr = 0;
rtems_id readers_id[N_READER];
rtems_id writers_id[N_WRITER];

rtems_task Init(rtems_task_argument ignored)


{
int i, flag;
rtems_time_of_day time;
int code;
time.year = 2008;
time.month = 12;
time.day = 16;
time.hour = 9;
time.minute = 0;
time.second = 0;

12
time.ticks = 0;
if (rtems_clock_set(&time) != RTEMS_SUCCESSFUL)
{
printf("Failure upon rtems_clock_set call\n");
exit(3);
}

if (rtems_semaphore_create(rtems_build_name(’m’, ’u’, ’t’, ’x’), 1,


RTEMS_COUNTING_SEMAPHORE, 0, &mutex_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"mutex\" creation\n");
exit(1);
}
if (rtems_semaphore_create(rtems_build_name(’d’, ’b’, ’\0’, ’\0’), 1,
RTEMS_COUNTING_SEMAPHORE, 0, &db_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"db\" creation\n");
exit(1);
}
if (rtems_semaphore_create(rtems_build_name(’t’, ’s’, ’\0’, ’\0’), 1,
RTEMS_COUNTING_SEMAPHORE, 0, &ts_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"ts\" creation\n");
exit(1);
}
if (rtems_semaphore_create(rtems_build_name(’e’, ’m’, ’p’, ’\0’), MAXBUF,
RTEMS_COUNTING_SEMAPHORE, 0, &empty_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"empty\" creation\n");
exit(1);
}
if (rtems_semaphore_create(rtems_build_name(’f’, ’u’, ’l’, ’\0’), 0,
RTEMS_COUNTING_SEMAPHORE, 0, &full_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"full\" creation\n");
exit(1);
}

/* create reader processes */


for (i = 0; i < N_READER; i++)
{
if (rtems_task_create(rtems_build_name(’r’, ’e’, ’0’ + i, ’\0’), 1,
RTEMS_MINIMUM_STACK_SIZE *2, RTEMS_DEFAULT_MODES | RTEMS_TIMESLICE,
RTEMS_DEFAULT_ATTRIBUTES, &readers_id[i]) != RTEMS_SUCCESSFUL)
{
printf("Failure upon creation of reader %d\n", i);
exit(2);
}
}

13
/* create writer processes */
for (i = 0; i < N_WRITER; i++)
{
if ((code = rtems_task_create(rtems_build_name(’w’, ’r’, ’0’ + i, ’\0’),
RTEMS_MINIMUM_STACK_SIZE *2, RTEMS_DEFAULT_MODES | RTEMS_TIMESLICE,
RTEMS_DEFAULT_ATTRIBUTES, &writers_id[i])) != RTEMS_SUCCESSFUL)
{
printf("Failure upon creation of writer %d, code = %d\n", i, code);
exit(2);
}
}

/* start processes */
flag = N_READER > N_WRITER;
for (i = 0; i < (!flag ? N_READER : N_WRITER); i++)
{
if (rtems_task_start(readers_id[i], reader, i) != RTEMS_SUCCESSFUL)
{
printf("Failure upon start of reader %d\n", i);
exit(2);
}
if (rtems_task_start(writers_id[i], writer, i) != RTEMS_SUCCESSFUL)
{
printf("Failure upon start of writer %d\n", i);
exit(2);
}
}

for (i = (!flag ? N_READER : N_WRITER); i < (flag ? N_READER : N_WRITER); i++


{
if (flag)
{
if (rtems_task_start(readers_id[i], reader, i) != RTEMS_SUCCESSFUL)
{
printf("Failure upon start of reader %d\n", i);
exit(2);
}
} else
{
if (rtems_task_start(writers_id[i], writer, i) != RTEMS_SUCCESSFUL)
{
printf("Failure upon start of writer %d\n", i);
exit(2);
}
}
}
rtems_task_delete( RTEMS_SELF );
}

14
rtems_task reader(int n)
{
int i;
while (1)
{
enter_reader();
printf("Reader %d ha letto: %d\n", n, buffer[out_ptr]);
out_ptr = (out_ptr + 1) % MAXBUF;
usleep(200000);
exit_reader();
}
}

rtems_task writer(int n)
{
int d = 0, i;
while (1)
{
enter_writer();
buffer[in_ptr] = n+d;
printf("Writer %d ha scritto: %d\n", n, buffer[in_ptr]);
in_ptr = (in_ptr + 1) % MAXBUF;
d+= N_WRITER;
exit_writer();
}
}

void enter_reader(void)
{
if (rtems_semaphore_obtain(full_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"full\" acquisition\n");
exit(1);
}

if (rtems_semaphore_obtain(ts_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)


!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"ts\" acquisition\n");
exit(1);
}
if (rtems_semaphore_release(ts_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"ts\" release\n");
exit(1);

15
}
if (rtems_semaphore_obtain(mutex_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"mutex\" acquisition\n");
exit(1);
}
rc++;
if (rc == 1)
if (rtems_semaphore_obtain(db_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"db\" acquisition\n");
exit(1);
}
if (rtems_semaphore_release(mutex_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"mutex\" release\n");
exit(1);
}
}

void exit_reader(void)
{
if (rtems_semaphore_obtain(mutex_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"mutex\" acquisition\n");
exit(1);
}
rc--;
if (rc == 0)
if (rtems_semaphore_release(db_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"db\" release\n");
exit(1);
}
if (rtems_semaphore_release(mutex_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"mutex\" release\n");
exit(1);
}
if (rtems_semaphore_release(empty_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"empty\" release\n");
exit(1);
}

16
void enter_writer(void)
{
if (rtems_semaphore_obtain(empty_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"empty\" acquisition\n");
exit(1);
}

if (rtems_semaphore_obtain(ts_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)


!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"ts\" acquisition\n");
exit(1);
}
if (rtems_semaphore_obtain(db_id, RTEMS_WAIT, RTEMS_NO_TIMEOUT)
!= RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"db\" acquisition\n");
exit(1);
}
}

void exit_writer(void)
{
if (rtems_semaphore_release(full_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"full\" release\n");
exit(1);
}

if (rtems_semaphore_release(db_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"db\" release\n");
exit(1);
}
if (rtems_semaphore_release(ts_id) != RTEMS_SUCCESSFUL)
{
printf("Failure upon semaphore \"ts\" release\n");
exit(1);
}
}

/* configuration information */

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER

17
#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER

#define CONFIGURE_RTEMS_INIT_TASKS_TABLE

#define CONFIGURE_MAXIMUM_TASKS (2*(N_WRITER + N_READER))

#define CONFIGURE_INIT

#include <rtems/confdefs.h>

/* end of file */

18