世界您好! 深入浸入终端


一篇有关Sishny printf分析的文章启发了我写这篇文章。 但是,错过了片刻之后数据进入终端设备的方式。 在本文中,我想纠正此缺陷并分析终端中的数据路径。 我们还将弄清Terminal与Shell的区别,Pseudoterminal是什么,Terminal Emulator如何工作等等。


基础知识


首先让我们了解一下Terminal,Shell,Console是什么,Terminal Emulator与普通Terminal有何不同,以及为什么如此命名。 已经有很多关于此的信息,因此您在这里不会听到新的消息。 这里几乎所有信息都是从Internet上获取的,我将在文章结尾处提供链接。 谁已经知道所有这些内容意味着什么,可以安全地跳过此部分。




航站楼


终端是显示器和键盘(即物理设备)的组合。 在终端成为这种特定组合之前,它们是一种称为电传打字机(电传打字机,电传打字机或TTY)的设备,即打印机和键盘的组合。 通常,多个终端连接到同一台计算机。 因此,可以在同一台计算机上为多个用户工作,并且每个人都有自己的会话,彼此独立。 终端之所以这样命名是因为它位于终端电缆的末端。


这是电传打字机


Teletype

这是终端


Terminal



主控台


控制台(控制台) -直接连接到计算机的终端。 事实是,大多数终端都是隐式连接的,但至少有一个直接连接到计算机。 允许控制台使用严格定义的人员圈子,因为它允许您配置计算机。




壳牌


如果前两个是物理设备,则此定义专门指软件。


Shell是命令行解释器。 主要目的是运行其他程序。 有大量不同的Shell。 最常见的是Bash(如Wikipedia所说,来自英语的Bourne Again SHell,是“ Born Again” Shell(即“复活” Shell)的双关语)。 其他示例:Dash(轻量级Shell,如果在/ bin / sh中运行二进制文件,则可用),Zsh。




当然,终端和控制台都不得不在现代中找到自己的反映。 因此,我们将进一步考虑诸如Terminal EmulatorVirtual Console之类的东西。


终端模拟器


终端仿真器 -老式终端的仿真器。 无法与X Window系统直接交互的程序-Bash,Vim等需要终端仿真器。


让我们首先确定终端的职责:


  1. 将用户输入传输到计算机
  2. 将计算机输出传送到显示器

因此,我们的终端仿真器执行的操作完全相同:它将用户输入传递给正在运行的程序,并在显示屏上显示该程序的输出。 无论如何,含义仍然存在-在用户和正在运行的程序之间,存在某种负责输入/输出的层。 终端仿真器的示例:gnome-terminal,xterm,konsole。


请不要混淆外壳程序和终端仿真器!
终端仿真器是一个GUI应用程序,即X Window系统中的一个窗口。 Shell是命令行解释器,即只是命令执行程序,它没有图形化的Shell。 说得很对,您没有启动Bash ,而是运行Terminal Emulator,后者在内部启动了Bash 。 终端仿真器和Bash绝对是2个不同的程序。 第一个完全负责输入/输出,第二个-处理命令。


在本文的进一步内容中,对终端的所有引用将引用终端仿真器。




虚拟控制台(虚拟终端)


按Ctrl + Alt + FN,其中N通常具有1到6的值。您刚才看到的称为虚拟控制台(虚拟控制台)或虚拟终端(虚拟终端)。 还记得我之前说的关于终端的内容吗? 许多终端连接到一台计算机,每个终端是一个单独的会话,彼此独立。 虚拟控制台重复了这个想法:您的计算机内部可能有几个独立的会话(但是,计算机资源显然仍是共享的)。


您可以将这个实体命名为Virtual Console和Virtual Terminal,因为根据定义,控制台是直接连接到计算机的终端,但是从某种意义上说,所有虚拟终端都直接连接到计算机。




TTY设备


每个终端都分配有自己的TTY设备 (终端设备),该设备提供控制台。 虽然您不太可能找到电传打字机,但是TTY的降低一直持续到今天。


TTY设备包含两个基本组件:


  1. 设备驱动 他负责将键盘输入传递给程序,并负责在屏幕上显示程序输出。
  2. TTY线学科 (俄语-线学科)。 线路规程是驱动程序访问接口,但是,它给TTY设备带来了很多逻辑。 可以说,线规代理会致电给驾驶员。 我们将在本文中找到该组件的职责范围是什么。

构建TTY设备:



TTY设备有3种类型:


  1. 控制台设备 -提供虚拟控制台操作。 该设备的输入和输出完全由内核控制。
  2. PTY设备 (伪终端)-在窗口界面中提供终端操作。 该设备的输入和输出由在用户空间中运行的终端仿真器控制。
  3. 串行设备 -直接与硬件通信。 它通常不直接使用,而是作为终端设备体系结构组织中的最低级别存在。

在本文中,我们将专门讨论第二种TTY设备-伪终端。




TTY线学科


我们开始研究TTY设备系列的规范。


线规的第一个重要特征是它负责处理I / O。 例如,这包括处理控制字符(请参阅控制字符 )和格式化输出。 例如,您输入了任何文本,但是突然您意识到自己在写东西时想错了并且想要删除它-这就是行规的作用。


我们将详细分析在终端中运行Bash时发生的确切情况。 默认情况下,TTY设备在启用回显的情况下以规范模式运行。 回声是您在屏幕上输入的字符的显示。


例如,当我们输入字符a ,该字符将发送到TTY设备,但会被设备的TTY行的纪律所截获。 她将一个字符读入内部缓冲区,看到echo模式已打开,然后在屏幕上显示该字符。 此时,仍然无法读取终端设备所连接的程序。 让我们按键盘上的backspace键。 符号^? 再次受到线规的拦截,后者意识到用户要删除最后输入的字符,因此从其内部缓冲区中删除了该字符,并从屏幕上也删除了该字符。 现在,如果我们按Enter键,则TTY线路纪律最终会将以前写入内部规范缓冲区(包括LF)的所有内容发送到终端设备的读取缓冲区。 同时,在屏幕上显示字符CR和LF,以将光标移至新行-这是输出的格式。


这就是规范模式的工作方式-仅在按Enter ,将控制字符处理并格式化输出后,才将所有输入的字符传输到设备。


TTY行编辑


TTY线路编辑是负责处理线路规程中输入的组件。 应该说, 行编辑是一个通用概念,它与输入处理有关。 例如,Bash和Vim有自己的行编辑。


我们可以使用stty程序控制当前TTY设备的线路的学科设置。 让我们尝试一下。


打开Bash或任何其他Shell,然后键入:


 stty icanon -echo 

现在尝试输入内容,您将看不到输入内容(不用担心,您仍然可以将输入内容传递给程序)。 您刚刚禁用了回显-即在屏幕上显示输入的字符。 现在输入:


 stty raw echo 

尝试输入内容。 您将看到结论是如何被打破的。 但是,为了获得更多效果,让我们进入Dash type /bin/sh 。 现在,尝试输入特殊字符( Ctrl +键盘上的任何字符)或仅按Enter 。 您感到困惑-屏幕上这些奇怪的字符是什么? 事实是,除了进入学科最简单的命令行管理程序之外,我们还禁用了Line Editing Bash,现在我们可以有力地观察到并主要包含了该行的原始学科模式。 此模式根本不处理输入,也不格式化输出。 为什么需要原始模式? 例如,对于Vim :它会打开整个终端窗口并处理输入本身,至少使行规的特殊符号不会与Vim本身的特殊符号相交。


为了获得更多理解,让我们看一下定制控制字符。 stty <control-character> <string>命令将帮助我们解决此问题。
输入Bash:


 stty erase 0 

现在, erase控制字符将分配给字符0backspace按钮通常很重要^? ,但是现在这个特殊字符将按字面意义发送到PTS读取缓冲区-自己尝试。 现在,您可以使用键盘上的0按钮擦除字符,因为您自己要求tty行规矩将输入的字符识别为erase控制字符。 您可以使用命令stty erase ^\?返回设置stty erase ^\? 或者只是关闭终端,因为我们只影响了当前的tty设备。


您可以在man stty中找到更多信息。




终端仿真器和伪终端


每次我们在X Window系统中打开一个新终端时,GNOME终端服务器都会产生一个新进程并在其中启动默认程序。 通常,这是某种Shell(例如Bash)。


与正在运行的程序的通信通过所谓的伪终端(pseudo-terminal,PTY)进行。 伪终端本身存在于内核中,但是它从用户空间(从终端仿真器)接收输入。


伪终端由以下两个虚拟TTY设备组成
1) PTY主站(PTM) -伪终端的开头部分。 GNOME终端服务器使用它来将键盘输入传输到终端内运行的程序,以及读取程序输出和显示输出。 GNOME终端服务器又通过X协议与X Window系统通信。
2) PTY从站(PTS) -伪终端的从站部分。 由终端内运行的程序使用,以读取键盘输入并在屏幕上显示输出。 至少程序本身是这样认为的(我将进一步解释这意味着什么)。


