tty_driver-line_discipline

1. line discipline

line discipline 介于 TTY 层和具体的串口驱动 ( 比如 serial8250 ) 之间。

发送数据时,应用程序通过系统调用向 TTY 设备文件写入数据,进而调用 TTY 层驱动程序执行写操作。 TTY 层驱动程序调用 line discipline 的写函数,根据 TTY 设置的参数对写入的数据进行格式化,然后通过具体的串口驱动发送。

接收数据时,具体的串口驱动收到数据后,根据 TTY 的设置参数对数据进行处理,

根据内核中 drivers/tty/n_tty.c 中的注释,读和写可以视为消费者和生产者。两个过程操作时需要用到 line discipline ,对输入和输出进行格式化。

一个 line discipline 由两部分组成:

  1. line discipline 包含的所有操作,对应数据结构 struct tty_ldisc_ops
    TTY 使用的 line discipline 保存在变量 tty_ldiscs 中,这个变量通过函数 tty_register_ldisc 添加新成员。

    // drivers/tty/tty_ldisc.c
    static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
    
    tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc) 
    { 	tty_ldiscs[disc] = new_ldisc;	}
    
    // drivers/tty/n_tty.c
    #define N_TTY 0
    static struct tty_ldisc_ops n_tty_ops = {
        .magic           = TTY_LDISC_MAGIC,
        .name            = "n_tty",
        .open            = n_tty_open,
        .close           = n_tty_close,
        .flush_buffer    = n_tty_flush_buffer,
        .read            = n_tty_read,
        .write           = n_tty_write,
        .ioctl           = n_tty_ioctl,
        .set_termios     = n_tty_set_termios,
        .poll            = n_tty_poll,
        .receive_buf     = n_tty_receive_buf,
        .write_wakeup    = n_tty_write_wakeup,
        .receive_buf2	 = n_tty_receive_buf2,
    };
    n_tty_init -> tty_register_ldisc(N_TTY, &n_tty_ops)
    
    // kernel/printk/printk.c
    console_init -> n_tty_init
    
  2. line discipline 所有操作使用的参数,对应的数据结构是 struct n_tty_data ,保存在 struct tty_structdisc_data 成员中:

    struct n_tty_data {
        /* producer-published */
        size_t read_head;			// 当前 write 操作的 index
        size_t commit_head;
        size_t canon_head;			// canonical 模式 write 操作的 index
        size_t echo_head;
        size_t echo_commit;
        size_t echo_mark;
        DECLARE_BITMAP(char_map, 256);
    
        /* private to n_tty_receive_overrun (single-threaded) */
        unsigned long overrun_time;
        int num_overrun;
    
        /* non-atomic */
        bool no_room;
    
        /* 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;
    
        /* shared by producer and consumer */
        char read_buf[N_TTY_BUF_SIZE];		// 保存数据的 buf
        DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);	// flag = 1 表示 EOL
        unsigned char echo_buf[N_TTY_BUF_SIZE];		// 保存 echo 数据的 buf
    
        /* consumer-published */
        size_t read_tail;			// 当前 read 操作的 index
        size_t line_start;			// 新行的 index
    
        /* protected by output lock */
        unsigned int column;
        unsigned int canon_column;
        size_t echo_tail;
    
        struct mutex atomic_read_lock;
        struct mutex output_lock;
    };
    

disc_data 包含 line discipline 对输入和输入执行所做的处理,即根据 TTY 的参数处理数据,其中的部分参数介绍如下:

icanon 指明终端的工作模式:

canonical 模式下

  1. 数据按行进行输入,输入以下行分隔符 ( NL , EOL , EOL2 ,行首的 EOF ) 表示一个输入行。 EOF 的情况, read 返回的缓冲区包含行分隔符。

  2. 开启了行编辑 ( ERASE , KILL ;如果设置了 IEXTEN ,还包括 WERASE , REPRINT , LNEXT ) 。一次 read 最多返回一行输入;如果 read 请求的字节数少于当前输入行的字节数,只会读取请求的字节数,剩余的字符由下一次 read 获取。

  3. 行的最大长度为 4096 个字符 ( 包括结尾的行分隔符 ) ,大于 4096 字符的部分会被截断。 4095 以后的字符依然进行输入处理 ( 比如 ISIGECHO* 处理 ) ,但是 4095 后的字符到行分隔符之间的部分都会被抛弃。保证终端总是能够接收多行,读取至少一行。

