Linux设备模型之tty驱动架构分析

简介: ------------------------------------------ 本文系本站原创,欢迎转载! 转载请注明出处:http://ericxiao.cublog.cn/ ------------------------------------------ 一:前言 Tty这个名称源于电传打字节的简称。
------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://ericxiao.cublog.cn/
------------------------------------------
一:前言
Tty这个名称源于电传打字节的简称。在linux表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题.
二:tty驱动概貌
Tty架构如下所示:
如上图所示,用户空间主要是通过设备文件同tty_core交互.tty_core根据用空间操作的类型再选择跟line discipline和tty_driver交互.例如设置硬件的ioctl指令就直接交给tty_driver处理。Read和write操作就会交给line discipline处理.
Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端 线程 的输入与输出规范设置.主要用来进行输入/输出数据的预处理。处理之后。就会将数据交给tty_driver
Tty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串.将其传给终端设备。
值得注意的是,这个架构没有为tty_drivero提供read操作。也就是说tty_core  和line discipline都没有办法从tty_driver里直接读终端信息。这是因为tty_driver对就的hardware并不一定是输入数据和输出数据的共同负载者。例如控制终端,输出设备是显示器。输入设备是键盘。基于这样的原理。在line discipline中有一个输入缓存区。并提供了一个名叫receive_buf()的接口函数。对应的终端设备只要调用line discipine的receiver_buf函数,将数据写入到输入缓存区就可以了。
如果一个设备同时是输入设备又是输出设备。那在设备的中断处理中调用receive_buf()将数据写入即可.
 
三:tty驱动接口分析
具体的tty驱动设计可以参考LDD3。这里只对它的接口实现做一个分析.tty driver的所有操作都包含在tty_driver中。内核即供了一个名叫alloc_tty_driver()来分配这个tty_driver。当然我们也可以在自己的驱动中将它定义成一个静态的结构。对tty_driver进行一些必要的初始化之后,调用tty_register_driver()将其注册.
alloc_tty_driver()接口代码如下所示:
struct tty_driver *alloc_tty_driver(int lines)
{
         struct tty_driver *driver;
 
         driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
         if (driver) {
                   driver->magic = TTY_DRIVER_MAGIC;
                   driver->num = lines;
                   /* later we'll move allocation of tables here */
         }
         return driver;
}
这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.
在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.
 
tty_register_driver()用来注册一个tty_driver。代码如下:
int tty_register_driver(struct tty_driver *driver)
{
         int error;
         int i;
         dev_t dev;
         void **p = NULL;
 
         //TTY_DRIVER_INSTALLED:已安装的
         if (driver->flags & TTY_DRIVER_INSTALLED)
                   return 0;
 
         //TTY_DRIVER_DEVPTS_MEM:使用devpts进行动态内存映射
         if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
                   p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
                   if (!p)
                            return -ENOMEM;
         }
 
         //注册字符设备号
         //如果没有指定driver->major
         if (!driver->major) {
                   error = alloc_chrdev_region(&dev, driver->minor_start,
                                                        driver->num, driver->name);
                   if (!error) {
                            driver->major = MAJOR(dev);
                            driver->minor_start = MINOR(dev);
                   }
         } else {
                   dev = MKDEV(driver->major, driver->minor_start);
                   error = register_chrdev_region(dev, driver->num, driver->name);
         }
         if (error
                   kfree(p);
                  return error;
         }
 
         if (p) {
                   driver->ttys = (struct tty_struct **)p;
                   driver->termios = (struct ktermios **)(p + driver->num);
                   driver->termios_locked = (struct ktermios **)
                                                                 (p + driver->num * 2);
         } else {
                   driver->ttys = NULL;
                   driver->termios = NULL;
                   driver->termios_locked = NULL;
         }
 
         //注册字符设备
         cdev_init(&driver->cdev, &tty_fops);
         driver->cdev.owner = driver->owner;
         error = cdev_add(&driver->cdev, dev, driver->num);
         if (error) {
                   unregister_chrdev_region(dev, driver->num);
                   driver->ttys = NULL;
                   driver->termios = driver->termios_locked = NULL;
                   kfree(p);
                   return error;
         }
 
         //指定默认的put_char
         if (!driver->put_char)
                   driver->put_char = tty_default_put_char;
 
         mutex_lock(&tty_mutex);
         list_add(&driver->tty_drivers, &tty_drivers);
         mutex_unlock(&tty_mutex);
 
         //如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理
         if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
                   for (i = 0; i num; i++)
                       tty_register_device(driver, i, NULL);
         }
         proc_tty_register_driver(driver);
         return 0;
}
这个函数操作比较简单。就是为tty_driver创建字符设备。然后将字符设备的操作集指定为tty_fops.并且将tty_driver挂载到tty_drivers链表中.其实这个链表的作用跟我们之前分析的input子系统中的input_dev[ ]数组类似。都是以设备号为关键字找到对应的driver.
特别的。如果没有定义TTY_DRIVER_DYNAMIC_DEV.还会在sysfs中创建一个类设备.这样主要是为了udev管理设备.
以流程图的方式将上述操作表示如下:
 
 
四:设备文件的操作
设备文件的操作是本节分析的重点。它的主要操作是将各项操作对应到ldsic或者是tty_driver.
 