记录在PTS设备中的任何数据都是PTM设备的输入,也就是说,它在PTM设备上变得可读。 反之亦然:PTM设备中记录的任何数据都是PTS设备的输入。 这是GNOME终端服务器与终端中运行的程序进行通信的方式。 每个PTM设备都与自己的PTS设备关联。


启动新终端的过程如下所示:
1)GNOME终端服务器通过在特殊设备/ dev / ptmx上调用open()函数来创建主设备和从设备。 open()调用返回已创建的PTM设备master_fd的文件描述符。
2)GNOME终端服务器通过调用fork()创建一个新进程。 此过程将成为新的终端。
3)在PTS终端中,设备打开文件描述符0、1、2(分别为stdin,stdout和stderr)。 现在,标准终端I / O流到该设备。
4)通过调用exec()函数在终端中启动所需的程序。 通常会启动一些Shell(例如Bash)。 随后从Bash启动的任何程序都将具有与Bash本身相同的文件描述符,也就是说,程序流将定向到PTS设备。


您可以使用ls -la /proc/self/fd亲自查看标准终端输出流的方向:


PTS设备位于/ dev / pts / N路径上,而到PTM设备的路径根本使我们不感兴趣。 事实是GNOME终端服务器已经具有用于打开的PTM设备的文件描述符,并且不需要它的路径,但是,在子进程中,我们必须通过调用open()函数在标准输出流上打开PTS设备,这需要文件的路径。


还记得我说过使用PTS设备的程序只认为它直接与终端通信吗? 事实是PTS也是终端设备 (TTY设备),但是PTS设备与实际TTY设备之间的区别在于PTS设备不是从键盘接收输入,而是从主设备接收输入,并且输出不进入显示器,而是进入显示器。主设备。 这就是为什么伪终端如此命名的原因-伪终端仅模仿(还是??)终端。 终端仿真器和伪终端之间的区别在于,终端仿真器只是一个图形程序,允许您直接在窗口界面内运行终端,但是此功能是使用伪终端实现的。


PTS设备是TTY设备这一事实非常重要。 原因如下:


  1. 终端设备所连接的程序具有常规终端的所有功能。 例如:禁用回显,禁用/启用规范视图。
  2. 该程序在知道已连接终端设备的情况下(据说该程序具有控制终端),可以交互工作并要求用户输入。 例如,要求输入用户名和密码。
  3. 还有一个TTY线路纪律,因此我们能够在控制字符到达程序之前对其进行处理,以及格式化程序的输出。

PTM设备也是TTY设备,但是由于它不用作控制终端,因此它没有任何作用。 此外,由于PTM设备的线路规则设置为原始模式,因此,在将数据从PTS传输到PTM设备时不执行处理。 但是,从用户空间进行的read()write()调用仍然首先由这两种设备上的线路规程服务。 正如我们稍后将看到的那样,这一刻将发挥更大的作用。


GNOME终端服务器与终端中运行的程序之间的通信过程如下:



值得更详细地研究线路规则在伪终端的两个部分之间进行通信时所扮演的角色。 在此,线路规则负责处理从PTM传递到PTS设备的数据 ,并负责将数据从伪终端的一部分传递到另一部分。 当我们进入PTS设备驱动程序时,我们会参与PTM设备的线路规程,反之亦然。




虚拟设备


您可能会想到,您可以沿着/ dev / pts / N路径打开文件,并从文件中写入或读取数据,就像从常规文本文件中读取数据一样? 是的,由于Unix的基本原理(所有内容都是文件),因此类Unix系统上的所有设备都是文件。 但是,没有特殊的设备文件(英语-设备文件)是文本文件。 此类设备称为虚拟设备 -即它们仅存在于内存中,而不存在于磁盘上。


不要尝试将这些文件作为常规文本文件打开。 但是,您可以通过write()read()操作使用这些设备,其调用将由设备驱动程序提供。 让我们尝试去做。


打开两个终端窗口,然后在每个命令中输入tty 。 该命令将显示哪个TTY设备正在为当前活动的终端服务。 现在输入echo "Hello, World!" > /dev/pts/N echo "Hello, World!" > /dev/pts/N ,在第一个终端窗口中,其中N是第二个窗口设备的PTS索引,切换到第二个窗口,您将在第一个窗口中看到输入。 现在,您已将数据写入第二个窗口的PTS设备, 就好像是由该终端中运行的程序完成的一样





伪终端设备


