织梦CMS - 轻松建站从此开始!

罗索

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

jackyhwei 发布于 2010-05-05 12:49 点击:次 
在linux表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制 终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题.
TAG:

一:前言
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()接口代码如下所示:

  1. struct tty_driver *alloc_tty_driver(int lines) 
  2.       struct tty_driver *driver; 
  3.       driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL); 
  4.       if (driver) { 
  5.                driver->magic = TTY_DRIVER_MAGIC; 
  6.                driver->num = lines; 
  7.                /* later we'll move allocation of tables here */ 
  8.       } 
  9.       return driver; 


这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.
在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.
tty_register_driver()用来注册一个tty_driver。代码如下:

  1. int tty_register_driver(struct tty_driver *driver) 
  2.       int error; 
  3.       int i; 
  4.       dev_t dev; 
  5.       void **p = NULL; 
  6.       //TTY_DRIVER_INSTALLED:已安装的 
  7.       if (driver->flags & TTY_DRIVER_INSTALLED) 
  8.                return 0; 
  9.       //TTY_DRIVER_DEVPTS_MEM:使用devpts进行动态内存映射 
  10.       if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { 
  11.                p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL); 
  12.                if (!p) 
  13.                         return -ENOMEM; 
  14.       } 
  15.       //注册字符设备号 
  16.       //如果没有指定driver->major 
  17.       if (!driver->major) { 
  18.                error = alloc_chrdev_region(&dev, driver->minor_start, 
  19.                                                        driver->num, driver->name); 
  20.                if (!error) { 
  21.                         driver->major = MAJOR(dev); 
  22.                         driver->minor_start = MINOR(dev); 
  23.                } 
  24.       } else { 
  25.                dev = MKDEV(driver->major, driver->minor_start); 
  26.                error = register_chrdev_region(dev, driver->num, driver->name); 
  27.       } 
  28.       if (error 
  29.                kfree(p); 
  30.                return error; 
  31.       } 
  32.       if (p) { 
  33.                driver->ttys = (struct tty_struct **)p; 
  34.                driver->termios = (struct ktermios **)(p + driver->num); 
  35.                driver->termios_locked = (struct ktermios **) 
  36.                                                                 (p + driver->num * 2); 
  37.       } else { 
  38.                driver->ttys = NULL; 
  39.                driver->termios = NULL; 
  40.                driver->termios_locked = NULL; 
  41.       } 
  42.       //注册字符设备 
  43.       cdev_init(&driver->cdev, &tty_fops); 
  44.       driver->cdev.owner = driver->owner; 
  45.       error = cdev_add(&driver->cdev, dev, driver->num); 
  46.       if (error) { 
  47.                unregister_chrdev_region(dev, driver->num); 
  48.                driver->ttys = NULL; 
  49.                driver->termios = driver->termios_locked = NULL; 
  50.                kfree(p); 
  51.                return error; 
  52.       } 
  53.       //指定默认的put_char 
  54.       if (!driver->put_char) 
  55.                driver->put_char = tty_default_put_char; 
  56.       mutex_lock(&tty_mutex); 
  57.       list_add(&driver->tty_drivers, &tty_drivers); 
  58.       mutex_unlock(&tty_mutex); 
  59.       //如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理 
  60.       if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { 
  61.                for (i = 0; i num; i++) 
  62.                       tty_register_device(driver, i, NULL); 
  63.       } 
  64.       proc_tty_register_driver(driver); 
  65.       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()。代码如下:

  1. static int tty_open(struct inode *inode, struct file *filp) 
  2.       struct tty_struct *tty; 
  3.       int noctty, retval; 
  4.       struct tty_driver *driver; 
  5.       int index; 
  6.       dev_t device = inode->i_rdev; 
  7.       unsigned short saved_flags = filp->f_flags; 
  8.       nonseekable_open(inode, filp); 
  9. retry_open: 
  10.       //O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端 
  11.       //noctty:需不需要更改当前进程的控制终端 
  12.       noctty = filp->f_flags & O_NOCTTY; 
  13.       index = -1; 
  14.       retval = 0; 
  15.       mutex_lock(&tty_mutex); 
  16.       //设备号(5,0) 即/dev/tty.表示当前进程的控制终端 
  17.       if (device == MKDEV(TTYAUX_MAJOR, 0)) { 
  18.                tty = get_current_tty(); 
  19.                //如果当前进程的控制终端不存在,退出 
  20.                if (!tty) { 
  21.                         mutex_unlock(&tty_mutex); 
  22.                         return -ENXIO; 
  23.                } 
  24.                //取得当前进程的tty_driver 
  25.                driver = tty->driver; 
  26.                index = tty->index; 
  27.                filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ 
  28.                /* noctty = 1; */ 
  29.                goto got_driver; 
  30.       } 
  31. #ifdef CONFIG_VT 
  32.       //设备号(4,0).即/dev/tty0:表示当前的控制台 
  33.       if (device == MKDEV(TTY_MAJOR, 0)) { 
  34.                extern struct tty_driver *console_driver; 
  35.                driver = console_driver; 
  36.                //fg_console: 表示当前的控制台 
  37.                index = fg_console; 
  38.                noctty = 1; 
  39.                goto got_driver; 
  40.       } 
  41. #endif 
  42.       //设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console() 
  43.       if (device == MKDEV(TTYAUX_MAJOR, 1)) { 
  44.                driver = console_device(&index); 
  45.                if (driver) { 
  46.                         /* Don't let /dev/console block */ 
  47.                         filp->f_flags |= O_NONBLOCK; 
  48.                         noctty = 1; 
  49.                         goto got_driver; 
  50.                } 
  51.                mutex_unlock(&tty_mutex); 
  52.                return -ENODEV; 
  53.       } 
  54.       //以文件的设备号为关键字,到tty_drivers中搜索所注册的driver 
  55.       driver = get_tty_driver(device, &index); 
  56.       if (!driver) { 
  57.                mutex_unlock(&tty_mutex); 
  58.                return -ENODEV; 
  59.       } 
  60. got_driver: 
  61.       //index表示它的次设备号 
  62.       retval = init_dev(driver, index, &tty); 
  63.       mutex_unlock(&tty_mutex); 
  64.       if (retval) 
  65.                return retval; 
  66.       filp->private_data = tty; 
  67.       file_move(filp, &tty->tty_files); 
  68.       check_tty_count(tty, "tty_open"); 
  69.       if (tty->driver->type == TTY_DRIVER_TYPE_PTY && 
  70.          tty->driver->subtype == PTY_TYPE_MASTER) 
  71.                noctty = 1; 
  72. #ifdef TTY_DEBUG_HANGUP 
  73.       printk(KERN_DEBUG "opening %s...", tty->name); 
  74. #endif 
  75.       if (!retval) { 
  76.                if (tty->driver->open) 
  77.                         retval = tty->driver->open(tty, filp); 
  78.                else 
  79.                         retval = -ENODEV; 
  80.       } 
  81.       filp->f_flags = saved_flags; 
  82.       if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && 
  83.                                                        !capable(CAP_SYS_ADMIN)) 
  84.                retval = -EBUSY; 
  85.       if (retval) { 
  86. #ifdef TTY_DEBUG_HANGUP 
  87.                printk(KERN_DEBUG "error %d in opening %s...", retval, 
  88.                          tty->name); 
  89. #endif 
  90.                release_dev(filp); 
  91.                if (retval != -ERESTARTSYS) 
  92.                         return retval; 
  93.                if (signal_pending(current)) 
  94.                         return retval; 
  95.                schedule(); 
  96.                /* 
  97.                * Need to reset f_op in case a hangup happened. 
  98.                */ 
  99.                if (filp->f_op == &hung_up_tty_fops) 
  100.                         filp->f_op = &tty_fops; 
  101.                goto retry_open; 
  102.       } 
  103.       mutex_lock(&tty_mutex); 
  104.       spin_lock_irq(&current->sighand->siglock); 
  105.       //设置当前进程的终端 
  106.       if (!noctty && 
  107.          current->signal->leader && 
  108.          !current->signal->tty && 
  109.          tty->session == NULL) 
  110.                __proc_set_tty(current, tty); 
  111.       spin_unlock_irq(&current->sighand->siglock); 
  112.       mutex_unlock(&tty_mutex); 
  113.       tty_audit_opening(); 
  114.       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))的操作:

  1. Tty_ldisc_get(): 
  2. struct tty_ldisc *tty_ldisc_get(int disc) 
  3.       unsigned long flags; 
  4.       struct tty_ldisc *ld; 
  5.       if (disc = NR_LDISCS) 
  6.                return NULL; 
  7.       spin_lock_irqsave(&tty_ldisc_lock, flags); 
  8.       ld = &tty_ldiscs[disc]; 
  9.       /* Check the entry is defined */ 
  10.       if (ld->flags & LDISC_FLAG_DEFINED) { 
  11.                /* If the module is being unloaded we can't use it */ 
  12.                if (!try_module_get(ld->owner)) 
  13.                         ld = NULL; 
  14.                else /* lock it */ 
  15.                         ld->refcount++; 
  16.       } else 
  17.                ld = NULL; 
  18.       spin_unlock_irqrestore(&tty_ldisc_lock, flags); 
  19.       return ld; 


