linux tty core 源码分析(5)

tty设备的读操作tty_write首先对读操作的需求做检查,然后调用ldisc->write操作默认即write_chain函数。wrtie_chain通过tty->ops->write或者tty->ops->flush_chars把数据写入到设备中,两者都实现时后者有限。其中write_room函数是用来检测缓存

于空间.

 

/**
 * tty_write  - write method for tty device file
 * @file: tty file pointer
 * @buf: user data to write
 * @count: bytes to write
 * @ppos: unused
 *
 * Write data to a tty device via the line discipline.
 *
 * Locking:
 *  Locks the line discipline as required
 *  Writes to the tty driver are serialized by the atomic_write_lock
 * and are then processed in chunks to the device. The line discipline
 * write method will not be involked in parallel for each device
 *  The line discipline write method is called under the big
 * kernel lock for historical reasons. New code should not rely on this.
 */

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 ;
(!tty || !tty->ops->write ||
  (test_bit(TTY_IO_ERROR, &tty->flags)))
   return -EIO;
 /* Short term debug to catch buggy drivers */
 if (tty->ops->write_room == NULL)
  printk(KERN_ERR "tty driver %s lacks a write_room method./n",
   tty->driver->name);
 ld = tty_ldisc_ref_wait(tty);
 if (!ld->ops->write)
  ret = -EIO;
 else
  ret = do_tty_write(ld->ops->write, tty, file, buf, count); //调用线路规程write函数即write_chain写入,下面分析
 tty_ldisc_deref(ld);
 return ret;
}

 

分配临时缓存给tty->write_buf并把用户空间数据拷贝进去然后调用线路规程写函数即write_chain。

/*
 * Split writes up in sane blocksizes to avoid
 * denial-of-service type attacks
 */
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 < 0)
  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)
  chunk = count;

 /* write_buf/write_cnt is protected by the atomic_write_lock mutex */
 if (tty->write_cnt < chunk) {
  unsigned char *buf;

  if (chunk < 1024)
   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;
 }

 /* 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;
  ret = write(tty, file, tty->write_buf, size);
  if (ret <= 0)
   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;
}

//write_chain主要根据数据是否是经过加工的调用tty->ops->flush_chars或者tty->ops->write把数据写入设备

//当写入的空间不足时,且数据没有完全写完则调用schedule()把写操作加入写等待队列

 

/**
 * write_chan  - write function for tty
 * @tty: tty device
 * @file: file object
 * @buf: userspace buffer pointer
 * @nr: size of I/O
 *
 * Write function of the terminal device. This is serialized with
 * respect to other write callers but not to termios changes, reads
 * and other such events. We must be careful with N_TTY as the receive
 * code will echo characters, thus calling driver write methods.
 *
 * This code must be sure never to sleep through a hangup.
 */

static ssize_t write_chan(struct tty_struct *tty, struct file *file,
     const unsigned char *buf, size_t nr)
{
 const unsigned char *b = buf;
 DECLARE_WAITQUEUE(wait, current);
 int c;
 ssize_t retval = 0;

 /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */

 

//TOSTOP标志设置时若后台进程试图写控制台时将发出SIGTTOU信号,也即执行下面的操作
 if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) { 
  retval = tty_check_change(tty); //进程控制终端相关设置
  if (retval)
   return retval;
 }

 add_wait_queue(&tty->write_wait, &wait);  //代表写进程的等待队列项加入到写等待队列中
 while (1) {
  set_current_state(TASK_INTERRUPTIBLE);
  if (signal_pending(current)) {
   retval = -ERESTARTSYS;
   break;
  }
  if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
   retval = -EIO;
   break;
  }

 

//OPOST设置,则操作可以选择加工过的输入

 //TTY_HW_COOK_OUT若设置通知线路加工其输出的数据,否则只做拷贝

  if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
   while (nr > 0) {

    //opost_block申请写入空间,并把数据根据加工要求成然后调用tty_operations->write成块写入tty设备
    ssize_t num = opost_block(tty, b, nr);
    if (num < 0) {
     if (num == -EAGAIN)
      break;
     retval = num;
     goto break_out;
    }
    b += num;
    nr -= num;
    if (nr == 0)
     break;
    c = *b;
    if (opost(c, tty) < 0)
     break;
    b++; nr--;
   }
   if (tty->ops->flush_chars)
    tty->ops->flush_chars(tty);
  } else {
   while (nr > 0) {
    c = tty->ops->write(tty, b, nr);
    if (c < 0) {
     retval = c;
     goto break_out;
    }
    if (!c)
     break;
    b += c;
    nr -= c;
   }
  }
  if (!nr)
   break;
  if (file->f_flags & O_NONBLOCK) {
   retval = -EAGAIN;
   break;
  }
  schedule();
 }
break_out:
 __set_current_state(TASK_RUNNING);
 remove_wait_queue(&tty->write_wait, &wait);
 return (b - buf) ? b - buf : retval;
}

你可能感兴趣的:(linux,struct,File,user,Signal,locking)