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 状态,执行调度程序,转而执行其他进程,等待被唤醒。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。