Linux设备模型之tty驱动架构分析 (转载) 转自:http://ericxiao.cublog.cn/
一:前言TTY这个名称源于电传打字节的简称,在Linux系统表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备的键盘鼠标,或输出设备的显示器和串口终端,也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,Linux系统是怎么将它们统一建模的呢? 这就是我们今天要讨论的问题。二:tty驱动概貌Tty架构如下所示:如上图所示,用户空间主要是通过设备文件同tty_core交互。tty_core根据用空间操作的类型再选择跟line discipline和tty_driver交互。例如设置硬件的ioctl指令就直接交给tty_driver处理。Read和write操作就会交给 line discipline处理。Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置.主要用来进行输入/输出数据的预处理。处理之后。就会将数据交给tty_driverTty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串,再将其传给终端设备。值得注意的是,这个架构没有为tty_driver提供read操作。也就是说tty_core 和line discipline都没有办法从tty_driver里直接读终端信息。这是因为tty_driver对应的hardware并不一定是输入数据和输出 数据的共同负载者。例如控制终端,输出设备是显示器。输入设备是键盘。基于这样的原理。在line discipline中有一个输入缓存区。并提供了一个名叫receive_buf()的接口函数。对应的终端设备只要调用line discipine的receiver_buf函数,将数据写入到输入缓存区就可以了。如果一个设备同时是输入设备又是输出设备。那在设备的中断处理中调用receive_buf()将数据写入即可.三:tty驱动接口分析具体的tty驱动设计可以参考LDD3。这里只对它的接口实现做一个分析.tty driver的所有操作都包含在tty_driver中。内核即供了一个名叫alloc_tty_driver()来分配这个tty_driver。当然 我们也可以在自己的驱动中将它定义成一个静态的结构。对tty_driver进行一些必要的初始化之后,调用tty_register_driver() 将其注册.alloc_tty_driver()接口代码如下所示:- struct tty_driver *alloc_tty_driver(int lines)
-
{
-
struct tty_driver *driver;
-
-
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
-
if (driver) {
-
driver->magic = TTY_DRIVER_MAGIC;
-
driver->num = lines;
-
/* later we'll move allocation of tables here */
-
}
-
return driver;
-
}
这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.tty_register_driver()用来注册一个tty_driver。代码如下:- int tty_register_driver(struct tty_driver *driver)
-
{
-
int error;
-
int i;
-
dev_t dev;
-
void **p = NULL;
-
-
//TTY_DRIVER_INSTALLED:已安装的
-
if (driver->flags & TTY_DRIVER_INSTALLED)
-
return 0;
-
-
//TTY_DRIVER_DEVPTS_MEM:使用devpts进行动态内存映射
-
if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
-
p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
-
if (!p)
-
return -ENOMEM;
-
}
-
-
//注册字符设备号
-
//如果没有指定driver->major
-
if (!driver->major) {
-
error = alloc_chrdev_region(&dev, driver->minor_start,
-
driver->num, driver->name);
-
if (!error) {
-
driver->major = MAJOR(dev);
-
driver->minor_start = MINOR(dev);
-
}
-
} else {
-
dev = MKDEV(driver->major, driver->minor_start);
-
error = register_chrdev_region(dev, driver->num, driver->name);
-
}
-
if (error
-
kfree(p);
-
return error;
-
}
-
-
if (p) {
- driver->ttys = (struct tty_struct **)p;
- driver->termios = (struct ktermios **)(p + driver->num);
- driver->termios_locked = (struct ktermios **)(p + driver->num * 2);
-
} else {
- driver->ttys = NULL;
- driver->termios = NULL;
- driver->termios_locked = NULL;
-
}
-
-
//注册字符设备
-
cdev_init(&driver->cdev, &tty_fops);
-
driver->cdev.owner = driver->owner;
-
error = cdev_add(&driver->cdev, dev, driver->num);
-
if (error) {
-
unregister_chrdev_region(dev, driver->num);
-
driver->ttys = NULL;
-
driver->termios = driver->termios_locked = NULL;
-
kfree(p);
-
return error;
-
}
-
-
//指定默认的put_char
-
if (!driver->put_char)
-
driver->put_char = tty_default_put_char;
-
-
mutex_lock(&tty_mutex);
-
list_add(&driver->tty_drivers, &tty_drivers);
-
mutex_unlock(&tty_mutex);
-
-
//如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理
-
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
-
for (i = 0; i num; i++)
-
tty_register_device(driver, i, NULL);
-
}
-
proc_tty_register_driver(driver);
-
return 0;
-
}
这个函数操作比较简单。就是为tty_driver创建字符设备。然后将字符设备的操作集指定为tty_fops.并且将tty_driver挂载到 tty_drivers链表中.其实这个链表的作用跟我们之前分析的input子系统中的input_dev[ ]数组类似。都是以设备号为关键字找到对应的driver.特别的。如果没有定义TTY_DRIVER_DYNAMIC_DEV.还会在sysfs中创建一个类设备.这样主要是为了udev管理设备.以流程图的方式将上述操作表示如下:四:设备文件的操作设备文件的操作是本节分析的重点。它的主要操作是将各项操作对应到ldsic或者是tty_driver.4.1:打开tty设备的操作从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open()。代码如下:- static int tty_open(struct inode *inode, struct file *filp)
-
{
-
struct tty_struct *tty;
-
int noctty, retval;
-
struct tty_driver *driver;
-
int index;
-
dev_t device = inode->i_rdev;
-
unsigned short saved_flags = filp->f_flags;
-
-
nonseekable_open(inode, filp);
-
-
retry_open:
-
//O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端
-
-
//noctty:需不需要更改当前进程的控制终端
-
noctty = filp->f_flags & O_NOCTTY;
-
index = -1;
-
retval = 0;
-
-
mutex_lock(&tty_mutex);
-
-
//设备号(5,0) 即/dev/tty.表示当前进程的控制终端
-
if (device == MKDEV(TTYAUX_MAJOR, 0)) {
-
tty = get_current_tty();
-
//如果当前进程的控制终端不存在,退出
-
if (!tty) {
-
mutex_unlock(&tty_mutex);
-
return -ENXIO;
-
}
-
-
//取得当前进程的tty_driver
-
driver = tty->driver;
-
index = tty->index;
-
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
-
/* noctty = 1; */
-
goto got_driver;
-
}
-
#ifdef CONFIG_VT
-
//设备号(4,0).即/dev/tty0:表示当前的控制台
-
if (device == MKDEV(TTY_MAJOR, 0)) {
-
extern struct tty_driver *console_driver;
-
driver = console_driver;
-
//fg_console: 表示当前的控制台
-
index = fg_console;
-
noctty = 1;
-
goto got_driver;
-
}
-
#endif
-
//设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()
-
if (device == MKDEV(TTYAUX_MAJOR, 1)) {
-
driver = console_device(&index);
-
if (driver) {
-
/* Don't let /dev/console block */
-
filp->f_flags |= O_NONBLOCK;
-
noctty = 1;
-
goto got_driver;
-
}
-
mutex_unlock(&tty_mutex);
-
return -ENODEV;
-
}
-
-
//以文件的设备号为关键字,到tty_drivers中搜索所注册的driver
-
driver = get_tty_driver(device, &index);
-
if (!driver) {
-
mutex_unlock(&tty_mutex);
-
return -ENODEV;
-
}
-
got_driver:
-
//index表示它的次设备号
-
retval = init_dev(driver, index, &tty);
-
mutex_unlock(&tty_mutex);
-
if (retval)
-
return retval;
-
-
filp->private_data = tty;
-
file_move(filp, &tty->tty_files);
-
check_tty_count(tty, "tty_open");
-
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
-
tty->driver->subtype == PTY_TYPE_MASTER)
- noctty = 1;
-
#ifdef TTY_DEBUG_HANGUP
-
printk(KERN_DEBUG "opening %s...", tty->name);
-
#endif
-
if (!retval) {
-
if (tty->driver->open)
-
retval = tty->driver->open(tty, filp);
-
else
-
retval = -ENODEV;
-
}
-
filp->f_flags = saved_flags;
-
-
if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
-
!capable(CAP_SYS_ADMIN))
-
retval = -EBUSY;
-
-
if (retval) {
-
#ifdef TTY_DEBUG_HANGUP
-
printk(KERN_DEBUG "error %d in opening %s...", retval,
-
tty->name);
-
#endif
-
release_dev(filp);
-
if (retval != -ERESTARTSYS)
-
return retval;
-
if (signal_pending(current))
-
return retval;
-
schedule();
-
/*
-
* Need to reset f_op in case a hangup happened.
-
*/
-
if (filp->f_op == &hung_up_tty_fops)
-
filp->f_op = &tty_fops;
-
goto retry_open;
-
}
-
-
mutex_lock(&tty_mutex);
-
spin_lock_irq(¤t->sighand->siglock);
-
-
//设置当前进程的终端
-
if (!noctty &&
-
current->signal->leader &&
-
!current->signal->tty &&
-
tty->session == NULL)
-
__proc_set_tty(current, tty);
-
spin_unlock_irq(¤t->sighand->siglock);
-
mutex_unlock(&tty_mutex);
-
tty_audit_opening();
-
return 0;
-
}
注意在这里有个容易忽略的操作:init_dev()。Init_dev() -à initialize_tty_struct() à tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));看一下tty_ldisc_assign(tty, tty_ldisc_get(N_TTY))的操作:Tty_ldisc_get():- struct tty_ldisc *tty_ldisc_get(int disc)
-
{
-
unsigned long flags;
-
struct tty_ldisc *ld;
-
-
if (disc = NR_LDISCS)
-
return NULL;
-
-
spin_lock_irqsave(&tty_ldisc_lock, flags);
-
-
ld = &tty_ldiscs[disc];
-
/* Check the entry is defined */
-
if (ld->flags & LDISC_FLAG_DEFINED) {
-
/* If the module is being unloaded we can't use it */
-
if (!try_module_get(ld->owner))
-
ld = NULL;
-
else /* lock it */
-
ld->refcount++;
-
} else
-
ld = NULL;
-
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-
return ld;
-
}
这个函数的操作为到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.tty_ldisc_assign操作如下:- static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
-
{
-
tty->ldisc = *ld;
-
tty->ldisc.refcount = 0;
-
}
即将取出来的idisc作为tty->ldisc字段.在这段代码中涉及到了tty_driver,tty_struct, struct tty_ldisc.这三者之间的关系用下图表示如下:在这里,为tty_struct的ldisc是默认指定为tty_ldiscs[N_TTY].该ldisc对应的是控制终端的线路规范。可以在用空间用 带TIOCSETD的ioctl调用进行更改.将上述open用流程图的方式表示如下:4.2:设备文件的write操作设备文件的write操作对应tty_fops->write即tty_write().代码如下:- 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;
-
if (!tty || !tty->driver->write ||
-
(test_bit(TTY_IO_ERROR, &tty->flags)))
-
return -EIO;
-
-
ld = tty_ldisc_ref_wait(tty);
-
if (!ld->write)
-
ret = -EIO;
-
else
-
ret = do_tty_write(ld->write, tty, file, buf, count);
-
tty_ldisc_deref(ld);
-
return ret;
-
}
在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用 do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.Do_tty_write代码分段分析如下:- 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
-
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 = count;
-
-
/* write_buf/write_cnt is protected by the atomic_write_lock mutex */
-
if (tty->write_cnt
-
unsigned char *buf;
-
-
if (chunk
-
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;
-
}
-
默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.
-
Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里
-
Tty->write_cnt是临时缓存区的大小。
-
在这里,必须要根据一次写的数据量对这个临时缓存区做调整
-
-
/* 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;
-
lock_kernel();
-
ret = write(tty, file, tty->write_buf, size);
-
unlock_kernel();
-
if (ret
-
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;
-
}
后面的操作就比较简单了。先将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的 时间戳.Write操作的流程图如下示:在这里,我们只看到将数据写放到了ldisc->write().没有看到与tty_driver相关的部份。实际上在ldisc中对写入的数据做 预处理过后,还是会调用tty_driver->write()将其写入硬件.4.3:设备文件的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);
-
lock_kernel();
-
if (ld->read)
-
i = (ld->read)(tty, file, buf, count);
-
else
-
i = -EIO;
-
tty_ldisc_deref(ld);
-
unlock_kernel();
-
if (i > 0)
-
inode->i_atime = current_fs_time(inode->i_sb);
-
return i;
-
}
这个read操作就更简单。直接调用ldsic->read()完成工作流程图如下:五:小结在tty设备文件的操作中。Open操作会进行一系统初始化。然后调用ldsic->open tty_driver->open。在write和read调用中只tty_core只会用到 ldisc->wirte/ldisc->read。除了上面分析的几个操作之外,还有一个ioctl操作,以及它封装的几个 termios。这些ioctl类的操作会直接和tty_driver相关联。
阅读(4027) | 评论(0) | 转发(8) |