TTY 线路规程

TTY 线路规程处理来自/发送到 tty 设备的所有传入和传出字符。默认线路规程是 N_TTY。如果为 tty 建立任何其他规程失败,它也是一个回退方案。如果甚至 N_TTY 也失败,则 N_NULL 接管。它永远不会失败,但也不会处理任何字符——它会丢弃它们。

注册

线路规程使用 tty_register_ldisc() 注册,并传递 ldisc 结构。在注册时,规程必须准备好使用,并且可能在调用返回成功之前被使用。如果调用返回错误,则不会被调用。不要重用 ldisc 编号,因为它们是用户空间 ABI 的一部分,并且覆盖现有的 ldisc 将导致守护程序吃掉您的计算机。即使使用相同的数据,您也绝不能在顶部重新注册线路规程,否则您的计算机再次会被守护程序吃掉。为了删除线路规程,请调用 tty_unregister_ldisc()

请注意此警告:ldisc 表中注册的 tty_ldisc 结构的引用计数域统计使用此规程的线路数。tty 内 tty_ldisc 结构的引用计数统计此瞬间 ldisc 的活动用户数。实际上,它统计了 ldisc 方法中正在执行的线程数(加上即将进入和退出的线程数,尽管这个细节并不重要)。

int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc)

安装线路规程

参数

struct tty_ldisc_ops *new_ldisc

指向 ldisc 对象的指针

描述

将新的线路规程安装到内核中。该规程设置为未引用,然后从此开始可供内核使用。

锁定:采用 tty_ldiscs_lock 来防止 ldisc 竞争

void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc)

卸载线路规程

参数

struct tty_ldisc_ops *ldisc

ldisc 编号

描述

从内核中删除线路规程,前提是它当前未使用。

锁定:采用 tty_ldiscs_lock 来防止 ldisc 竞争

其他函数

void tty_ldisc_flush(struct tty_struct *tty)

刷新线路规程队列

参数

struct tty_struct *tty

要刷新 ldisc 的 tty

描述

刷新此 tty 的线路规程队列(如果有)和 tty 翻转缓冲区。

int tty_set_ldisc(struct tty_struct *tty, int disc)

设置线路规程

参数

struct tty_struct *tty

要设置的终端

int disc

线路规程编号

描述

设置 tty 线的规程。必须从进程上下文中调用。ldisc 更改逻辑必须保护自身免受任何重叠的 ldisc 更改(包括 pty 对的另一端)、tty/pty 对的一侧关闭以及最终的挂起。

线路规程操作参考

struct tty_ldisc_ops

ldisc 操作

定义:

struct tty_ldisc_ops {
    char *name;
    int num;
    int (*open)(struct tty_struct *tty);
    void (*close)(struct tty_struct *tty);
    void (*flush_buffer)(struct tty_struct *tty);
    ssize_t (*read)(struct tty_struct *tty, struct file *file, u8 *buf, size_t nr, void **cookie, unsigned long offset);
    ssize_t (*write)(struct tty_struct *tty, struct file *file, const u8 *buf, size_t nr);
    int (*ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
    int (*compat_ioctl)(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, const struct ktermios *old);
    __poll_t (*poll)(struct tty_struct *tty, struct file *file, struct poll_table_struct *wait);
    void (*hangup)(struct tty_struct *tty);
    void (*receive_buf)(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count);
    void (*write_wakeup)(struct tty_struct *tty);
    void (*dcd_change)(struct tty_struct *tty, bool active);
    size_t (*receive_buf2)(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count);
    void (*lookahead_buf)(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count);
    struct module *owner;
};

成员

名称

此 ldisc 在 /proc/tty/ldiscs 中呈现的名称

编号

为此 ldisc 保留的 N_* 编号(N_TTYN_HDLC 等)

打开

[TTY] int ()(struct tty_struct *tty)

当线路规程与 tty 关联时调用此函数。在成功完成之前,不会发生对该 tty 的线路规程的其他调用。它应该初始化 ldisc 所需的任何状态,并将 tty->receive_room 设置为线路规程愿意从驱动程序接收的最大数据量,并在单次调用 receive_buf() 时。返回错误将阻止附加 ldisc。

可选。可以休眠。

关闭

[TTY] void ()(struct tty_struct *tty)

当线路规程正在关闭时调用此函数,原因要么是 tty 正在关闭,要么是 tty 正在更改为使用新的线路规程。在执行时,没有其他用户将进入此 tty 的 ldisc 代码。

可选。可以休眠。

flush_buffer

[TTY] void ()(struct tty_struct *tty)

此函数指示线路规程清除其可能已排队以传递给用户模式进程的任何输入字符的缓冲区。它可以在打开和关闭之间的任何时间点调用。

可选。

读取

[TTY] ssize_t ()(struct tty_struct *tty, struct file *file, u8 *buf, size_t nr)

当用户请求从 tty 读取数据时,会调用此函数。线路规程将返回它为用户缓冲的所有字符。如果未定义此函数,用户将收到一个 EIO 错误。可能会并行发生多次读取调用,ldisc 必须处理序列化问题。

可选:除非提供,否则返回 EIO。可以休眠。

[TTY] ssize_t ()(struct tty_struct *tty, struct file *file, const u8 *buf, size_t nr)

当用户请求写入 tty 时,会调用此函数。线路规程将字符传递给底层 tty 设备进行传输,可以选择先对字符进行一些处理。如果未定义此函数,用户将收到一个 EIO 错误。

可选:除非提供,否则返回 EIO。可以休眠。

ioctl

[TTY] int ()(struct tty_struct *tty, unsigned int cmd, unsigned long arg)

当用户请求一个不由 tty 层或底层 tty 驱动程序处理的 ioctl 时,会调用此函数。它旨在用于影响线路规程操作的 ioctl。请注意,ioctl 的搜索顺序为 (1) tty 层,(2) tty 底层驱动程序,(3) 线路规程。因此,底层驱动程序可以在线路规程有机会看到它之前“捕获” ioctl 请求。

可选。

compat_ioctl

[TTY] int ()(struct tty_struct *tty, unsigned int cmd, unsigned long arg)

处理 64 位系统上 32 位进程的 ioctl 调用。

请注意,只有那些既不是“指向兼容结构的指针”也不是 tty 通用的 ioctl 才会在此处处理。一些需要整数或指向字长敏感结构的指针的私有 ioctl 属于此处,但大多数 ldisc 将会很高兴地将其保留为 NULL

可选。

set_termios

[TTY] void ()(struct tty_struct *tty, const struct ktermios *old)

此函数通知线路规程 termios 结构已发生更改。

可选。

poll

[TTY] int ()(struct tty_struct *tty, struct file *file, struct poll_table_struct *wait)

当用户尝试在 tty 设备上选择/轮询时,会调用此函数。处理轮询请求完全是线路规程的责任。

可选。

hangup

[TTY] void ()(struct tty_struct *tty)

在挂断时调用。告诉规程它应该停止与 tty 驱动程序的 I/O。驱动程序应尽快执行此操作,但应等待任何挂起的驱动程序 I/O 完成。不会再发生对 ldisc 代码的进一步调用。

可选。可以休眠。

receive_buf

[DRV] void ()(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count)

底层 tty 驱动程序会调用此函数,以将硬件接收到的字符发送到线路规程进行处理。cp 是指向设备接收到的输入字符缓冲区的指针。fp 是指向标志字节数组的指针,该数组指示字符是否在接收时存在奇偶校验错误等。fp 可以为 NULL,表示接收到的所有数据都是 TTY_NORMAL

可选。

write_wakeup

[DRV] void ()(struct tty_struct *tty)

底层 tty 驱动程序会调用此函数,以发出信号,通知线路规程应尝试向底层驱动程序发送更多字符以进行传输。如果线路规程没有更多数据要发送,它可以直接返回。如果线路规程确实有一些数据要发送,请启动一个 tasklet 或 workqueue 来进行实际的数据传输。不要在此钩子中发送数据,这可能会导致死锁。

可选。

dcd_change

[DRV] void ()(struct tty_struct *tty, bool active)

告诉规程 DCD 引脚已更改其状态。仅由 N_PPS(每秒脉冲)线路规程使用。

可选。

receive_buf2

[DRV] ssize_t ()(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count)

底层 tty 驱动程序会调用此函数,以将硬件接收到的字符发送到线路规程进行处理。cp 是指向设备接收到的输入字符缓冲区的指针。fp 是指向标志字节数组的指针,该数组指示字符是否在接收时存在奇偶校验错误等。fp 可以为 NULL,表示接收到的所有数据都是 TTY_NORMAL。如果已分配,则优先使用此函数进行自动流量控制。

可选。

lookahead_buf

[DRV] void ()(struct tty_struct *tty, const u8 *cp, const u8 *fp, size_t count)

底层 tty 驱动程序会为未被 ->receive_buf() 或 ->receive_buf2() 使用的字符调用此函数。它对于处理高优先级字符(例如软件流量控制字符)很有用,这些字符否则可能会卡在中间缓冲区中,直到 tty 有空间接收它们。ldisc 必须能够稍后处理同一字符的 ->receive_buf() 或 ->receive_buf2() 调用(例如,通过跳过由 ->lookahead_buf() 已经处理的高优先级字符的操作)。

可选。

owner

包含此 ldisc 的模块(用于引用计数)

描述

此结构定义了 tty 线路规程实现和 tty 例程之间的接口。可以定义上述例程。除非另有说明,否则它们是可选的,并且可以使用 NULL 指针填充。

标记为 [TTY] 的钩子从 TTY 核心调用,[DRV] 的钩子从 tty_driver 端调用。

驱动程序访问

线路规程方法可以调用底层硬件驱动程序的方法。这些方法记录在 struct tty_operations 中。

TTY 标志

线路规程方法可以访问 tty_struct.flags 字段。请参阅 TTY 结构

锁定

tty 层调用线路规程函数的调用者需要获取线路规程锁。来自驱动程序端的调用也是如此,但尚未强制执行。

struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)

