
Me inspiró para escribir este artículo un artículo sobre el análisis de Sishny printf . Sin embargo, se perdió un momento sobre la forma en que van los datos después de que ingresan al dispositivo terminal. En este artículo quiero corregir este defecto y analizar la ruta de datos en el terminal. También descubriremos cómo Terminal se diferencia de Shell, qué es Pseudoterminal, cómo funcionan los emuladores de terminal y mucho más.
Los fundamentos
Primero, comprendamos qué es Terminal, Shell, Console, cómo Terminal Emulator difiere de Terminal ordinario, y por qué se llama así. Ya se ha escrito mucha información sobre esto, por lo que no escuchará nada nuevo aquí. Casi toda la información aquí fue tomada de Internet, proporcionaré enlaces al final del artículo. Quien ya sabe lo que significan todas estas cosas, puede saltarse esta sección de manera segura.
Terminal
Un terminal es una combinación de una pantalla y un teclado, es decir, un dispositivo físico. Antes de que los terminales se convirtieran en esta combinación particular, eran un tipo de dispositivo llamado teleimpresora (teletipo, teletipo o TTY para abreviar), es decir, una combinación de una impresora y un teclado. Típicamente, múltiples terminales están conectados a la misma computadora. Por lo tanto, era posible trabajar para varios usuarios en la misma computadora, y cada uno tenía su propia sesión, independiente de los demás. El terminal se llamó así porque estaba ubicado al final del cable del terminal.
Esto es teletipo :

Y esta es la Terminal :

Consola
Consola (consola) : un terminal que está conectado directamente a la computadora. El hecho es que la mayoría de los terminales estaban conectados implícitamente, pero al menos uno estaba conectado directamente a la computadora. La consola podía usar un círculo de personas estrictamente definido, ya que le permitía configurar la computadora.
Concha
Si los dos anteriores son dispositivos físicos, entonces esta definición se refiere exclusivamente al software.
Shell es un intérprete de línea de comando. El objetivo principal es ejecutar otros programas. Hay una gran cantidad de conchas diferentes. El más común es Bash (del inglés Bourne Again SHell, que, como sugiere Wikipedia , es un juego de palabras para Shell "nacido de nuevo", es decir, un Shell "revivido"). Otros ejemplos: Dash (un Shell ligero, disponible si ejecuta el binario en / bin / sh), Zsh.
Por supuesto, tanto los terminales como las consolas no pudieron sino encontrar su reflejo en los tiempos modernos. Por lo tanto, además consideraremos cosas como Terminal Emulator y Virtual Console .
Emulador de terminal
Emulador de terminal : un emulador del buen terminal antiguo. Se requiere un emulador de terminal para los programas que no pueden interactuar directamente con el sistema X Window: Bash, Vim y otros.
Primero establezcamos las responsabilidades de la terminal:
- Transferir la entrada del usuario a una computadora
- Entrega de salida de computadora a la pantalla
Por lo tanto, nuestro emulador de terminal hace exactamente lo mismo: proporciona la entrada del usuario al programa en ejecución y también muestra la salida del programa en la pantalla. En cualquier caso, el significado permanece: entre el usuario y el programa en ejecución, hay algún tipo de capa responsable de la entrada / salida. Ejemplos de emulador de terminal: gnome-terminal, xterm, konsole.
¡No confunda el emulador de shell y terminal!
Terminal Emulator es una aplicación GUI, es decir, una ventana en el sistema X Window. Shell es un intérprete de línea de comandos, es decir, solo un ejecutor de comandos, no tiene un shell gráfico. Hablando correctamente, no ejecutas Bash , ejecutas Terminal Emulator, que inicia Bash dentro de sí mismo . Terminal Emulator y Bash son absolutamente 2 programas diferentes. El primero es el único responsable de la entrada / salida, el segundo, de procesar los comandos.
Además en el artículo, todas las referencias al terminal se referirán al emulador de terminal.
Consola virtual (terminal virtual)
Presione Ctrl + Alt + FN, donde N generalmente tiene valores del 1 al 6. Lo que acaba de ver se llama Consola virtual (consola virtual) o Terminal virtual (terminal virtual). ¿Recuerdas lo que dije antes sobre las terminales? Muchas terminales estaban conectadas a una computadora y cada terminal era una sesión separada, independiente de las demás. La consola virtual repite esta idea: puede haber varias sesiones independientes dentro de su computadora (sin embargo, los recursos de la computadora aún se comparten obviamente).
Puede nombrar esta entidad como Consola virtual y Terminal virtual, porque, por definición, una consola es un terminal conectado directamente a una computadora, pero todos los terminales virtuales están, en cierto sentido, conectados directamente a una computadora.
Dispositivos TTY
A cada terminal se le asigna su propio dispositivo TTY (dispositivo terminal), que proporciona la consola. Aunque es poco probable que encuentres teletipos, pero la reducción en TTY ha sobrevivido hasta nuestros días.
Un dispositivo TTY consta de dos componentes fundamentales:
- Controlador de dispositivo Es responsable de entregar la entrada del teclado al programa y de mostrar la salida del programa en la pantalla.
- TTY Line Discipline ( disciplina de línea rusa). La disciplina de línea es la interfaz de acceso del controlador, que, sin embargo, aporta mucha lógica al dispositivo TTY. Podemos decir que la disciplina de línea llama al conductor. ¿Cuál es el área de responsabilidad de este componente? Lo descubriremos durante el artículo.
Construir dispositivo TTY:

Hay 3 tipos de dispositivos TTY:
- Dispositivo de consola : proporciona operación de consola virtual. La entrada y salida de este dispositivo está totalmente controlada por el núcleo.
- Dispositivo PTY (pseudo-terminal): proporciona la operación del terminal en la interfaz de la ventana. La entrada y salida de este dispositivo está controlada por un emulador de terminal que opera en el espacio del usuario.
- Dispositivo en serie : se comunica directamente con el hardware. Por lo general, no se usa directamente, pero existe como el nivel más bajo en la organización de la arquitectura de un dispositivo terminal.
En este artículo, hablaremos específicamente sobre el segundo tipo de dispositivos TTY: pseudo terminales.
Disciplina de línea TTY
Comenzamos a examinar la disciplina de la línea de dispositivos TTY.
La primera característica importante de una disciplina de línea es que es responsable del procesamiento de E / S. Esto incluye, por ejemplo, procesar caracteres de control (ver Caracteres de control ) y formatear la salida. Por ejemplo, ingresa cualquier texto, pero de repente se da cuenta de que se equivocó al escribir algo y quiere borrarlo; aquí es donde entra en juego la disciplina de línea.
Analizaremos en detalle qué sucede exactamente cuando trabajamos en Bash ejecutándose en la terminal. Por defecto, un dispositivo TTY funciona en modo canónico con eco habilitado . Un eco es una visualización de los caracteres que ingresó en la pantalla.
Cuando ingresamos, por ejemplo, el carácter a
, este carácter se envía al dispositivo TTY, pero es interceptado por la disciplina de la línea TTY del dispositivo. Ella lee un personaje en su búfer interno, ve que el modo de echo
está activado y muestra el personaje en la pantalla. En este momento, todavía no hay nada disponible para leer en el programa al que está conectado el dispositivo terminal. Presionemos la tecla de backspace
en el teclado. Símbolo ^?
nuevamente interceptado por la disciplina de la línea, y este último, al darse cuenta de que el usuario desea borrar el último carácter ingresado, elimina este carácter de su búfer interno y también lo borra de la pantalla. Ahora, si presionamos Enter, la disciplina de línea TTY finalmente envía al búfer de lectura del dispositivo terminal todo lo que se escribió previamente en el búfer de disciplina interna, incluido LF. Al mismo tiempo, los caracteres CR y LF se muestran en la pantalla para mover el cursor a una nueva línea; este es el formato de la salida.
Así es como funciona el modo canónico: transfiere todos los caracteres ingresados al dispositivo solo después de presionar Enter
, procesa los caracteres de control y formatea la salida.
Edición de línea TTY
La edición de línea TTY es el componente responsable de procesar la entrada en la disciplina de línea. Debe decirse que la Edición de línea es un concepto general y se relaciona con el procesamiento de entrada. Por ejemplo, Bash y Vim tienen su propia edición de línea.
Podemos controlar la configuración de disciplina de la línea del dispositivo TTY actual utilizando el programa stty . Experimentemos un poco.
Abra Bash o cualquier otro Shell y escriba:
stty icanon -echo
Ahora intente escribir algo y no verá su entrada (no se preocupe, aún puede pasar la entrada al programa). Acaba de deshabilitar el eco, es decir, la visualización de los caracteres ingresados en la pantalla. Ahora ingrese:
stty raw echo
Intenta escribir algo. Ya ves cómo se rompe la conclusión. Pero para más efecto, vayamos a Dash - escriba /bin/sh
. Ahora intente ingresar caracteres especiales ( Ctrl
+ cualquier carácter en el teclado) o simplemente presione Enter
. Estás perplejo, ¿qué son estos extraños personajes en la pantalla? El hecho es que, después de haber ingresado al Shell más simple, además de la Edición de línea de la disciplina en sí, también deshabilitamos Line Editing Bash, y ahora podemos observar con fuerza y principal el efecto de la inclusión del modo crudo de disciplina de la línea. Este modo no procesa la entrada en absoluto y no formatea la salida. ¿Por qué se necesita el modo sin formato? Por ejemplo, para Vim : se abre en toda la ventana de terminal y procesa la entrada en sí, al menos para que los símbolos especiales de disciplina de línea no se crucen con símbolos especiales de Vim.
Para una mayor comprensión, veamos cómo personalizar los caracteres de control. El stty <control-character> <string>
nos ayudará con esto.
Ingrese en Bash:
stty erase 0
Ahora el carácter de control de erase
se asignará al carácter 0
. El botón de backspace
generalmente importa ^?
, pero ahora este carácter especial se enviará al búfer de lectura PTS literalmente; pruébelo usted mismo. Ahora puede borrar caracteres usando el botón 0
en el teclado, porque usted mismo le pidió a tty line disciplina que reconozca el carácter ingresado como un carácter de control de erase
. Puede devolver la configuración con el comando stty erase ^\?
o simplemente cerrando la terminal, porque solo afectamos al dispositivo tty actual.
Puede encontrar más información en man stty .
Emulador de Terminal y Pseudoterminal
Cada vez que abrimos una nueva terminal en el sistema X Window, el servidor de terminal GNOME genera un nuevo proceso y lanza el programa predeterminado en él. Por lo general, este es algún tipo de Shell (por ejemplo, Bash).
La comunicación con el programa en ejecución ocurre a través del llamado Pseudoterminal (pseudo-terminal, PTY). El pseudo-terminal en sí existe en el núcleo, sin embargo, recibe información del espacio del usuario, desde el emulador de terminal.
El pseudo-terminal consta de los siguientes dos dispositivos TTY virtuales :
1) PTY master (PTM) : la parte principal del pseudo-terminal. Utilizado por GNOME Terminal Server para transferir la entrada del teclado a un programa que se ejecuta dentro del terminal, así como para leer la salida del programa y mostrar la salida. El GNOME Terminal Server, a su vez, se comunica con el sistema X Window a través del protocolo X.
2) PTY esclavo (PTS) : parte esclava del pseudo-terminal. Utilizado por un programa que se ejecuta dentro del terminal para leer la entrada del teclado y mostrar la salida en la pantalla. Al menos, el programa en sí lo cree (explicaré lo que esto significa, un poco más adelante).
Cualquier dato registrado en el dispositivo PTS es la entrada del dispositivo PTM, es decir, se vuelve legible en el dispositivo PTM. Y viceversa: cualquier dato registrado en el dispositivo PTM es la entrada del dispositivo PTS. Así es como se comunican el Terminal Server de GNOME y el programa que se ejecuta dentro del terminal. Cada dispositivo PTM está asociado con su propio dispositivo PTS.
El proceso de lanzamiento de una nueva terminal se ve así:
1) GNOME Terminal Server crea dispositivos maestros y esclavos llamando a la función open () en un dispositivo especial / dev / ptmx . La llamada open () devuelve el descriptor de archivo del dispositivo PTM creado: master_fd .
2) GNOME Terminal Server crea un nuevo proceso llamando a fork()
. Este proceso será la nueva terminal.
3) En el terminal PTS, el dispositivo se abre en los descriptores de archivo 0, 1, 2 (stdin, stdout y stderr, respectivamente). Ahora, el terminal estándar de E / S fluye a este dispositivo.
4) El programa deseado se inicia en el terminal llamando a la función exec()
. Algunos Shell generalmente se inician (por ejemplo, Bash). Cualquier programa que se inicie posteriormente desde Bash tendrá los mismos descriptores de archivo que Bash, es decir, los flujos del programa se dirigirán al dispositivo PTS.
Puede ver por sí mismo hacia dónde se dirigen los flujos de salida de terminal estándar utilizando el ls -la /proc/self/fd
:

El dispositivo PTS se encuentra en la ruta / dev / pts / N , y la ruta al dispositivo PTM no nos interesa en absoluto. El hecho es que GNOME Terminal Server ya tiene un descriptor de archivo para el dispositivo PTM abierto y no necesita una ruta hacia él, sin embargo, en el proceso secundario, debemos abrir el dispositivo PTS en flujos de salida estándar llamando a la función open()
, que requiere la ruta al archivo.
¿Recuerdas que dije que un programa que usa un dispositivo PTS solo piensa que se comunica directamente con el terminal? El hecho es que el PTS también es un dispositivo terminal ( dispositivo TTY), pero la diferencia entre el dispositivo PTS y el dispositivo TTY real es que el dispositivo PTS recibe la entrada no desde el teclado, sino desde el dispositivo maestro, y la salida no va a la pantalla, sino a dispositivo maestro Es por eso que el pseudo-terminal se llama así: el pseudo-terminal solo imita (de nuevo ??) al terminal. La diferencia entre el emulador de terminal y el pseudo-terminal es que el emulador de terminal es solo un programa gráfico que le permite ejecutar el terminal directamente dentro de la interfaz de la ventana, pero esta característica se implementa usando el pseudo-terminal.
El hecho de que el dispositivo PTS sea un dispositivo TTY es muy importante. He aquí por qué:
- El programa al que está conectado el dispositivo terminal tiene todas las capacidades de un terminal convencional. Por ejemplo: deshabilitar echo, deshabilitar / habilitar vista canónica.
- El programa, sabiendo que un dispositivo terminal está conectado a él (se dice que el programa tiene un terminal de control), puede trabajar de manera interactiva y pedirle al usuario su entrada. Por ejemplo, solicite un nombre de usuario y contraseña.
- También hay una disciplina de línea TTY, por lo que tenemos la capacidad de procesar caracteres de control antes de que lleguen al programa, así como formatear la salida del programa.
El dispositivo PTM también es un dispositivo TTY, pero esto no juega ningún papel, ya que no se utiliza como terminal de control. Además, la disciplina de línea del dispositivo PTM se establece en modo sin procesar, por lo tanto, el procesamiento no se realiza cuando se transfieren datos del PTS al dispositivo PTM. Sin embargo, las llamadas a read()
y write()
desde el espacio del usuario todavía son atendidas primero por la disciplina de línea en ambos dispositivos. Este momento jugará un papel aún mayor, como veremos más adelante.
El proceso de comunicación entre el Terminal Server de GNOME y el programa que se ejecuta dentro del terminal es el siguiente:

