Introduzione ai sistemi operativi con UNIX
Prec 3. Alcune system call di UNIX per l'I/O Succ

3.2 write

La system call duale di read(2) è la write(2). In UNIX quasi tutte le operazioni di I/O vengono effettuate tramite queste due sole system call read(2) e write(2).

ssize_t
     write(int d, const void *buf, size_t nbytes);
i parametri sono analoghi a quelli della read(2):
d
descrittore che riferisce il dispositivo da scrivere
buf
puntatore al buffer da cui leggere i byte da scrivere
nbytes
numero di byte da scrivere
Il valore di ritorno rappresenta il numero di byte effettivamente scritti, mentre se viene ritornato -1, come nel caso della read(2) viene anche assegnato il codice di errore ad errno.

Quando la write non può scrivere tutti i suoi dati e restituisce un valore minore di nbytes? Occorre tenere presente che read(2)/write(2) sono funzioni generiche per l'I/O ed operano su diversi tipi di dispositivi. Ad esempio operano sia su file regolari che speciali di dispositivo, sia su socket usati per la comunicazione tra processi locali o remoti, sia sul terminale. Quando viene creato un socket, oppure viene aperto un file tramite la system call open(2) che vedremo a breve, il sistema operativo assegna la risorsa al processo che l'ha richiesta. Il processo (o uno dei suoi figli) per riferirsi alla risorsa utilizza un identificatore unico, che è semplicemente un numero intero ritornato ad esempio dalla open(2) e che si chiama descrittore associato alla risorsa. Esso non identifica univocamente la risorsa, bensì un percorso di accesso alla risorsa da parte di un certo processo. Processi diversi o lo stesso processo, ad esempio possono leggere lo stesso file tramite diversi descrittori, oppure alternativamente può accadere che tramite un descrittore un file venga letto, mentre tramite un altro descrittore lo stesso file venga scritto (per un esempio pratico vedere l'opzione -f del comando tail(1)). Il descrittore è un indice o puntatore ad una struttura dati del sistema operativo che descrive la risorsa.

In condizioni normali di scrittura su un file regolare la write(2) scriverà sempre il numero di byte specificati e lo ritornerà, oppure restituisce -1 in caso di errore. Ad esempio se tentate di scrivere un file di 2MB su un dischetto da 1.44MB con una sola operazione di write(2), niente verrà scritto, il file rimarrà vuoto se è stato appena creato e la write ritornerà -1 e un codice di errore 28 (ENOSPC) che corrisponde ad un messaggio del tipo "No space left on device".

La manpage di write(2) dice che il valore di ritorno potrà essere inferiore a nbytes, senza che si verifichi errore, nel caso in cui la write(2) venga usata su oggetti per l'I/O in modalità non bloccante, come si fa spesso con i socket. Si veda a proposito fcntl(2) e signal(3). In tal caso occorre tenere in considerazione il valore di ritorno di write(2) e ritentare la scrittura dei byte rimanenti finchè non si verifica errore o tutti i dati sono stati scritti, proprio per essere sicuri di aver scritto tutto.

Per esemplificare ecco una versione semplificata del comando cat(1) realizzata tramite la read(2) e la write(2) appena descritte:

/* mycat.c :) */

#include <stdio.h>
#include <unistd.h>

int main()
{ char buf[BUFSIZ];
  int n;

  while ((n=read(0, buf, BUFSIZ)) > 0)
    write(1, buf, n);

  exit(n);
}
BUFSIZ rappresenta un valore di ampiezza di buffer adatto al sistema (vanno bene anche eventuali suoi multipli) ed è definito in /usr/include/stdio.h:
#define BUFSIZ  1024            /* size of buffer used by setbuf */
La shell di UNIX, ossia l'interprete dei comandi apre il file il cui descrittore è 0, che viene chiamato standard input (stdin in breve) e anche quello il cui descrittore è 1, che viene detto standard output (stdout in breve). Il descrittore 2 invece corrisponde allo stderr. Il nostro programma mycat sarà eseguito dalla shell come processo figlio ed erediterà questi descrittori, percui può utilizzarli senza dover ricorrere alla open(2). La shell permette di decidere a quali tipi di file associare lo stdin e lo stdin. Se non viene specificato altrimenti la shell associa lo stdin alla tastiera e lo stdout e lo stderr allo schermo:
$ ./mycat
mycat is
mycat is
called Garfield.
called Garfield.
^D
$

Prec Indice Succ
read Livello superiore open