4.1:打开tty设备的操作
从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open()。代码如下:
static int tty_open(struct inode *inode, struct file *filp)
{
         struct tty_struct *tty;
         int noctty, retval;
         struct tty_driver *driver;
         int index;
         dev_t device = inode->i_rdev;
         unsigned short saved_flags = filp->f_flags;
 
         nonseekable_open(inode, filp);
 
retry_open:
         //O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端
 
         //noctty:需不需要更改当前进程的控制终端
         noctty = filp->f_flags & O_NOCTTY;
         index  = -1;
         retval = 0;
 
         mutex_lock(&tty_mutex);
 
         //设备号(5,0) 即/dev/tty.表示当前进程的控制终端
         if (device == MKDEV(TTYAUX_MAJOR, 0)) {
                   tty = get_current_tty();
                   //如果当前进程的控制终端不存在,退出
                   if (!tty) {
                            mutex_unlock(&tty_mutex);
                            return -ENXIO;
                   }
 
                   //取得当前进程的tty_driver
                   driver = tty->driver;
                   index = tty->index;
                   filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
                   /* noctty = 1; */
                   goto got_driver;
         }
#ifdef CONFIG_VT
         //设备号(4,0).即/dev/tty0:表示当前的控制台
         if (device == MKDEV(TTY_MAJOR, 0)) {
                   extern struct tty_driver *console_driver;
                   driver = console_driver;
                   //fg_console: 表示当前的控制台
                   index = fg_console;
                   noctty = 1;
                   goto got_driver;
         }
#endif
         //设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()
         if (device == MKDEV(TTYAUX_MAJOR, 1)) {
                   driver = console_device(&index);
                   if (driver) {
                            /* Don't let /dev/console block */
                            filp->f_flags |= O_NONBLOCK;
                            noctty = 1;
                            goto got_driver;
                   }
                   mutex_unlock(&tty_mutex);
                   return -ENODEV;
         }
 
         //以文件的设备号为关键字,到tty_drivers中搜索所注册的driver
         driver = get_tty_driver(device, &index);
         if (!driver) {
                   mutex_unlock(&tty_mutex);
                   return -ENODEV;
         }
got_driver:
         //index表示它的次设备号
         retval = init_dev(driver, index, &tty);
         mutex_unlock(&tty_mutex);
         if (retval)
                   return retval;
 
         filp->private_data = tty;
         file_move(filp, &tty->tty_files);
         check_tty_count(tty, "tty_open");
         if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
             tty->driver->subtype == PTY_TYPE_MASTER)
                   noctty = 1;
#ifdef TTY_DEBUG_HANGUP
         printk(KERN_DEBUG "opening %s...", tty->name);
