Ovvero come si scambiavano messaggi instantanei due utenti Unix prima dell’avvento di WhatsApp e Telegram.
Qualche giorno fa stavo ragionando sui primi sistemi di messaggistica su ARPANET [E] e mi è tornato in mente il comando Unix write, quello che consente a un utente di scrivere direttamente sul terminale di un altro utente collegato alla stessa macchina.
In sostanza ricordavo [B] che Ray Tomlison aveva inventato lo standard nome@host per consegnare un messaggio testuale all’utenti Unix nome sul computer remoto host. Lui lo faceva con due programmi seprarati SNDMSG (che consentiva di lasciare dei messaggi ad altri utenti sullo stesso computer) , e CPYNET, che permetteva di copiare file da un computer ARPANET a un altro. Questa cosa mi ha ricordato che all’università utilizzavo un comando (che non rammentavo) per inviare un messaggio immediato ad un utente ad un’altra macchina. Non è una mail, è un messaggio immediato, tipo Telegram, e non è quello che faceva il prototipo di posta elettronica scritto da Tomlinson.
Cercando di ricordare questa cosa che sembrava una curiosità da archeologia informatica, invece, si è aperto un piccolo mondo.
Supponiamo allora di avere due utenti collegati alla stessa macchina Linux. Dal mio terminale eseguo:
sudo write marcob tty3
e scrivo:
ciao Marco come stai?
Passando alla console dell’altro utente, su tty3, compare qualcosa del tipo:
Message from marcob@bombelli (as root) on pts/1 at 19:30 ciao Marco come stai?
La prima sorpresa è questa:
perché sto lavorando dalla sessione grafica (GNOME) su tty2, ma il messaggio dice che proviene da pts/1?
La risposta è che la console grafica e il terminale che vediamo nella finestra non sono la stessa cosa.
Sommario
Cos’è un terminale e cos’è una sessione grafica GNOME
A questo punto conviene chiarire una distinzione che oggi è facile perdere di vista.
Se non lo avete mai provato è divertnte: premete contemporaneamente sulla tasietera CTRL-ALT-F3. Vi si presenta uno schermo nero con scritto:
marcob@bombelli
E basta. Non c’è mouse, non c’è possibilità di fare screenshot… è un bagno di umiltà che vi catapulta indietro di 40 anni dove c’era solo tastiera e monitor.
Quando con Ctrl+Alt+F3 passo a tty3, non sto aprendo una normale finestra di terminale: sto passando a una console virtuale testuale di Linux.
Lì non c’è GNOME, non c’è un terminal emulator e, soprattutto, non c’è l’ambiente grafico a cui siamo abituati. Il sistema mi mette direttamente a disposizione uno schermo testuale e una tastiera.
Per questo l’esperienza è molto diversa.
Su tty3 posso digitare comandi e vedere caratteri, ma normalmente non ho:
- il mouse per selezionare il testo;
- il copia e incolla dell’ambiente grafico;
- menu e finestre;
- strumenti grafici per fare uno screenshot;
- tutte le comodità offerte dal desktop.
È, in un certo senso, molto più vicino all’idea storica di terminale: un dispositivo attraverso il quale un utente comunica con il sistema usando essenzialmente input e output testuale.
Quando invece lavoro in GNOME e apro l’applicazione Terminale, la situazione è completamente diversa.
Ho prima di tutto una sessione grafica, associata per esempio a:
/dev/tty2
All’interno di quella sessione gira GNOME. Quando apro GNOME Terminal, il programma crea uno pseudo-terminale, per esempio:
/dev/pts/1
La shell bash comunica con quello pseudo-terminale come se avesse davanti un vero terminale.
La differenza può essere schematizzata così:
Console virtuale: tastiera | /dev/tty3 | bash | schermo testuale
mentre nell’ambiente grafico:
tastiera + mouse
|
GNOME
|
GNOME Terminal
|
pseudo-terminale
|
/dev/pts/1
|
bash
Nel secondo caso il terminale che vediamo è quindi una simulazione software di un terminale, inserita dentro un ambiente grafico.
Questo spiega anche perché posso selezionare il testo con il mouse, copiarlo, incollarlo, ridimensionare la finestra o fare uno screenshot: queste funzioni non appartengono al terminale Unix in sé, ma al programma grafico che lo emula e al desktop che lo contiene.
In altre parole, quando sono su tty3 sto usando direttamente una console testuale del sistema; quando sono dentro GNOME Terminal sto usando una finestra grafica che si comporta, agli occhi della shell, come un terminale.
Ed è proprio per rendere possibile questa illusione che esistono gli pseudo-terminali /dev/pts/*.
Quindi: la sessione grafica può essere associata a una console virtuale, ma quando apro GNOME Terminal viene creato uno pseudo-terminale. La shell bash che utilizzo nella finestra può quindi avere come terminale:
/dev/pts/1
Lo si può verificare banalmente con il comando: da sessione grafica GNOME ottengo
$ tty /dev/pts/0
Dal terminale virtuale (/dev/tty3) invece
$ tty /dev/tty3
Anche solo scrivere questo articolo amplifica questa differenza: mentre posso fare copia incolla di /dev/pts/0 dal terminale GNOME, non posso farlo dal terminale virtuale! Devo ricopiare a mano /dev/tty3!
Il percorso è quindi, semplificando:
GNOME | tty2 | GNOME Terminal | pseudo-terminale | /dev/pts/1 | bash
La sigla pts significa storicamente pseudo-terminal slave: uno pseudo-terminale è formato da una coppia di estremità, una controllata dal terminal emulator e una che viene presentata alla shell come se fosse un vero terminale.
Dall’altra parte, invece, tty3 è una vera console virtuale Linux:
/dev/tty3
Quindi tty3 e pts/3 non hanno alcun rapporto particolare: il numero uguale sarebbe solo una coincidenza.
Inciso: l’attributo virtuale è perché ho solo un terminale reale nel mio PC: il monitor; però in questo stesso terminale reale posso vedere più terminali virtuali.
La seconda sorpresa arriva osservando cosa fa realmente write.
Concettualmente è molto vicino a:
echo "ciao" > /dev/tty3
Il terminale è infatti rappresentato da un device file e un processo può scriverci dentro.
write aggiunge naturalmente parecchia logica: individua il terminale dell’utente, verifica i permessi, controlla se i messaggi sono consentiti e gestisce l’interazione.
Ma il principio rimane sorprendentemente semplice:
tastiera | /dev/pts/1 | programma write | /dev/tty3 | schermo dell'altro utente
E qui arriva la parte che mi ha interessato di più.
Il programma write deve leggere ciò che digito e scriverlo sull’altro terminale.
In termini Unix:
read() write()
Esattamente le stesse primitive che avevo già incontrato lavorando con i socket.
Con un terminale:
/dev/pts/1
|
read()
|
programma
|
write()
|
/dev/tty3
Con una connessione di rete:
programma
|
write()
|
socket
|
rete
|
socket
|
read()
|
programma
E improvvisamente la frase Unix
everything is a file
diventa molto meno astratta.
Non significa naturalmente che un socket sia un file memorizzato su disco. Significa piuttosto che Unix cerca di fornire un’interfaccia uniforme a oggetti molto diversi.
File, terminali, pipe e socket possono essere gestiti attraverso file descriptor e primitive come read() e write().
Anche i famosi:
0 = stdin 1 = stdout 2 = stderr
sono semplicemente file descriptor già aperti dal processo.
Nel mio terminale grafico posso verificarlo con:
readlink /proc/$$/fd/0 readlink /proc/$$/fd/1 readlink /proc/$$/fd/2
e scoprire che tutti e tre, normalmente, puntano proprio al mio pseudo-terminale:
/dev/pts/1
Sono partito da un vecchio comando per mandare un messaggio a un altro utente e sono finito dentro uno dei principi fondamentali dell’architettura Unix.
Non è un post sull’AI, probabilmente non farà grandi numeri.
Ma trovo ancora molto affascinante scoprire che sotto strumenti modernissimi continuano a vivere idee progettate decenni fa.
Credo che questo diventerà il primo di una piccola serie su terminali, file descriptor, pipe, socket e processi Unix.
Bibliografia
- [E] Quando è nato Internet? La storia completa dalla nascita a oggi
- [B] Fondamenti e storia di internet
- With a little help from my friend ChatGPT (OpenAI).
- With a little help from my friend Claude (Anthropic).
- Tanenbaum et al., Reti di calcolatori, Pearson 2022

Commenti recenti