上面分析了用户空间从tty读数据的过程,读数据时从tty->read_buf那么tty->read_buf中的数据从而而来呢?这就是我们今天要讨论的问题。tty_struct结构中有个 struct tty_bufhead buf 成员,比如当tty串口中有数据过来时就会产生中断,tty就利用tty.buf中的成员保存中断到来的数据,在合适的机会再用tty_flip_buffer_push类函数把tty->buf中的数据保存到tty->read_buf中去,从而就达到数据来源的效果。具体源码如下:

 

先看看下面两个数据结构 struct tty_buffer是接收中断数据的一个缓存结构,

struct tty_buffer {
 struct tty_buffer *next;
 char *char_buf_ptr;                //数据缓存
 
unsigned char *flag_buf_ptr;  //数据标志缓存
 int used;                                 //已用数据大小
 int size;                                  //缓存空间的大小
 int commit;                              //已提交数据大小
 int read;                                  //已读走数据大小
 /* Data points here */
 unsigned long data[0];
};


 


struct tty_bufhead {                    //tty_struct结构中临时缓存区的管理结构
 struct delayed_work work;          //工作队列中的一个工作项
 spinlock_t lock;
 struct tty_buffer *head; /* Queue head */
 struct tty_buffer *tail; /* Active buffer */
 struct tty_buffer *free; /* Free queue head */
 int memory_used;  /* Buffer space used excluding
        free queue */
};

在中断程序中我们一般通过int tty_insert_flip_char 把接收到的数据放入tty_struct 的临时缓存中,tty->buf.head指向临时缓存链表的表头,tty->buf.tail指向待操作的临时缓存;

static inline int tty_insert_flip_char(struct tty_struct *tty,
     unsigned char ch, char flag)
{
 struct tty_buffer *tb = tty->buf.tail;  
 if (tb && tb->used < tb->size) {         //操作缓存存在并有空间
  tb->flag_buf_ptr[tb->used] = flag;   //数据的标志位
  tb->char_buf_ptr[tb->used++] = ch; //接收的数据
  return 1;
 }
 return tty_insert_flip_string_flags(tty, &ch, &flag, 1); //操作缓存不存在或者空间不够时的操作
}

 

//tty_insert_flip_string_flags 在空间不足的情况下申请临时空间并把接收的数据及标志保存到临时缓存,并返回保存数据大小
/**
 * tty_insert_flip_string_flags - Add characters to the tty buffer
 * @tty: tty structure
 * @chars: characters
 * @flags: flag bytes
 * @size: size
 *
 * Queue a series of bytes to the tty buffering. For each character
 * the flags array indicates the status of the character. Returns the
 * number added.
 *
 * Locking: Called functions may take tty->buf.lock
 */

int tty_insert_flip_string_flags(struct tty_struct *tty,
  const unsigned char *chars, const char *flags, size_t size)
{
 int copied = 0;
 do {
  int space = tty_buffer_request_room(tty, size - copied);//申请临时缓存空间
  struct tty_buffer *tb = tty->buf.tail;
  /* If there is no space then tb may be NULL */
  if (unlikely(space == 0))
   break;
  memcpy(tb->char_buf_ptr + tb->used, chars, space);
  memcpy(tb->flag_buf_ptr + tb->used, flags, space);
  tb->used += space;
  copied += space;
  chars += space;
  flags += space;
  /* There is a small chance that we need to split the data over
     several buffers. If this is the case we must loop */
 } while (unlikely(size > copied));
 return copied;
}

 

当接收到的外界数据保存到临时缓存后通过tty_flip_buffer_push把临时缓存中的数据发送到tty->read_buf中供用户空间读取

/**
 * tty_flip_buffer_push - terminal
 * @tty: tty to push
 *
 * Queue a push of the terminal flip buffers to the line discipline. This
 * function must not be called from IRQ context if tty->low_is set.
 *
 * In the event of the queue being busy for flipping the work will be
 * held off and retried later.
 *
 * Locking: tty buffer lock. Driver locks in low latency mode.
 */

void tty_flip_buffer_push(struct tty_struct *tty)
{
 unsigned long flags;
 spin_lock_irqsave(&tty->buf.lock, flags);
 if (tty->buf.tail != NULL)
  tty->buf.tail->commit = tty->buf.tail->used;
 spin_unlock_irqrestore(&tty->buf.lock, flags);

 if (tty->low_latency) //tty->low_latency  //设置时直接提交到tty->read_buf 否则采用工作队列方式
 
 flush_to_ldisc(&tty->buf.work.work);
 else
  schedule_delayed_work(&tty->buf.work, 1);
}

 

//把临时缓存中的数据提交到线路规程中去即tty->read_buf中去
/**
 * flush_to_ldisc
 * @work: tty structure passed from work queue.
 *
 * This routine is called out of the software interrupt to flush data
 * from the buffer chain to the line discipline.
 *
 * Locking: holds tty->buf.lock to guard buffer list. Drops the lock
 * while invoking the line discipline receive_buf method. The
 * receive_buf method is single threaded for each tty instance.
 */

