1. 程式人生 > 實用技巧 >tty_driver-task_sync

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 狀態,執行排程程式,轉而執行其他程序,等待被喚醒。