linux设备驱动之控制台驱动

来源:互联网 发布:python爬虫教程知乎 编辑:程序博客网 时间:2024/05/16 00:42

 一:前言

  我们在之前分析过input子系统和tty设备驱动架构.今天需要将两者结合起来.看看linux中的控制台是怎么样实现的.

  二:控制台驱动的初始化

  之前在分析tty驱动架构的时候曾分析到.主设备为4,次设备为0的设备节点,即/dev/tty0为当前的控制终端.

  有tty_init()中,有以下代码段:

  static int __init tty_init(void)

  {

  ……

  ……

  #ifdef CONFIG_VT

  cdev_init(&vc0_cdev, &console_fops);

  if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||

  register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)

  panic("Couldn't register /dev/tty0 driver/n");

  device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), "tty0");

  vty_init();

  #endif

  return 0;

  }

  CONFIG_VT:是指配置虚拟终端.即我们所说的控制台.在此可以看到TTY_MAJOR(4),0对应的设备节点操作集为console_fops.

  继续跟进vty_init()

  int __init vty_init(void)

  {

  vcs_init();

  console_driver = alloc_tty_driver(MAX_NR_CONSOLES);

  if (!console_driver)

  panic("Couldn't allocate console driver/n");

  console_driver->owner = THIS_MODULE;

  console_driver->name = "tty";

  console_driver->name_base = 1;

  console_driver->major = TTY_MAJOR;

  console_driver->minor_start = 1;

  console_driver->type = TTY_DRIVER_TYPE_CONSOLE;

  console_driver->init_termios = tty_std_termios;

  console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;

  tty_set_operations(console_driver, &con_ops);

  if (tty_register_driver(console_driver))

  panic("Couldn't register console driver/n");

  kbd_init();

  console_map_init();

  #ifdef CONFIG_PROM_CONSOLE

  prom_con_init();

  #endif

  #ifdef CONFIG_MDA_CONSOLE

  mda_console_init();

  #endif

  return 0;

  }

  经过我们之前的tty驱动架构分析,这段代码看起来就比较简单了,它就是注册了一个tty驱动.这个驱动对应的操作集是位于con_ops里面的.

  仔细看.在之后还会调用kbd_init().顾名思义,这个是一个有关键盘的初始化.控制终端跟键盘有什么关系呢?在之前分析tty的时候,曾提到过,. 对于控制台而言,它的输入设备是键盘鼠标,它的输出设备是当前显示器.这两者是怎么关联起来的呢?不着急.请看下面的分析.三:控制台的open操作

  在前面分析了,对应console的操作集为con_ops.定义如下:

  static const struct file_operations console_fops = {

  .llseek                = no_llseek,

  .read                   = tty_read,

  .write                  = redirected_tty_write,

  .poll           = tty_poll,

  .ioctl          = tty_ioctl,

  .compat_ioctl    = tty_compat_ioctl,

  .open                  = tty_open,

  .release    = tty_release,

  .fasync               = tty_fasync,

  };

  里面的函数指针值我们都不陌生了,在之前分析的tty驱动中已经分析过了.

  结合前面的tty驱动分析.我们知道在open的时候,会调用ldisc的open和tty_driver.open.

  对于ldisc默认是tty_ldiscs[0].我们来看下它的具体赋值.

  console_init():

  void __init console_init(void)

  {

  initcall_t *call;

  /* Setup the default TTY line discipline. */

  (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

  /*

  * set up the console device so that later boot sequences can

  * inform about problems etc..

  */

  call = __con_initcall_start;

  while (call < __con_initcall_end) {

  (*call)();

  call++;

  }

  }

  在这里,通过tty_register_ldisc.将tty_ldisc_N_TTY注册为了第N_TTY项.即第1项. tty_ldisc_N_TTY定义如下:

  struct tty_ldisc 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            = read_chan,

  .write           = write_chan,

  .ioctl           = n_tty_ioctl,

  .set_termios     = n_tty_set_termios,

  .poll            = normal_poll,

  .receive_buf     = n_tty_receive_buf,

  .write_wakeup    = n_tty_write_wakeup

  }

  对应的open操作为n_tty_open:

  static int n_tty_open(struct tty_struct *tty)

  {

  if (!tty)

  return -EINVAL;

  /* This one is ugly. Currently a malloc failure here can panic */

  if (!tty->read_buf) {

  tty->read_buf = alloc_buf();

  if (!tty->read_buf)

  return -ENOMEM;

  }memset(tty->read_buf, 0, N_TTY_BUF_SIZE);

  reset_._flags(tty);

  tty->column = 0;

  n_tty_set_termios(tty, NULL);

  tty->minimum_to_wake = 1;

  tty->closing = 0;

  return 0;

  }

  它为tty->read_buf分配内存.这个buffer空间大小为N_TTY_BUF_SIZE.read_buf实际上就是从按键的缓存区.然后调用reset_flags()来初始化tty中的一些字段:

  static void reset_buffer_flags(struct tty_struct *tty)

  {

  unsigned long flags;

  spin_lock_irqsave(&tty->read_lock, flags);

  tty->read_head = tty->read_tail = tty->read_cnt = 0;

  spin_unlock_irqrestore(&tty->read_lock, flags);

  tty->canon_head = tty->canon_data = tty->erasing = 0;

  memset(&tty->read_flags, 0, sizeof tty->read_flags);

  n_tty_set_room(tty);

  check_unthrottle(tty);

  }

  这里比较简,不再详细分析.在这里要注意几个tty成员的含义:

  Tty->read_head, tty->read_tail , tty->read_cnt分别代表read_buf中数据的写入位置,读取位置和数据总数.read_buf是一个环形缓存区.

  n_tty_set_room()是设备read_buf中的可用缓存区

  check_unthrottle():是用来判断是否需要打开”阀门”,允许输入数据流入

  对于console tty_driver对应的open函数如下示:

  static int con_open(struct tty_struct *tty, struct file *filp)

  {

  unsigned int currcons = tty->index;

  int ret = 0;

  acquire_console_sem();

  if (tty->driver_data == NULL) {

  ret = vc_allocate(currcons);

  if (ret == 0) {

  struct vc_data *vc = vc_cons[currcons].d;

  tty->driver_data = vc;

  vc->vc_tty = tty;

  if (!tty->winsize.ws_row && !tty->winsize.ws_col) {

  tty->winsize.ws_row = vc_cons[currcons].d->vc_rows;

  tty->winsize.ws_col = vc_cons[currcons].d->vc_cols;

  }

  release_console_sem();

  vcs_make_sysfs(tty);

  return ret;

  }

  }

  release_console_sem();

  return ret;

  }

  tty->index表示的是tty_driver所对示的设备节点序号.在这里也就是控制台的序列.用alt+fn就可以切换控制终端.

  在这里,它主要为vc_cons[ ]数组中的对应项赋值.并将tty和vc建立关联.

  四:控制台的read操作

  从tty驱动架构中分析可得到,最终的read操作会转入到ldsic->read中进行.

  相应tty_ldisc_N_TTY的read操作如下.这个函数代码较长,分段分析如下:

  static ssize_t read_chan(struct tty_struct *tty, struct file *file,

  unsigned char __user *buf, size_t nr)

  {

  unsigned char __user *b = buf;

  DECLARE_WAITQUEUE(wait, current);

  int c;

  int minimum, time;

  ssize_t retval = 0;

  ssize_t size;

  long timeout;

  unsigned long flags;

  do_it_again:

  if (!tty->read_buf) {

  printk(KERN_ERR "n_tty_read_chan: read_buf == NULL?!?/n");

  return -EIO;

  }

  c = job_control(tty, file);

  if (c < 0)

  return c;

  minimum = time = 0;

  timeout = MAX_SCHEDULE_TIMEOUT;if (!tty->icanon) {

  time = (HZ / 10) * TIME_CHAR(tty);

  minimum = MIN_CHAR(tty);

  if (minimum) {

  if (time)

  tty->minimum_to_wake = 1;

  else if (!waitqueue_active(&tty->read_wait) ||

  (tty->minimum_to_wake > minimum))

  tty->minimum_to_wake = minimum;

  } else {

  timeout = 0;

  if (time) {

  timeout = time;

  time = 0;

  }

  tty->minimum_to_wake = minimum = 1;

  }

  }

  首先,检查read操作的合法性,read_buf是否已经建立.然后再根据操作的类型来设置tty-> minimum_to_wake.这个成员的含义即为: 如果读进程在因数据不足而睡眠的情况下,数据到达并超过了minimum_to_wake.就将这个读进程唤醒.具体的唤醒过程我们在遇到的时候再进行分析.

  /*

  *      Internal serialization of reads.

  */

  //不允许阻塞

  if (file->f_flags & O_NONBLOCK) {

  if (!mutex_trylock(&tty->atomic_read_lock))

  return -EAGAIN;

  } else {

  if (mutex_lock_interruptible(&tty->atomic_read_lock))

  return -ERESTARTSYS;

  }

  add_wait_queue(&tty->read_wait, &wait);

  在不允许睡眠的情况下,调用mutex_trylock()去获得锁.如果锁被占用,马上返回.否则用可中断的方式去获取锁,如果取锁错误,返回失败.如果取锁成功,将进程加至等待队列.在没有数据可读的情况下,直接睡眠.如果有数据可读,将其移出等待队列即可.

  while (nr) {

  /* First test for status change. */

  if (tty->packet && tty->link->ctrl_status) {

  unsigned char cs;

  if (b != buf)

  break;

  cs = tty->link->ctrl_status;

  tty->link->ctrl_status = 0;

  if (tty_put_user(tty, cs, b++)) {

  retval = -EFAULT;

  b--;

  break;

  }

  nr--;

  break;

  }

  接下来就是一个漫长的while循环,用来读取数据,一直到数据取满为止.如果tty->packet被置为1.即为信包模式,通常用在伪终端设备. 如果tty->link->ctrl_status有数据.则说明如果链路状态发生改变,需要提交此信息.在这种情况下,将其直接copy到用户空间即可.

  /* This statement must be first before checking for input

  so that any interrupt will set the state back to

  TASK_RUNNING. */

  set_current_state(TASK_INTERRUPTIBLE);

  if (((minimum - (b - buf)) < tty->minimum_to_wake) &&

  ((minimum - (b - buf)) >= 1))

  tty->minimum_to_wake = (minimum - (b - buf));

  if (!input_available_p(tty, 0)) {

  if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {

  retval = -EIO;

  break;

  }

  if (tty_hung_up_p(file))

  break;

  if (!timeout)

  break;

  if (file->f_flags & O_NONBLOCK) {

  retval = -EAGAIN;

  break;

  }

  if (signal_pending(current)) {

  retval = -ERESTARTSYS;

  break;

  }

  n_tty_set_room(tty);

  timeout = schedule_timeout(timeout);

  continue;

  }

  __set_current_state(TASK_RUNNING);

 

 先将进程设为TASK_INTERRUPTIBLE状态.再调用input_available_p()来判断可数据供读取.如果没有.则进程睡眠.如果有数据,则将进程状态设为TASK_RUNNING.在终端接收数据的处理过程中,有两种方式,一种是规范模式.一种是原始模式.在规范模式下,终端需要对数据里面的一些特殊字符做处理.在原始模式下.终端不会对接收到的数据做任何的处理.在这里input_available_p()在判断是否有数据可读也分两种情况进行,对于规范模式,看是否有已经转换好的数据,对于原始模式,判断接收的信息总数

  /* Deal with packet mode. */

  //packet模式`忽略

  if (tty->packet && b == buf) {

  if (tty_put_user(tty, TIOCPKT_DATA, b++)) {

  retval = -EFAULT;

  b--;

  break;

  }

  nr--;

  }

  if (tty->icanon) {

  /* N.B. avoid overrun if nr == 0 */

  while (nr && tty->read_cnt) {

  int eol;

  eol = test_and_clear_bit(tty->read_tail,

  tty->read_flags);

  c = tty->read_buf[tty->read_tail];

  spin_lock_irqsave(&tty->read_lock, flags);

  tty->read_tail = ((tty->read_tail+1) &

  (N_TTY_BUF_SIZE-1));

  tty->read_cnt--;

  if (eol) {

  /* this test should be redundant:

  * we shouldn't be reading data if

  * canon_data is 0

  */

  if (--tty->canon_data < 0)

  tty->canon_data = 0;

  }

  spin_unlock_irqrestore(&tty->read_lock, flags);

  //如果没有到结束字符,将字符copy到数据空间

  //__DISABLED_CHAR是不需要copy到用户空间的

  if (!eol || (c != __DISABLED_CHAR)) {

  if (tty_put_user(tty, c, b++)) {

  retval = -EFAULT;

  b--;

  break;

  }

  nr--;

  }

  if (eol) {

  //如果遇到行结束符.就可以退出了

  tty_audit_push(tty);

  break;

  }

  }

  if (retval)

  break;

  } else {

  //非加工模式,直接copy

  int uncopied;

  //环形缓存,copy两次

  uncopied = copy_from_read_buf(tty, &b, &nr);

  uncopied += copy_from_read_buf(tty, &b, &nr);

  if (uncopied) {

  retval = -EFAULT;

  break;

  }

  }

原创粉丝点击