Home Corsi Programmazione Linux Le chiamate di sistema

In evidenza

SMAU a Bologna con Research to Business

SMAU a Bologna con Research to Business

Il 6 e 7 giugno il Roadshow Smau Business torna a far tappa al Padiglione 33 della Fiera di Bologna e...
Gli articolisti del Portale

Gli articolisti del Portale

Abbiamo deciso di dare più visibilità a tutti coloro che hanno pubblicato articoli per noi, questo perché...
SMAU festeggia il suo quinto anno nel NordEst a Padova

SMAU festeggia il suo quinto anno nel NordEst a Padova

Il 18 e 19 aprile al padiglione 5 di Padova Fiere si terrà la prossima tappa di SMAU Business Roadshow. All'evento...
SMAU risale l’Italia e arriva a Roma

SMAU risale l’Italia e arriva a Roma

Il 21 e 22 marzo nella Nuova Fiera di Roma si terrà la prossima tappa di SMAU Business Roadshow. L'evento...

Bandi di Concorso

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Esperto di servizi e tecnologie cloud al Digit PA

Procedura comparativa per il conferimento di un incarico di collaborazione coordinata e continuativa per un profilo senior di “Esperto di servizi e tecnologie cloud”, nell’ambito del progetto europeo “Open Dai” presso l' Ente nazionale per la digitalizzazione della Pubblica Amministrazione...

18 Apr 2012 - Letture:34

Selezione per addetto servizi informatici all'Autorità Portuale di Brindisi

