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_filp
對 buf->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_port 的 tty_buffer 內,然後將 buffer 的 work item 放到等待佇列 system_unbound_wq 。
後者由 worker 執行緒執行後,將 tty_buffer 內的資料根據 tty 的 termios 資訊進行處理,然後 flush 到 disc_data 內的 read_buf 中,並喚醒等待佇列 read_wait 。
tty 層驅動的接收函式 n_tty_read
在判斷 disc_data 的 read_buf 中沒有可用資料後,呼叫 tty_buffer_flush_work(tty->port);
主動執行 flush_to_ldisc
。
2. wait queue
開啟一個裝置檔案時,最終呼叫 tty_port_block_til_ready
把當前程序新增到等待佇列,設定為 TASK_INTERRUPTIBLE 狀態,執行排程程式,轉而執行其他程序,等待被喚醒。