Authors: | Mario Aldea Rivas | aldeam@unican.es |
Michael González Harbour | mgh@unican.es |
Examples of use:
$ mgcc -g -O2 extra_obj.o my_program.c
$ mgcc use_math.c -lm
$ mgnatmake -gnato my_program.adb
There are some restrictions:
$ mgnatmake extra_obj.o -llibrary my_program.adb
For Ada programs also mgnatbind and mgnatlink
are provided.
When using some Ada or GNAT standard packages from an Ada program an error
message like this could be produced at binding time:
error: "xxx.adb" must be recompiled ("s-osinte.ads" has been modified)
This is due to the fact the application is trying to use one standard package not compiled at MaRTE OS installation (only a part of Gnat Run-Time is compiled by default). Just compile your application with the "-a" flag to allow compilation of any required standard package:
$ mgnatmake <other flags> -a my_program.adb
In order to debug an application it should be compiled with the -g flag.
To synchronize the execution with the remote debugger running in the host
is necessary to add these lines to the application code (possibly at the
very beginning of your program):
Ada program: |
... with Debug_Marte; use Debug_Marte; ... procedure My_Proc is begin ... Debug_Marte.Init_Serial_Communication_With_Gdb (Serial_Port_1); Debug_Marte.Set_Break_Point_Here; Application's code; |
C program: |
... #include <debug_marte.h> ... int main() { ... init_serial_communication_with_gdb (SERIAL_PORT_1); set_break_point_here; Application's code; |
The application executes until the first Debug_Marte.Set_Break_Point_Here or set_break_point_here is reached. At this point the execution is stopped and the target is ready to synchronize with the debugger.
At the MaRTE installation path run gdb:
$ cd /installation/path/marte
$ gdb mprogram
The connection with the target is performed by executing the following "gdb" command:
(gdb) target remote /dev/ttyS0
Where '/dev/ttyS0' is the device file for the serial port 1. After this you should get a message like this:
Remote debugging using /dev/ttyS0
main () at hello_world.c:25
25 printf("\nHello, I'm a C program running on the MaRTE OS.\n\n");
All these steps can be performed at once executing:
$ gdb -x /installation/path/marte/utils/marte_db
When executing gdb as above it is possible to reconnect with the target using the macro connect:
(gdb) connect
For more information look at the GRUB page: http://www.gnu.org/software/grub/.
The netboot program has been taken from the OSKit distribution: http://www.cs.utah.edu/flux/oskit/.
[Note] Also "Etherboot" can be used to boot MaRTE
applications. An Etherboot image can be created at http://rom-o-matic.net/
as explained in the installation guide "INSTALL".
A very easy way of doing this is copying the user's application to the 'boot-floppy' with the name of 'netboot'. By doing this the user's application will be launched directly by GRUB instead the 'netboot' program:
$ mcopy mprogram a:netboot
The label at the GRUB prompt can be changed by editing the file 'boot/grub/menu.lst' in the 'bootfloppy' disk.
Remember that the modified 'boot-floppy' cannot be used any more to boot
applications across the Ethernet. For this purpose you must create it
again using the 'mkbootfloppy' script.
But there are some reasons why you can be interested on recompile MaRTE OS:
$ mkkernel -gnatn -O3 -gnatp
The "-f" flag can be added to force the recompilation of all the kernel,
otherwise only modified and related packages will be compiled.
$ mklibmc -O3
$ mkall -gnatn -O3 -gnatp
Automatically run 'make' in each driver directory containing a 'GNUmakefile'. After that, all the objects files found in drivers directories are copied to the drivers library 'lib/libdrivers.o'. (See also chapter "MaRTE OS devices").
For example, for a minimum size kernel a 'gnat.adc' file with the following pragmas can be used:
pragma Ravenscar;
pragma Restrictions (Max_Tasks => 2); -- Or the number of tasks
of your application
And then everything (application code, MaRTE OS kernel and the part of the Gnat Run-Time required for your application), should be recompiled with the command:
$ mgnatmake -gnatp -O3 -a -f your_application.adb
$ mkkernel -gnata -a -f ("-f" force recompilations)
Enabling "Assert" pragmas can be useful if you are modifying MaRTE OS to check the consistency of your changes.
The kernel also can be configured to display some (lot) debugging messages on console. They can report all relevant events about context switches, mutexes, signals, timed events, etc. This functionality can be useful for kernel developers in some specific situations. In the general case the number of messages displayed is too huge to be analysed, and the use of the debugger will be preferred. To enable the debugging messages edit the file 'kernel/debug_messages.ads', set to true some (or all) the boolean constants below the label "General Messages" and recompile the kernel with assertions enabled:
$ mkkernel -gnata -a -f
MaRTE OS provides a standard method for installing and using device drivers. This method allow programmers to share their drivers with other people in a simple way.
The implemented model is similar what is used in most UNIX-like operating systems. Applications access devices through "device files" using standard file operations (open, close, write, read, ioctl).
The drivers installation in MaRTE OS is explained bellow. The best way of understanding that process is looking at drivers included in MaRTE OS distribution. The simpler examples are "Demo_Driver_C" and "Demo_Driver_Ada".
A driver can be written using both Ada or C programming languages. The code file(s) must be included in a subdirectory of 'drivers/'.
An Ada driver can provide some the following functions (usually all the functions will be provided):
function My_Driver_Create return Int;
function My_Driver_Remove return Int;
function My_Driver_Open (Fd : in File_Descriptor;
Mode : in File_Access_Mode)
return Int;
function My_Driver_Close (Fd : in File_Descriptor) return Int;
function My_Driver_Read (Fd : in File_Descriptor;
Buffer_Ptr : in Buffer_Ac;
Bytes_To_Read : in Unsigned_32)
return Int;
function My_Driver_Write (Fd : in File_Descriptor;
Buffer_Ptr : in Buffer_Ac;
Bytes_To_Write : in Unsigned_32)
return Int;
function My_Driver_Ioctl (Fd : in File_Descriptor;
Request : in Ioctl_Option_Value;
Ioctl_Data_Ptr : in Buffer_Ac)
return Int;
For a C driver the function prototypes are:
int my_driver_create (int arg);
int my_driver_remove ();
int my_driver_open (int file_descriptor, int file_access_mode);
int my_driver_close (int file_descriptor);
ssize_t my_driver_read (int file_descriptor, void *buffer, size_t bytes);
ssize_t my_driver_write (int file_descriptor, void *buffer, size_t bytes);
int my_driver_ioctl (int file_descriptor, int request, void* argp);
For C drivers a specification Ada file must be written to make accessible
the C functions to MaRTE kernel. To write that file you can use drivers/demo_driver_c/demo_driver_c_import.ads
as a template. Also a GNUmakefile file should be included
in driver's directory since mkdrivers will automatically
run make in each driver directory containing a GNUmakefile.
After that, all the objects files found in drivers directories are copied
to the drivers library lib/libdrivers.o.
Drivers and device files included in the system are registered in kernel/k-devices_table.ads
file. In this file there are two tables:
In order to include a new driver add and entry to The_Driver_Table, choosing an unused number. That number will be the major number that identifies your driver in the system. (You can take as example the commented entry for "Demo_Driver_Ada" or "Demo_Driver_C").
Now you must add unless one device file in The_Device_Files_Table. The name of your device file can be whatever you want ("/dev" prefix is recommended for analogy with UNIX systems).
As mayor number should be chosen the one previously associated with your driver. As minor number can be used whatever you want, this number is useful to distinguish between different files associated with the same major number.
In the field Ceiling you can put any value within the
priorities range used in the system (usually between 0 and 31). To know
more about this field read "Mutual exclusion among threads using the same
file descriptor".
A few special things is necessary to do in case you want to change one of the standard input, output or error devices.
Since we want to use these devices from the very beginning of an application execution, even before the file system has been properly setup, some direct hooks to basic functions of these devices have to be provided in file kernel/kernel_console.ads.
If you are changing the standard input device, you should modify the following "hook" in kernel/kernel_console.ads:
If you are changing the standard output device, you should modify the following "hooks" in kernel/kernel_console.ads:
If you are changing the standard error device, you should modify the following "hooks" in kernel/kernel_console.ads:
There is an important restriction for interrupt handlers code: "they should not call any potentially blocking operation". This includes writing directly to stderr or stdout devices because a mutex is used to protect every driver in MaRTE OS. So, interrupt handlers are not allowed to use standard input/output functions as printf or the package Ada.Text_IO.
As alternative, C interrupt handlers must use printc (to write on console) and printe (to write on standard error). These functions write directly in the devices without passing through the file system. For Ada drivers package Kernel_Console must be used instead of Ada.Text_IO.
Apart from the explained before for interrupt handlers, there is no restrictions for code to be used inside drivers, so they could use any standard POSIX or Ada interfaces. Anyway, it is recommended for Ada drivers not to use the "POSIX.Ada" interface. There are two reasons for that:
To avoid this dependency MaRTE OS provides two packages: Marte_Hardware_Interrupts and Marte_Semaphores to be used inside drivers instead of their POSIX equivalents (POSIX_Hardware_Interrupts and POSIX_Semaphores).
Each driver with files not written in Ada should include in its directory a 'GNUmakefile' with the rules to compile those files.
MaRTE utilities 'mkkernel' and 'mkdrivers' automatically run 'make' in each driver directory containing a 'GNUmakefile'. After that, all the objects files found in drivers directories are copied to the drivers library 'lib/libdrivers.a'.
'mkkernel', apart from calling 'mkdrivers', makes all MaRTE OS kernel. Then it must be executed after changing drivers and device files definition tables in 'kernel/k-devices_table.ads'.
So, the general way of keeping everything updated after a change in device drivers code or definition tables is executing 'mkkernel' (see also chapter "Compiling MaRTE kernel and libraries"). However, if you have only changed your driver code (without touching 'kernel/k-devices_table.ads') running 'mkdrivers' would be enough.
It is important to notice that, when using Ada drivers from Ada applications, 'mgnatmake' will do all the work (it is not necessary to execute 'mkkernel' nor 'mkdrivers'). This is because all dependencies in an Ada application are known at compilation time (the Ada library records that information), and then, necessary compilations will be performed assuring the application consistency.
A different mutex is associated with every file descriptor. It is a "Priority Protection Protocol" mutex with the ceiling assigned to the device file in 'The_Device_Files_Table'.
A thread will take the mutex before performing some action on a file. So mutual exclusion is automatically achieved among threads using the same file descriptor to access a file.
It is important to notice there is no mutual exclusion among threads using different file descriptors (obtained in different calls to 'open' on the same device file). This fact can be useful, for example, in devices that allows simultaneous write and read operations without any kind of synchronization between them.
MaRTE OS provides some operations to be specifically used inside drivers. These operations are provided by Ada package 'Drivers_Marte':
package Drivers_MaRTE is
...
procedure Get_Major_Number (Fd : in File_Descriptor;
Mj : out Major;
Invalid_Fd : out Boolean);
-- Gets major number from a file descriptor
procedure Get_Minor_Number (Fd : in File_Descriptor;
Mn : out Minor;
Invalid_Fd : out Boolean);
-- Gets major minor from a file descriptor
procedure Get_Mutex_Descriptor
(Fd : in File_Descriptor;
Md : out Kernel.Mutexes.Mutex_Descriptor;
Invalid_Fd : out Boolean);
-- Gets descriptor of mutex associated with a file descriptor
procedure Set_POSIX_Error (Error : in Kernel.Error_Code);
-- Sets the value of the POSIX error for the calling task.
-- Useful to return a specific value of POSIX error from
-- a function of the driver.
function Get_POSIX_Error return Kernel.Error_Code;
...
end Drivers_MaRTE;
For drivers written in C the headers file 'include/drivers/drivers_marte.h'
with the following prototypes:
/* Gets the major number
* Returns -1 for an invalid file descriptor */
int get_major (int filedes);
/* Gets the minor number
* Returns -1 for an invalid file descriptor */
int get_minor (int filedes);
/* Gets the mutex associated with the file descriptor
* Returns NULL for an invalid file descriptor */
pthread_mutex_t *get_mutex (int filedes);
Device drivers framework was a result of Francisco Guerreira's Master
Degree Project.
The scheduling information is parsed and transformed into a set of files that can serve as input for 'gnuplot', a plotting program with which the information is displayed graphically.
In order to use the 'tasks_inspector' tool first the kernel have to be recompiled with the boolean constant 'Tasks_Inspector_Messages' set to 'True' (file 'kernel/debug_messages.ads'). The serial port used to communicate the host and target computers can be configured with the 'Tasks_Inspector_Serial_Port' parameter in the same file. After changing these parameters the kernel should be recompiled executing:
$ mkkernel -gnata -a -f
Using 'tasks_inspector' involves the following steps:
A new "xterm" window will appear displaying a message like this:
Getting scheduling information from the serial port... (finish with Ctrl-C)
A less intrusive way of analysing the scheduling behaviour of an application is using the MaRTE OS implementation of the POSIX Trace standard done by Agustín Espinosa (Universidad Politécnica de Valencia). A patch is available at MaRTE OS home page (currently only for version 0.86).
The configuration parameters are defined as constants in the file kernel/configuration_parameters.ads. Below each parameter there is a short description of its meaning.
For example the maximum number of tasks (or threads) that is possible
to create is set by constant Num_User_Tasks_Mx (10 by
default). If you want to create more tasks just change this value and recompile
the kernel with the command:
$ mkkernel -a -gnatn -gnatp -O3
(or whatever other compiler options you want)
It is important to notice that GNAT uses about 12Kb of dynamic memory per
Ada task. So, as you increment the number of tasks it is necessary to increment
the size of the dynamic memory pool. To do that, edit the file kernel/configuration_parameters.ads
and change the value assigned to constant Dynamic_Memory_Pool_Size_In_Bytes
(300Kb by default).
In the directories 'misc/' and 'include/misc/' there are some extra
utilities that although they do not belong to the kernel or the POSIX interface
can be useful for some applications:
Files 'misc/console_management.ads', 'misc/console_management.adb' and
'include/misc/console_management.h'.
Files 'misc/execution_load.ads', 'misc/execution_load.adb', 'misc/load.c'
and 'include/misc/load.h'.
Files 'misc/serial_console.ads', 'misc/serial_console.adb' and 'include/misc/serial_console.h'.
|
|
|
|