等待 tty ldisc

参数

struct tty_struct *tty

tty 设备

描述

取消引用终端的线路规程并获取对其的引用。如果线路规程正在变化,则耐心等待直到它更改。

注意 1:不得从 IRQ/计时器上下文中调用。调用者还必须小心,不要持有其他锁,这些锁会与规程更改(例如现有的 ldisc 引用,我们会检查)发生死锁。

注意 2:file_operations 例程(read/poll/write)应使用此函数来等待任何 ldisc 生命周期事件完成。

返回

如果 tty 已挂断并且未用新的文件描述符重新打开,则返回 NULL,否则返回有效的 ldisc 引用

struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)

获取 tty ldisc

参数

struct tty_struct *tty

tty 设备

描述

取消引用终端的线路规程并获取对其的引用。如果线路规程正在变化,则返回 NULL。可以从 IRQ 和计时器函数调用。

void tty_ldisc_deref(struct tty_ldisc *ld)

释放 tty ldisc 引用

参数

struct tty_ldisc *ld

要释放的引用

描述

撤消 tty_ldisc_ref()tty_ldisc_ref_wait() 的效果。可以在 IRQ 上下文中调用。

虽然这些函数比旧代码稍慢,但它们的影响应该很小,因为大多数接收逻辑都使用翻转缓冲区,并且它们只需要在通过驱动程序向上推送位时才需要获取引用。

注意:tty_ldisc_ops.open()tty_ldisc_ops.close()tty_driver.set_ldisc() 函数在 ldisc 不可用时被调用。因此,如果在这些函数中使用 tty_ldisc_ref(),则会在此情况下失败。ldisc 和驱动程序代码在调用自己的函数时必须小心。

内部函数

struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)

获取 ldisc 的引用

参数

struct tty_struct *tty

tty 设备

int disc

ldisc 编号

描述

获取线路规程的引用。处理引用计数和模块锁定计数。如果规程不可用,则会加载其模块(如果可能)。

锁定:采用 tty_ldiscs_lock 来防止 ldisc 竞争

返回

  • - 如果规程索引不在 [N_TTY .. NR_LDISCS] 范围内,或者规程未注册,则返回 EINVAL

  • - 如果 request_module() 无法加载或注册规程,则返回 EAGAIN

  • - 如果分配失败,则返回 ENOMEM

  • 否则,返回指向规程的指针并增加引用计数

void tty_ldisc_put(struct tty_ldisc *ld)

释放 ldisc

参数

struct tty_ldisc *ld

要释放的 ldisc

描述

tty_ldisc_get() 的补充。