static void flush_to_ldisc(struct work_struct *work)
{
 struct tty_struct *tty =
  container_of(work, struct tty_struct, buf.work.work);
 unsigned long  flags;
 struct tty_ldisc *disc;
 struct tty_buffer *tbuf, *head;
 char *char_buf;
 unsigned char *flag_buf; //tty存在线路规程并且设置了线路规程

 disc = tty_ldisc_ref(tty);
 if (disc == NULL) /*  !TTY_LDISC */
  return;

 spin_lock_irqsave(&tty->buf.lock, flags);
 /* So we know a flush is running */
 set_bit(TTY_FLUSHING, &tty->flags); //设置数据提交标志
 head = tty->buf.head;
 if (head != NULL) {
  tty->buf.head = NULL;
  for (;;) {
   int count = head->commit - head->read; //确定要提交数据的大小
   if (!count) {  //没有数据要提交就继续下个临时缓存
    if (head->next == NULL)
     break;
    tbuf = head;
    head = head->next;
    tty_buffer_free(tty, tbuf);
    continue;
   }
   /* Ldisc or user is trying to flush the buffers
      we are feeding to the ldisc, stop feeding the
      line discipline as we want to empty the queue */
   if (test_bit(TTY_FLUSHPENDING, &tty->flags))
    break;
   if (!tty->receive_room) { //tty->read_buf 空间不够时提交到工作队列中去处理
    schedule_delayed_work(&tty->buf.work, 1);
    break;
   }
   if (count > tty->receive_room)
    count = tty->receive_room;
   char_buf = head->char_buf_ptr + head->read;
   flag_buf = head->flag_buf_ptr + head->read;
   head->read += count;
   spin_unlock_irqrestore(&tty->buf.lock, flags);
   disc->ops->receive_buf(tty, char_buf,
       flag_buf, count); //调用线路规程的ldisc->receive_buf把数据从临时缓存提交到tty->read_buf
   spin_lock_irqsave(&tty->buf.lock, flags);
  }
  /* Restore the queue head */
  tty->buf.head = head;
 }
 /* We may have a deferred request to flush the input buffer,
    if so pull the chain under the lock and empty the queue */
 if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {  //提交工作处于等待状态
  __tty_buffer_flush(tty);
  clear_bit(TTY_FLUSHPENDING, &tty->flags);
  wake_up(&tty->read_wait); //唤醒等待读的等待队列,为提交赢得空间
 }
 clear_bit(TTY_FLUSHING, &tty->flags);
 spin_unlock_irqrestore(&tty->buf.lock, flags);

 tty_ldisc_deref(disc);
}

 

 

/**
 * n_tty_receive_buf - data receive
 * @tty: terminal device
 * @cp: buffer
 * @fp: flag buffer
 * @count: characters
 *
 * Called by the terminal driver when a block of characters has
 * been received. This function must be called from soft contexts
 * not from interrupt context. The driver is responsible for making
 * calls one at a time and in order (or using flush_to_ldisc)
 */

static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
         char *fp, int count)
{
 const unsigned char *p;
 char *f, flags = TTY_NORMAL;
 int i;
 char buf[64];
 unsigned long cpuflags;

 if (!tty->read_buf)
  return;

 if (tty->real_raw) {    //非规范模式的数据处理,这里有两次操作是考虑环形缓存的临界状态
  spin_lock_irqsave(&tty->read_lock, cpuflags);
  i = min(N_TTY_BUF_SIZE - tty->read_cnt,
   N_TTY_BUF_SIZE - tty->read_head);
  i = min(count, i);
  memcpy(tty->read_buf + tty->read_head, cp, i);
  tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
  tty->read_cnt += i;
  cp += i;
  count -= i;

  i = min(N_TTY_BUF_SIZE - tty->read_cnt,
   N_TTY_BUF_SIZE - tty->read_head);
  i = min(count, i);
  memcpy(tty->read_buf + tty->read_head, cp, i);
  tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
  tty->read_cnt += i;
  spin_unlock_irqrestore(&tty->read_lock, cpuflags);
 } else {  //规范模式具体大家去分析
  for (i = count, p = cp, f = fp; i; i--, p++) {
   if (f)
    flags = *f++;
   switch (flags) {
   case TTY_NORMAL:
    n_tty_receive_char(tty, *p);
    break;
   case TTY_BREAK:
    n_tty_receive_break(tty);
    break;
   case TTY_PARITY:
   case TTY_FRAME:
    n_tty_receive_parity_error(tty, *p);
    break;
   case TTY_OVERRUN:
    n_tty_receive_overrun(tty);
    break;
   default:
    printk(KERN_ERR "%s: unknown flag %d/n",
           tty_name(tty, buf), flags);
    break;
   }
  }
  if (tty->ops->flush_chars)
   tty->ops->flush_chars(tty);
 }

 n_tty_set_room(tty);

 if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {
  kill_fasync(&tty->fasync, SIGIO, POLL_IN);
  if (waitqueue_active(&tty->read_wait))
   wake_up_interruptible(&tty->read_wait);
 }

 /*
  * Check the remaining room for the input canonicalization
  * mode.  We don't want to throttle the driver if we're in
  * canonical mode and don't have a newline yet!
  */
 if (tty->receive_room < TTY_THRESHOLD_THROTTLE)  //j接收空间过小可以设置阈值
  tty_throttle(tty);
}

 

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