tty设备的读操作tty_write首先对读操作的需求做检查,然后调用ldisc->write操作默认即write_chain函数。wrtie_chain通过tty->ops->write或者tty->ops->flush_chars把数据写入到设备中,两者都实现时后者有限。其中write_room函数是用来检测缓存
于空间.
/**
* tty_write - write method for tty device file
* @file: tty file pointer
* @buf: user data to write
* @count: bytes to write
* @ppos: unused
*
* Write data to a tty device via the line discipline.
*
* Locking:
* Locks the line discipline as required
* Writes to the tty driver are serialized by the atomic_write_lock
* and are then processed in chunks to the device. The line discipline
* write method will not be involked in parallel for each device
* The line discipline write method is called under the big
* kernel lock for historical reasons. New code should not rely on this.
*/
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct tty_struct *tty;
struct inode *inode = file->f_path.dentry->d_inode;
ssize_t ret;
struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
if (tty_paranoia_check(tty, inode, "tty_write")) //幻数检查及设备结构相关检测
return =-EIO ;
(!tty || !tty->ops->write ||
(test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
printk(KERN_ERR "tty driver %s lacks a write_room method./n",
tty->driver->name);
ld = tty_ldisc_ref_wait(tty);
if (!ld->ops->write)
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count); //调用线路规程write函数即write_chain写入,下面分析
tty_ldisc_deref(ld);
return ret;
}
分配临时缓存给tty->write_buf并把用户空间数据拷贝进去然后调用线路规程写函数即write_chain。
/*
* Split writes up in sane blocksizes to avoid
* denial-of-service type attacks
*/
static inline ssize_t do_tty_write(
ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
struct tty_struct *tty,
struct file *file,
const char __user *buf,
size_t count)
{
ssize_t ret, written = 0;
unsigned int chunk;
ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
if (ret < 0)
return ret;
/*
* We chunk up writes into a temporary buffer. This
* simplifies low-level drivers immensely, since they
* don't have locking issues and user mode accesses.
*
* But if TTY_NO_WRITE_SPLIT is set, we should use a
* big chunk-size..
*
* The default chunk-size is 2kB, because the NTTY
* layer has problems with bigger chunks. It will
* claim to be able to handle more characters than
* it actually does.
*
* FIXME: This can probably go away now except that 64K chunks
* are too likely to fail unless switched to vmalloc...
*/
chunk = 2048;
if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
chunk = 65536;
if (count < chunk)
chunk = count;
/* write_buf/write_cnt is protected by the atomic_write_lock mutex */
if (tty->write_cnt < chunk) {
unsigned char *buf;
if (chunk < 1024)
chunk = 1024;
buf = kmalloc(chunk, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto out;
}
kfree(tty->write_buf);
tty->write_cnt = chunk;
tty->write_buf = buf;
}
/* Do the write .. */
for (;;) {
size_t size = count;
if (size > chunk)
size = chunk;
ret = -EFAULT;
if (copy_from_user(tty->write_buf, buf, size))
break;
ret = write(tty, file, tty->write_buf, size);
if (ret <= 0)
break;
written += ret;
buf += ret;
count -= ret;
if (!count)
break;
ret = -ERESTARTSYS;
if (signal_pending(current))
break;
cond_resched();
}
if (written) {
struct inode *inode = file->f_path.dentry->d_inode;
inode->i_mtime = current_fs_time(inode->i_sb);
ret = written;
}
out:
tty_write_unlock(tty);
return ret;
}
//write_chain主要根据数据是否是经过加工的调用tty->ops->flush_chars或者tty->ops->write把数据写入设备
//当写入的空间不足时,且数据没有完全写完则调用schedule()把写操作加入写等待队列
/**
* write_chan - write function for tty
* @tty: tty device
* @file: file object
* @buf: userspace buffer pointer
* @nr: size of I/O
*
* Write function of the terminal device. This is serialized with
* respect to other write callers but not to termios changes, reads
* and other such events. We must be careful with N_TTY as the receive
* code will echo characters, thus calling driver write methods.
*
* This code must be sure never to sleep through a hangup.
*/
static ssize_t write_chan(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
//TOSTOP标志设置时若后台进程试图写控制台时将发出SIGTTOU信号,也即执行下面的操作
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty); //进程控制终端相关设置
if (retval)
return retval;
}
add_wait_queue(&tty->write_wait, &wait); //代表写进程的等待队列项加入到写等待队列中
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
//OPOST设置,则操作可以选择加工过的输入
//TTY_HW_COOK_OUT若设置通知线路加工其输出的数据,否则只做拷贝
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > 0) {
//opost_block申请写入空间,并把数据根据加工要求成然后调用tty_operations->write成块写入tty设备
ssize_t num = opost_block(tty, b, nr);
if (num < 0) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == 0)
break;
c = *b;
if (opost(c, tty) < 0)
break;
b++; nr--;
}
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
while (nr > 0) {
c = tty->ops->write(tty, b, nr);
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;
}
schedule();
}
break_out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
return (b - buf) ? b - buf : retval;
}