首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

打通linux的tty驱动的数据链路

打通linux的tty驱动的数据链路

一、首先把tty驱动在linux中的分层结构理清楚:


  自上而下分为TTY核心层、TTY线路规程、TTY驱动。
  二、TTY核心层与线路规程层分析
  用户空间的程序直接对tty核心层进行读写等相关操作,在tty_io.c中:
  int__init tty_init(void)
  {
  cdev_init(&tty_cdev,&tty_fops);
  if(cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
  register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty")< 0)
  panic("Couldn'tregister /dev/tty driver\n");
  device_create(tty_class,NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
  ………
  }
  以上的一段初始化代码可以获取以下信息:
  注册了一个字符驱动,用户空间操作对应到tty_fops结构体里的函数:
  staticconst struct file_operations tty_fops = {
  。llseek =no_llseek,
  。read =tty_read,
  。write =tty_write,
  。poll =tty_poll,
  。unlocked_ioctl =tty_ioctl,
  。compat_ioctl =tty_compat_ioctl,
  。open =tty_open,
  。release =tty_release,
  。fasync =tty_fasync,
  };
  对于字符设备驱动,我们知道,读写操作一一对应于fops.
  tty_open:
  static int tty_open(struct inode *inode, struct file *filp)
  {
  int index;
  dev_tdevice = inode->i_rdev;
  structtty_driver *driver;
  ……
  driver= get_tty_driver(device, &index);
  ……
  tty= tty_init_dev(driver, index, 0);
  ……
  retval= tty_add_file(tty, filp);
  ……
  if(tty->ops->open)
  retval= tty->ops->open(tty, filp);
  }
  get_tty_driver是根据设备号device,通过查找tty_drivers全局链表来查找tty_driver.
  tty_init_dev是初始化一个tty结构体:
  tty->driver= driver;
  tty->ops= driver->ops;
  并建立线路规程:
  ldops= tty_ldiscs[N_TTY];
  ld->ops= ldops;
  tty->ldisc= ld;
  其实tty_ldiscs[N_TTY]在console_init中确定,该函数在内核启动的时候调用。
  tty_register_ldisc(N_TTY,&tty_ldisc_N_TTY);
  则:tty_ldiscs[N_TTY]=&tty_ldisc_N_TTY;
  struct tty_ldisc_ops tty_ldisc_N_TTY = {
  。magic = TTY_LDISC_MAGIC,
  。name = "n_tty",
  。open = n_tty_open,
  。close = n_tty_close,
  。flush_buffer = n_tty_flush_buffer,
  。chars_in_buffer= n_tty_chars_in_buffer,
  。read = n_tty_read,
  。write = n_tty_write,
  。ioctl = n_tty_ioctl,
  。set_termios = n_tty_set_termios,
  。poll = n_tty_poll,
  。receive_buf = n_tty_receive_buf,
  。write_wakeup = n_tty_write_wakeup
  };
  tty_add_file主要是将tty保存到file的私有变量private_data中。
  tty->ops->open的调用,实则上就是应用driver->ops->open.这样,我们就从tty核心层到tty驱动层了。
  tty_write:
  static ssize_t tty_write(struct file *file, const char __user *buf,
  size_t count, loff_t *ppos)
  {
  ………
  ld= tty_ldisc_ref_wait(tty);
  if(!ld->ops->write)
  ret= -EIO;
  else
  ret= do_tty_write(ld->ops->write, tty, file, buf, count);
  ………
  }
  从以上这个函数里,可以看到tty_write调用路线规程的write函数,所以,我们来看ldisc中的write函数是怎样的。经过一些操作后,最终调用:
  tty->ops->flush_chars(tty);
  tty->ops->write(tty,b, nr);
  显然,这两个函数,都调用了tty_driver操作函数,因为在之前的tty_open函数中有了tty->ops=driver-> ops这样的操作。那么这个tty_driver是怎样的呢,在TTY系统中,tty_driver是需要在驱动层注册的。注册的时候就初始化了ops, 也就是说,接下来的事情要看tty_driver的了。
  tty_read:
  static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
  loff_t *ppos)
  {
  ………
  ld= tty_ldisc_ref_wait(tty);
  if(ld->ops->read)
  i= (ld->ops->read)(tty, file, buf, count);
  else
  i= -EIO;
  ……
  }
  像tty_write的一样,在tty_read里,也调用了线路规程的对应read函数。不同的是,这个read没有调用tty_driver里ops的read,而是这样:
  uncopied= copy_from_read_buf(tty, &b, &nr);
  uncopied+= copy_from_read_buf(tty, &b, &nr);
  从函数名来看copy_from_read_buf,就是从read_buf这个缓冲区拷贝数据。实际上是在tty->read_buf的末尾 tty->read_tail中读取数据。那么read_buf中的数据是怎么来的呢?猜想,那肯定是tty_driver干的事了。
  tty_ioctl:
  long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
  {
  ……
  switch(cmd) {
  case… …… :
  ………
  }
  }
  就是根据cmd的值进行相关操作,有对线路规程操作的,有直接通过tty_driver操作的。
  三、TTY驱动层分析
  接下来看,TTY驱动层是怎样的:
  TTY驱动层是根据不同的硬件操作来完成相应的操作,这里我们以串口为例。
  串口作为一个标准的设备,把共性的分离出来,就成了uart层,特性成了serial层。
  主要是serial层作为一个驱动模块加载。以8250.c为例:
  static int __init serial8250_init(void)
  {
  ………
  serial8250_reg.nr= UART_NR;
  ret= uart_register_driver(&serial8250_reg);
  ………
  serial8250_register_ports(&serial8250_reg,&serial8250_isa_devs->dev);
  ………
  }
  #define UART_NR CONFIG_SERIAL_8250_NR_UARTS
  CONFIG_SERIAL_8250_NR_UARTS是在配置内核的时候定义的,表示支持串口的个数。
  static struct uart_driver serial8250_reg = {
  。owner =THIS_MODULE,
  。driver_name ="serial",
  。dev_name ="ttyS",
  。major =TTY_MAJOR,
  。minor =64,
  。cons =SERIAL8250_CONSOLE,
  };
  在驱动层里有几个重要的数据结构:
  structuart_driver;
  structuart_state ;
  structuart_port;
  structtty_driver;
  structtty_port;
  实际上,理清了这几个结构体的关系,也就理清了TTY驱动层。
  uart_register_driver:
  这个函数主要是向TTY核心层注册一个TTY驱动:
  retval= tty_register_driver(normal);
  其中normal是tty_driver.
  另外,还会对tty_driver和uart_driver之间进行某些赋值和指针连接。我们最关心的是,给tty_driver初始化了操作函数uart_ops,这样,在tty核心层就可以通过uart_ops来对UART层进行操作。
  serial8250_register_ports:
 链路也连通。
返回列表