tty_driver-set_termios
1. 设置 tty 参数
设置 tty 参数时会设置 tty_struct 内 disc_data 的一些成员变量:
struct n_tty_data {
...
/* must hold exclusive termios_rwsem to reset these */
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
unsigned char push:1;
...
}
在内核源码目录执行 grep -nr 'ldata->real_raw' drivers/*
命令,结果显示只有 drivers/tty/n_tty.c 的 n_tty_set_termios
函数对 real_raw 进行了修改。
也就是说,修改 tty 参数的操作只能通过 n_tty_set_termios
函数完成。
1.1. n_tty_set_termios
函数接收两个参数:第一个是要设置参数的 tty ,第二个是该 tty 的原参数。
tty_struct 的 disc_data 保存 line discipline 相关的数据, 即 n_tty_set_termios
函数的操作对象。
static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
{
struct n_tty_data *ldata = tty->disc_data;
/*
* 1. old = NULL , 没有旧参数 —— 初始化 ldata
* 2. (old->clflag ^ tty->termios.c_lflag) ,新旧参数之间的差异
* (ICANON | EXTPROC) , canonical 模式或 LINEMODE ( man stty )
* 发生 canonical 模式或 LINEMODE 的切换
*/
if (!old || (old->c_lflag ^ tty->termios.c_lflag) & (ICANON | EXTPROC)) {
// 重置 read_flags 为 0 ,行起始位置指向 read_tail
bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
ldata->line_start = ldata->read_tail;
/*
* 1. tty 处于 noncanonical 模式 —— 设置 canon_head 为 read_tail
* 2. ldata 中没有数据
*/
if (!L_ICANON(tty) || !read_cnt(ldata)) {
ldata->canon_head = ldata->read_tail;
ldata->push = 0;
} else { // tty 处于 canonical 模式,并且 ldata 有数据
// 置位 read_flags 中的 read_head 前一字节,作为 buf 的结束标志
set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
ldata->read_flags);
// canon_head 指向 read_head
ldata->canon_head = ldata->read_head;
// push 的含义未知
ldata->push = 1;
}
ldata->commit_head = ldata->read_head;
ldata->erasing = 0;
ldata->lnext = 0;
}
// 设置 icanon 标志,表明是否工作在 canonical 模式
ldata->icanon = (L_ICANON(tty) != 0);
// 下列输入参数都需要对输入数据进行处理
if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
I_PARMRK(tty)) {
/*
* char_map 对应 256 个 ASCII 字符,
* 如果 char_map 中对应的字符置位,表明需要对这些特殊字符进行处理。
* 下面的 if 语句根据设置的参数设置 char_map 中对应的字符标志位。
*/
bitmap_zero(ldata->char_map, 256);
if (I_IGNCR(tty) || I_ICRNL(tty))
set_bit('\r', ldata->char_map);
if (I_INLCR(tty))
set_bit('\n', ldata->char_map);
if (L_ICANON(tty)) {
set_bit(ERASE_CHAR(tty), ldata->char_map);
set_bit(KILL_CHAR(tty), ldata->char_map);
set_bit(EOF_CHAR(tty), ldata->char_map);
set_bit('\n', ldata->char_map);
set_bit(EOL_CHAR(tty), ldata->char_map);
if (L_IEXTEN(tty)) {
set_bit(WERASE_CHAR(tty), ldata->char_map);
set_bit(LNEXT_CHAR(tty), ldata->char_map);
set_bit(EOL2_CHAR(tty), ldata->char_map);
if (L_ECHO(tty))
set_bit(REPRINT_CHAR(tty),
ldata->char_map);
}
}
if (I_IXON(tty)) {
set_bit(START_CHAR(tty), ldata->char_map);
set_bit(STOP_CHAR(tty), ldata->char_map);
}
if (L_ISIG(tty)) {
set_bit(INTR_CHAR(tty), ldata->char_map);
set_bit(QUIT_CHAR(tty), ldata->char_map);
set_bit(SUSP_CHAR(tty), ldata->char_map);
}
clear_bit(__DISABLED_CHAR, ldata->char_map);
ldata->raw = 0;
ldata->real_raw = 0;
} else {
/*
* stty -F /dev/ttyS0 raw 会设置 ldata->raw = 1
* 针对 serial tty 驱动, tty->driver->flags 设置了
* TTY_DRIVER_REAL_RAW 标志,因此 ldata->real_raw = 1
*/
ldata->raw = 1;
if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
(I_IGNPAR(tty) || !I_INPCK(tty)) &&
(tty->driver->flags & TTY_DRIVER_REAL_RAW))
ldata->real_raw = 1;
else
ldata->real_raw = 0;
}
/*
* Fix tty hang when I_IXON(tty) is cleared, but the tty
* been stopped by STOP_CHAR(tty) before it.
*/
// 之前开启了 IXON ,当前没有开启,并且 tty 被 IXON 停止了
if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) {
/*
* start_tty -> __start_tty -> uart_start
* tty_wakeup
* tty_wakeup 调用 wake_up_interrutible_poll 唤醒
* tty->write_wait 等待队列上的进程
*/
start_tty(tty);
process_echoes(tty);
}
/* The termios change make the tty ready for I/O */
// 唤醒 write_wait 和 read_wait 上的进程
wake_up_interruptible(&tty->write_wait);
wake_up_interruptible(&tty->read_wait);
}
n_tty_set_termios
函数的引用有 n_tty_open
,两个函数都是 n_tty_ops 的成员,即
static struct tty_ldisc_ops n_tty_ops = {
...
.open = n_tty_open,
.set_termios = n_tty_set_termios,
...
};
在内核源码下执行 grep -nr 'ld->ops->set_termios' drivers/tty/*
,从输出结果看,直接调用 set_termios
函数的有 tty_set_termios
。
1.2. n_tty_open
n_tty_open
调用 n_tty_set_termios
时,第二个参数为 NULL ,即旧的 tty 参数不存在。
n_tty_open
只有 tty_ldisc_open
会调用,调用后者的函数有很多,都位于 drivers/tty/tty_ldisc.c 中。
其中较为常用的函数为 tty_ldisc_reinit
, tty_ldisc_setup
, tty_set_ldisc
。
三个函数的引用分别为:
tty_ldisc_reinit
在tty_reopen
执行时, tty 没有 ldisc 时调用;还有tty_ldisc_hangup
传入的参数 reinit 为 1 时调用。tty_ldisc_setup
在tty_init_dev
中调用。tty_set_disc
在tiocsetd
中调用,后者用于实现tty_ioctl
。
作者:glob
出处:http://www.cnblogs.com/adera/
欢迎访问我的个人博客:https://blog.globs.site/
本文版权归作者和博客园共有,转载请注明出处。