Avviso di selezione pubblica per titoli e prova orale per l’assunzione di n. 1 “addetto ai servizi informatici”, a tempo pieno ed determinato triennale, previo periodo di prova, da inquadrare nell’Area Amministrativa. (Inquadramento al III Livello del C.C.N.L. dei Lavoratori...

12 Feb 2012 - Letture:435

Concorso pubblico per informatico alla Corte dei Conti

E' indetto un concorso pubblico, per   esami,   per   il reclutamento di tre unità di informatici da inquadrare nella III area, fascia retributiva F1, da destinare agli Uffici della Corte dei Conti con sede in Roma. Requisiti: laurea triennale in scienze e...

05 Feb 2012 - Letture:306

Istruttore informatico L.68/1999 al Comune di Potenza

Concorso pubblico, per esami, per la copertura di n. 2 posti di Istruttore Informatico, categoria C, posizione economica C1, a tempo pieno e indeterminato, con riserva assoluta alle categorie dilavoratori di cui all'art. 1 della legge n. 68/1999 (norme per...

21 Gen 2012 - Letture:244

Senior Business Consultant presso Lombardia Informatica

Selezione per figura Senior Business Consultant presso Lombardia Informatica. Il collaboratore dovrà fornire consulenza strategica e di business sulle tematiche verticali della Socio-Sanità. Requisiti di ammissione: Laurea in Ingegneria, Economia e Commercio o Scienze dell’Informazione; Percorsi di formazione professionale in ambito sanitario e socio-sanitario, su...

11 Gen 2012 - Letture:284

Bando progetto TAG (Toscana Area Giovani) per giovani talenti digitali

UPI Toscana ha indetto un avviso pubblico per la selezione di 20 giovani (2 per ciascuna delle 10 province della Toscana), da impiegare come formatori all'interno del Progetto TAG (Toscana Area Giovani). Il bando è finalizzato a valorizzare al meglio i...

03 Gen 2012 - Letture:271

Avviso pubblico per giovani dottori di ricerca per Uffici di Gabinetto

Avviso pubblico per il conferimento di n. 6 incarichi presso gli Uffici di diretta collaborazione del Ministro dell'istruzione dell'università e della ricerca, per soggetti estranei alla pubblica amministrazione. Ambiti di esperienza e Aree di competenza: Nuovi Media: nuove forma di comunicazione tra...

02 Gen 2012 - Letture:217

I più attivi

Dati e punteggi dell'ultimo anno

Giovanna Casamassima Giovanna Casamassima
46 articoli
15,989 punti totali
Rodolfo Giometti Rodolfo Giometti
49 articoli
5,061 punti totali
Vittorio D'Aversa Vittorio D'Aversa
33 articoli
3,425 punti totali
Fabio Bronzini Fabio Bronzini
9 articoli
0,318 punti totali
Fulvio Lucchetti Fulvio Lucchetti
9 articoli
0,275 punti totali

Ci hanno visitato

Oggi:1447
Ieri:2498
Totali (14/04/09):1162294

I nostri numeri

Articoli pubblicati: 552
Iscritti al portale: 766
Iscritti all'Elenco: 184
Iscritti ML Discussioni: 351
Iscritti ML Articoli: 30
Iscritti ML Lavoro: 166
 
Le chiamate di sistema Stampa E-mail
(0 voti, media 0 di 5)
Area Corsi - Programmazione Linux
Scritto da Rodolfo Giometti   
Martedì 13 Luglio 2010 12:57
Articolo letto 1621 volte

La divisione tra spazio di nucleo (kernel space) e spazio utente (user space) è alla base della teoria dei sistemi operativi e serve per implementare tutta una serie di politiche di gestione dei processi, del sistema e della loro sicurezza. I processi girano nello spazio utente dove il loro operato viene controllato dalle entità che sopravvivono nello spazio di nucleo. In questo modo è possibile controllare l'evolvere di ogni processo e bloccare qualsiasi operazione che va contro la sicurezza e la stabilità del sistema.

Questo fa sì che un processo non possa distruggere se stesso o gli altri, si ha cioè una sorta di protezione dello spazio di memoria di ogni processo. Un processo, infatti, potrebbe sporcare lo spazio di memoria di un altro (compromettendone quindi il corretto funzionamento): o in maniera involontaria (errore di programmazione) o in maniera volontaria (per guadagnarne il controllo).

Per realizzare questa forma di controllo, ogni qual volta cioè un processo deve richiedere un accesso ad una risorsa del sistema, si usa una chiamata di sistema (o system call), si realizza quindi il passaggio da spazio utente a spazio di nucleo in una forma di tipo controllato: si verificano i parametri di ingresso e le credenziali del processo invocante e, se va tutto bene, si procede all'esecuzione della richiesta.

Quando un processo utilizza una chiamata di sistema si trova perciò ad eseguire nello spazio di nucleo.

Cosa sono e cosa non sono

Occorre però stare attenti nel non confondersi nella differenza tra chiamata di sistema e funzione (di libreria e non). Le chiamate di sistema sono implementate nel nucleo ed eseguono nello spazio di nucleo, le funzioni no (o almeno non in toto)! Ad esempio la write() è una chiamata di sistema mentre la printf() è una funzione; quest'ultima utilizza però la write() al suo interno per eseguire.

Questa divisione è mantenuta anche dalle pagine di man, infatti sia ha (dal comando man man):

The table below shows the section numbers of the manual followed by the types of pages they contain.
1  Executable programs or shell commands
2  System calls (functions provided by the kernel)
3  Library calls (functions within system libraries)
4  Special files (usually found in /dev)
5  File formats and conventions eg /etc/passwd
6  Games
7  Macro packages and conventions eg man(7), groff(7).
8  System administration commands (usually only for root)
9  Kernel routines [Non standard]

In sostanza le chiamate di sistema sono delle speciali funzioni messe a disposizione dei processi dal nucleo del sistema operativo  (o kernel) e ne implementano la sua interfaccia verso i processi e lo spazio utente.

Le chiamate di sistema in dettaglio

La seguente lista non è (e non vuole) essere esaustiva dell'argomento ma vuole solo evidenziare alcune delle più importanti chiamate di sistema (e forse le più usate) e che tratteremo un po' più nel dettaglio in seguito.

La open()

La chiamata di sistema open() serve per iniziare l'accesso ad un file (o ad un dispositivo). Il prototipo è definito come:

int open(const char *pathname, int flags);

Ogni cosa all'interno di un sistema UNIX è considerato un file (astrazione file) e quindi anche i vari dispositivi: HD, floppy, tastiera, monitor, schede di I/O ecc., sono rappresentati da file presenti nel filesystem. La open() quindi, che solitamente si utilizza per aprire un file, si usa anche per aprire un qualsiasi dispositivo hardware presente all'interno del sistema.

La open() associa al file/dispositivo identificato dal nome in pathname un descrittore di file (ritornato come valore di ritorno) che verrà poi usato per accedere al file/dispositivo dalle altre chiamate di sistema. Il descrittore di file identifica univocamente all'interno del processo il file/dispositivo.

Direttamente dalle pagine di man si legge:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);