noncanonical 模式下

输入立即可得 ( 不需要用户输入行分隔符 ) ,不会执行输入处理操作,行编辑也会关闭。读缓冲区只会接受 4095 个字符 —— 如果切换到 canonical 模式,留有一个行分隔符的空间。 MIN ( c_cc[VMIN] ) 和 TIME ( c_cc[VTIME] ) 决定何时 read 完成,包括以下四种情况:

  1. MIN == 0 , TIME == 0 ( polling read )
    如果有数据可用, read 立即返回,返回的数据少于可用的字节数,或者请求的字节数;如果没有数据可用,返回 0 。

  2. MIN > 0 , TIME == 0 ( blocking read )
    read 阻塞,直到有 MIN 字节可用,返回最多请求的字节数。

  3. MIN == 0 , TIME > 0 ( read with timeout )
    TIME 指明计时器的值,以秒记。调用 read 时计时器开始。至少有一字节数据,或者计时器超时后 read 返回。如果超时没有任何输入, read 返回 0 。如果调用 read 时数据已经可用,执行方式和调用 read 后立即收到数据一样。

  4. MIN > 0 , TIME > 0 ( read with interbyte timeout )
    TIME 指明计时器的值,以秒记。收到第一个字节启动后,每次收到新的字节,计时器都会重置。 read 在满足以下条件时返回:

    • 收到了 MIN 个字节
    • interbyte 计时器超时
    • 收到了 read 请求的字节数

由于计时器在收到第一个字节后才会开始,因此至少会收到一个字节。如果调用 read 时数据已经可用,执行方式和调用 read 后立即收到数据一样。

POSIX 没有指明 O_NONBLOCK 文件状态标志是否在 MIN 和 TIME 设置之前生效。如果设置了 O_NONBLOCK ,无论 MIN 和 TIME 如何设置, canonical 模式的 read 可能会立即返回。而且,如果没有数据可用, POSIX 允许 noncanonical 模式的 read 操作返回 0 , -1 或者 EAGAIN 。


Raw mode

cfmakeraw 设置终端工作在 raw 模式下,和老的版本 7 终端驱动类似:一个字符一个字符的输入,关闭回响,关闭终端的所有输入输出字符的特殊处理,终端的属性设置如下:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
				| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

Line control

#include <termios.h>
#include <unistd.h>

int tcsendbreak(int fd, int duration);

int tcdrain(int fd);

int tcflush(int fd, int queue_selector);

int tcflow(int fd, int action);

void cfmakeraw(struct termios *termios_p);

speed_t cfgetispeed(const struct termios *termios_p);

speed_t cfgetospeed(const struct termios *termios_p);

int cfsetispeed(struct termios *termios_p, speed_t speed);

int cfsetospeed(struct termios *termios_p, speed_t speed);

int cfsetspeed(struct termios *termios_p, speed_t speed);

如果终端使用异步串行数据发送, tcsendbreak 持续发送一个连续的 0 值比特流。如果持续的时间 drain 为 0 ,发送至少 0.25s 的 0 值比特,不超过 0.5s 。如果持续时间不为 0 ,发送 0 值比特一段跟实现相关的时间。

如果终端没有使用异步串行数据发送, tcsendbreak 直接返回。

tcdrain 等待所有输出到 fd 指明的对象发送完成。

tcflush 丢弃写入到 fd 指明的、但是还没有发送的数据;或者收到但还没有读取的数据,取决于 queue_selector

  • TCIFLUSH —— flush 收到但没有读取的数据
  • TCOFLUSH —— flush 写入但没有发送的数据
  • TCIOFLUSH —— flush 收到但没有读取、写入但没有发送的数据

tcflow 挂起 fd 指明的数据发送或者接收,取决于 action :

  • TCOOF —— 挂起输出
  • TCOON —— 重启挂起的输出
  • TCIOFF —— 发送一个 STOP 字符,停止终端向系统发送数据
  • TCION —— 发送一个 START 字符,开始终端向系统发送数据

打开一个终端文件时,默认输入和输出都是挂起的。

1.1. n_tty_write

