tty_driver-task_sync

1. workqueue

tty 借助 workqueue 实现串口读写操作之间的同步。

tty_port 结构体中的 struct tty_bufhead buf 成员有一个 struct work_struct work 成员, drivers/tty/tty_buffer.c 中的 tty_buffer_init 函数对其进行初始化:

INIT_WORK(&buf->work, flush_to_ldisc)

即设置 buf->work->func = flush_to_ldisc

tty_schedule_filpbuf->work 进行了使用,将任务挂到 workqueue system_unbound_wq ,等待任务被调度执行,即执行 flush_to_ldisc

void tty_schedule_flip(struct tty_port *port)
{
	struct tty_bufhead *buf = &port->buf;

	/* paired w/ acquire in flush_to_ldisc(); ensures
	 * flush_to_ldisc() sees buffer data.
	 */
	smp_store_release(&buf->tail->commit, buf->tail->used);
	queue_work(system_unbound_wq, &buf->work);
}
EXPORT_SYMBOL(tty_schedule_flip);

针对 serial8250 , 相关的函数调用路径为 serial8250_rx_chars -> tty_flip_buffer_push -> tty_schedule_flip
驱动层接收数据后,将保存接收到数据的 tty 端口的缓冲区对应的 work item 放到 workqueue system_unbound_wq

work item 被 worker 线程执行时,调用 flush_to_ldisc

1.1. flush_to_ldisc

flush work 所属的 tty_port 的缓冲区中数据到 line discipline :

static void flush_to_ldisc(struct work_struct *work)
{
	struct tty_port *port = container_of(work, struct tty_port, buf.work);
	struct tty_bufhead *buf = &port->buf;

	mutex_lock(&buf->lock);

	while (1) {
		struct tty_buffer *head = buf->head;
		struct tty_buffer *next;
		int count;

		/* Ldisc or user is trying to gain exclusive access */
		if (atomic_read(&buf->priority))
			break;

		/* paired w/ release in __tty_buffer_request_room();
		 * ensures commit value read is not stale if the head
		 * is advancing to the next buffer
		 */
		next = smp_load_acquire(&head->next);
		/* paired w/ release in __tty_buffer_request_room() or in
		 * tty_buffer_flush(); ensures we see the committed buffer data
		 */
		count = smp_load_acquire(&head->commit) - head->read;

		// 当前 buf 为空,释放掉,处理下一个 buf
		if (!count) {
			if (next == NULL)
				break;
			buf->head = next;
			tty_buffer_free(port, head);
			continue;
		}

		/*
		 * receive_buf -> tty_port_default_receive_buf ->
		 * tty_ldisc_receive_buf -> 
		 * 		* n_tty_receive_buf2
		 * 		- n_tty_receive_buf
		 */
		count = receive_buf(port, head, count);
		if (!count)
			break;
		head->read += count;
	}

	mutex_unlock(&buf->lock);

}

n_tty_receive_buf2 是带有流控的操作,通过 n_tty_receive_buf_common 实现。

1.2. n_tty_receive_buf2

从传入的 tty 的缓冲区中 cp 中读取数据,将每个字符的 flag 保存在 fp 中, n_tty_receive_buf2 调用时 flow = 1

static int
n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
			 char *fp, int count, int flow)
{
	struct n_tty_data *ldata = tty->disc_data;
	int room, n, rcvd = 0, overflow;

	down_read(&tty->termios_rwsem);

	while (1) {
		/*
		 * When PARMRK is set, each input char may take up to 3 chars
		 * in the read buf; reduce the buffer space avail by 3x
		 *
		 * If we are doing input canonicalization, and there are no
		 * pending newlines, let characters through without limit, so
		 * that erase characters will be handled.  Other excess
		 * characters will be beeped.
		 *
		 * paired with store in *_copy_from_read_buf() -- guarantees
		 * the consumer has loaded the data in read_buf up to the new
		 * read_tail (so this producer will not overwrite unread data)
		 */
		size_t tail = smp_load_acquire(&ldata->read_tail);

		// 计算可用空间,至少为 1
		room = N_TTY_BUF_SIZE - (ldata->read_head - tail);
		if (I_PARMRK(tty))	// PARMRK 导致可用空间变成 1/3
			room = (room + 2) / 3;
		room--;
		if (room <= 0) {

			// canonical 模式并且 head = tail 
			overflow = ldata->icanon && ldata->canon_head == tail;
			if (overflow && room < 0)
				ldata->read_head--;
			room = overflow;
			ldata->no_room = flow && !room;
		} else
			overflow = 0;

		n = min(count, room);
		if (!n)
			break;

		/* ignore parity errors if handling overflow */
		if (!overflow || !fp || *fp != TTY_PARITY)

			// __receive_buf 根据 tty 工作的模式接收数据
			__receive_buf(tty, cp, fp, n);

		cp += n;
		if (fp)
			fp += n;
		count -= n;
		rcvd += n;
	}

	tty->receive_room = room;

	/* Unthrottle if handling overflow on pty */
	if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
		if (overflow) {
			tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
			tty_unthrottle_safe(tty);
			__tty_set_flow_change(tty, 0);
		}
	} else
		n_tty_check_throttle(tty);

	up_read(&tty->termios_rwsem);

	return rcvd;
}