我们越来越接近本文的最后一部分,但是在此之前,我们先来看看Linux的“幕后”-在内核级别考虑伪终端的设备。 会有很多代码,但是我将尝试尽可能详细地解释每个给定的代码块,减少不重要的细节并按顺序进行。


在开始之前,我们将介绍所谓的“组件篮”。 当我们沿着核心前进时,我们将向它添加越来越多的组件,并在它们之间找到联系。 希望这可以帮助您更好地了解伪终端设备。 让我们开始吧。


Linux启动时,它将加载必要的设备驱动程序。 我们的伪终端也有这样的驱动程序。 其注册从调用此函数开始:


 static int __init pty_init(void) { legacy_pty_init(); unix98_pty_init(); // <- ,    return 0; } device_initcall(pty_init); // ,       

对于所有现代系统,将unix98_pty_init()函数:


 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"); /* Now create the /dev/ptmx special device */ 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"); 

在这里,我们对三件事感兴趣:


  1. 为pty主驱动程序和pty从设备调用tty_set_operatons
  2. ptmx_open函数,用于在打开特殊设备/ dev / ptmx时创建伪终端的两个部分。 重要说明:/ dev / ptmx不是PTM设备,而只是用于创建新的伪终端的接口。
  3. 注册PTM和PTS设备驱动程序。

让我们按顺序进行:


1. tty_set_operations


tty_set_operations()函数只是为当前驱动程序设置一个函数表:


 void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op) { driver->ops = op; }; 

tty_operations结构是一个功能表,用于访问设备TTY驱动程序功能。


我将在结构pty_unix98_opsptm_unix98_ops最重要的东西,它们是伪终端相应部分的功能表:


 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, // ... }; static const struct tty_operations pty_unix98_ops = { .install = pty_unix98_install, .remove = pty_unix98_remove, .open = pty_open, .close = pty_close, .write = pty_write, // ... }; 

在这里,您可以观察到pty_write函数, pty_write函数已经在Sishny printf上的文章中很熟悉了-我们稍后再返回。


让我们将此结构添加到组件篮中:


如您所见,两个驱动程序的主要方法完全没有区别。 顺便说一下,请注意,没有用于read()操作的功能-没有像pty_read() 。 事实是,阅读将完全由学科来服务。 因此,我们了解了生产线规程的第二个重要特征-从TTY设备读取数据。




2. ptmx_open


现在让我们继续到ptmx_open()


 static int ptmx_open(struct inode *inode, struct file *filp) { struct tty_struct *tty; //    -   ! fsi = devpts_acquire(filp); //     devpts index = devpts_new_index(fsi); //       /dev/pts // ... tty = tty_init_dev(ptm_driver, index); // ... devpts_pty_new(fsi, index, tty->link); //     /dev/pts retval = ptm_driver->ops->open(tty, filp); //  PTM ,   } 

我们对tty_init_dev()函数感兴趣,其中第一个参数是PTM设备驱动程序,第二个参数是设备索引。 在这里,我们离开了PTY驱动程序的责任范围,并转到该文件,该文件仅对常规TTY设备负责,而对伪终端一无所知。


 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); /* * Structures all installed ... call the ldisc open routines. */ retval = tty_ldisc_setup(tty, tty->link); //  ,       return tty; } 

首先,我们将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); //  tty_struct tty_ldisc_init(tty) //      tty_struct tty->driver = driver; //       tty_struct tty->ops = driver->ops; //        tty_struct.     tty->index = idx; //   tty  return tty; } 

我们唯一感兴趣的是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; //        tty_struct return 0; } 

哪个调用tty_ldisc_get()


 static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) { struct tty_ldisc *ld; //    struct tty_ldisc_ops *ldops; //     ldops = get_ldops(disc); //      .   ,       .   - N_TTY ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL | __GFP_NOFAIL); ld->ops = ldops; //       ld->tty = tty; //    tty_struct   .          return ld; } 

因此,我们检查了对alloc_tty_struct()函数的调用,该函数创建了tty_struct结构以及行规-tty_ldisc结构。 两种结构相互之间都有链接。 让我们仔细看看这些结构。


  • tty_struct是用于访问TTY设备驱动程序和其他一些字段的结构。 看起来像这样:

 struct tty_struct { struct tty_driver *driver; //  TTY  const struct tty_operations *ops; //  .    ,   driver->ops,       int index; //   struct tty_ldisc *ldisc; //     struct tty_struct *link; //     PTY // ... } 

  • tty_ldisc是该设备的TTY行的规则结构。 它仅包含两个字段,外观如下:

 struct tty_ldisc { struct tty_ldisc_ops *ops; //    struct tty_struct *tty; //   tty_struct  .       }; 

似乎没什么复杂的? 让我们将到目前为止考虑的所有结构添加到我们的篮中,并按照与代码中连接它们相同的方式链接它们:
tty_struct


但是我们只为PTM设备创建了tty_struct。 PTS设备呢? 为此,我们返回tty_init_dev()函数,并回想一下我们应该调用tty_driver_install_tty()函数:


 /** * This method is responsible * for ensuring any need additional structures are allocated and configured. */ 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); } 

注释告诉我们,此方法负责创建各种其他结构。 PTS设备将成为我们的附加结构。 我承认,这对我来说非常令人惊讶,因为它是该死的整个设备,而不仅仅是一些额外的结构! 但是我们都知道所有设备都只是某种结构,所以继续前进。 好的,什么是driver-> ops->在这里安装 ? 为此,请再次查看PTM驱动程序的功能表:


 static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, // ... 

并且我们了解到我们对pty_unix98_install()函数感兴趣:


 static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) { return pty_common_install(driver, tty, false); } 