DESCRIPTION
The open() system call is used to convert a pathname into
a file descriptor (a small, non-negative integer for use
in subsequent I/O as with read, write, etc.).
When the
call is successful, the file descriptor returned will be
the lowest file descriptor not currently open for the pro-
cess. This call creates a new open file, not shared with
any other process. (But shared open files may arise via
the fork(2) system call.) The new file descriptor is set
to remain open across exec functions (see fcntl(2)). The
file offset is set to the beginning of the file.

The parameter flags is one of O_RDONLY, O_WRONLY or O_RDWR
which request opening the file read-only, write-only or
read/write, respectively, bitwise-or'd with zero or more

La close()

La chiamata di sistema close() serve per informare il sistema che non vogliamo più accedere ad un file (o ad un dispositivo). Il prototipo è definito come:

int close(int fd);

Questa chiamata di sistema rilascia tutte quelle risorse che il sistema ha creato per gestire il file, il che non significa (si badi bene) che il file viene chiuso (!), altri processi infatti possono aver fatto open() sul nostro file e quindi il sistema si deve guardare bene dal chiuderlo!

Molto spesso questa chiamata non viene fatta esplicitamente nei processi che eseguono la open() (nei nostri programmi, infatti, spesso non la mettiamo!) ma viene comunque invocata dal sistema quando questo termina.

Direttamente dalle pagine di man si legge:

int close(int fd);

DESCRIPTION
close closes a file descriptor, so that it no longer
refers to any file and may be reused.
Any locks held on
the file it was associated with, and owned by the process,
are removed (regardless of the file descriptor that was
used to obtain the lock).

If fd is the last copy of a particular file descriptor the
resources associated with it are freed; if the descriptor
was the last reference to a file which has been removed
using unlink(2) the file is deleted.

Not checking the return value of close is a common but
nevertheless serious programming error. File system
implementations which use techniques as ``write-behind''
to increase performance may lead to write(2) succeeding,
although the data has not been written yet.

A successful close does not guarantee that the data has
been successfully saved to disk, as the kernel defers
writes. It is not common for a filesystem to flush the
buffers when the stream is closed. If you need to be sure
that the data is physically stored use fsync(2) or
sync(2), they will get you closer to that goal (it will
depend on the disk hardware at this point).

La read()

La chiamata di sistema read() serve per leggere dati da un descrittore di file quindi, per quanto visto prima, da un file o un dispositivo, ma non solo! Infatti la read() la si può usre per leggere dati anche da un socket (si veda più avanti); questa chiamata di sistema ha quindi un campo d'azione molto vasto. Il prototipo è definito come:

ssize_t read(int fd, void *buf, size_t count);

Con la read() si cerca quindi di leggere fino a count byte dal descrittore di file fd mettendo il risultato nell'area di memoria puntata da buf. Si noti che la read() può ritornare anche meno byte di quelli specificati dal parametro count: questa caratteristica (contenuta nella semantica della read()) va tenuta bene in mente, soprattutto quando la si va ad implementare nel nucleo come metodo read() di un device driver (argomento da trattare in un altro articolo di questo corso).

Direttamente dalle pagine di man si legge:

ssize_t read(int fd, void *buf, size_t count);

DESCRIPTION
read() attempts to read up to count bytes from file
descriptor fd into the buffer starting at buf.

If count is zero, read() returns zero and has no other
results. If count is greater than SSIZE_MAX, the result
is unspecified.

