终端设备驱动之tty_read函数

《linux内核源代码情景分析》读书笔记和网上找的资料。


大家都知道在具体的tty_driver结构中,没有read函数,那怎样进行读的呢?

看到上面的那个图,应该能大致知道终端设备的工作方式,通过中断把数据存入缓冲区,上层在进行读取。

下面就顺着tty_read函数看下具体的过程:源码如下:


static ssize_t tty_read(struct file * file, char * buf, size_t count, 
loff_t *ppos)
{
int i;
struct tty_struct * tty;
struct inode *inode;


/* Can't seek (pread) on ttys.  */
if (ppos != &file->f_pos)
return -ESPIPE;


tty = (struct tty_struct *)file->private_data;

在打开文件是设置的,现在通过他找到目标设备的tty_struct结构。
inode = file->f_dentry->d_inode;
if (tty_paranoia_check(tty, inode->i_rdev, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;


/* This check not only needs to be done before reading, but also
  whenever read_chan() gets woken up after sleeping, so I've
  moved it to there.  This should only be done for the N_TTY
  line discipline, anyway.  Same goes for write_chan(). -- jlc. */
#if 0
if ((inode->i_rdev != CONSOLE_DEV) && /* don't stop on /dev/console */
   (tty->pgrp > 0) &&
   (current->tty == tty) &&
   (tty->pgrp != current->pgrp))
if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp))
return -EIO;
else {
(void) kill_pg(current->pgrp, SIGTTIN, 1);
return -ERESTARTSYS;
}
#endif
lock_kernel();
if (tty->ldisc.read)
i = (tty->ldisc.read)(tty,file,buf,count);

代表终端设备的tty_ldisc数据结构。源码如下:

struct tty_ldisc tty_ldisc_N_TTY = {
TTY_LDISC_MAGIC, /* magic */
"n_tty", /* name */
0, /* num */
0, /* flags */
n_tty_open, /* open */
n_tty_close, /* close */
n_tty_flush_buffer, /* flush_buffer */
n_tty_chars_in_buffer, /* chars_in_buffer */
read_chan, /* read */
write_chan, /* write */
n_tty_ioctl, /* ioctl */
n_tty_set_termios, /* set_termios */
normal_poll, /* poll */
n_tty_receive_buf, /* receive_buf */
n_tty_receive_room, /* receive_room */
n_tty_write_wakeup /* write_wakeup */
};

函数read_chan源码如下:

static ssize_t read_chan(struct tty_struct *tty, struct file *file,
unsigned char *buf, size_t nr)
{
unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);

......

if (file->f_flags & O_NONBLOCK) {
if (down_trylock(&tty->atomic_read))
return -EAGAIN;
}
else {
if (down_interruptible(&tty->atomic_read))
return -ERESTARTSYS;
}


add_wait_queue(&tty->read_wait, &wait);

在当前进程的系统堆栈中准备下一个wait_queue_t数据结构wait,并不它挂入目标终端的等待队列read_wait中,是终端设备的驱动程序在有数据可读时可唤醒这个进程。

........

if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
   ((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf));

指着b开始时指向用户空间的缓冲区buf,随着字符的读出而向前推进,所以(b-buf)就是已经读出的字符数。

if (!input_available_p(tty, 0)) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}

检查输入缓冲区中有否数据可供读出。如果缓冲区中没有字符可供读出,则当前进程一般要睡眠等待。到缓冲区中有了可供读出的字符时才会被唤醒。

if (tty->icanon) { 规范模式,就是经处理的数据
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
  int eol;


eol = test_and_clear_bit(tty->read_tail,
&tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &
 (N_TTY_BUF_SIZE-1));
tty->read_cnt--;
if (eol) {
/* this test should be redundant:
* we shouldn't be reading data if
* canon_data is 0
*/
if (--tty->canon_data < 0)
tty->canon_data = 0;
}
spin_unlock_irqrestore(&tty->read_lock, flags);


if (!eol || (c != __DISABLED_CHAR)) {
put_user(c, b++);
nr--;
}

逐个字符地调用put_user将其复制到用户空间去。
if (eol)
break;
}
} else {   非规范模式
int uncopied;
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);

通过此函数进行成片的复制,由于缓冲区是环形的,缓冲区的字符可能分成两端,所以调用两次。
if (uncopied) {
retval = -EFAULT;
break;
}
}


/* If there is enough space in the read buffer now, let the
* low-level driver know. We use n_tty_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.
*/
if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE)
check_unthrottle(tty);

注释很详细,主要是缓冲区可以重新接收数据了,就打开开关让底层的驱动接受数据。


if (b - buf >= minimum)
break;
if (time)
timeout = time;
}
clear_bit(TTY_DONT_FLIP, &tty->flags);
up(&tty->atomic_read);
remove_wait_queue(&tty->read_wait, &wait);


if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum;


current->state = TASK_RUNNING;
size = b - buf;
if (size) {
retval = size;
if (nr)
      clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
goto do_it_again;


return retval;
}

注(摘于书中):一般而言,一个典型的读终端过程可以分成下列三部分,或者这三部分的循环:

(1)、当前进程企图从终端的缓冲区读出,但是因为缓冲区中尚无足够的字符可供读出而受阻,进入睡眠;

(2)、然后,但使用者在键盘(一个特例,可以是其他设备)上输入字符,底层驱动程序将足够的字符写入缓冲区以后,就把睡眠的进程唤醒;(设备驱动的主体)

(3)、睡眠的进程被唤醒以后,继续完成读出。
else
i = -EIO;
unlock_kernel();
if (i > 0)
inode->i_atime = CURRENT_TIME;
return i;
}

这个tty_read函数的主体就是调用tty->ldisc.read函数,完成冲缓冲区read_buf到用户空间的复制。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值