1.3. __receive_buf

n_tty_receive_buf_common 的主要功能通过 __receive_buf 完成,将 cp 中的数据拷贝到 tty->disc_data 中的 buf 。

static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
			  char *fp, int count)
{
	struct n_tty_data *ldata = tty->disc_data;

    // ISTRIP 指剥去第 8 bit , IUCLC 需要 IEXTEN 才能生效
	bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty));

    // real_raw 对 cp 中字符不进行任何处理,直接拷贝到 tty->disc_data 的 buf
	if (ldata->real_raw)
		n_tty_receive_buf_real_raw(tty, cp, fp, count);

    /* 
     * raw 模式根据 fp 对应字符的 flag 处理字符,可以处理的字符包括:
     * TTY_NORMAL , TTY_BREAK , TTY_PARITY , TTY_FRAME , TTY_OVERRUN
     * 处理过的字符放到 disc_data 的 buf 中
     */
	else if (ldata->raw || (L_EXTPROC(tty) && !preops))
		n_tty_receive_buf_raw(tty, cp, fp, count);

    /*
     * uart_close -> tty_port_close -> tty_port_close_start ,
     * 设置 tty->closing = 1 ,表明 tty 处于正在关闭的状态。
     * n_tty_receive_buf_closing 循环处理每个字符,针对每个字符
     * 调用 n_tty_receive_char_closing ,执行以下操作:
     * 1. 如果 tty 开启了 I_ISTRIP ,将字符最高位清零
     * 2. 如果开启了 I_IUCLC ,将字符转化为小写
     * 3. 如果开启了 I_IXON ,字符是停止字符,调用 stop_tty ;
     *    否则如果是开始字符,或者 tty 处于停止状态,并且满足一堆条件,
     *    调用 start_tty 和 process_echoes
     */
	else if (tty->closing && !L_EXTPROC(tty))
		n_tty_receive_buf_closing(tty, cp, fp, count);
	else {
		if (ldata->lnext) {
			char flag = TTY_NORMAL;

			if (fp)
				flag = *fp++;
			n_tty_receive_char_lnext(tty, *cp++, flag);
			count--;
		}

        // standard 方法需要多执行一些判断
		if (!preops && !I_PARMRK(tty))
			n_tty_receive_buf_fast(tty, cp, fp, count);
		else
			n_tty_receive_buf_standard(tty, cp, fp, count);

		flush_echoes(tty);
		if (tty->ops->flush_chars)
			tty->ops->flush_chars(tty);
	}

	if (ldata->icanon && !L_EXTPROC(tty))
		return;

	/* publish read_head to consumer */
	smp_store_release(&ldata->commit_head, ldata->read_head);

    // ldata 中有可用数据,唤醒 read_wait 上的进程
	if (read_cnt(ldata)) {
		kill_fasync(&tty->fasync, SIGIO, POLL_IN);
		wake_up_interruptible_poll(&tty->read_wait, POLLIN);
	}
}

1.4. 总结

接收数据时, serial8250 驱动将接收到的数据保存在 tty_porttty_buffer 内,然后将 buffer 的 work item 放到等待队列 system_unbound_wq
后者由 worker 线程执行后,将 tty_buffer 内的数据根据 tty 的 termios 信息进行处理,然后 flush 到 disc_data 内的 read_buf 中,并唤醒等待队列 read_wait

tty 层驱动的接收函数 n_tty_read 在判断 disc_dataread_buf 中没有可用数据后,调用 tty_buffer_flush_work(tty->port); 主动执行 flush_to_ldisc

2. wait queue

打开一个设备文件时,最终调用 tty_port_block_til_ready 把当前进程添加到等待队列,设置为 TASK_INTERRUPTIBLE 状态,执行调度程序,转而执行其他进程,等待被唤醒。

posted @ 2020-08-07 11:25  glob  阅读(793)  评论(0编辑  收藏  举报