其中调用pty_common_install()函数:


 static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, bool legacy) { struct tty_struct *o_tty; // tty_struct    PTY -    PTS  //    ,       install.   ,   PTM     tty_struct,        if (driver->subtype != PTY_TYPE_MASTER) return -EIO; o_tty = alloc_tty_struct(driver->other, idx); tty->link = o_tty; o_tty->link = 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 .




. , /dev/ptmx . , PTS , , PTM , :





世界您好!


. "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; //... ret = file->f_op->write(file, buf, count, pos); //... return ret; } 

write() . , :


 static const struct file_operations tty_fops = { // ... .write = tty_write, // ... 

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() :


 /** * n_tty_write - write function for tty * @tty: tty device * @file: file object * @buf: userspace buffer pointer * @nr: size of I/O */ 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; // b - ,       "Hello, World!".          int c; //    //     PTS ,  write()    0,  ,     while (nr > 0) { c = tty->ops->write(tty, b, nr); //  write()       TTY  if (!c) break; b += c; //     nr -= c; //      :  -  -  -  } } 

, "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; //      PTY.    -  PTM  if (c > 0) { //    PTM  c = tty_insert_flip_string(to->port, buf, c); //     ,       if (c) { tty_flip_buffer_push(to->port); tty_wakeup(tty); } } return c; } 

:


  __vfs_write() -> // 1- :   tty_write() -> do_tty_write() -> n_tty_write() -> // 2- :   pty_write() // 3- :  

. , 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); //      int space = __tty_buffer_request_room(port, goal, flags); //     struct tty_buffer *tb = port->buf.tail; //       if (unlikely(space == 0)) break; memcpy(char_buf_ptr(tb, tb->used), chars, space); //      tb->used += space; copied += space; chars += space; /* There is a small chance that we need to split the data over several buffers. If this is the case we must loop */ } while (unlikely(size > copied)); return copied; } 

, flip buffer , , . , — PTM , .


, "Hello, World!" PTM . GNOME Terminal Server poll() ( I/O) master . , ? 不管如何 , , — .


tty_flip_buffer_push() ( pty_write):


 /** * tty_flip_buffer_push - terminal * @port: tty port to push * * Queue a push of the terminal flip buffers to the line discipline. * Can be called from IRQ/atomic context. * * In the event of the queue being busy for flipping the work will be * held off and retried later. */ void tty_flip_buffer_push(struct tty_port *port) { tty_schedule_flip(port); } 

tty_schedule_flip() , , :


 /** * tty_schedule_flip - push characters to ldisc * @port: tty port to push from * * Takes any pending buffers and transfers their ownership to the * ldisc side of the queue. It then schedules those characters for * processing by the line discipline. */ void tty_schedule_flip(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; /* paired w/ acquire in flush_to_ldisc(); ensures * flush_to_ldisc() sees buffer data. */ 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); //   tty_port PTM . tty_port -       TTY  struct tty_bufhead *buf = &port->buf; struct tty_buffer *head = buf->head; // ... receive_buf(port, head); // ... } 

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() -> // 3- :  PTS  tty_insert_flip_string + tty_flip_buffer_push() -> tty_schedule_flip() -> --- //    PTM  flush_to_ldisc() -> // 2- :   PTM  receive_buf() -> n_tty_receive_buf -> n_tty_receive_buf_common -> __receive_buf() 

, 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 -> ->  



结论


. :


  1. TTY
  2. ,

, ! - — , !


资料来源


Source: https://habr.com/ru/post/zh-CN460257/


All Articles