Seconda lezione

I/O su Unix - a basso livello

Nel sistema operativo in uso sulle macchine del laboratorio (linux, che è una implementazione di Unix), la comunicazione con le periferiche avviene attraverso la creazione di canali di comunicazione, identificati da file descriptors, che operano su un albero di file (filesystem).
In questi canali di comunicazione, fluiscono sequenze di byte (o, se preferiamo, di char), senza ulteriore interpretazione o organizzazione. Le operazioni fondamentali che il sistema operativo è in grado di gestire a basso livello sono le seguenti:

open Apertura di un canale di comunicazione fra un processo ed un file identificato nella struttura gerarchica del filesystem (Es: /home/prelz/.login, /dev/urandom, etc.)
lseek Posizionamento relativo o assoluto all'interno del file. (Può essere interpretato in vari modi a seconda del tipo di file).
read Lettura di un certo numero di byte dal canale di comunicazione.
write Scrittura di un certo numero di byte nel canale di comunicazione.
fstat Richiesta al filesystem di informazioni su un determinato file.
ioctl Esecuzione di funzioni di controllo, o diagnostiche.
close Chiusura del canale di comunicazione.

Queste funzioni vengono eseguite ad un livello molto più basso rispetto a quello delle funzioni di "Text I/O" che si utilizzano di consueto in C++. Fra questo livello e quello della programmazione I/O C++ è inoltre interposto uno strato di buffering che permette di disaccoppiare le caratteristiche fisiche dei dispositivi utilizzati per la comunicazione.
Per avvicinarsi (anche se non molto) alle funzioni di basso livello, si possono usare i metodi di "Binary I/O" get(), read(), put(), write(), come ad esempio in questo .
Se vogliamo vedere cosa succede a basso livello, possiamo utilizzare la funzione di process tracing, accessibile ad esempio attraverso il comando strace:

> strace ./ex1
execve("./ex1", ["./ex1"], [/* 31 vars */]) = 0
(...)
open("/dev/urandom", O_RDONLY)          = 3
fstat(3, {st_mode=S_IFCHR|0444, st_rdev=makedev(1, 9), ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffffc1c2e0) = -1 EINVAL (Invalid argument)
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2aaaaaac0000
read(3, "O\312kh\232\252Y\nW\217\367\251\341GI\32W\240\224\32\334"..., 8192) = 8192
(...)

La famiglia di iostream

Nel corso di Lab. Calcolo I sono stati usati numerose volte oggetti di tipo fstream. Senza pretese di comprendere subito i dettagli, osserviamo come fstream faccia parte di una articolata famiglia, descritta da questa figura (tratta dall'utile riferimento di http://www.cplusplus.com - la figura si trova anche nel Capitolo 9 di "C++ in nutshell"):

Osserviamo dove si situano all'interno di questa gerarchia oggetti che conosciamo: std::cout (è un std::ostream), std::fstream (è un std::iostream). Cosa possono avere in comune questi due oggetti ? Come (e dove) descrivere queste caratteristiche comuni ?

Di questa famiglia, forse gli oggetti meno familiari sono gli std::stringstream, che possiamo vedere in azione in questo .
Gli std::stringstream consentono di applicare una stringa di char in memoria le stesse operazioni che si potrebbero applicare ad un file (dopo tutto, come abbiamo visto, ogni file permette comunque di gestire una sequenza di byte/char).

I/O manipulators

Lo stato di un qualsiasi stream della famiglia di iostream può essere variato attraverso manipolatori. Alcuni manipolatori si trovano predefiniti negli include file <ios>, <istream>, <ostream>, <iomanip>:

<ios> std::(no)boolalpha Attiva la conversione in stringa dei tipi bool (true/false anziché 1/0).
<ios> std::dec, std::hex, std::oct Cambia la base per la conversione dei numeri rispettivamente in decimale, ottale, esadecimale
<iomanip> std::setbase(int base) Modifica la base per la conversione dei numeri. Le basi ammesse sono 8, 10 o 16.
<ios> std::(no)showbase Aggiunge '0' o '0x' rispettivamente prima dei numeri ottali ed esadecimali.
<ios> std::fixed, std::scientific Modifica la conversione dei tipi di dati non interi in virgola fissa, oppure in notazione esponenziale.
<ios> std::(no)showpos Mostra un segno '+' anche quando non sarebbe necessario.
<ios> std::(no)showpoint Mostra un punto decimale anche quando non sarebbe necessario.
<ios> std::(no)uppercase Usa lettere maiuscole nella stampa dei numeri (X, E, A-F etc.).
<iomanip> std::setprecision(int n) Modifica la precisione utilizzata per la conversione dei valori numerici.
<iomanip> std::setfill(char c) Modifica il carattere di riempimento usato per la stampa.
<iomanip> std::setw(int n) Modifica la larghezza del campo utilizzato per la conversione del successivo valore (gli spazi sono riempiti con il carattere definito in setfill.
<ios> std::internal, std::left, std::right Determina la posizione dell'argomento in seguito alla conversione in stringa. Giustificato (con eventuali caratteri definiti in setfill aggiunti dopo il segno o prefisso), allineato a sinistra o a destra.
<ios> std::(no)skipws Ignora gli spazi bianchi (o caratteri corrispondenti a spazio bianco, come \t, \n, \r) all'inizio di una fase di input
<istream> std::ws Consuma immediatamente gli spazi bianchi (o caratteri corrispondenti a spazio bianco).
<ios> std::(no)unitbuf Causa uno svuotamento automatico (flush) del buffer al termine di ogni operazione di output.
<ostream> std::flush Causa lo svuotamento immediato (flush) del buffer.
<ostream> std::endl Causa l'emissione in output di un carattere di fine riga e lo svuotamento (flush) del buffer.
<ostream> std::ends Causa l'emissione in output di un carattere di fine stringa (NUL).

Esercizio: Scrivere un programma che legge un file qualsiasi e stampa i singoli byte del contenuto in formato esadecimale.