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

来源:互联网 发布:解放军军装大全软件 编辑:程序博客网 时间:2024/06/05 01:06
Linux设备模型之tty驱动架构分析
    

一:前言

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

01.struct tty_driver *alloc_tty_driver(int lines) 02.{ 03.      struct tty_driver *driver; 04.      driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL); 05.      if (driver) { 06.               driver->magic = TTY_DRIVER_MAGIC; 07.               driver->num = lines; 08.               /* later we'll move allocation of tables here */ 09.      } 10.      return driver; 11.} 

这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.

在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.

tty_register_driver()用来注册一个tty_driver。代码如下:

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

这个函数操作比较简单。就是为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()。代码如下:

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

注意在这里有个容易忽略的操作: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))的操作:

01.Tty_ldisc_get(): 02.struct tty_ldisc *tty_ldisc_get(int disc) 03.{ 04.      unsigned long flags; 05.      struct tty_ldisc *ld; 06.      if (disc = NR_LDISCS) 07.               return NULL; 08.      spin_lock_irqsave(&tty_ldisc_lock, flags); 09.      ld = &tty_ldiscs[disc]; 10.      /* Check the entry is defined */ 11.      if (ld->flags & LDISC_FLAG_DEFINED) { 12.               /* If the module is being unloaded we can't use it */ 13.               if (!try_module_get(ld->owner)) 14.                        ld = NULL; 15.               else /* lock it */ 16.                        ld->refcount++; 17.      } else 18.               ld = NULL; 19.      spin_unlock_irqrestore(&tty_ldisc_lock, flags); 20.      return ld; 21.} 

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

01.static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) 02.{ 03.      tty->ldisc = *ld; 04.      tty->ldisc.refcount = 0; 05.}

即将取出来的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().代码如下:

01.static ssize_t tty_write(struct file *file, const char __user *buf, 02.                                                       size_t count, loff_t *ppos) 03.{ 04.      struct tty_struct *tty; 05.      struct inode *inode = file->f_path.dentry->d_inode; 06.      ssize_t ret; 07.      struct tty_ldisc *ld; 08.      tty = (struct tty_struct *)file->private_data; 09.      if (tty_paranoia_check(tty, inode, "tty_write")) 10.               return -EIO; 11.      if (!tty || !tty->driver->write || 12.               (test_bit(TTY_IO_ERROR, &tty->flags))) 13.                        return -EIO; 14.      ld = tty_ldisc_ref_wait(tty); 15.      if (!ld->write) 16.               ret = -EIO; 17.      else 18.               ret = do_tty_write(ld->write, tty, file, buf, count); 19.      tty_ldisc_deref(ld); 20.      return ret; 21.} 

在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.

如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.

Do_tty_write代码分段分析如下:

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

默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.

Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里

Tty->write_cnt是临时缓存区的大小。

在这里,必须要根据一次写的数据量对这个临时缓存区做调整

01.      /* Do the write .. */ 02.      for (;;) { 03.               size_t size = count; 04.               if (size > chunk) 05.                        size = chunk; 06.               ret = -EFAULT; 07.               if (copy_from_user(tty->write_buf, buf, size)) 08.                        break; 09.               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; 32.}  

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

Write操作的流程图如下示:

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

4.3:设备文件的read操作

01.static ssize_t tty_read(struct file *file, char __user *buf, size_t count, 02.                        loff_t *ppos) 03.{ 04.      int i; 05.      struct tty_struct *tty; 06.      struct inode *inode; 07.      struct tty_ldisc *ld; 08.      tty = (struct tty_struct *)file->private_data; 09.      inode = file->f_path.dentry->d_inode; 10.      if (tty_paranoia_check(tty, inode, "tty_read")) 11.               return -EIO; 12.      if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) 13.               return -EIO; 14.      /* We want to wait for the line discipline to sort out in this 15.         situation */ 16.      ld = tty_ldisc_ref_wait(tty); 17.      lock_kernel(); 18.      if (ld->read) 19.               i = (ld->read)(tty, file, buf, count); 20.      else 21.               i = -EIO; 22.      tty_ldisc_deref(ld); 23.      unlock_kernel(); 24.      if (i > 0) 25.               inode->i_atime = current_fs_time(inode->i_sb); 26.      return i; 27.}  

这个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的操作我们之后以控制终端为例进行分析.

0 0
原创粉丝点击