对于 TTY 文件,使用的 static struct tty_ldisc_opsn_tty_ops ,其中 write 函数为 n_tty_write

tty_writen_tty_write 的整个函数调用路径为: tty_write -> do_tty_write -> n_tty_write

static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
			   const unsigned char *buf, size_t nr)
{
	const unsigned char *b = buf;

	// 定义一个等待队列项 wait ,成员函数为 woken_wake_function
	DEFINE_WAIT_FUNC(wait, woken_wake_function);
	int c;
	ssize_t retval = 0;

	/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
	if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
		retval = tty_check_change(tty);
		if (retval)
			return retval;
	}

	down_read(&tty->termios_rwsem);

	/* Write out any echoed characters that are still pending */
	process_echoes(tty);

	/*
	 * 清除 wait 的 WQ_FLAG_EXCLUSIVE 标志,
	 * 添加 wait 到等待队列 write_wait
	 */
	add_wait_queue(&tty->write_wait, &wait);
	while (1) {

		// 当前进程处于挂起状态,设置返回值为 -ERESTARTSYS 
		if (signal_pending(current)) {
			retval = -ERESTARTSYS;
			break;
		}

		// tty 文件处于挂起状态,或者 pty 的另一端没有使用者
		if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
			retval = -EIO;
			break;
		}

		// 开启了实现相关 ( 对于特殊字符的处理方法不同 ) 的输出处理标志
		if (O_OPOST(tty)) {
			while (nr > 0) {

				// 对 b 内的数据进行输出处理并输出
				ssize_t num = process_output_block(tty, b, nr);

				/*
				 * 只有 process_output_block 调用 uart_write 函数
				 * 会返回负值,即 -EL3HLT ,意味着在端口被关闭后调用了
				 * uart_write 函数。
				 */
				if (num < 0) {

					// serial 条件不可能成立
					if (num == -EAGAIN)
						break;
					retval = num;
					goto break_out;
				}
				b += num;
				nr -= num;
				if (nr == 0)
					break;
				c = *b;

				/*
				 * process_ouput 先调用 tty_write_room 获取
				 * 可用的空间,然后调用 do_output_char 输出字符
				 * 可用空间为 0 时返回值小于 0 。
				 */
				if (process_output(c, tty) < 0)
					break;
				b++; nr--;
			}

			/*
			 * 上面 while 中的 break 条件成立才会执行,
			 * 即 uart_flush_chars -> uart_start
			 */
			if (tty->ops->flush_chars)
				tty->ops->flush_chars(tty);
		} else {
			struct n_tty_data *ldata = tty->disc_data;

			while (nr > 0) {
				mutex_lock(&ldata->output_lock);

				// 对输出数据不进行处理直接输出
				c = tty->ops->write(tty, b, nr);
				mutex_unlock(&ldata->output_lock);
				if (c < 0) {
					retval = c;
					goto break_out;
				}
				if (!c)
					break;
				b += c;
				nr -= c;
			}
		}
		if (!nr)
			break;
		if (file->f_flags & O_NONBLOCK) {
			retval = -EAGAIN;
			break;
		}
		up_read(&tty->termios_rwsem);

		wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);

		down_read(&tty->termios_rwsem);
	}
break_out:

	// 设置当前线程为运行状态,将 wait 从 write_wait 中删除
	remove_wait_queue(&tty->write_wait, &wait);

	// 数据还没有写完,并且 async ,设置 TTY_DO_WRITE_WAKEUP
	if (nr && tty->fasync)
		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
	up_read(&tty->termios_rwsem);

	// 返回写的字节数,或者错误代码
	return (b - buf) ? b - buf : retval;
}

1.1.1. process_output_block

内核中注释说明函数的功能如下:

进行 OPOST 处理后,输出一块字符,返回输出的字符数。

这个函数用于加速处理块输出数据时的块控制台写操作,只会处理正常找到的简单情况,帮助控制台驱动生成块级符号,从而提升性能。