这个函数的操作为到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.
tty_ldisc_assign操作如下:

  1. static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) 
  2.       tty->ldisc = *ld; 
  3.       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().代码如下:

  1. static ssize_t tty_write(struct file *file, const char __user *buf, 
  2.                                                        size_t count, loff_t *ppos) 
  3.       struct tty_struct *tty; 
  4.       struct inode *inode = file->f_path.dentry->d_inode; 
  5.       ssize_t ret; 
  6.       struct tty_ldisc *ld; 
  7.       tty = (struct tty_struct *)file->private_data; 
  8.       if (tty_paranoia_check(tty, inode, "tty_write")) 
  9.                return -EIO; 
  10.       if (!tty || !tty->driver->write || 
  11.                (test_bit(TTY_IO_ERROR, &tty->flags))) 
  12.                         return -EIO; 
  13.       ld = tty_ldisc_ref_wait(tty); 
  14.       if (!ld->write) 
  15.                ret = -EIO; 
  16.       else 
  17.                ret = do_tty_write(ld->write, tty, file, buf, count); 
  18.       tty_ldisc_deref(ld); 
  19.       return ret; 


在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.
如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.
Do_tty_write代码分段分析如下:

  1. static inline ssize_t do_tty_write( 
  2.       ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), 
  3.       struct tty_struct *tty, 
  4.       struct file *file, 
  5.       const char __user *buf, 
  6.       size_t count) 
  7.       ssize_t ret, written = 0; 
  8.       unsigned int chunk; 
  9.       ret = tty_write_lock(tty, file->f_flags & O_NDELAY); 
  10.       if (ret 
  11.                return ret; 
  12.       /* 
  13.       * We chunk up writes into a temporary buffer. This 
  14.       * simplifies low-level drivers immensely, since they 
  15.       * don't have locking issues and user mode accesses. 
  16.       * 
  17.       * But if TTY_NO_WRITE_SPLIT is set, we should use a 
  18.       * big chunk-size.. 
  19.       * 
  20.       * The default chunk-size is 2kB, because the NTTY 
  21.       * layer has problems with bigger chunks. It will 
  22.       * claim to be able to handle more characters than 
  23.       * it actually does. 
  24.       * 
  25.       * FIXME: This can probably go away now except that 64K chunks 
  26.       * are too likely to fail unless switched to vmalloc... 
  27.       */ 
  28.       chunk = 2048; 
  29.       if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) 
  30.                chunk = 65536; 
  31.       if (count 
  32.                chunk = count; 
  33.       /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ 
  34.       if (tty->write_cnt 
  35.                unsigned char *buf; 
  36.                if (chunk 
  37.                         chunk = 1024; 
  38.                buf = kmalloc(chunk, GFP_KERNEL); 
  39.                if (!buf) { 
  40.                         ret = -ENOMEM; 
  41.                         goto out; 
  42.                } 
  43.                kfree(tty->write_buf); 
  44.                tty->write_cnt = chunk; 
  45.                tty->write_buf = buf; 
  46.       } 


默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.
Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里
Tty->write_cnt是临时缓存区的大小。
在这里,必须要根据一次写的数据量对这个临时缓存区做调整

  1.       /* Do the write .. */ 
  2.       for (;;) { 
  3.                size_t size = count; 
  4.                if (size > chunk) 
  5.                         size = chunk; 
  6.                ret = -EFAULT; 
  7.                if (copy_from_user(tty->write_buf, buf, size)) 
  8.                         break
  9.                lock_kernel(); 
  10.                ret = write(tty, file, tty->write_buf, size); 
  11.                unlock_kernel(); 
  12.                if (ret 
  13.                         break
  14.                written += ret; 
  15.                buf += ret; 
  16.                count -= ret; 
  17.                if (!count) 
  18.                         break
  19.                ret = -ERESTARTSYS; 
  20.                if (signal_pending(current)) 
  21.                         break
  22.                cond_resched(); 
  23.       } 
  24.       if (written) { 
  25.                struct inode *inode = file->f_path.dentry->d_inode; 
  26.                inode->i_mtime = current_fs_time(inode->i_sb); 
  27.                ret = written; 
  28.       } 
  29. out: 
  30.       tty_write_unlock(tty); 
  31.       return ret; 


后面的操作就比较简单了。先将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的时间戳.
Write操作的流程图如下示:

在这里,我们只看到将数据写放到了ldisc->write().没有看到与tty_driver相关的部份。实际上在ldisc中对写入的数据做预处理过后,还是会调用tty_driver->write()将其写入硬件.
4.3:设备文件的read操作

  1. static ssize_t tty_read(struct file *file, char __user *buf, size_t count, 
  2.                         loff_t *ppos) 
  3.       int i; 
  4.       struct tty_struct *tty; 
  5.       struct inode *inode; 
  6.       struct tty_ldisc *ld; 
  7.       tty = (struct tty_struct *)file->private_data; 
  8.       inode = file->f_path.dentry->d_inode; 
  9.       if (tty_paranoia_check(tty, inode, "tty_read")) 
  10.                return -EIO; 
  11.       if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) 
  12.                return -EIO; 
  13.       /* We want to wait for the line discipline to sort out in this 
  14.          situation */ 
  15.       ld = tty_ldisc_ref_wait(tty); 
  16.       lock_kernel(); 
  17.       if (ld->read) 
  18.                i = (ld->read)(tty, file, buf, count); 
  19.       else 
  20.                i = -EIO; 
  21.       tty_ldisc_deref(ld); 
  22.       unlock_kernel(); 
  23.       if (i > 0) 
  24.                inode->i_atime = current_fs_time(inode->i_sb); 
  25.       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的操作我们之后以控制终端为例进行分析.


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/26185/showart_1386422.html

(秩名)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201005/9330.html]
本文出处:blog.chinaunix.net 作者:秩名
顶一下
(9)
90%
踩一下
(1)
10%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容