void tty_set_termios_ldisc(struct tty_struct *tty, int disc)

设置 ldisc 字段

参数

struct tty_struct *tty

tty 结构

int disc

线路规程编号

描述

对于实际的处理器来说,这可能有点过分了,但它们不在热路径上,所以一点规程不会有任何坏处。

与线路规程相关的 tty_struct 字段被重置,以防止 ldisc 驱动程序为新的 ldisc 实例重用过时的信息。

锁定:获取 termios_rwsem

int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)

打开线路规程

参数

struct tty_struct *tty

我们要打开 ldisc 的 tty

struct tty_ldisc *ld

要打开的规程

描述

一个辅助打开方法。也是一个方便的调试和检查点。

锁定:始终在 BTM 已持有的情况下调用。

void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)

关闭线路规程

参数

struct tty_struct *tty

我们要打开 ldisc 的 tty

struct tty_ldisc *ld

要关闭的规程

描述

一个辅助关闭方法。也是一个方便的调试和检查点。

int tty_ldisc_failto(struct tty_struct *tty, int ld)

ldisc 回退的辅助函数

参数

struct tty_struct *tty

要在其上打开 ldisc 的 tty

int ld

我们试图回退到的 ldisc

描述

当切换回旧的 ldisc 失败并且我们需要附加某些东西时,尝试恢复 tty 的辅助函数。

void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)

tty ldisc 更改的辅助函数

参数

struct tty_struct *tty

要恢复的 tty

struct tty_ldisc *old

先前的 ldisc

描述

当由于打开错误而导致线路规程更改失败时,恢复先前的线路规程或 N_TTY

void tty_ldisc_kill(struct tty_struct *tty)

拆除 ldisc

参数

struct tty_struct *tty

要释放的 tty

描述

执行 ldisc 的最终关闭并重置 tty->ldisc

void tty_reset_termios(struct tty_struct *tty)

重置终端状态

参数

struct tty_struct *tty

要重置的 tty

描述

将终端恢复到驱动程序的默认状态。

int tty_ldisc_reinit(struct tty_struct *tty, int disc)

重新初始化 tty ldisc

参数

struct tty_struct *tty

要重新初始化的 tty

int disc

要重新初始化的线路规程

描述

通过关闭当前实例(如果存在)并打开一个新实例,完全重新初始化线路规程状态。如果打开新的非 N_TTY 实例时发生错误,则会删除该实例,并将 tty->ldisc 重置为 NULL。然后,调用者可以使用 N_TTY 重试。

返回

如果成功,则为 0,否则为错误代码 < 0

void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)

挂断 ldisc 重置

参数

struct tty_struct *tty

正在挂断的 tty

bool reinit

是否重新初始化 tty

描述

某些 tty 设备在收到挂断事件时会重置其 termios。在这种情况下,我们还必须在重置 termios 数据之前正确切换回 N_TTY

锁定:我们可以获取 ldisc 互斥锁,因为其余代码会小心处理此问题。

在 pty 对的情况下,这发生在 tty 本身的 close() 路径中,因此我们必须小心锁定规则。

int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)

打开线路规程

参数

struct tty_struct *tty

正在关闭的 tty

struct tty_struct *o_tty

pty/tty 对的成对 tty

描述

在首次打开 tty/pty 对时调用,目的是设置线路规程并将其绑定到 tty。由于设备尚未激活,因此不存在锁定问题。

void tty_ldisc_release(struct tty_struct *tty)

释放线路规程

参数

struct tty_struct *tty

正在关闭的 tty(或 pty 对的一端)

描述

在最终关闭 tty 或 pty 对时调用,目的是关闭线路规程层。退出时,每个 tty 的 ldisc 均为 NULL

int tty_ldisc_init(struct tty_struct *tty)

为新的 tty 设置 ldisc

参数

struct tty_struct *tty

正在分配的 tty

描述

为新分配的 tty 设置线路规程对象。请注意,调用此函数时,tty 结构尚未完全设置好。

void tty_ldisc_deinit(struct tty_struct *tty)

为新的 tty 清理 ldisc

参数

struct tty_struct *tty

最近分配的 tty

描述

调用此函数时,tty 结构不能完全设置好 ( tty_ldisc_setup() )。