#endif
         if (!retval) {
                   if (tty->driver->open)
                            retval = tty->driver->open(tty, filp);
                   else
                            retval = -ENODEV;
         }
         filp->f_flags = saved_flags;
 
         if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
                                                        !capable(CAP_SYS_ADMIN))
                   retval = -EBUSY;
 
         if (retval) {
#ifdef TTY_DEBUG_HANGUP
                   printk(KERN_DEBUG "error %d in opening %s...", retval,
                          tty->name);
#endif
                   release_dev(filp);
                   if (retval != -ERESTARTSYS)
                            return retval;
                   if (signal_pending(current))
                            return retval;
                   schedule();
                   /*
                    * Need to reset f_op in case a hangup happened.
                    */
                   if (filp->f_op == &hung_up_tty_fops)
                            filp->f_op = &tty_fops;
                   goto retry_open;
         }
 
         mutex_lock(&tty_mutex);
         spin_lock_irq(&current->sighand->siglock);
 
         //设置当前进程的终端
         if (!noctty &&
             current->signal->leader &&
             !current->signal->tty &&
             tty->session == NULL)
                   __proc_set_tty(current, tty);
         spin_unlock_irq(&current->sighand->siglock);
         mutex_unlock(&tty_mutex);
         tty_audit_opening();
         return 0;
}
注意在这里有个容易忽略的操作:init_dev()。
Init_dev() -à initialize_tty_struct() à tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
看一下tty_ldisc_assign(tty, tty_ldisc_get(N_TTY))的操作:
Tty_ldisc_get():
struct tty_ldisc *tty_ldisc_get(int disc)
{
         unsigned long flags;
         struct tty_ldisc *ld;
 
         if (disc = NR_LDISCS)
                   return NULL;
 
         spin_lock_irqsave(&tty_ldisc_lock, flags);
 
         ld = &tty_ldiscs[disc];
         /* Check the entry is defined */
         if (ld->flags & LDISC_FLAG_DEFINED) {
                   /* If the module is being unloaded we can't use it */
                   if (!try_module_get(ld->owner))
                            ld = NULL;
                   else /* lock it */
                            ld->refcount++;
         } else
                   ld = NULL;
         spin_unlock_irqrestore(&tty_ldisc_lock, flags);
         return ld;
}
这个函数的操作为到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.
 
tty_ldisc_assign操作如下:
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
{
         tty->ldisc = *ld;
         tty->ldisc.refcount = 0;
}
即将取出来的idisc作为tty->ldisc字段.
在这段代码中涉及到了tty_driver,tty_struct, struct tty_ldisc.这三者之间的关系用下图表示如下:
 
在这里,为tty_struct的ldisc是默认指定为tty_ldiscs[N_TTY].该ldisc对应的是控制终端的线路规范。可以在用空间用带TIOCSETD的ioctl调用进行更改.
将上述open用流程图的方式表示如下:
 
