Applicazioni realtime con Linux

RTLinux

In alcune discussioni mi fu chiesto cosa si intendono per programmazione realtime o sistemi operativi realtime. Realtime viene, spesso, confuso con interattivo. Il realtime interrompe il processo corrente per passare le risorse fondamentali ad un processo con priorità più alta. Rimane, tuttavia, allo sviluppatore il compito di creare il codice realtime.

RTLinux è un kernel realtime che coesiste con il kernel di Linux. Con esso è possibile creare thread in standard POSIX.1b.

I thread sono anche nominati processi leggeri (lightweight process). Condividono lo stesso spazio di indirizzamento; a differenza dei processi, i quali allocano tutte le risorse necessarie per essere eseguiti.

Lo scopo principale, per cui sono scritti i sistemi realtime come RTLinux, sta nella possibilità di dedicare più operazioni a processi critici. Questo approccio non è garantito nei sistemi non real time.

Un "kernel linux" assegna ad ogni task a livello utente, un tempo limite di esecuzione dopo il quale le risorse vengono assegnate ad un altro task e così via. Normalmente questo non crea problemi: navigo con Mozilla, scrivo con StarOffice, scarico con gtm. Se però sto acquisendo dati con una scheda, questo approccio non garantisce in primo luogo:

  1. che tutti i segnali siano acquisiti regolarmente;

  2. in secondo luogo che la CPU risolva e calcoli algoritmi di codifica e decodifca applicati al segnale.

RTLinux si infila tra il "kernel Linux" e l'hardware del calcolatore (il codice realtime gira nello spazio kernel, non in quello utente). Modifica lo scheduling dei processi sostituendolo con uno a priorità fissa, assegnando priorità minima al "kernel linux" che di fatto è un task indipendente. In figura 1 e 2 si possono notare le differenze tra le due tipologie di kernel rispettivamente non realtime e realtime.













RTLinux intercetta tutti gli interrupt hardware e maschera quelli diretti al "kernel Linux" che diventano interrupt software (a bassa priorità).

Le applicazioni RTLinux sono moduli del kernel che vengono caricati in memoria nello spazio kernel (kernel space); i processi RTLinux non fanno uso di memoria virtuale (il processo è bloccato nella memoria fisica) ed hanno accesso diretto all'hardware.

Installazione di RTLinux

I prerequisiti per l'installazione sono una distribuzione Linux installata nel vostro Hard Disk ed un kernel versione 2.4.4. Il kernel è scaricabile all'indirizzo ftp://ftp.kernel.org. Il pacchetto RTLinux è scaricabile all'indirizzo http://www.fsmlabs.com. La versione testata è RTLinux V3.1.

A questo punto si procede come segue:

Creare la directory /usr/src/rtlinux:

linux:~ # mkdir /usr/src/rtlinux

Copiare e decomprimere l'archivio compresso del kernel:

linux:/usr/src/rtlinux # tar xpjf linux-2.4.4.tar.bz2

Copiare e decomprimere l'archivio compresso di RTLinux:

linux:/usr/src/rtlinux # tar xpzf rtlinux-3.1.tar.gz

Se c'è spazio sufficiente su disco fisso vengono create due directory la prima di nome linux e la seconda di nome rtlinux-3.1. Creare un link simbolico nella directory rtlinux-3.1 alla directory del kernel.

linux:/usr/src/rtlinux/rtlinux-3.1 # ln -sf ../linux .

Configurare e compilare il nuovo kernel non prima di averlo aggiornato con la patch corretta.

linux:/usr/src/rtlinux/rtlinux-3.1/linux # patch -p1 < ../kernel_patch-2.4.4

E’ presente anche una patch per il kernel 2.2.19. Configurare il kernel (non vi sono voci riguardanti il realtime). Compilare il kernel e gli eventuali moduli; quindi copiarlo nella directory /boot. Consiglio di assegnarli un nome appropriato pena la sostituzione di quello preesistente. In sintesi:

linux:~ # make menuconfig
linux:~ # make dep; make bzImage; make modules; make modules_install
linux:~ # cp arch/i386/boot/bzImage /boot/vmlinuz-2.4.4-rtl

