在用户空间对tty设备进行读操作,经过系统调用进入到tty核心层执行的第一个函数是tty_read()。在tty_read()函数中,从文件描述符file的私有数据结构中获得tty_struct,然后再从tty_struct中获取线路规程描述符。取得线路规程描述符后,直接调用线路规程的read()函数。
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
int i;
struct tty_struct *tty;
struct inode *inode;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
inode = file->f_path.dentry->d_inode;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* We want to wait for the line discipline to sort out in this
situation */
ld = tty_ldisc_ref_wait(tty);
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count);
else
i = -EIO;
tty_ldisc_deref(ld);
if (i > 0)
inode->i_atime = current_fs_time(inode->i_sb);
return i;
}
线路规程N_TTY的读操作函数为n_tty_read(),在该函数中把数据拷贝到用户空间分两种情况处理,一种是标准模式,另一种是非标准模式。在标准模式下,如果没有设置O_NONBLOCK,读操作只有在遇到文件结束符或者各行的字符都已编辑完毕后才返回。在非标准始模式下,由VMIN和VTIME两个设定来控制读操作的行为。第一种情况:VMIN和VTIME都为0,不管有没有读到数据,read()会立即返回;第二种情况:VMIN 为0,VTIME大于0,如果有读到数据,read()立即返回,如果没有读到数据,则等待VTIME时间后返回;第三种情况:VMIN大于0,VTIME等于0,read()一直阻塞直到读到VMIN个数据后返回;第四种情况:VMIN和VTIME都大于0,read()保持阻塞直到读到第一个字符,读到第一个字符后开始计时,此后如果时间到了VTIME或者时间没到但读到了VMIN个字符则返回,若在时间未到之前又读到一个字符(此时字符总数不够)则重新计时。回到n_tty_read()中,首先判断是否有数据可读,如果没有则调用schedule_timeout()把当前进程挂起,直到有数据可读或者超时后被唤醒继续执行。接下来是数据的读取过程,如果是标准模式,直接从tty->read_buf中读取字符,然后调用tty_put_user()把字符拷贝到用户空间。在非标准模式下,利用copy_from_read_buf直接把字符拷贝到用户空间。copy_from_read_buf()执行了两次,那是因为read_buf是个环形缓存区,第一次读是从read_tail到buf_end这部分数据,第二次读是从buf_end(因为是环形,所以也是buf_start)到read_head这部分数据。在copy_from_read_buf()里,用copy_to_user()直接把tty->read_buf里面的数据拷贝到用户空间。
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
<span style="white-space:pre"> </span>……
while (nr) {
/* First test for status change. */
if (packet && tty->link->ctrl_status) {
unsigned char cs;
if (b != buf)
break;
spin_lock_irqsave(&tty->link->ctrl_lock, flags);
cs = tty->link->ctrl_status;
tty->link->ctrl_status = 0;
spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);
if (tty_put_user(tty, cs, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
break;
}
/* This statement must be first before checking for input
so that any interrupt will set the state back to
TASK_RUNNING. */
set_current_state(TASK_INTERRUPTIBLE);
if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf));
if (!input_available_p(tty, 0)) {
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
/* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
timeout = schedule_timeout(timeo