天天看点

linux tty core 源码分析(4)

上面分析了用户空间从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;                                  //已读走数据大小

 unsigned long data[0];

};

struct tty_bufhead {                    //tty_struct结构中临时缓存区的管理结构

 struct delayed_work work;          //工作队列中的一个工作项

 spinlock_t lock;

 struct tty_buffer *head; 

 struct tty_buffer *tail; 

 struct tty_buffer *free; 

 int memory_used;  

};

在中断程序中我们一般通过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 在空间不足的情况下申请临时空间并把接收的数据及标志保存到临时缓存,并返回保存数据大小

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 (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;

 } 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中去

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) 

  return;

 spin_lock_irqsave(&tty->buf.lock, flags);

 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;

   }

   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);

  }

  tty->buf.head = head;

 }

 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);

}

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);

 }

 if (tty->receive_room < TTY_THRESHOLD_THROTTLE)  //j接收空间过小可以设置阈值

  tty_throttle(tty);

}

继续阅读