Questa dispensa vuole illustrare alcuni aspetti che riguardano l’ambiente di sviluppo in un sistema UNIX in generale e GNU/Linux in particolare, in modo da presentare al lettore diverse soluzioni e/o spunti di approfondimento.
Si presenteranno non solo gli strumenti di sviluppo fondamentali ma anche alcuni suggerimenti sui sistemi di aiuto alla programmazione (e non solo), sulla documentazione e il reperimento delle informazione e su alcune tecniche di debugging.
Gli strumenti di aiuto
Quando abbiamo a che fare con un sistema complesso come un sistema UNIX è fondamentale conoscere e saper utilizzare gli strumenti di aiuto, questo perché tali strumenti ci permettono di avere informazioni su:
- i comandi di sistema disponibili;
- i file di configurazione dei vari programmi installati;
- i prototipi e il funzionamento delle funzioni (soprattutto quelle in C);
- ecc.
In ogni sistema UNIX è presente il comando man, con il quale è possibile avere una serie di informazioni riguardanti l’utilizzo di tutti i comandi, la sintassi e le opzioni dei file di configurazione del sistema, i prototipi e la semantica delle funzioni di programmazione per più linguaggi, ecc..
Riporto un esempio dell’output del comando:
$ man man
MAN(1) Manual pager utils MAN(1)
NAME
man - an interface to the on-line reference manuals
SYNOPSIS
man [-C file] [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L
locale] [-m system[,...]] [-M path] [-S list] [-e extension] [-i|-I]
[--regex|--wildcard] [--names-only] [-a] [-u] [-P pager] [-r prompt]
[-7] [-E encoding] [--no-hyphenation] [-p string] [-t] [-T[device]]
[-H[browser]] [-X[dpi]] [-Z] [[section] page ...] ...
man -k [apropos options] regexp ...
man -f [whatis options] page ...
man -l [-C file] [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L
locale] [-P pager] [-r prompt] [-7] [-E encoding] [-p string] [-t]
[-T[device]] [-H[browser]] [-X[dpi]] [-Z] file ...
man -w|-W [-C file] [-d] [-D] page ...
man -c [-C file] [-d] [-D] page ...
man [-hV]
DESCRIPTION
man is the system’s manual pager. Each page argument given to man is
normally the name of a program, utility or function. The manual page
associated with each of these arguments is then found and displayed. A
section, if provided, will direct man to look only in that section of
the manual. The default action is to search in all of the available
sections, following a pre-defined order and to show only the first page
found, even if page exists in several sections.
Nei sistemi GNU/Linux esiste però anche un altro comando per ottenere aiuto, e molto più potente di man: il comando info. Con info è possibile avere on-line il manuale di tutta una serie di pacchetti software a cominciare da gcc, make, gdb, ld, ecc.. In pratica il manuale del prodotto software è distribuito insieme ad esso in forma elettronica.
In più se abbiamo il sorgente texinfo del nostro manuale (e, di solito, questo viene sempre fatto nel mondo del software libero) possiamo ottenerne anche una in formato HTML, PDF, PS, ecc., quindi è possibile perfino stamparlo.
Documentazione
Purtroppo (o per fortuna) non esiste molta documentazione sulla programmazione all’interno del kernel in rete, ecco perché il mezzo migliore per capire come funzionano le cose in Linux è di leggerne il codice sorgente!
Use the source, Luke!
Esistono comunque, per chi si trova in difficoltà o non ha tempo di studiarsi il codice da solo, una serie di risorse alternative:
- Siti in rete: LWN.net, LKML.org
- Libri specilistici: Linux Device Driver, Understanding the Linux Kernel, Linux Kernel in a Nutshell
- Consulenti specializzati.
Â
I tool di compilazione
Il compilatore
Il codice di Linux è scritto in C, quindi per poterci lavorare occorre un compilatore, Ie il compilatore utilizzato è il gcc (GNU C compiler).
Non so se il codice di Linux possa essere compilato anche con altri compilatori (personalmente non ne sono a conoscenza), ma poiché il codice utilizza molte delle caratteristiche specifiche del gcc e quest’ultimo è pure reperibile gratuitamente in rete, di fatto il gcc è il solo compilatore che conviene utilizzare (per non parlare poi della sue qualità ).
Come è noto un compilatore C è formato da più componenti (cpp, cc1, eventualmente as e lo stesso ld), e gcc non è diverso, e alle volte può essere utile arrestare la compilazione subito dopo l’esecuzione di uno di questi stadi intermedi per esigenze di debugging. Per far questo, direttamente dalle pagine di man del gcc si legge:
-c Compile or assemble the source files, but do not
link. The compiler output is an object file corre-
 sponding to each source file.