Per chi non ha familiarità con queste procedure consiglio, di consultare gli “Appunti di Informatica Libera” di Daniele Giacomini (http://linuxdidattica.org) o ricercare la documentazione presente in Internet. Configurare LiLO o GRUB (dipende dalla vostra distribuzione, verificatelo prima di procedere) e riavviare il computer.

La versione del kernel fin qui utilizzata è un po' datata e tutte le distribuzioni uscite nell'ultimo anno contengono un kernel più recente ma soprattutto che supporta una nutrita famiglia di file system e di dispositivi hardware.

Verificare che almeno la partizione di root sia formattata con uno dei filesystem supportati dal kernel 2.4.4 pena un messaggio di kernel panic. Anche in stabilità si possono verificare gravi problemi. Durante i miei test ho notato un blocco del computer se tentavo di accedere a device USB tipo stampante, modem e dischi esterni. Problema che non si è verificato con il mouse sempre USB. Ugualmente si possono verificare inconvenienti con schede SCSI o altre periferiche.

Nei passi successivi si installa RTLinux e i suo moduli:

linux:~ # cd /usr/src/rtlinux/rtlinux-3.1
linux:~ # make menuconfig
linux:~ # make install

Anche per RTLinux 'make menuconfig' può venir sostituita con 'make config' o 'make xconfig'. Lasciare invariate le opzioni, quelle di default garantiscono un corretto funzionamento. I più smaliziati possono cimentarsi in coraggiosi esperimenti.

Nella directory /usr/rtlinux vengono copiati i file di supporto ed i file oggetto. Il file rtl.mk contiene le variabili per una corretta compilazione dei vostri moduli. Da utilizzare con il comando 'make'.

Nella sottodirectory examples vi sono degli esempi per testare le capacità realtime del kernel, non prima però di avvere avviato RTLinux.

Avvio di RTLinux

RTLinux viene caricato e scaricato in o dalla memoria con lo script 'rtlinux'.

linux:~ # rtlinux start
Scheme: (-) not loaded, (+) loaded
	(+) mbuff
	(+) rtl
	(+) rtl_fifo
	(+) rtl_posixio
	(+) rtl_sched
	(+) rtl_time

Se non ci sono errori tutti i moduli di base vengono caricati (controllare il contenuto della directory /usr/rtlinux/modules). Questi moduli servono anche per poter sfruttare i FIFO (rtl_fifo e rtl_posixio) come la memoria condivisa (mbuff).

linux:~ # rtlinux stop

Come prima, al posto dei segni (+) compaiono tanti segni (-). Lo stato di avvio dei moduli viene controllato con 'rtlinux status'.

Le API di RTLinux

Le API (Application Programmin Interface) sono una rosa di funzioni pensate per un ogni ambiente di sviluppo. Le API più conosciute sono quelle grafiche, GTK+ come le QT sono delle API per gestire l'interfaccia grafica.

I task RTLinux sono moduli che funzionano in kernel space per questo alla classica istruzione int main () dobbiamo sostituire la coppia int init_module () e void cleanup_module (). La prima funzione carica il modulo in memoria la seconda lo elimina da essa. I programmi che operano su questi file oggetto sono 'insmod <modulo>' e 'rmmod <modulo>'.

RTLinux è progettato per distribuire in modo efficiente il lavoro di più thread. Ogni thread occupa lo stesso spazio di indirizzi e condivide le risorse con gli altri thread lanciati dal processo padre. Alle funzioni di base per creare e modificare i thread ne vengono aggiunte altre. Il controllo dei thread ora è esteso anche nello scheduling ad intervalli prefissati.

La comunicazioni fra thread avviene con meccanici di tipo IPC (Inter-Process Communication) come: Mutex, memoria condivisa, realtime FIFO, Semafori.

Test di RTLinux

In Listato 1 il codice in linguaggio "C" di una applicazione RTLinux che stampa la scritta "Hello, world!".

Per sfruttare le risorse del kernel RTLinux bisogna scrivere il codice sorgente come un modulo del kernel. Il file sorgente è mostrato nel Listato 1 (file hello_world_rtl.c). In questo caso è propedeutica la programmazione con thread.

Per compilare copiare il file rtl.mk nella directory contenente il codice sorgente, supponiamo che il percorso sia /root/rtl:

linux:/root/rtl # cp /usr/rtlinux/rtl.mk Makefile

Modificare il file aggiungendo in coda le righe seguenti:

# Makefile
SOURCES = hello_world_rtl.c
OBJECTS = $(SOURCES:.c=.o)

all	:	$(OBJECTS)

%.o : %.c
	$(CC) $(CFLAGS) $(INCLUDE) $(LIBS) -c $< -o $@

clean :
	rm -f *.o

Compilare digitando il comando 'make':

linux:~ # make

Per avviare il codice digitare:

linux:~ # rtlinux start hello_world_rtl

Analogamente è possibili digitare il comando 'insmod'. Per scaricare il codice oggetto digitare:

linux:~ # rtlinux stop hello_world



/*
 * Listato 1
 * hello_world_rtl.c
 */
#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <rtl.h>
#include <time.h>
#include <pthread.h>
#include <linux/module.h>
#include <linux/errno.h>

pthread_t t;
pthread_t s;

static char *msg [] = {"Hello, ", "World!\n", NULL};

void *print_func (void *);

int init_module(void) {
	pthread_create (&t, NULL, print_func, (void *) msg [0]);
	pthread_create (&s, NULL, print_func, (void *) msg [1]);

	return 0;
}

void cleanup_module(void) {
	pthread_delete_np (t);
	pthread_delete_np (s);
}

void * print_func(void *msg)
{
	rtl_printf ("%s", (char *)msg);
}

Per visualizzare l'output fornito da questo modulo è necessario utilizzare il comando 'dmesg' oppure controllare dove il kernel invia i messaggi di log. Il demone che si incarica di questo è 'syslogd' ed il file di configurazione è /etc/syslog.conf. E' preferibile modificare quest'ultimo file in modo che i messaggi siano reinderizzati direttamente in console oppure in xconsole se si programma con X window. La funzione pthread_delete_np (non portable) elimina i thread in esecuzione quando il modulo viene rimosso. La funzione rtl_printf viene utilizzata in sostituzione della printk, in quanto più sicura per la programmazione con thread.

IPC

Terminiamo con due programmi molto semplici. Il primo in codice realtime (il codice sorgente si trova in Listato 3) scrive su un FIFO i numeri interi da 0 a 9 in ordine crescente. I device FIFO (/dev/rtf0.../dev/rtf63) vengono creati automaticamente durante l'installazione di RTLinux. Il secondo programma è una applicazione Linux a tutti gli effetti la quale apre un device FIFO e ne legge il contenuto in uscita. Questo sistema è un esempio di intercomunicazione tra processi. Oltre ai FIFO, che sono unidirezionali, è possibile utilizzare oggetti Mutex, memoria condivisa e semafori.

Per compilare l'applicazione Linux utilizzare il comando seguente:

linux:~ # gcc -Wall -o app_fifo app_fifo.c

Conclusioni

Sono realmente soddisfatto della semplicità con cui è possibile creare applicazioni realtime con RTLinux. Gli esempi qui riportati sono molto semplici, tuttavia offrono un approccio che richiede poco tempo per essere subito operativi e stimolati per provare questo interessante prodotto. Ulteriori informazioni sono reperibili al sito ufficiale di RTLinux http://www.fsmlabs.com. Oltre alla versione Open Source ne esiste una professional con un costo iniziale di poco superiore al milione di lire.

/*
 * Listato 3
 * app_fifo.c
 */
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <rtl_fifo.h>

int main () {
	int i, n, fifo;

	if ((fifo = open ("/dev/rtf0", O_RDONLY)) == -1) {
		printf ("Errore nell'apertura del fifo.\n");
		exit (-1);
	}

	while (1) {
		if ((n = read (fifo, &i, sizeof (int))) == -1) {
			printf ("Errore nella lettura del fifo.\n");
			exit (-1);
		}

		printf ("Letto valore %d\n", i);
	}

 	return 0;
}



/* Listato 2
 * fifo.c
 */
#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#include <rtl.h>
#include <rtl_fifo.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <linux/module.h>
#include <linux/errno.h>

#define SIZE_FIFO 256 /* buffer di 256 byte */

static pthread_t t;
static int fifo;

void * fifo_func (void *);

int init_module(void) {
	rtf_create (0, SIZE_FIFO); /* crea il FIFO */

	return pthread_create (&t, NULL,fifo_func, NULL);
}

void cleanup_module(void) {
	pthread_delete_np (t);
	close (fifo);
	rtf_destroy (0); /* elimina il FIFO */
}

void * fifo_func(void *msg)
{
	int i = 0;
	struct sched_param p;

	p.sched_priority = 5; /* imposta la priorita’ del processo */
	pthread_setschedparam (t, SCHED_FIFO, &p);
	pthread_make_periodic_np (t, gethrtime (), 500000000); /* esegui 2 volte al secondo */

	fifo = open (“/dev/rtf0”, O_NONBLOCK);

	while (1) {
		pthread_wait_np ();

		if (i > 9) {
			i = 0;
		}

		write (0, &i, sizeof (int)); /* scrive nel FIFO */
		i++;
	}
}
Umberto Zanatta