RETURN VALUE
On success, the number of bytes read is returned (zero
indicates end of file), and the file position is advanced
by this number. It is not an error if this number is
smaller than the number of bytes requested; this may hap-
pen for example because fewer bytes are actually available
right now (maybe because we were close to end-of-file, or
because we are reading from a pipe, or from a terminal),
or because read() was interrupted by a signal.
On error,
-1 is returned, and errno is set appropriately. In this
case it is left unspecified whether the file position (if
any) changes.

La write()

La chiamata di sistema write(), in maniera del tutto duale rispetto alla read() serve per scrivere dati su un descrittore di file. Il prototipo è definito come:

ssize_t write(int fd, const void *buf, size_t count);

Con questa chiamata di sistema si cerca di scrivere fino a count byte nel descrittore di file fd prendendo i dati dall'area di memoria puntata da buf.

Si noti che la write() (come la read()) può prendere meno byte di quelli specificati dal parametro count: questa caratteristica (contenuta nella semantica della write()) va tenuta bene in mente, soprattutto quando la si va ad implementare nel nucleo come metodo write() di un device driver (argomento da trattare in un altro articolo di questo corso).

Direttamente dalle pagine di man si legge:

ssize_t write(int fd, const void *buf, size_t count);

DESCRIPTION
write writes up to count bytes to the file referenced by
the file descriptor fd from the buffer starting at buf.

POSIX requires that a read() which can be proved to occur
after a write() has returned returns the new data.
Note that not all file systems are POSIX conforming.

RETURN VALUE
On success, the number of bytes written are returned (zero
indicates nothing was written).
On error, -1 is returned,
and errno is set appropriately. If count is zero and the
file descriptor refers to a regular file, 0 will be
returned without causing any other effect. For a special
file, the results are not portable.

La ioctl()

La chiamata di sistema ioctl() serve per modificare l'impostazione corrente e/o le modalità di accesso al dispositivo individuato dal descrittore di file passato come parametro (la ioctl() fa, di solito, sempre riferimento ad un dispositivo). Il prototipo è definito come:

int ioctl(int fd, int request, ...);

Con questa chiamata di sistema si possono inviare degli speciali comandi ad un dispositivo e quindi può essere usata per espletare tutte quelle funzionalità e/o impostazioni che non sarebbero possibili utilizzando la sola open().

La sua importanza è strategica quando si deve implementare in un device driver dei metodi per fornire all'utente il pieno controllo del dispositivo stesso. Ad esempio per implementare speciali comandi di configurazione (baud rate, risoluzioni video, ecc.).

Direttamente dalle pagine di man si legge:

int ioctl(int fd, int request, ...)

[The "third" argument is traditionally char *argp, and
will be so named for this discussion.]

DESCRIPTION
The ioctl function manipulates the underlying device
parameters of special files. In particular, many operat-
ing characteristics of character special files (e.g. ter-
minals) may be controlled with ioctl requests.
The argu-
ment d must be an open file descriptor.
An ioctl request has encoded in it whether the argument is
an in parameter or out parameter, and the size of the
argument argp in bytes. Macros and defines used in speci-
fying an ioctl request are located in the file .

La select() e la poll()

Le chiamate di sistema select() e poll() servono per verificare la possibilità di eseguire delle operazioni di I/O su di una serie di file, dispositivi e/o socket. I prototipi sono definiti come:

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);

L'uso di queste due chiamate di sistema è sostanzialmente simile ed esse vengono utilizzate per testare se uno o più descrittori di file è pronto per una data operazione di I/O (si veda più avanti). Da notare che per quanto riguarda i dispositivi hardware, se il device driver associato non implementa i metodi relativi a queste chiamate di sistema, allora su quel dispositivo non è possibile usare queste chiamate di sistema!

Direttamente dalle pagine di man di select() si legge:

DESCRIPTION
The functions select and pselect wait for a number of file
descriptors to change status.

Three independent sets of descriptors are watched. Those
listed in readfds will be watched to see if characters
become available for reading (more precisely, to see if a
read will not block - in particular, a file descriptor is
also ready on end-of-file), those in writefds will be
watched to see if a write will not block, and those in
exceptfds will be watched for exceptions. On exit, the
sets are modified in place to indicate which descriptors
actually changed status.