-S Stop after the stage of compilation proper; do not
assemble. The output is an assembler code file for
each non-assembler input file specified.
-E Stop after the preprocessing stage; do not run the
compiler proper. The output is preprocessed source
code, which is sent to the standard output.
-dM Tell the preprocessor to output only a list of the
macro definitions that are in effect at the end of
preprocessing. Used with the `-E' option.
Il linker
Come intuibile (e già accennato) il linker utilizzato è ld (GNU linker). Verrà utilizzato per collegare (linkare) tra loro i vari moduli che compongono il nostro codice di nucleo.
Il linker, però, lo si può utilizzare anche insieme a speciali direttive (chiamate ld-script) per fare in modo di rilocare a piacere il codice di nucleo. Direttamente dalle pagine di info di ld:
The main purpose of the linker script is to describe how the
sections in the input files should be mapped into the output file, and
to control the memory layout of the output file. Most linker scripts
do nothing more than this. However, when necessary, the linker script
can also direct the linker to perform many other operations, using the
commands described below.
The linker always uses a linker script. If you do not supply one
yourself, the linker will use a default script that is compiled into the
linker executable. You can use the `--verbose' command line option to
display the default linker script. Certain command line options, such
as `-r' or `-N', will affect the default linker script.
make e i makefile
Il tool di gestione della compilazione utilizzato è make (GNU make).
Senza voler entrare nel dettaglio di funzionamento di make, ci basti sapere che questo tool ci aiuta nella gestione della fase di compilazione. In particolare si occuperà lui di dare icomandi giusti, e nella sequenza corretta, affinché il compilatore e il linker generino il codice corretto.
In particolare ci permetterà di gestire facilmente ed in maniera efficiente i parametri di configurazione della nostra applicazione. Quando si realizza una nuova applicazione che (si spera) andrà a far parte del codice standard di Linux, questo strumento risulta allora fondamentale e dovrà inoltre essere configurato insieme all’applicazione di configurazione del kernel chiamata menuconfig (il nome non e proprio quello ma passatemi il termine n.d.a.).
Questa applicazione riesce a determinare il come un certo progetto (insieme di file) va compilato a seconda delle istruzioni che gli vengono impartite in file speciali denominati makefile. In un makefile (che di solito si chiama Makefile) si specificano le dipendenze tra i vari file che compongono il progetto e i comandi da eseguire per ottenere il risultato finale. Direttamente dalle pagine di info di make:
The GNU `make' utility automatically determines which pieces of a
large program need to be recompiled, and issues the commands to
recompile them.
Riporto di seguito un semplice makefile per compilare un modulo del nucleo:
TARGET = module.o
CFLAGS += -Wall -O -D__KERNEL__ -DMODULE
$(TARGET) : $(TARGET:.o=.c)
$(CC) $(CFLAGS) -c $^
Un makefile può anche essere molto più complesso se gli si richiedono di eseguire diverse operazioni (come ad esempio eseguire delle operazioni di installazione del codice appena compilato o di ripulire il progetto dai file generati dal compilatore); ad esempio con il seguente makefile:
TARGET = timer.o
KERNELDIR = /usr/src/linux
INCLUDEDIR = $(KERNELDIR)/include
override CFLAGS += -Wall -O -D__KERNEL__ -DMODULE -I$(INCLUDEDIR)
$(TARGET) : $(TARGET:.o=.c)
$(CC) $(CFLAGS) -c $^
.PHONY : clean
clean :
rm -f $(TARGET)
si può usare il comando seguente per generare un codice oggetto:
$ make KERNELDIR=/usr/src/linux-2.4.18
cc -Wall -O -I/usr/src/linux-2.4.18/include -D__KERNEL__ -DMODULE -c timer.c
E questo per cancellare il file appena generato, ripulendo così la directory del progetto per una eventuale distribuzione del pacchetto software;
$ make clean
rm -f timer.o
Se vogliamo invece produrre del codice che si (spera) verrà inserito nella distribuzione standard del codice di Linux, occorre allora modificare non solo il/i Makefile del kernel ma anche il processo di configurazione del kernel stesso (menuconfig), modificando i file Kconfig (nel 2.4 si chiamavano Kconfig.in).
I file Kconfig vengono poi letti dai vari comandi di configurazione standard del nucleo:
- make config – solo testo
- make menuconfig – solo testo ma utilizzando la libreria ncurses
- make xconfig – grafico
Riporto di seguito un esempio di file Kconfig:
#
# PPS support configuration
#
menu "PPS support"
config PPS
tristate "PPS support"
depends on EXPERIMENTAL
help
PPS (Pulse Per Second) is a special pulse provided by some GPS
antennae. Userland can use it to get a high-precision time
reference.
Some antennae's PPS signals are connected with the CD (Carrier
Detect) pin of the serial line they use to communicate with the
host. In this case use the SERIAL_LINE client support.
Some antennae's PPS signals are connected with some special host
inputs so you have to enable the corresponding client support.
To compile this driver as a module, choose M here: the module
will be called pps_core.ko.
config PPS_DEBUG
bool "PPS debugging messages"
depends on PPS
help
Say Y here if you want the PPS support to produce a bunch of debug
messages to the system log. Select this if you are having a
problem with PPS support and want to see more of what is going on.
source drivers/pps/clients/Kconfig
endmenu
I Makefile del kernel 2.6 sono stati molto potenziati rispetto a quelli del 2.4 in modo da semplificare al massimo la definzione delle operazioni di compilazione dei vari sottosistemi/moduli del kernel; il tutto poi in modo del tutto trasparente al programmatore. Ad esempio la definizione di file oggetto composti da più di un file sorgente è molto semplificata così come lo è la definizione di eventuali flag di compilazione:
#
# Makefile for the PPS core.
#
pps_core-y := pps.o kapi.o sysfs.o
obj-$(CONFIG_PPS) := pps_core.o
obj-y += clients/
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
Il debugging
strace
Per le operazioni di debugging si possono scegliere diversi strumenti che operano nello spazio utente, come il gdb (The GNU Debugger) e/o strace. Esistono poi anche altre tecniche di debugging come, ad esempio, l’uso delle printk() e del proc filesystem ma che non vedremo ora.
L’uso del gdb è poco usato (almeno io non lo reputo molto efficiente e portabile), mentre l’uso di strace è decisamente più versatile. Ma cosa è strace? Non è altro che una applicazione che permette di vedere tutte le chiamate di sistema che un processo fa durante la sua esecuzione. Risulta allora evidente come esso sia molto utile per testare le varie chiamate di sistema che, scrivendo ad esempio un driver, dovremo necessariamente implementare. Dalle pagine di man di strace:
STRACE(1) STRACE(1)
NAME
strace - trace system calls and signals
SYNOPSIS
strace [ -dffhiqrtttTvxx ] [ -acolumn ] [ -eexpr ] ... [ -ofile ] [
-ppid ] ... [ -sstrsize ] [ -uusername ] [ -Evar=val ] ... [ -Evar ]
... [ command [ arg ... ] ]
strace -c [ -eexpr ] ... [ -Ooverhead ] [ -Ssortby ] [ command [ arg
... ] ]
DESCRIPTION
In the simplest case strace runs the specified command until it exits.
It intercepts and records the system calls which are called by a pro-
cess and the signals which are received by a process. The name of each
system call, its arguments and its return value are printed on standard
error or to the file specified with the -o option.
strace is a useful diagnostic, instructional, and debugging tool. Sys-
tem administrators, diagnosticians and trouble-shooters will find it
invaluable for solving problems with programs for which the source is
not readily available since they do not need to be recompiled in order
to trace them. Students, hackers and the overly-curious will find that
a great deal can be learned about a system and its system calls by
tracing even ordinary programs. And programmers will find that since
system calls and signals are events that happen at the user/kernel
interface, a close examination of this boundary is very useful for bug
isolation, sanity checking and attempting to capture race conditions.
Each line in the trace contains the system call name, followed by its
arguments in parentheses and its return value. An example from strac-
ing the command ``cat /dev/null'' is:
open("/dev/null", O_RDONLY) = 3
Errors (typically a return value of -1) have the errno symbol and error
string appended.
open("/foo/bar", O_RDONLY) = -1 ENOENT (No such file or directory)
Naturalmente strace da solo non può bastare per effettuare complesse operazioni di debug del codice di nucleo, si usano anche altre tecniche, alcune anche molto «banali» che prevedono l’uso delle funzione di nucleo printk() come accennato prima, oppure l’utilizzare i normali strumenti di manipolazione dei file presenti in ogni sistema UNIX per stimolare il codice di nucleo.
Strumenti per la manipolazione dei file
L’ambiente UNIX è caratterizzato dal possedere l’astrazione file, la possibilità cioè, di considerare ogni dispositivo come se fosse un file; questo concetto è molto potente perché ci permette di utilizzare uno stesso strumento su entità fisicamente molto differenti. In una macchina UNIX quindi qualsiasi cosa viene vista come un file: il video, la tastiera, la memoria, i dischi, ecc..
Questo concetto ci permette quindi di utilizzare uno stesso programma per copiare un file in un altro, come per copiare il contenuto di un disco in un file o in unaltro disco. Ad esempio per copiare il contenuto di un disco su di un altro si può dare il comando:
$ cat /dev/hda > /dev/hdb
Poiché un dispositivo è visto come un file, è facile capire come anche il comando cat possa essere usato per stimolare un dispositivo.
cat
Questo comando, di solito, si usa per leggere il contenuto di un file ma durante lo sviluppo di un driver può essere anche usato per testare i metodi open(), close(), read() e write(). Con il comando cat posso quindi scrivere o leggere dati da e nel mio dispositivo. Ecco come posso, ad esempio, scattare una foto da un frame grabber mappato sul file /dev/framegrabber:
$ cat /dev/framegrabber > mypic
dd
Questo comando è molto interessante perché permette di utilizzare/testare anche il metodo lseek(). In pratica si può usare per andare a leggere zone diverse di un dispositivo senza dover necessariamente partire dalla posizione 0. Sempre considerando il framegrabber di prima possiamo, ad esempio, leggere la quarta immagine (sempre che il dispositivo preveda questa caratteristica e che la dimensione delle immagine sia di 64Kb) usando il comando:
$ dd if=/dev/framegrabber of=mypic bs=64K count=1 skip=3
Strumenti per la gestione dei pacchetti di rete
L’unico aspetto che non può essere inserito nell’astrazione file, è la rete. Il sottosistema di rete, proprio di tutti i sistemi UNIX, non soddisfa totalmente l’astrazione file. In questo caso quindi, per gestire e/o visualizzare i pacchetti che due o più macchine di scambiano via rete, dobbiamo usare strumenti specifici per tale scopo.
ping, route e tcpdump
Con il comando ping è possibile generare dei pacchetti verso un dato host contenenti pattern specifici.
Con il comando route è possibile, invece, dire al sistema su quale interfaccia di rete indirizzare i pacchetti per la spedizione.
Ad esempio, per aggiungere una rotta di default verso il gateway uso il comando:
$ route add default gw 192.168.32.10
Con il comando tcpdump, in fine, è possibile monitorare tutti i pacchetti che passano su di un collegamento di rete.
Il comando tcpdump è particolarmente interessante perché ci permette di sapere se il nostro sistema ha effettivamente inviato il pacchetto o no. Ad esempio possiamo tenere sotto controllo l’invio di pacchetti da parte del computer denominato computer1 tramite il comando:
$ tcpdump host computer1
Â
Â
Questa dispensa del corso Programmazione Linux è opera di Rodolfo Giometti (Copyright © 2003-2009) ed è rilasciata dall’autore «as is» (così com’è) e distribuita sotto licenza Creative Commons Attribuzione – Condividi allo stesso modo 2.5 Italia.
Lascia un commento