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
For Ada programs also mgnatbind is provided.
$ mgnatmake -gnata -Imarte_src_dirs my_program.adb
(using -Imarte_src_dirs is equivalent to -aI$MPATH/kernel -aI$MPATH/sll -aI$MPATH/arch/hwi -aI$MPATH/misc -aI$MPATH/posix5 -aIall_the_drivers_directories -aO$MPATH/lib)
A cross development environment uses two computers: the host and the target. The host computer is a PC with Linux, GNAT and MaRTE_OS, where we write and make the application. The target is the computer where the application will be executed. As target MaRTE OS only requires a PC with a 386 processor or above and a boot device: floppy, hard disk, Flash-RAM, PXE, ...
Usualy the application will be transferred from the host to the target across Ethernet. Probably the easiest way of doing it is using a floppy disk and "Etherboot" (for more information visit "MaRTE OS Boot process (x86 architecture)" it also describes other booting alternatives involving hard disk, Flash-RAM, PXE, etc.).
It can also be used and emulator to emulate a real target computer. More information about this topic in the documentation section of the MaRTE OS web page.
[Note] "Etherboot": http://www.etherboot.org/. An Etherboot image can be created at http://rom-o-matic.net/
as explained in the installation guide "INSTALL".
[Note] "MaRTE OS Boot process (x86 architecture)": MaRTE OS Boot process (x86 architecture) in the documentation section of the MaRTE OS web page.
[Note] "Hello MaRTE OS using an emulator": Hello MaRTE OS using an emulator in the documentation section of the MaRTE OS web page.
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:
$ 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
the x86 architecture if you are using the QEMU emulator as a target PC
you can use its built-in debugging capabilities. (More in the tutorials
section):
$ qemu -kernel marte_program -s -S &
$ ddd &
(gdb) target remote localhost:1234
(gdb) break main
(gdb) cont
$ cd $HOME/marte
$ mgnatmake -g hello_world.adb -o mprogram
or$ mgcc -g hello_world_c.c -o mprogram
$ gdb mprogram
A very easy way of doing this is using the "grup-floppy image" provided in the download section of the MaRTE OS web page. In order to create a grup floppy, untar the file and copy the disk image to a floppy:
$ tar zxf grubfloppy.img.tgz
$ dd if=grubfloppy.img of=/dev/fd0
Then, every time you want to run a MaRTE OS application copy the mprogram to the floppy:
$ mcopy my_program a:mprogram
Finally insert the floppy in your target computer and reset it.
Remember the MaRTE application (mprogram) must fit in the disk (< 1.4 Mb).
Information about how to use other boot devices in: "MaRTE OS Boot process (x86 architecture)": MaRTE OS Boot process (x86 architecture) in the documentation section of the MaRTE OS web page.
But there are some reasons why you can be interested on recompile MaRTE OS:
This script can also be used to change the current architecture:
$ msetcurrentarch linux
or
$ msetcurrentarch linux_lib
or
$ msetcurrentarch x86 [i386 | pi | pii]
The "processor" flag allows to specify the processor to be used in the x86 architecture:
$ 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.
The -compile_only_kernel flag can be added to speed up compilation process when you don't need to compile the drivers nor the libmc.a.
$ mklibmc -O3
Automatically run 'make' in each driver directory containing a 'GNUmakefile'. After that, all the objects files found in drivers directories are moved to the objects library lib/. (See also chapter "MaRTE OS devices").
$ mkrtsmarteuc -gnato -g
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 recompile MaRTE kernel with the command:
$ mkmarte -gnatp -O3
The size of the final executable can be reduced by stripping all symbols that are not needed for relocation processing using the command:
$ objcopy -O elf32-i386 --strip-unneeded mprogram
$ mkmarte -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/marte-debug_messages.ads', set to true some (or all) the boolean constants below the label "General Messages" and recompile the kernel with assertions enabled:
$ mkmarte -gnata
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/marte-kernel-devices_table.ads'.
So, the general way of keeping everything updated after a change in device drivers code or definition tables is executing 'mkmarte' (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 'mkmarte' 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.
It is important to notice that, by default, there is no mutual exclusion among threads using the same device. 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'. For more information about that functions read the source file misc/divers_marte.ads.
For drivers written in C exists the headers file include/drivers/drivers_marte.h.
This tool (in experimental phase) allows analysing the execution flow of an application. The relevant scheduling information can be stored or sent from the target to the host computer, to be analysed after the application execution finishes.
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 boolean constant 'Tasks_Inspector_Messages' must be set to 'True' (file 'kernel/marte-debug_messages.ads') and then the kernel have to be recompiled:
$ mkmarte -gnata
In Linux_Lib architecture, the scheduling information always is stored in the file 'trace_marte.dat', but in x86 architecture it can be chossen between differnt mechanisms to send the information from the target to the host:
In x86 architecture also the logger mechanism can be specified:
Also some specific services can be enabled or disabled to improve kernel performance or footprint. If you want to change the default values the kernel have to be recompiled with the new ones.
The configuration parameters are defined as constants in the file kernel/marte-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 (20 by
default in x86 architecture). If you want to create more tasks just change this value and recompile
the kernel using the mkmarte script:
$ mkmarte -gnatn -gnatp -O3 (or whatever other compiler options you want)
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'.
In Ada there are two versions of this package:
Files
'misc/execution_load_loop.ads', 'misc/execution_load_loop.adb',
'misc/execution_load.ads', 'misc/execution_load.adb', 'misc/load.c' and
'include/misc/load.h'.
Allows applications to change the output system console between the monitor and the serial line.
If you are using QEMU emulator you can redirect again the serial port to stdio to read the messages in the terminal (with scrolling!) with the followin flag: "-serial stdio"
$ qemu -kernel marte_program -serial stdio
Files 'misc/console_switcher.ads' and 'include/drivers/console_switcher.h'.
|
|
|
|