Vale la pena examinar con más detalle el papel que juega la disciplina de línea en la comunicación entre ambas partes de un pseudo-terminal. Aquí, la disciplina de línea es responsable del procesamiento de los datos que pasan del PTM al dispositivo PTS , así como del envío de datos de una parte del pseudo terminal a otra. Cuando estamos en el controlador del dispositivo PTS, aplicamos la disciplina de línea del dispositivo PTM y viceversa.
Dispositivos virtuales
Probablemente habría pensado que podría abrir el archivo a lo largo de la ruta / dev / pts / N y escribir o leer datos de él, como de un archivo de texto normal. Sí, todos los dispositivos en sistemas tipo Unix son archivos, gracias al principio fundamental de Unix, que establece que todo es un archivo. Sin embargo, ningún archivo de dispositivo especial (inglés - archivo de dispositivo) son archivos de texto. Dichos dispositivos se denominan dispositivos virtuales , es decir, existen exclusivamente en la memoria, no en el disco.
No intente abrir estos archivos como archivos de texto normales. Sin embargo, puede usar estos dispositivos mediante operaciones de write()
y read()
, cuya llamada será atendida por el controlador del dispositivo. Intentemos hacerlo.
Abra dos ventanas de terminal e ingrese tty
en cada comando. Este comando mostrará qué dispositivo TTY está sirviendo el terminal actualmente activo. Ahora ingrese echo "Hello, World!" > /dev/pts/N
echo "Hello, World!" > /dev/pts/N
en la primera ventana de terminal, donde N es el índice PTS del segundo dispositivo de ventana, cambie a la segunda ventana y verá su entrada desde la primera ventana. Ahora ha escrito los datos en el dispositivo PTS de la segunda ventana como si lo hubiera hecho un programa que se ejecuta en ese terminal .