Mentre dalle pagine di man di poll() si legge:

DESCRIPTION
poll is a variation on the theme of select. It specifies
an array of nfds structures of type
        struct pollfd {
                int fd;        /* file descriptor */
                short events;  /* requested events */
                short revents; /* returned events */
        };

La mmap()

La chiamata di sistema mmap() serve per trasfomare un l'oggetto riferito da un descrittore di file in un area di memoria. Il prototipo è definito come:

void *mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

Questa chiamata di sistema è una delle più potenti che i sistemi UNIX-like ci mettono a disposizione perché ci permette di operare su di un oggetto, sia esso un file o un dispositivo, come se si operasse in un area di memoria! Quando è applicata ad un dispositivo permette di accedere direttamente all'area di memoria interna al dispositivo stesso, evitando quindi la doppia copia dei dati che si avrebbe utilizzando la read() o la write() (i dati sono già disponibili nella memoria del dispositivo senza doverli ricopiare nel buffer del processo che li vuole leggere/scrivere); questo permette di avere alte performance di accesso ai dati da e verso un dispositivo.

La mmap() si può usare anche per implementare la forma di Inter Process Communication detta shared memory.

Direttamente dalle pagine di man si legge:

DESCRIPTION
The mmap function asks to map length bytes starting at
offset offset from the file (or other object) specified by
the file descriptor fd into memory, preferably at address
start.
This latter address is a hint only, and is usually
specified as 0. The actual place where the object is
mapped is returned by mmap. The prot argument describes
the desired memory protection (and must not conflict with
the open mode of the file). It has bits
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
The flags parameter specifies the type of the mapped
object, mapping options and whether modifications made to
the mapped copy of the page are private to the process or
are to be shared with other references. It has bits
MAP_SHARED Share this mapping with all other processes
           that map this object. Storing to the region is
           equivalent to writing to the file. The file
           may not actually be updated until msync(2) or
           munmap(2) are called.

Si noti che quando questa viene usata per mappare un dispositivo si deve usare il flag MAP_SHARED la quale permette di accedere direttamente al file sottostante.

La fcntl()

La chiamata di sistema fcntl() serve per impostare alcuni flag in un descrittore di file. Il prototipo è definito come:

int fcntl(int fd, int cmd, ... /* arg */ );

Questa chiamata di sistema può venir utilizzata per molti scopi, tra cui, molto importante, quello in cui  viene usata per richiedere la notifica asincrona di una operazione di I/O (si veda più avanti).

Direttamente dalle pagine di man si legge:

DESCRIPTION
fcntl() performs one of the operations described below on the open file
descriptor fd.
The operation is determined by cmd.

fcntl() can take an optional third argument. Whether or not this argu-
ment is required is determined by cmd. The required argument type is
indicated in parentheses after each cmd name (in  most  cases, the
required type is long, and we identify the argument using the name
arg), or void is specified if the argument is not required.

File descriptor flags
The following commands manipulate the flags  associated with a file
descriptor. Currently, only one such flag is defined: FD_CLOEXEC, the
close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor
will remain open across an execve(2), otherwise it will be closed.

F_GETFD (void)
Read the file descriptor flags; arg is ignored.

F_SETFD (long)
Set the file descriptor flags to the value specified by arg.

La lseek()

La chiamata di sistema lseek() serve per modificare il valore dell'offset del descrittore di file ad un ben determinato valore; la prossima operazione di I/O, quindi, opererà da quel punto in poi. Il prototipo è definito come:

off_t lseek(int fd, off_t offset, int whence);

Questa chiamata di sistema, di solito, non viene implementata direttamente in un device driver (tant'è che ven'è già una di default nel sistema) a meno che non si abbiano esigenze particolari. Essa può assumere ad esempio significato per un frame grabber nel momento in cui decidiamo di scattare una nuova foto ogni qual volta che un processo accede ad un'area di memoria collegata ad un dato certo frame.

Direttamente dalle pagine di man si legge:

DESCRIPTION
The lseek() function repositions the offset of the open file associated
with the file descriptor fd to the argument offset according to the
directive whence
as follows:

SEEK_SET
The offset is set to offset bytes.

SEEK_CUR
The offset is set to its current location plus offset bytes.

SEEK_END
The offset is set to the size of the file plus offset bytes.

The lseek() function allows the file offset to be set beyond the end of
the file (but this does not change the size of the file). If data is
later written at this point, subsequent reads of the data in the gap (a
"hole") return null bytes ('\0') until data is actually written into
the gap.

La socket()

Questa chiamata di sistema viene usata per creare un endpoint (o socket) per la comunicazione in rete e non. Il prototipo è definito come:

int socket(int domain, int type, int protocol);

Il tipo di comunicazione (o domain) viene specificato dal parametro domain:

Name                Purpose                          Man page
AF_UNIX, AF_LOCAL   Local communication              unix(7)
AF_INET             IPv4 Internet protocols          ip(7)
AF_INET6            IPv6 Internet protocols          ipv6(7)
AF_IPX              IPX - Novell protocols
AF_NETLINK          Kernel user interface device     netlink(7)
AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
AF_AX25             Amateur radio AX.25 protocol
AF_ATMPVC           Access to raw ATM PVCs
AF_APPLETALK        Appletalk                        ddp(7)
AF_PACKET           Low level packet interface       packet(7)

mentre il parametro type caratterizza la semantica della comunicazione che vogliamo instaurare:

SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based
                byte  streams.  An out-of-band data transmission mecha-
                nism may be supported.

SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages
                of a fixed maximum length).


SOCK_SEQPACKET  Provides  a  sequenced,  reliable,  two-way connection-
                based data transmission path  for  datagrams  of  fixed
                maximum  length;  a  consumer  is  required  to read an
                entire packet with each input system call.

SOCK_RAW        Provides raw network protocol access.

SOCK_RDM        Provides a reliable datagram layer that does not  guar-
                antee ordering.

SOCK_PACKET     Obsolete  and  should  not be used in new programs; see
                packet(7).

In fine, l'ultimo parametro specifica un particolare protocollo da utilizzare all'interno del nostro socket. Se il protocollo è univoco all'interno della coppia domain/protocol, semplicemente il parametro diventa 0, altrimenti assume un valore particolare e significativo all'interno della coppia domain/protocol specificate.

La setsockopt() e la getsockopt()

Queste chiamate di sistema sono utilizzate per modificare le impostazioni di un socket ottenuto dalla chiamata di sistema socket(). I prototipi sono definiti come:

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

Il livello a cui deve agire la chiamata di sistema viene impostato nel parametro level e la richiesta è individuata da optname, mentre i parametri della richiesta sono optval e optlen.

Direttamente dalle pagine di man si legge:

DESCRIPTION
getsockopt() and setsockopt()  manipulate options for the socket
referred to by the file descriptor sockfd. Options may exist at multi-
ple protocol levels; they are always present at the uppermost socket
level.

When manipulating socket options, the level at which the option resides
and the name of the option must be specified. To manipulate options at
the sockets API level, level is specified as SOL_SOCKET. To manipulate
options  at any other level the protocol number of the appropriate pro-
tocol controlling the option is supplied. For example, to indicate
that  an  option is to be interpreted by the TCP protocol, level should
be set to the protocol number of TCP; see getprotoent(3).

The arguments optval and optlen are used to access option values for
setsockopt(). For  getsockopt() they identify a buffer in which the
value for the requested option(s) are to be returned. For getsock-
opt(), optlen is a value-result argument, initially containing the size
of the buffer pointed to by optval, and modified on return to  indicate
the  actual size of the value returned. If no option value is to be
supplied or returned, optval may be NULL.

Optname and any specified options are passed uninterpreted to the
appropriate protocol module for interpretation. The include file
contains definitions for socket level options, described
below. Options at other protocol levels vary in format and name; con-
sult the appropriate entries in section 4 of the manual.

Most socket-level options utilize an int argument for optval. For set-
sockopt(), the argument should be non-zero to enable a boolean option,
or zero if the option is to be disabled.