static ssize_t process_output_block(struct tty_struct *tty,
				    const unsigned char *buf, unsigned int nr)
{
	struct n_tty_data *ldata = tty->disc_data;
	int	space;
	int	i;
	const unsigned char *cp;

	mutex_lock(&ldata->output_lock);

	/*
	 * 如果 tty->ops->write_room 定义,就调用,比如 
	 * uart_write_room:
	 * 从 tty->driver_data 获取 uart_state,进而获取 xmit ,
	 * 从而得到 xmit 中的空闲空间。
	 * 否则返回 2048 。
	 */
	space = tty_write_room(tty);
	if (!space) {
		mutex_unlock(&ldata->output_lock);
		return 0;
	}

	// 实际输出的字符数不能大于可用空间
	if (nr > space)
		nr = space;

	// 依次处理 buf 中的每个字符
	for (i = 0, cp = buf; i < nr; i++, cp++) {
		unsigned char c = *cp;

		switch (c) {
		case '\n':

			// 不输出回车 CR
			if (O_ONLRET(tty))
				ldata->column = 0;

			// 将 NL 映射为 CR-NL
			if (O_ONLCR(tty))
				goto break_out;

			// canon_column = 0
			ldata->canon_column = ldata->column;
			break;
		case '\r':

			// 不在 column 0 输出回车 CR ,并且 column = 0
			if (O_ONOCR(tty) && ldata->column == 0)
				goto break_out;

			// 将 CR 映射为 NL
			if (O_OCRNL(tty))
				goto break_out;

			// 设置 column 为 0 ,从行首输出
			ldata->canon_column = ldata->column = 0;
			break;
		case '\t':
			goto break_out;
		case '\b':

			// 退格
			if (ldata->column > 0)
				ldata->column--;
			break;
		default:

			// 非控制字符
			if (!iscntrl(c)) {

				// 将小写字符映射为大写字符
				if (O_OLCUC(tty))
					goto break_out;
				if (!is_continuation(c, tty))
					ldata->column++;
			}
			break;
		}
	}
break_out:

	/*
	 * uart_write :
	 * 从 tty->driver_data 获取 uart_state ,进而获取
	 * state->xmit ,将 buf 内的 i 字节数据拷贝到 xmit 中,
	 * 调用 __uart_start 发送 xmit 内的数据。 
	 */
	i = tty->ops->write(tty, buf, i);

	mutex_unlock(&ldata->output_lock);
	return i;
}

1.1.2. do_output_char

内核中该函数的注释如下:

这个函数帮助处理单个输出字符,包括特殊字符: TAB , CR , LF 等,执行 OPOST 处理,将结果保存到 tty 驱动的 write buffer 。返回使用的缓冲区空间的字符数,无可用空间时返回 0 。

这个函数在调用 process_output_block 后调用,后者遇到特殊字符就会跳出循环,输出已经处理的输出字符,从而调用 do_output_char 处理特殊字符,完成后再次调用 process_output_block ,从而处理缓冲区中的所有数据。

static int do_output_char(unsigned char c, struct tty_struct *tty, int space)
{
	struct n_tty_data *ldata = tty->disc_data;
	int	spaces;

	if (!space)
		return -1;

	switch (c) {
	case '\n':
		if (O_ONLRET(tty))
			ldata->column = 0;

		// 映射 CR 为 CR-NL ,需要两个字符的空间
		if (O_ONLCR(tty)) {
			if (space < 2)
				return -1;
			ldata->canon_column = ldata->column = 0;
			tty->ops->write(tty, "\r\n", 2);
			return 2;
		}
		ldata->canon_column = ldata->column;
		break;
	case '\r':
		if (O_OCRNL(tty)) {
			c = '\n';
			if (O_ONLRET(tty))
				ldata->canon_column = ldata->column = 0;
			break;
		}
		ldata->canon_column = ldata->column = 0;
		break;
	case '\t':
		spaces = 8 - (ldata->column & 7);
		if (O_TABDLY(tty) == XTABS) {
			if (space < spaces)
				return -1;
			ldata->column += spaces;
			tty->ops->write(tty, "        ", spaces);
			return spaces;
		}
		ldata->column += spaces;
		break;
	case '\b':
		if (ldata->column > 0)
			ldata->column--;
		break;
	default:
		if (!iscntrl(c)) {
			if (O_OLCUC(tty))
				c = toupper(c);
			if (!is_continuation(c, tty))
				ldata->column++;
		}
		break;
	}

	/*
	 * 如果 tty->ops->put_char 定义,调用,即 uart_put_char;
	 * 否则调用 tty->ops->write 输出字符。
	 * uart_put_char 将字符插入到 xmit 中后即返回。
	 */
	tty_put_char(tty, c);
	return 1;
}