Dispositivo pseudo terminal
Nos estamos acercando cada vez más a la parte final del artículo, pero antes de eso echamos un vistazo "bajo el capó" de Linux: considere el dispositivo del pseudo-terminal a nivel del núcleo. Habrá mucho código, pero intentaré explicar cada bloque de código dado con el mayor detalle posible, reducir los detalles sin importancia e ir secuencialmente.
Antes de comenzar, presentamos la llamada "cesta de componentes". A medida que avanzamos a lo largo del núcleo, le agregaremos más y más componentes y encontraremos una conexión entre ellos. Espero que esto te ayude a comprender mejor el dispositivo pseudo-terminal. Empecemos
Cuando se inicia Linux, carga los controladores de dispositivo necesarios. Nuestro pseudo-terminal también tiene dicho controlador. Su registro comienza con una llamada a esta función:
static int __init pty_init(void) { legacy_pty_init(); unix98_pty_init();
Para todos los sistemas modernos, la función unix98_pty_init()
se llamará:
static void __init unix98_pty_init(void) { ptm_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(ptm_driver)) panic("Couldn't allocate Unix98 ptm driver"); pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(pts_driver)) panic("Couldn't allocate Unix98 pts driver"); ptm_driver->driver_name = "pty_master"; ptm_driver->name = "ptm"; ptm_driver->major = UNIX98_PTY_MASTER_MAJOR; ptm_driver->minor_start = 0; ptm_driver->type = TTY_DRIVER_TYPE_PTY; ptm_driver->subtype = PTY_TYPE_MASTER; ptm_driver->init_termios = tty_std_termios; ptm_driver->init_termios.c_iflag = 0; ptm_driver->init_termios.c_oflag = 0; ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; ptm_driver->init_termios.c_lflag = 0; ptm_driver->init_termios.c_ispeed = 38400; ptm_driver->init_termios.c_ospeed = 38400; ptm_driver->other = pts_driver; tty_set_operations(ptm_driver, &ptm_unix98_ops); pts_driver->driver_name = "pty_slave"; pts_driver->name = "pts"; pts_driver->major = UNIX98_PTY_SLAVE_MAJOR; pts_driver->minor_start = 0; pts_driver->type = TTY_DRIVER_TYPE_PTY; pts_driver->subtype = PTY_TYPE_SLAVE; pts_driver->init_termios = tty_std_termios; pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; pts_driver->init_termios.c_ispeed = 38400; pts_driver->init_termios.c_ospeed = 38400; pts_driver->other = ptm_driver; tty_set_operations(pts_driver, &pty_unix98_ops); if (tty_register_driver(ptm_driver)) panic("Couldn't register Unix98 ptm driver"); if (tty_register_driver(pts_driver)) panic("Couldn't register Unix98 pts driver"); tty_default_fops(&ptmx_fops); ptmx_fops.open = ptmx_open; cdev_init(&ptmx_cdev, &ptmx_fops); if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) || register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0) panic("Couldn't register /dev/ptmx driver"); device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
Aquí estamos interesados en 3 cosas:
- Llama a
tty_set_operatons
para el controlador maestro pty y los dispositivos esclavos pty. - La función
ptmx_open
, que es responsable de crear ambas partes del pseudo-terminal al abrir el dispositivo especial / dev / ptmx . Importante: / dev / ptmx no es un dispositivo PTM, sino solo una interfaz para crear un nuevo pseudo-terminal. - Registre los controladores de dispositivo PTM y PTS.
Vamos en orden:
1. tty_set_operations
La función tty_set_operations () simplemente configura una tabla de funciones para el controlador actual:
void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op) { driver->ops = op; };
La estructura tty_operations es una tabla de funciones que se utiliza para acceder a las funciones del controlador TTY del dispositivo.
pty_unix98_ops
lo más importante en las estructuras pty_unix98_ops
y ptm_unix98_ops
, que son la tabla de funciones para las partes correspondientes del pseudo-terminal:
static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, .remove = pty_unix98_remove, .open = pty_open, .close = pty_close, .write = pty_write,
Aquí puede observar la función pty_write, pty_write
ya es familiar en el artículo sobre Sishny printf; volveremos a ella un poco más tarde.
Agreguemos esta estructura a nuestra cesta de componentes:

Como puede ver, los métodos principales de ambos controladores no son para nada diferentes. Por cierto, observe que no hay una función para la operación read (), no hay nada como pty_read()
. El hecho es que la lectura será servida únicamente por disciplina de línea. Por lo tanto, aprendemos sobre la segunda característica importante de la disciplina de línea: leer datos de un dispositivo TTY.
2. ptmx_open
Ahora pasemos a ptmx_open () :
static int ptmx_open(struct inode *inode, struct file *filp) { struct tty_struct *tty;
Estamos interesados en la función tty_init_dev()
, donde el primer argumento es el controlador del dispositivo PTM, y el segundo es el índice del dispositivo. Aquí dejamos la zona de responsabilidad del controlador PTY y vamos al archivo, que solo es responsable de los dispositivos TTY generales y no sabe nada acerca de nuestro pseudo terminal.
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = alloc_tty_struct(driver, idx); retval = tty_driver_install_tty(driver, tty); retval = tty_ldisc_setup(tty, tty->link);
Primero, alloc_tty_struct()
:
struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = kzalloc(sizeof(*tty), GFP_KERNEL);
Lo único que nos interesa aquí es la función tty_ldisc_init()
:
int tty_ldisc_init(struct tty_struct *tty) { struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); if (IS_ERR(ld)) return PTR_ERR(ld); tty->ldisc = ld;
Que llama a tty_ldisc_get()
:
static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) { struct tty_ldisc *ld;
Entonces, examinamos la llamada a la función alloc_tty_struct()
, que crea la estructura tty_struct junto con la disciplina de línea, la estructura tty_ldisc . Ambas estructuras tienen enlaces entre sí. Echemos un vistazo más de cerca a estas estructuras.
- tty_struct es una estructura para acceder al controlador de dispositivo TTY y algunos otros campos. Se ve así:
struct tty_struct { struct tty_driver *driver;
- tty_ldisc es la estructura para la disciplina de la línea TTY del dispositivo. Consta de solo dos campos y tiene el siguiente aspecto:
struct tty_ldisc { struct tty_ldisc_ops *ops;
Parece que no es nada complicado? Agreguemos todas las estructuras consideradas hasta este momento a nuestra cesta y vinculémoslas de la misma manera que están conectadas en el código:

Pero creamos tty_struct solo para el dispositivo PTM. ¿Qué pasa con el dispositivo PTS? Para hacer esto, volvemos a la función tty_init_dev()
y recordamos que se espera que llamemos a la función tty_driver_install_tty()
:
static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty) { return driver->ops->install ? driver->ops->install(driver, tty) : tty_standard_install(driver, tty); }
El comentario nos dice que este método es responsable de crear varias estructuras adicionales. Dispositivo PTS y será nuestra estructura adicional. Lo admito, fue extremadamente sorprendente para mí, porque es, ¡demonios, todo el dispositivo, y no solo algún tipo de estructura adicional! Pero todos entendemos que todos los dispositivos son solo una especie de estructura, así que sigan adelante. Ok, ¿qué es driver-> ops-> install here ? Para hacer esto, mire nuevamente la tabla de funciones para el controlador PTM:
static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install,
Y entendemos que estamos interesados en la función pty_unix98_install()
:
static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) { return pty_common_install(driver, tty, false); }
Que llama a la función pty_common_install()
:
static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, bool legacy) { struct tty_struct *o_tty;
, PTS tty_struct , PTS . . tty_struct PTS .
, TTY ( - ?).
— , PTM, PTS :
static const struct file_operations tty_fops = { .llseek = no_llseek, .read = tty_read, .write = tty_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync, .show_fdinfo = tty_show_fdinfo, };
, TTY .
Listo , /dev/ptmx . , PTS , , PTM , :

Hola mundo
. "Hello, World!", .
#include <stdio.h> void main() { printf("Hello, World!\n"); }
, "Hello, World!" . , , , . , . stdout /dev/null — . , Linux.
Unix write() , read() , close() , write() /dev/pts/0 __vfs_write()
:
ssize_t __vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret;
write() . , :
static const struct file_operations tty_fops = {
tty_write()
:
static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct tty_struct *tty = file_tty(file); struct tty_ldisc *ld; ssize_t ret; ld = tty_ldisc_ref_wait(tty); ret = do_tty_write(ld->ops->write, tty, file, buf, count); tty_ldisc_deref(ld); return ret; }
tty_struct TTY , write() . :
static struct tty_ldisc_ops n_tty_ops = { .write = n_tty_write,
n_tty_write()
:
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) { const unsigned char *b = buf;
, "Hello, World!" write() PTS . :
static const struct tty_operations pty_unix98_ops = { .write = pty_write,
pty_write()
:
static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c) { struct tty_struct *to = tty->link;
:
__vfs_write() ->
. , PTM . , .
, flip buffer . Flip buffer — , . tty driver , . , . , , . , , . - flip buffer — (, - , flip).
, . tty_insert_flip_string()
tty_insert_flip_string_fixed_flag()
, PTM :
int tty_insert_flip_string_fixed_flag(struct tty_port *port, const unsigned char *chars, char flag, size_t size) { int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
, flip buffer , , . , — PTM , .
, "Hello, World!" PTM . GNOME Terminal Server poll() ( I/O) master . , ? No importa como. , , — .
tty_flip_buffer_push()
( pty_write):
void tty_flip_buffer_push(struct tty_port *port) { tty_schedule_flip(port); }
tty_schedule_flip()
, , :
void tty_schedule_flip(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; smp_store_release(&buf->tail->commit, buf->tail->used); queue_work(system_unbound_wq, &buf->work); }
, work (, - ) , — , flush_to_ldisc()
:
static void flush_to_ldisc(struct work_struct *work) { struct tty_port *port = container_of(work, struct tty_port, buf.work);
receive_buf()
__receive_buf()
, :
static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { struct n_tty_data *ldata = tty->disc_data; bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); if (ldata->real_raw) n_tty_receive_buf_real_raw(tty, cp, fp, count); else if (ldata->raw || (L_EXTPROC(tty) && !preops)) n_tty_receive_buf_raw(tty, cp, fp, count); else if (tty->closing && !L_EXTPROC(tty)) n_tty_receive_buf_closing(tty, cp, fp, count); else { if (ldata->lnext) { char flag = TTY_NORMAL; if (fp) flag = *fp++; n_tty_receive_char_lnext(tty, *cp++, flag); count--; } if (!preops && !I_PARMRK(tty)) n_tty_receive_buf_fast(tty, cp, fp, count); else n_tty_receive_buf_standard(tty, cp, fp, count); } if (read_cnt(ldata)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); wake_up_interruptible_poll(&tty->read_wait, EPOLLIN); } }
, n_tty_receive_buf ( , _raw) read_buf , TTY . PTM raw , read_buf. , PTM PTS , .
, :
... pty_write() ->
, PTM — PTS .
: PTM . GNOME Terminal Server "Hello, World!", read() PTM . read() write() — n_tty_read()
. , , — read_buf — . GNOME Terminal Server X Server, .
, "Hello, World!" :
-> PTY slave -> PTY master -> GNOME-TERMINAl-SERVER -> X Server -> ->
Conclusión
. :
- TTY
- ,
, ! - — , !
Fuentes