For a description of the available socket options see socket(7) and the
appropriate protocol man pages.

Tecniche di accesso ai dispositivi

Come abbiamo avuto modo di vedere, per accedere ad un dispositivo possiamo usare una serie di chiamate di sistema ognuna delle quali ben specifica per un dato compito. Analizziamo allora alcune tecniche che si possono utilizzare per accedere ai dati di un dispositivo hardware utilizzando quanto visto.

Il blocking I/O

Questa tecnica è decisamente la più semplice ed efficace da implementare perché non utilizza le signal e non ha cicli attivi di attesa. Di contro ha il fatto che il processo che vuole effettuare l'operazione di I/O si sospende fintantoché l'operazione non è fattibile e quindi, nel frattempo, non può eseguire altre operazioni.

Per implementare questa tecnica, di per se semplicissima, basta fare:

fd = open("/dev/...", ...);
read(fd, buf, count);

Quando la read() ritorna allora l'operazione di I/O è stata completata (con successo o meno). È chiaro però che se abbiamo più dispositivi su cui effettuare le nostre operazioni di I/O una cosa del genere è sbagliata:

read(fd1, buf1, count1);
read(fd2, buf2, count1);
read(fd3, buf3, count1);

E' possibile infatti che il processo si blocchi in attesa di dati sul descrittore fd1 mentre il descrittore fd2 ha i dati pronti che, però, non verranno mai letti fino a che la prima read() non ritorna.

La soluzione giusta si ottiene utilizzando le chiamata di sistema select():

FD_ZERO(&fds);
FD_SET(fd1, &fds);
FD_SET(fd2, &fds);
FD_SET(fd3, &fds);

select(max(fd1, fd2, fd3)+1, &fds, NULL, NULL, NULL);

can_read_fd1 = FD_ISSET(fd1, &fds);
can_read_fd2 = FD_ISSET(fd2, &fds);
can_read_fd3 = FD_ISSET(fd3, &fds);

oppure con la poll():

nfsd[0].fd = fd1;
nfsd[0].events = POLLIN | POLLPRI;
nfsd[1].fd = fd2;
nfsd[1].events = POLLIN | POLLPRI;
nfsd[2].fd = fd3;
nfsd[2].events = POLLIN | POLLPRI;

poll(nfsd, 3, -1);

can_read_fd1 = nfsd[0].revents&(POLLIN | POLLPRI);
can_read_fd2 = nfsd[1].revents&(POLLIN | POLLPRI);
can_read_fd3 = nfsd[2].revents&(POLLIN | POLLPRI);

L'I/O asincrono

Usando la chiamata di sistema fcntl() è possibile istruire il sistema in modo tale che esso ci invii una signal quando una data operazione di I/O è fattibile (notifica asincrona delle operazioni di I/O). Utilizzare questa tecnica ci permette quindi di poter fare qualcos'altro durante l'attesa.

Questa tecnica può essere implementata facilmente ma necessita di una buona gestione delle signal (cosa non è banale). Si può, ad esempio, definire un gestore della SIGIO del tipo:

volatile int finished;
void sigio_handler(int unused)
{
        finished = 1;
}

e quindi eseguire i seguenti comandi per dire al sistema di avvertirci ogni qual volta è possibile eseguire una operazione di I/O sul dispositivo legato al descrittore di file fd:

signal(SIGIO, sigio_handler);
flags = fcntl(fd, F_GETFL);
fcntl(devfd, F_SETFL, flags|FASYNC);

quindi con il codice seguente, mentre si aspetta la signal, si può fare qualcos'altro:

printf("doing something else..");
finished = 0;
while (!finished) {
        printf(".");
        fflush(stdout);
        sleep(1);
}

Il polling

Se decidiamo di non utilizzare le signal possiamo implementare una tecnica di polling la quale ci permetta anch'essa di poter fare qualcos'altro durante l'attesa dei dati. Quello che perdiamo però è la notifica asincrona e quindi dobbiamo noi, ogni tanto, verificare che non ci siano dati disponibili.