4.2:设备文件的write操作
设备文件的write操作对应tty_fops->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;
         struct inode *inode = file->f_path.dentry->d_inode;
         ssize_t ret;
         struct tty_ldisc *ld;
 
         tty = (struct tty_struct *)file->private_data;
         if (tty_paranoia_check(tty, inode, "tty_write"))
                   return -EIO;
         if (!tty || !tty->driver->write ||
                   (test_bit(TTY_IO_ERROR, &tty->flags)))
                            return -EIO;
 
         ld = tty_ldisc_ref_wait(tty);
         if (!ld->write)
                   ret = -EIO;
         else
                   ret = do_tty_write(ld->write, tty, file, buf, count);
         tty_ldisc_deref(ld);
         return ret;
}
在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.
如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.
Do_tty_write代码分段分析如下:
static inline ssize_t do_tty_write(
         ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
         struct tty_struct *tty,
         struct file *file,
         const char __user *buf,
         size_t count)
{
         ssize_t ret, written = 0;
         unsigned int chunk;
 
         ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
         if (ret
                   return ret;
 
         /*
          * We chunk up writes into a temporary buffer. This
          * simplifies low-level drivers immensely, since they
          * don't have locking issues and user mode accesses.
          *
          * But if TTY_NO_WRITE_SPLIT is set, we should use a
          * big chunk-size..
          *
          * The default chunk-size is 2kB, because the NTTY
          * layer has problems with bigger chunks. It will
          * claim to be able to handle more characters than
          * it actually does.
          *
          * FIXME: This can probably go away now except that 64K chunks
          * are too likely to fail unless switched to vmalloc...
          */
         chunk = 2048;
         if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
                   chunk = 65536;
         if (count
                   chunk = count;
 
         /* write_buf/write_cnt is protected by the atomic_write_lock mutex */
         if (tty->write_cnt
                   unsigned char *buf;
 
                   if (chunk
                            chunk = 1024;
 
                   buf = kmalloc(chunk, GFP_KERNEL);
                   if (!buf) {
                            ret = -ENOMEM;
                            goto out;
                   }
                   kfree(tty->write_buf);
                   tty->write_cnt = chunk;
                   tty->write_buf = buf;
         }
默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.
Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里
Tty->write_cnt是临时缓存区的大小。
在这里,必须要根据一次写的数据量对这个临时缓存区做调整
 
         /* Do the write .. */
         for (;;) {
                   size_t size = count;
                   if (size > chunk)
                            size = chunk;
                   ret = -EFAULT;
                   if (copy_from_user(tty->write_buf, buf, size))
                            break;
                   lock_kernel();
                   ret = write(tty, file, tty->write_buf, size);
                   unlock_kernel();
                   if (ret
                            break;
                   written += ret;
                   buf += ret;
                   count -= ret;
                   if (!count)
                            break;
                   ret = -ERESTARTSYS;
                   if (signal_pending(current))
                            break;
                   cond_resched();
         }
         if (written) {
                   struct inode *inode = file->f_path.dentry->d_inode;
                   inode->i_mtime = current_fs_time(inode->i_sb);
                   ret = written;
         }
out:
         tty_write_unlock(tty);
         return ret;
}
后面的操作就比较简单了。先将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的时间戳.
Write操作的流程图如下示:
 
在这里,我们只看到将数据写放到了ldisc->write().没有看到与tty_driver相关的部份。实际上在ldisc中对写入的数据做预处理过后,还是会调用tty_driver->write()将其写入硬件.
 
4.3:设备文件的read操作
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
                            loff_t *ppos)
{
         int i;
         struct tty_struct *tty;
         struct inode *inode;
         struct tty_ldisc *ld;
 
         tty = (struct tty_struct *)file->private_data;
         inode = file->f_path.dentry->d_inode;
         if (tty_paranoia_check(tty, inode, "tty_read"))
                   return -EIO;
         if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
                   return -EIO;
 
         /* We want to wait for the line discipline to sort out in this
            situation */
         ld = tty_ldisc_ref_wait(tty);
         lock_kernel();
         if (ld->read)
                   i = (ld->read)(tty, file, buf, count);
         else
                   i = -EIO;
         tty_ldisc_deref(ld);
         unlock_kernel();
         if (i > 0)
                   inode->i_atime = current_fs_time(inode->i_sb);
         return i;
}
这个read操作就更简单。直接调用ldsic->read()完成工作
流程图如下:
 
五:小结
在tty设备文件的操作中。Open操作会进行一系统初始化。然后调用ldsic->open tty_driver->open。在write和read调用中只tty_core只会用到ldisc->wirte/ldisc->read.除了上面分析的几个操作之外,还有一个ioctl操作,以及它封装的几个termios。这些ioctl类的操作会直接和tty_driver相关联.
在这一节里,只对tty的构造做一个分析,具体ldisc的操作我们之后以控制终端为例进行分析.
 
阅读(77) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册

相关文章
|
2月前
|
机器学习/深度学习 自然语言处理 分布式计算
大规模语言模型与生成模型:技术原理、架构与应用
本文深入探讨了大规模语言模型(LLMs)和生成模型的技术原理、经典架构及应用。介绍了LLMs的关键特点,如海量数据训练、深层架构和自监督学习,以及常见模型如GPT、BERT和T5。同时,文章详细解析了生成模型的工作原理,包括自回归模型、自编码器和GANs,并讨论了这些模型在自然语言生成、机器翻译、对话系统和数据增强等领域的应用。最后,文章展望了未来的发展趋势,如模型压缩、跨模态生成和多语言多任务学习。
275 3
|
9天前
|
机器学习/深度学习 编解码 vr&ar
NeurIPS 2024最佳论文,扩散模型的创新替代:基于多尺度预测的视觉自回归架构
本文详细解读NeurIPS 2024最佳论文《视觉自回归建模:基于下一尺度预测的可扩展图像生成》。该研究提出VAR模型,通过多尺度token图和VAR Transformer结构,实现高效、高质量的图像生成,解决了传统自回归模型在二维结构信息、泛化能力和计算效率上的局限。实验表明,VAR在图像质量和速度上超越现有扩散模型,并展示出良好的扩展性和零样本泛化能力。未来研究将聚焦于文本引导生成和视频生成等方向。
55 8
NeurIPS 2024最佳论文,扩散模型的创新替代:基于多尺度预测的视觉自回归架构
|
3月前
|
存储 分布式计算 API
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
149 0
|
23天前
|
存储 运维 监控
Linux--深入理与解linux文件系统与日志文件分析
深入理解 Linux 文件系统和日志文件分析,对于系统管理员和运维工程师来说至关重要。文件系统管理涉及到文件的组织、存储和检索,而日志文件则记录了系统和应用的运行状态,是排查故障和维护系统的重要依据。通过掌握文件系统和日志文件的管理和分析技能,可以有效提升系统的稳定性和安全性。
44 7
|
1月前
|
机器学习/深度学习 测试技术 定位技术
新扩散模型OmniGen一统图像生成,架构还高度简化、易用
近期,一篇题为“OmniGen: Unified Image Generation”的论文介绍了一种新型扩散模型OmniGen,旨在统一图像生成任务。OmniGen架构简洁,无需额外模块即可处理多种任务,如文本到图像生成、图像编辑等。该模型通过修正流优化,展现出与现有模型相当或更优的性能,尤其在图像编辑和视觉条件生成方面表现突出。OmniGen仅含3.8亿参数,却能有效处理复杂任务,简化工作流程。尽管如此,OmniGen仍存在对文本提示敏感、文本渲染能力有限等问题,未来研究将继续优化其架构与功能。
59 16
|
26天前
|
监控 安全 Linux
启用Linux防火墙日志记录和分析功能
为iptables启用日志记录对于监控进出流量至关重要
|
2月前
|
机器学习/深度学习 自然语言处理 C++
TSMamba:基于Mamba架构的高效时间序列预测基础模型
TSMamba通过其创新的架构设计和训练策略,成功解决了传统时间序列预测模型面临的多个关键问题。
226 4
TSMamba:基于Mamba架构的高效时间序列预测基础模型
|
2月前
|
网络协议 网络架构
TCP/IP协议架构:四层模型详解
在网络通信的世界里,TCP/IP协议栈是构建现代互联网的基础。本文将深入探讨TCP/IP协议涉及的四层架构,以及每一层的关键功能和作用。
242 5
|
2月前
|
机器学习/深度学习 存储 人工智能
【AI系统】模型演进与经典架构
本文探讨了AI计算模式对AI芯片设计的重要性,通过分析经典模型结构设计与演进、模型量化与压缩等核心内容,揭示了神经网络模型的发展现状及优化方向。文章详细介绍了神经网络的基本组件、主流模型结构、以及模型量化和剪枝技术,强调了这些技术在提高模型效率、降低计算和存储需求方面的关键作用。基于此,提出了AI芯片设计应考虑支持神经网络计算逻辑、高维张量存储与计算、灵活的软件配置接口、不同bit位数的计算单元和存储格式等建议,以适应不断发展的AI技术需求。
54 5
|
2月前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。