|
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:
- disattivare il flag di blocco del descrittore di file impostandolo al valore O_NONBLOCK, oppure
- 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.
|