Il polling è la tecnica di I/O più semplice e facile da implementare ma è anche quella meno efficente: disperde tempo macchina in attese attive e non permette la notifica immediata della fattibilità dell'operazione di I/O.

Per implementare questa tecnica possiamo utilizzare diverse tecniche tra cui:

  1. disattivare il flag di blocco del descrittore di file impostandolo al valore O_NONBLOCK, oppure
  2. utilizzare la chiamata di sistema select()/poll().

Nel primo caso abbiamo due modi:

  • attraverso la open() su di un file non ancora aperto:
    fd = open("/dev/...", O_RDONLY | O_NONBLOCK);
  • attraverso la fcntl() su di un file già aperto:
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);

Così facendo le eventuali read() o write() eseguite sul descrittore di file fd risultano non bloccanti e si può sapere dal loro valore di ritorno quanti byte sono stati letti o scritti.

Per funzionare, questa tecnica, necessita che la gestione del flag O_NONBLOCK venga implementata nel device driver del dispositivo.

Il secondo caso, con la chiamata di sistema select() si ha:

FD_ZERO(&fds);
FD_SET(fd, &fds)
timeout.tv_sec = timeout.tv_usec = 0;

select(fd + 1, &fds, NULL, NULL, &timeout);

can_read = FD_ISSET(&fds);

mentre per la poll() si ha:

nfsd[0].fd = fd;
nfsd[0].events = POLLIN | POLLPRI;


poll(nfsd, 1, 0);

can_read = nfsd[0].revents&(POLLIN | POLLPRI);

Si noti che in quest'ultimo caso non si utiizza il flag O_NONBLOCK ma è richiesto che le chiamate di sistema select()/poll() siano implementate nel device driver del dispositivo.

Il Memory Mapped I/O

Questa tecnica è la più potente che i sistemi UNIX-like ci mettono a disposizione. In questo modo è possibile eseguire tutte le operazioni di I/O in maniera del tutto analoga al fatto di leggere/scrivere dati nella memoria (in questo caso la memoria interna al dispositivo).

Per implementare questa tecnica occorre che il driver del nostro dispositivo implementi il supporto per il metodo mmap() e  metta a disposizione del processo anche delle primitive di sincronizzazione (magari tramite ioctl()).

Una possibile implementazione di questa tecnica potrebbe essere:

/* Do mmap() of the device */
address = (unsigned long) mmap(0, vid_mbuf.size,
                               PROT_READ, MAP_SHARED, fdev, 0);

/* Get the first image */
vid_mmap.width = 640;
vid_mmap.height = 480;
vid_mmap.format = VIDEO_PALETTE_YUV422;
vid_mmap.frame = 0;

ioctl(fdev, VIDIOCMCAPTURE, &vid_mmap);

/* Wait for this frame */

ioctl(fdev, VIDIOCSYNC, 0);

/* Now address points to the image's data */
...

 


Questa dispensa del corso Programmazione Linux è opera di Rodolfo Giometti (Copyright © 2003-2010) ed è rilasciata dall'autore «as is» (così com'è) e distribuita sotto licenza Creative Commons Attribuzione – Condividi allo stesso modo 2.5 Italia.

 
 

In primo piano

Simons Voss: un mondo senza chiavi

Simons Voss: un mondo senza chiavi

Il sistema di gestione e di controllo degli accessi 3060 si presenta come un’alternativa con enormi vantaggi...
Pubblicità mirata dei prodotti

Pubblicità mirata dei prodotti

Hai un prodotto o un servizio da pubblicizzare? Fallo su consulenti-ict.it! Pubblicizzare un prodotto...
Recensioni sul Portale

Recensioni sul Portale

Hai un prodotto hardware/software o un libro riguardante uno dei temi dell'ICT? Vuoi farlo conoscere...

Offerte di lavoro

Visualizza Topic »

Eventi

Non ci sono eventi in programma
Maggio 2012
D L M M G V S
29 30 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 1 2
Giugno 2012
D L M M G V S
27 28 29 30 31 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Sondaggi

busyCaricamento Sondaggio...