一:前言
Tty(Teletype)这个名称源于电传打字节的简称。在linux表示各种终端,主要有串行端口终端、伪终端、控制终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题.
二:tty驱动概貌
Tty架构如下所示:
如上图所示,用户空间主要是通过设备文件同tty_core交互。tty_core根据用空间操作的类型再选择跟line discipline和tty_driver交互。例如设置硬件的ioctl指令就直接交给tty_driver处理。Read和write操作就会交给line discipline处理。
Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置。tty线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如PPP(端对端协议Peer-Peer Protocol)和Bluetooth(蓝牙技术)。处理之后。就会将数据交给tty_driver。
Tty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串。将其传给终端设备。(The driver’s job
is to format data that is sent to it in a manner that the hardware can understand, andreceive data from the hardware.)
值得注意的是,这个架构没有为tty_driver提供read操作。也就是说tty_core 和line discipline都没有办法从tty_driver里直接读终端信息。这是因为tty_driver对应的hardware并不一定是输入数据和输出数据的共同负载者。例如控制终端,输出设备是显示器,输入设备是键盘。基于这样的原理。在line discipline中有一个输入缓存区。并提供了一个名叫receive_buf()的接口函数。对应的终端设备只要调用line discipine的receive_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) {