1.2. n_tty_read

读操作对应的函数为 n_tty_read ,根据 TTY 的参数对收到的数据进行处理。

/*
 * @tty : tty 设备
 * @file : 文件对象
 * @buf :用户空间缓冲区
 * @nr : I/O 的大小
 */
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
			 unsigned char __user *buf, size_t nr)
{
	struct n_tty_data *ldata = tty->disc_data;
	unsigned char __user *b = buf;
	DEFINE_WAIT_FUNC(wait, woken_wake_function);
	int c;
	int minimum, time;
	ssize_t retval = 0;
	long timeout;
	int packet;
	size_t tail;

	c = job_control(tty, file);
	if (c < 0)
		return c;

	/*
	 *	Internal serialization of reads.
	 */
	if (file->f_flags & O_NONBLOCK) {
		if (!mutex_trylock(&ldata->atomic_read_lock))
			return -EAGAIN;
	} else {
		if (mutex_lock_interruptible(&ldata->atomic_read_lock))
			return -ERESTARTSYS;
	}

	down_read(&tty->termios_rwsem);

	minimum = time = 0;
	timeout = MAX_SCHEDULE_TIMEOUT;
	// noncanonical 模式
	if (!ldata->icanon) {
		minimum = MIN_CHAR(tty);
		if (minimum) {
			time = (HZ / 10) * TIME_CHAR(tty);
		} else {
			timeout = (HZ / 10) * TIME_CHAR(tty);
			minimum = 1;
		}
	}

	packet = tty->packet;
	tail = ldata->read_tail;

	// 添加 wait 到等待队列 tty->read_wait
	add_wait_queue(&tty->read_wait, &wait);
	while (nr) {
		/* First test for status change. */
		if (packet && tty->link->ctrl_status) {
			unsigned char cs;
			if (b != buf)
				break;
			spin_lock_irq(&tty->link->ctrl_lock);
			cs = tty->link->ctrl_status;
			tty->link->ctrl_status = 0;
			spin_unlock_irq(&tty->link->ctrl_lock);
			if (put_user(cs, b)) {
				retval = -EFAULT;
				break;
			}
			b++;
			nr--;
			break;
		}

		// read_buf 中无可用字节数
		if (!input_available_p(tty, 0)) {
			up_read(&tty->termios_rwsem);
			// 等待 tty->port.work 执行完毕
			tty_buffer_flush_work(tty->port);
			down_read(&tty->termios_rwsem);

			// tty->port.work 执行完毕后依然没有数据
			if (!input_available_p(tty, 0)) {

				// tty 另一端被关闭,返回 -EIO
				if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
					retval = -EIO;
					break;
				}

				// tty 被挂起
				if (tty_hung_up_p(file))
					break;

				/*
				 * noncanonical 模式, MIN 为 0 , timeout 为 0 ,
				 * polling read 模式,没有数据可用直接返回
				 */
				if (!timeout)
					break;

				// 非阻塞模式,可以进行重试,返回 -EAGAIN
				if (file->f_flags & O_NONBLOCK) {
					retval = -EAGAIN;
					break;
				}

				// 进程被挂起,返回 -ERESTARTSYS
				if (signal_pending(current)) {
					retval = -ERESTARTSYS;
					break;
				}
				up_read(&tty->termios_rwsem);

				// 以上条件都不满足,等待一段时间后再次尝试
				timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
						timeout);

				down_read(&tty->termios_rwsem);
				continue;
			}
		}

		// canonical 模式,
		if (ldata->icanon && !L_EXTPROC(tty)) {
			retval = canon_copy_from_read_buf(tty, &b, &nr);
			if (retval)
				break;
		} else {
			int uncopied;

			/* Deal with packet mode. */
			if (packet && b == buf) {
				if (put_user(TIOCPKT_DATA, b)) {
					retval = -EFAULT;
					break;
				}
				b++;
				nr--;
			}

			uncopied = copy_from_read_buf(tty, &b, &nr);
			uncopied += copy_from_read_buf(tty, &b, &nr);
			if (uncopied) {
				retval = -EFAULT;
				break;
			}
		}

		n_tty_check_unthrottle(tty);

		if (b - buf >= minimum)
			break;
		if (time)
			timeout = time;
	}
	if (tail != ldata->read_tail)
		n_tty_kick_worker(tty);
	up_read(&tty->termios_rwsem);

	remove_wait_queue(&tty->read_wait, &wait);
	mutex_unlock(&ldata->atomic_read_lock);

	if (b - buf)
		retval = b - buf;

	return retval;
}

