重温旧的 API

作者:Greg Kroah-Hartman

自本专栏开始以来已有一年多,由于 Linux 内核的快速发展,当时写的大部分内容现在都已过时。本月,我们将介绍之前讨论过的不同内核 API 的变化。

tty 变化

tty 层一直是周围最稳定的内核 API 之一,而且表现良好。缺乏适当的引用计数、在需要的地方强加锁定以及 tty 设备分配的奇怪方式都归因于它的年代久远。值得庆幸的是,Al Viro 最近在 2.5 内核系列中清理了 tty 层中的许多旧的无用代码。因此,对于任何想要编写新的 tty 驱动程序的人来说,许多事情都发生了变化。

在 2002 年 8 月和 10 月的 LJ 期刊 [可在 /article/5896/article/6226 获取] 中,我们讨论了 tty 层以及如何使用所有必要的功能回调来填充 struct tty_driver 结构。从那时起,创建了一个新的结构 struct tty_operations,用于保存所有功能回调。struct tty_driver 仍然包含较旧的函数指针,因此创建了一个新函数 tty_set_operations 来复制这些指针。希望这种重复很快就会消除。

许多变量已从 struct tty_driver 中移除。table、termios、termios_locked 和 refcount 字段已消失。tty 层现在处理所有适当的锁定和引用计数,而无需强制各个 tty 驱动程序为这些锁分配空间。

magic 和 num 变量不再需要由 tty 驱动程序显式设置。这些变量现在在一个新的函数 alloc_tty_driver 中设置,所有 tty 驱动程序必须调用该函数来为 tty 驱动程序分配空间。此驱动程序将要支持的不同 tty 设备的数量作为参数传递给该函数。例如,原始 tty 文章中介绍的 tiny tty 驱动程序将创建 struct tty_driver 为

/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);

过去,为 tty 驱动程序选择合适的名称很困难,因为 devfs 内核选项重载了 name 字段的用法。Christoph Hellwig 最终通过在 struct tty_driver 结构中引入一个名为 devfs_name 的新变量来解决了这个混乱。现在,name 字段应设置为一个简单的、小的名称,该名称显示在 tty proc 文件中。devfs_name 应设置为 devfs 用于为此驱动程序生成设备节点的名称。

在 2.5 内核系列中,MOD_INC_USE_COUNT 和 MOD_DEC_USE_COUNT 宏被声明为过于竞争,并且内核中几乎所有对它们的使用都被删除。为了做到这一点,模块引用计数被推高了一层,高于原始调用。这允许内核在跳转到模块之前增加模块的引用计数。同样,当内核完成模块时,它知道自动递减计数。

此模块更改在 tty 层中完成,因此没有 tty 驱动程序应包含 MOD_* 宏。相反,在 struct tty_driver 中添加了一个 owner 变量,以显示哪个模块拥有 tty 驱动程序。以下行显示如何正确设置此变量

tiny_tty_driver->owner = THIS_MODULE;

这告诉 tty 核心哪个模块与此 tty 驱动程序相关。

对于这些 tty 更改,以下是如何正确初始化 tty 驱动程序并在内核中注册它

#define TINY_TTY_MAJOR240 /* experimental range */
#define TINY_TTY_MINORS	255
                        /* use the whole major up */

static struct tty_operations serial_ops = {
    .open =       tiny_open,
    .close =      tiny_close,
    .write =      tiny_write,
    .write_room = tiny_write_room,
};

static struct tty_driver *tiny_tty_driver;

static int __init tiny_init(void)
{
    /* allocate the tty driver */
    tiny_tty_driver =
        alloc_tty_driver(TINY_TTY_MINORS);

    /* initialize the tty driver */
    tiny_tty_driver->owner = THIS_MODULE;
    tiny_tty_driver->driver_name = "tiny_tty";
    tiny_tty_driver->name = "ttty";
    tiny_tty_driver->devfs_name = "tts/ttty%d";
    tiny_tty_driver->major = TINY_TTY_MAJOR,
    tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
    tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL,
    tiny_tty_driver->flags =
        TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
    tiny_tty_driver->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag =
        B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver, &serial_ops);
    if (tty_register_driver(tiny_tty_driver)) {
       printk(KERN_ERR
              "failed to register tiny tty driver");
       return -1;
    }

    printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION);
    return 0;
}

