In Unix-like sistemi operativi, i flussi di ingresso, di uscita e di errore standard sono identificati dai descrittori di file 0
, 1
, 2
. Su Linux, questi sono visibili nel proc
filesystem in /proc/[pid]/fs/{0,1,2}
. Questi file sono in realtà collegamenti simbolici a un dispositivo pseudoterminale nella /dev/pts
directory.
Uno pseudoterminal (PTY) è una coppia di dispositivi virtuali, uno pseudoterminal master (PTM) e uno pseudoterminal slave (PTS) (collettivamente definiti come una coppia pseudoterminal ), che forniscono un canale IPC, un po 'come un tubo bidirezionale tra un programma che prevede essere collegato a un dispositivo terminale e un programma driver che utilizza lo pseudoterminale per inviare input e ricevere input dal precedente programma.
Un punto chiave è che lo schiavo pseudoterminale appare proprio come un terminale normale, ad esempio può essere commutato tra modalità canonica e non canonica (impostazione predefinita), in cui interpreta determinati caratteri di input, come generare un SIGINT
segnale quando un carattere di interruzione (normalmente generato premendo Ctrl+ Csulla tastiera) viene scritto sul master pseudoterminale o fa sì che il successivo read()
ritorni 0
quando viene rilevato un carattere di fine file (normalmente generato da Ctrl+ D). Altre operazioni supportate dai terminali sono l'attivazione o la disattivazione dell'eco, l'impostazione del gruppo di processi in primo piano, ecc.
Gli pseudoterminali hanno una serie di usi:
Consentono a programmi come ssh
far funzionare programmi orientati al terminale su un altro host collegato tramite una rete. Un programma orientato al terminale può essere qualsiasi programma, che normalmente verrebbe eseguito in una sessione terminale interattiva. L'input, l'output e l'errore standard di tale programma non possono essere collegati direttamente alla presa, poiché le prese non supportano la funzionalità relativa al terminale sopra menzionata.
Consentono a programmi come expect
guidare un programma interattivo orientato al terminale da uno script.
Sono utilizzati da emulatori di terminali tali da xterm
fornire funzionalità relative ai terminali.
Sono utilizzati da programmi come screen
multiplexare un singolo terminale fisico tra più processi.
Sono utilizzati da programmi come script
per registrare tutti gli input e output che si verificano durante una sessione di shell.
I PTY in stile Unix98 , utilizzati in Linux, sono configurati come segue:
Il programma del driver apre il multiplexer master pseudo-terminale su dev/ptmx
, sul quale riceve un descrittore di file per un PTM e un dispositivo PTS viene creato nella /dev/pts
directory. Ogni descrittore di file ottenuto aprendo /dev/ptmx
è un PTM indipendente con il proprio PTS associato.
Il programma del driver chiama fork()
per creare un processo figlio, che a sua volta esegue i seguenti passaggi:
Il bambino chiama setsid()
per iniziare una nuova sessione, di cui il bambino è il leader della sessione. Questo fa sì che il bambino perda il suo terminale di controllo .
Il bambino procede all'apertura del dispositivo PTS corrispondente al PTM creato dal programma del driver. Poiché il bambino è un leader della sessione, ma non ha un terminale di controllo, il PTS diventa il terminale di controllo del bambino.
Il bambino usa dup()
per duplicare il descrittore di file per il dispositivo slave su di esso input, output ed errore standard.
Infine, il bambino chiama exec()
per avviare il programma orientato al terminale che deve essere collegato al dispositivo pseudoterminale.
A questo punto, tutto ciò che il programma del driver scrive sul PTM, appare come input per il programma orientato al terminale sul PTS e viceversa.
Quando si opera in modalità canonica, l'ingresso nel PTS viene bufferizzato riga per riga. In altre parole, proprio come con i terminali regolari, la lettura del programma da un PTS riceve una riga di input solo quando un carattere di nuova riga viene scritto nel PTM. Quando la capacità di buffering è esaurita, ulteriori write()
chiamate si bloccano fino a quando parte dell'input è stata consumata.
Nel kernel di Linux, le chiamate relativi file system open()
, read()
, write()
stat()
ecc sono implementati nello strato virtuale Filesystem (VFS), che fornisce una divisa interfaccia del file system per i programmi userspace. VFS consente a diverse implementazioni di file system di coesistere all'interno del kernel. Quando i programmi di userspace chiamano le sopracitate chiamate di sistema, VFS reindirizza la chiamata all'implementazione del filesystem appropriata.
I dispositivi PTS in uso /dev/pts
sono gestiti dall'implementazione del devpts
file system definita in /fs/devpts/inode.c
, mentre il driver TTY che fornisce il ptmx
dispositivo in stile Unix98 è definito in drivers/tty/pty.c
.
Il buffering tra i dispositivi TTY e le discipline di linea TTY , come gli pseudoterminali, viene fornita una struttura di buffer mantenuta per ciascun dispositivo tty, definito ininclude/linux/tty.h
Prima della versione 3.7 del kernel, il buffer era un flip buffer :
#define TTY_FLIPBUF_SIZE 512
struct tty_flip_buffer {
struct tq_struct tqueue;
struct semaphore pty_sem;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int count;
int buf_num;
unsigned char char_buf[2*TTY_FLIPBUF_SIZE];
char flag_buf[2*TTY_FLIPBUF_SIZE];
unsigned char slop[4];
};
La struttura conteneva spazio di archiviazione diviso in due buffer di uguale dimensione. I buffer sono stati numerati 0
(prima metà di char_buf/flag_buf
) e 1
(seconda metà). Il driver ha archiviato i dati nel buffer identificato da buf_num
. L'altro buffer potrebbe essere scaricato nella disciplina di linea.
Il buffer è stato "capovolto" alternando buf_num
tra 0
e 1
. Quando buf_num
cambiato, char_buf_ptr
e flag_buf_ptr
era impostato all'inizio del buffer identificato da buf_num
, ed count
era impostato 0
.
Dalla versione 3.7 del kernel i buffer di vibrazione TTY sono stati sostituiti con oggetti allocati tramite kmalloc()
organizzati in anelli . In una situazione normale per una porta seriale guidata da IRQ a velocità tipiche, il loro comportamento è più o meno lo stesso del vecchio flip buffer; due buffer finiscono per essere allocati e il kernel scorre ciclicamente tra loro come prima. Tuttavia, quando si verificano ritardi o aumenta la velocità, la nuova implementazione del buffer funziona meglio poiché il pool di buffer può aumentare leggermente.