其中, input_available_p 定义如下:

static inline int input_available_p(struct tty_struct *tty, int poll)
{
	struct n_tty_data *ldata = tty->disc_data;

	// poll 模式, block  read 操作, MIN 为等待的字节数
	int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1;

	// canonical 模式下, canon_head == read_tail ,表明 buf 为空
	if (ldata->icanon && !L_EXTPROC(tty))
		return ldata->canon_head != ldata->read_tail;

	// noncanonical 模式下, buf 中的字符少于 1 就为空
	else
		return ldata->commit_head - ldata->read_tail >= amt;
}

1.2.1. canon_copy_from_read_buf

canonical 模式下拷贝数据到用户空间缓冲区:

static int canon_copy_from_read_buf(struct tty_struct *tty,
				    unsigned char __user **b,
				    size_t *nr)
{
	struct n_tty_data *ldata = tty->disc_data;
	size_t n, size, more, c;
	size_t eol;
	size_t tail;
	int ret, found = 0;

	/* N.B. avoid overrun if nr == 0 */
	if (!*nr)
		return 0;

	// nr+1 为请求的字节数,后者为 canonical 模式下可用的字节数
	n = min(*nr + 1, smp_load_acquire(&ldata->canon_head) - ldata->read_tail);

	tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);

	// size 为 read_tail + n
	size = min_t(size_t, tail + n, N_TTY_BUF_SIZE);

	n_tty_trace("%s: nr:%zu tail:%zu n:%zu size:%zu\n",
		    __func__, *nr, tail, n, size);

	eol = find_next_bit(ldata->read_flags, size, tail);
	more = n - (size - tail);
	if (eol == N_TTY_BUF_SIZE && more) {
		/* scan wrapped without finding set bit */
		eol = find_next_bit(ldata->read_flags, more, 0);
		found = eol != more;
	} else
		found = eol != size;

	n = eol - tail;
	if (n > N_TTY_BUF_SIZE)
		n += N_TTY_BUF_SIZE;
	c = n + found;

	if (!found || read_buf(ldata, eol) != __DISABLED_CHAR) {
		c = min(*nr, c);
		n = c;
	}

	n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu tail:%zu more:%zu\n",
		    __func__, eol, found, n, c, tail, more);

	// 将数据拷贝到用户空间缓冲区
	ret = tty_copy_to_user(tty, *b, tail, n);
	if (ret)
		return -EFAULT;
	*b += n;
	*nr -= n;

	if (found)
		clear_bit(eol, ldata->read_flags);
	smp_store_release(&ldata->read_tail, ldata->read_tail + c);

	if (found) {
		if (!ldata->push)
			ldata->line_start = ldata->read_tail;
		else
			ldata->push = 0;
		tty_audit_push();
	}
	return 0;
}

1.2.2. n_tty_check_unthrottle

static void n_tty_check_unthrottle(struct tty_struct *tty)
{
	if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
		if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
			return;
		n_tty_kick_worker(tty);

		// pty 唤醒与之相关联的 tty 设备的 write_wait 队列
		tty_wakeup(tty->link);
		return;
	}

	/* If there is enough space in the read buffer now, let the
	 * low-level driver know. We use chars_in_buffer() to
	 * check the buffer, as it now knows about canonical mode.
	 * Otherwise, if the driver is throttled and the line is
	 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
	 * we won't get any more characters.
	 */

	while (1) {
		int unthrottled;
		tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
		if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
			break;

		// 如果需要的话启动 input worker
		n_tty_kick_worker(tty);
		// uart_unthrottle -> serial8250_unthrottle , NULL
		unthrottled = tty_unthrottle_safe(tty);
		if (!unthrottled)
			break;
	}
	__tty_set_flow_change(tty, 0);
}
posted @ 2020-08-07 11:21  glob  阅读(877)  评论(0编辑  收藏  举报