1. 程式人生 > 實用技巧 >tty_driver-write

tty_driver-write

1. echo sdf > /dev/ttyS0

tty_cdev_add 給 tty driver 分配字元裝置,設定字元裝置的檔案操作函式指標指向 tty_fops ,後者的寫函式為 tty_write

static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
		unsigned int index, unsigned int count)
{
    ...
   	driver->cdevs[index]->ops = &tty_fops;
    ...
}

static const struct file_operations tty_fops = {
	.llseek		= no_llseek,
	.read		= tty_read,
	.write		= tty_write,
	.poll		= tty_poll,
	.unlocked_ioctl	= tty_ioctl,
	.compat_ioctl	= tty_compat_ioctl,
	.open		= tty_open,
	.release	= tty_release,
	.fasync		= tty_fasync,
	.show_fdinfo	= tty_show_fdinfo,
};

tty_write 只是做一些檢查,保證 line discipline 的寫函式需要的元件可用,真正的操作由 do_tty_write 呼叫 ld->ops->write 完成。

static ssize_t tty_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
{
	struct tty_struct *tty = file_tty(file);
 	struct tty_ldisc *ld;
	ssize_t ret;

	if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
		return -EIO;

    // tty->ops->write 在 n_tty_write 中使用
	if (!tty || !tty->ops->write ||	tty_io_error(tty))
			return -EIO;
	/* Short term debug to catch buggy drivers */

    // tty->ops->write_room 在 n_tty_write-> process_output_block 中使用
	if (tty->ops->write_room == NULL)
		tty_err(tty, "missing write_room method\n");
	ld = tty_ldisc_ref_wait(tty);
	if (!ld)
		return hung_up_tty_write(file, buf, count, ppos);
	if (!ld->ops->write)
		ret = -EIO;
	else
		ret = do_tty_write(ld->ops->write, tty, file, buf, count);
	tty_ldisc_deref(ld);
	return ret;
}

ld->ops->write 函式在《 tty_driver-line_discipline 》一文介紹。

2. echo sdf > /dev/tty

根據 tty_init 函式, /dev/tty 的檔案操作函式指標和 /dev/ttyS0 相同,因此寫 /dev/tty 的操作執行的函式和寫 /dev/ttyS0 相同。

3. echo sdf > /dev/tty1

/dev/ttyn 裝置的檔案操作指標也是 tty_fops ,因此向其寫入資料時,同樣會呼叫 tty_write 函式。
tty_write 呼叫 line discipline 的寫函式完成輸出操作,最終呼叫 con_write

輸出字串。

4. echo sdf > /dev/console

/dev/console 使用的檔案操作函式指標為:

static const struct file_operations console_fops = {
	.llseek		= no_llseek,
	.read		= tty_read,
	.write		= redirected_tty_write,
	.poll		= tty_poll,
	.unlocked_ioctl	= tty_ioctl,
	.compat_ioctl	= tty_compat_ioctl,
	.open		= tty_open,
	.release	= tty_release,
	.fasync		= tty_fasync,
};

因此寫操作呼叫的函式為 redirected_tty_write :函式首先獲取變數 struct file *redirect ,如果檔案指標存在,呼叫 vfs_writebuf 中內容輸出到 redirect 檔案;否則呼叫 tty_write 完成輸出操作。

ssize_t redirected_tty_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
{
	struct file *p = NULL;

	spin_lock(&redirect_lock);
	if (redirect)
		p = get_file(redirect);
	spin_unlock(&redirect_lock);

	if (p) {
		ssize_t res;
		res = vfs_write(p, buf, count, &p->f_pos);
		fput(p);
		return res;
	}
	return tty_write(file, buf, count, ppos);
}

4.1. 控制檯重定向 redirect

redirect 定義在 drivers/tty/tty_io.c 中,修改 direct 的函式只有 tioccons ,用於相應 tty ioctl 的 TIOCCONS 命令。

根據 man tty_ioctl 的輸出,這個命令的功能為:

重定向控制檯輸出,將原本要輸出到 /dev/console 或者 /dev/tty0 的內容重定向到給定的終端。如果終端是一個偽終端的主裝置,將其傳送到從裝置。

2.6.10 之前核心只要輸出沒有重定向,任何使用者都可以執行這個操作; 2.6.10 版本開始只有 CAP_SYS_ADMIN 程序可以執行這個操作。如果輸出已經進行了重定向,返回 EBUSY ;可以通過傳入 /dev/console 或者 /dev/tty0 結束重定向。

static struct file *redirect;

static int tioccons(struct file *file)
{
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

    // 傳入的檔案指標是 /dev/console 或者 /dev/tty0 ,結束重定向
	if (file->f_op->write == redirected_tty_write) {
		struct file *f;
		spin_lock(&redirect_lock);
		f = redirect;
		redirect = NULL;
		spin_unlock(&redirect_lock);
		if (f)
			fput(f);
		return 0;
	}
	spin_lock(&redirect_lock);

    // 如果已經設定了重定向,返回 EBUSY
	if (redirect) {
		spin_unlock(&redirect_lock);
		return -EBUSY;
	}

    // 設定重定向
	redirect = get_file(file);
	spin_unlock(&redirect_lock);
	return 0;
}