static void __exit tiny_exit (void)
{
    tty_unregister_driver(tiny_tty_driver);
}

module_init(tiny_init);
module_exit(tiny_exit);

tty 呼叫设备现在也已完全从内核中删除。2.5 内核树中任何使用呼叫支持的 tty 驱动程序都已将其删除。

更少的 ioctl

除了 tty 结构更改之外,还移除了一些 tty ioctl,特别是 TIOCMGET、TIOCMBIS、TIOCMBIC 和 TIOCMSET ioctl。它们已被添加到 struct tty_operations 结构中的两个新函数回调 tiocmget 和 tiocmset 替换。这些函数定义为

int (*tiocmget)(struct tty_struct *tty,
                struct file *file);
int (*tiocmset)(struct tty_struct *tty,
                struct file *file,
                unsigned int set,
                unsigned int clear);

当 tty 核心或用户想要知道特定 tty 端口的当前线路设置是什么时,将调用 tiocmget 函数。这几乎与旧的 TIOCMGET ioctl 调用工作方式相同。线路状态定义为不同的 MSR_* 值,但它们只是从函数调用返回,而不是像旧的 ioctl 要求驱动程序那样复制回用户空间。这是一个 tiocmget 函数可能看起来如何的示例

int
tiny_tiocmget(struct tty_struct *tty,
              struct file *file)
{
    struct tiny_private *tp = tty->private;
    unsigned int msr = tp->msr;
    unsigned int mcr = tp->mcr;
    unsigned int result = 0;

    result = ((mcr & MCR_DTR)    ? TIOCM_DTR: 0)
                                    /* DTR is set */
              | ((mcr & MCR_RTS) ? TIOCM_RTS: 0)
                                    /* RTS is set */
              | ((msr & MSR_CTS) ? TIOCM_CTS: 0)
                                    /* CTS is set */
              | ((msr & MSR_CD)  ? TIOCM_CAR: 0)
                         /* Carrier detect is set */
              | ((msr & MSR_RI)  ? TIOCM_RI:  0)
                         /* Ring Indicator is set */
              | ((msr & MSR_DSR) ? TIOCM_DSR: 0);
                                    /* DSR is set */

    return result;

当 tty 核心或用户想要设置或清除任何不同的线路设置时,将调用 tiocmset 函数。这个单一函数取代了 TIOCMBIS、TIOCMBIC 和 TIOCMSET ioctl 调用。此函数中的 set 和 clear 变量用于告知要设置哪些线路设置以及要清除哪些线路设置。不能同时要求清除和设置相同的线路设置,因此变量的处理顺序无关紧要。这是一个 tiocmset 函数的示例

int
tiny_tiocmset(struct tty_struct *tty,
              struct file *file,
              unsigned int set, unsigned int clear)
{
    struct tiny_private *tp = tty->private;

    if (set & TIOCM_RTS)
        mcr |= MCR_RTS;
    if (set & TIOCM_DTR)
        mcr |= MCR_RTS;
    if (set & TIOCM_LOOP)
        mcr |= MCR_LOOPBACK;

    if (clear & TIOCM_RTS)
        mcr &= ~MCR_RTS;
    if (clear & TIOCM_DTR)
        mcr &= ~MCR_RTS;
    if (clear & TIOCM_LOOP)
        mcr &= ~MCR_LOOPBACK;

    /* set the new MCR value in the device */
    tp->mcr = mcr;
    return 0;
}

usbserial 核心也受到这些 tty 核心更改的一些影响。tiocmget 和 tiocmset 函数已添加到 struct usb_serial_device_type 结构中。如果 usbserial 驱动程序提供这些回调,则对这些函数的 tty 调用将传递到较低的 usbserial 驱动程序。

致谢

我要感谢 Al Viro、Christoph Hellwig 和 Russell King 最终开始致力于清理 tty 层,使其达到内核其余部分的适当标准。他们的更改对于简化 tty 驱动程序接口起到了重要作用,使驱动程序作者可以专注于特定的硬件实现,而不必像以前那样担心内核交互。

Greg Kroah-Hartman 目前是各种不同驱动程序子系统的 Linux 内核维护者。他在 IBM 工作,从事与 Linux 内核相关的工作,可以通过 greg@kroah.com 联系到他。

加载 Disqus 评论