LinuxTTY 子系统5(基于Linux6.6)---tty驱动介绍
一、 TTY设备驱动的注册
对于drivers/tty/serial/8250/8250_core.c来说,主要涉及:
-
serial8250_init()--->uart_register_driver(&serial8250_reg)
-
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev)
-
serial8250_probe(struct platform_device *dev)
struct uart_driver serial8250_reg的定义如下:
drivers/tty/serial/8250/8250_core.c
static static struct uart_driver serial8250_reg = {
.owner = THIS_MODULE,
.driver_name = "serial",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,
.nr = UART_NR,
.cons = SERIAL8250_CONSOLE,
};
1.1、uart_register_driver分析
主要完成了一下功能:
-
分配数个uart_state结构体内存: (在uart_add_one_port()里会用到它来关联uart_port)
-
分配tty_driver。normal = alloc_tty_driver(drv->nr)
-
关联struct uart_driver和tty_driver:
uart_driver-> tty_driver= tty_driver; tty_driver ->driver_state = uart_driver; -
设置tty_driver的操作函数为uart_ops(tty_operations类型)中的操作函数:
-
调用tty_register_driver():根据tty_driver里的数据来注册字符设备(来自于uart_driver);并添加到tty_drivers链表;调用tty_register_device()产生设备文件。
drivers/tty/serial/serial_core.c
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal = NULL;
int i, retval; BUG_ON(drv->state); /*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
retval = -ENOMEM;
if (!drv->state)
goto out; normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out; drv->tty_driver = normal; normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
}
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
tty_driver_kref_put(normal);
out_kfree:
kfree(drv->state);
out:
return retval;
}
EXPORT_SYMBOL(uart_register_driver);
1.2、tty_register_driver分析
与传统的字符设备驱动程序完全一致,主要做了一下工作:
-
创建字符设备。
-
注册字符设备。
-
设置udev,创建/dev节点,名称为"%s%d", driver->name, index + driver->name_base,
normal->name = uart_driver->dev_name; //来自于uart_driver= "ttyS", //见struct uart_driver serial8250_reg的定义。
driver->name_base =0;
driver->num=(0--- driver->num); // driver->num = uart_driver->nr = UART_NR = 8
因此创建的节点名为:/dev/ttySx x=(0…7) -
Proc文件系统操作;
drivers/tty/tty_io.c
/**
* tty_register_driver -- register a tty driver
* @driver: driver to register
*
* Called by a tty driver to register itself.
*/
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
struct device *d;
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 < 0)
goto err;
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
error = tty_cdev_add(driver, dev, 0, driver->num);
if (error)
goto err_unreg_char;
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
if (IS_ERR(d)) {
error = PTR_ERR(d);
goto err_unreg_devs;
}
}
}
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
err_unreg_devs:
for (i--; i >= 0; i--)
tty_unregister_device(driver, i);
mutex_lock(&tty_mutex);
list_del(&driver->tty_drivers);
mutex_unlock(&tty_mutex);
err_unreg_char:
unregister_chrdev_region(dev, driver->num);
err:
return error;
}
EXPORT_SYMBOL(tty_register_driver);
此时,内核已经注册了tty_drivers到全局链表tty_drivers。
1.3、serial8250_register_ports()函数分析
主要完成以下任务:
-
为端口号line赋值 。
-
初始化定时器。
-
为uart_8250_port->uart_port.ops赋值= &serial8250_pops。
-
为uart_8250_port[].uart_port->device赋值。
-
将uart_8250_port[].uart_port挂入uart_driver->state[]->port 。
drivers/tty/serial/8250/8250_core.c
static void __init
serial8250_register_ports(struct uart_driver *drv, struct device *dev)
{
int i;
for (i = 0; i < nr_uarts; i++) {
struct uart_8250_port *up = &serial8250_ports[i];
if (up->port.type == PORT_8250_CIR)
continue;
if (up->port.dev)
continue;
up->port.dev = dev;
if (uart_console_registered(&up->port))
pm_runtime_get_sync(up->port.dev);
serial8250_apply_quirks(up);
uart_add_one_port(drv, &up->port);
}
}
1.4、serial8250_probe()函数分析
通过struct plat_serial8250_port *p = dev->dev.platform_data获取platform_device的设备私有数据(里面一般包括mapbase、irq、iotype等),将这些数据赋给uart_port,然后调用:
serial8250_register_port()--->uart_add_one_port(&serial8250_reg, &uart->port)。
将uart_port注册到uart_driver->state[]->port里面。
drivers/tty/serial/8250/8250_core.c
/*
* Register a set of serial devices attached to a platform device. The
* list is terminated with a zero flags entry, which means we expect
* all entries to have at least UPF_BOOT_AUTOCONF set.
*/
static int serial8250_probe(struct platform_device *dev)
{
struct plat_serial8250_port *p = dev_get_platdata(&dev->dev);
struct uart_8250_port uart;
int ret, i, irqflag = 0;
memset(&uart, 0, sizeof(uart));
if (share_irqs)
irqflag = IRQF_SHARED;
for (i = 0; p && p->flags != 0; p++, i++) {
uart.port.iobase = p->iobase;
uart.port.membase = p->membase;
uart.port.irq = p->irq;
uart.port.irqflags = p->irqflags;
uart.port.uartclk = p->uartclk;
uart.port.regshift = p->regshift;
uart.port.iotype = p->iotype;
uart.port.flags = p->flags;
uart.port.mapbase = p->mapbase;
uart.port.mapsize = p->mapsize;
uart.port.hub6 = p->hub6;
uart.port.has_sysrq = p->has_sysrq;
uart.port.private_data = p->private_data;
uart.port.type = p->type;
uart.bugs = p->bugs;
uart.port.serial_in = p->serial_in;
uart.port.serial_out = p->serial_out;
uart.dl_read = p->dl_read;
uart.dl_write = p->dl_write;
uart.port.handle_irq = p->handle_irq;
uart.port.handle_break = p->handle_break;
uart.port.set_termios = p->set_termios;
uart.port.set_ldisc = p->set_ldisc;
uart.port.get_mctrl = p->get_mctrl;
uart.port.pm = p->pm;
uart.port.dev = &dev->dev;
uart.port.irqflags |= irqflag;
ret = serial8250_register_8250_port(&uart);
if (ret < 0) {
dev_err(&dev->dev, "unable to register port at index %d "
"(IO%lx MEM%llx IRQ%d): %d\n", i,
p->iobase, (unsigned long long)p->mapbase,
p->irq, ret);
}
}
return 0;
}
二、设备的打开过程
以/dev/ttyS0为例。
根据系统在前面在此字符设备注册的fops,在open()后,系统应该是进入tty_fops的tty_open()函数。
可以明确:
tty_struct结构是在tty_open()时构建;
tty_struct保存在file->private_data;
以后的操作通过filp就可以找到tty_struct
然后通过tty_struct->tty_driver->open(tty_struct*, filp)调用的是tty_operations uart_ops.open =uart_open(serile_core.c);通过 uart_register_driver()->tty_set_operations(normal, &uart_ops)注册。
tty_operations里的函数都是以(tty_struct, file* filp) 为参数。
而在uart_open(tty_struct*, filp)里,进行一些初始化后,调用了uart_startup(state, 0),此函数主要做了两件事:
1)分配并初始化transmit 和 temporary缓冲区circ_buf
2)调用port->ops->startup(port
port=state.port |state = uart_driver->state[] |uart_driver=tty_struct->tty_driver->driver_state
arch/um/drivers/tty.c
static int tty_open(int input, int output, int primary, void *d,
char **dev_out)
{
struct tty_chan *data = d;
int fd, err, mode = 0;
if (input && output)
mode = O_RDWR;
else if (input)
mode = O_RDONLY;
else if (output)
mode = O_WRONLY;
fd = open(data->dev, mode);
if (fd < 0)
return -errno;
if (data->raw) {
CATCH_EINTR(err = tcgetattr(fd, &data->tt));
if (err)
return err;
err = raw(fd);
if (err)
return err;
}
*dev_out = data->dev;
return fd;
}
总结:
tty_open()后,创建了tty_struct,并保存在filp中;再调用uart层的tty_operations->uart_ops.open(),在里面创建了发送的circ_buf;然后调用了uart_port->uart_ops->open(tty, filp)。
tty_struct对应一个已经打开的具体tty设备。
三、TTY设备的读
TTY设备的读分为两部分:首先是进程读取tty_struct对应的缓冲区并阻塞当前进程;然后设备中断里,接收数据,唤醒进程的读操作。
程序首先进入tty_read():
-
首先通过file->private_data获取tty_struct,然后再获取tty_ldisc;
-
最后调用tty_ldisc->read。对于N_TTY即tty_ldisc_N_TTY.read()=read_chan()
drivers/tty/tty_io.c
/**
* tty_read - read method for tty device files
* @iocb: kernel I/O control block
* @to: destination for the data read
*
* Perform the read system call function on this terminal device. Checks
* for hung up devices before calling the line discipline method.
*
* Locking:
* Locks the line discipline internally while needed. Multiple read calls
* may be outstanding in parallel.
*/
static ssize_t tty_read(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || tty_io_error(tty))
return -EIO;
/* We want to wait for the line discipline to sort out in this
* situation.
*/
ld = tty_ldisc_ref_wait(tty);
if (!ld)
return hung_up_tty_read(iocb, to);
ret = -EIO;
if (ld->ops->read)
ret = iterate_tty_read(ld, tty, file, to);
tty_ldisc_deref(ld);
if (ret > 0)
tty_update_time(tty, false);
return ret;
}
-
初始化延迟工作队列:init_dev()==>initialize_tty_struct()==>INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc)
-
tty->read_wait只被n_tty_receive_buf()函数(或里面的分支)调用;
-
n_tty_receive_buf()只被flush_to_ldisc()调用
-
而tty_flip_buffer_push()有两种方式来调用flush_to_ldisc():
1)tty->low_latency===> flush_to_ldisc(&tty->buf.work.work);
2)schedule_delayed_work(&tty->buf.work, 1);
两者都是调用flush_to_ldisc(),不同点在于后者是延迟执行flush_to_ldisc()。延迟工作队列是在initialize_tty_struct()===>INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc);中进行初始化的。
对于驱动层,调用轨迹如下:
在open()操作里申请中断;在中断里唤醒进程。
tty_open()==>………==>serial8250_startup()==>serial_link_irq_chain()==>request_irq()--------申请中断
serial8250_interrupt()--------------------------------------------------------------------------------------------处理中断
->serial8250_handle_port()
->receive_chars()
-> uart_insert_char() //接收字符,存入tty_buffer,tty_struct包含tty_bufhead
->tty_insert_flip_char() //而tty_bufhead包含三个tty_buffer成员:head、tail、free
->tty_flip_buffer_push()
-> flush_to_ldisc()
-> n_tty_receive_buf()
-> memcpy(tty->read_buf + tty->read_head, cp, i); //拷贝数据至tty->read_buf
->tty->read_cnt += i //指示接收buff的字符数。
//与read_chan()-->input_available_p()
里对tty->read_cnt的判断对应
->wake_up(&tty->read_wait) //唤醒进程
大致是下图的流程:
调用tty_insert_flip_char或者tty_insert_flip_string将数据放入tty的缓存tty->tty_buffer;然后调用tty_flip_buffer_push(),将数据从tty缓存拷贝至ldisc缓存。
- tty_insert_flip_string:将hardware driver中的数据缓冲到tty_buffer中,而这个tty_buffer指针则是在tty_port->buf->tail.
- tty_flip_buffer_push:将tty_buffer也即tty驱动层缓冲区数据推到tty线路规程层缓冲区,否则tty核心层无法读取到数据,这样用户也就无法从tty核心层取到数据,可以理解为userspace->tty核心->line discipline->tty驱动.
- 源码中tty_flip_buffer_push会启动flush_to_ldisc的work, 在work进程中会把tty_buffer中的数据推到ldisc的缓冲区。
- userspace->read->tty_read->n_tty_read(tty_ldisc_ops)读取ldisc缓冲区数据
static ssize_t read_chan(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
int minimum, time;
ssize_t retval = 0;
ssize_t size;
long timeout;
unsigned long flags;
do_it_again:
if (!tty->read_buf) {
printk("n_tty_read_chan: called with read_buf == NULL?!?\n");
return -EIO;
}
c = job_control(tty, file);
if(c < 0)
return c;
minimum = time = 0;
timeout = MAX_SCHEDULE_TIMEOUT;
if (!tty->icanon) {
time = (HZ / 10) * TIME_CHAR(tty);
minimum = MIN_CHAR(tty);
if (minimum) {
if (time)
tty->minimum_to_wake = 1;
else if (!waitqueue_active(&tty->read_wait) ||
(tty->minimum_to_wake > minimum))
tty->minimum_to_wake = minimum;
} else {
timeout = 0;
if (time) {
timeout = time;
time = 0;
}
tty->minimum_to_wake = minimum = 1;
}
}
/*
* Internal serialization of reads.
*/
if (file->f_flags & O_NONBLOCK) {
if (!mutex_trylock(&tty->atomic_read_lock))
return -EAGAIN;
}
else {
if (mutex_lock_interruptible(&tty->atomic_read_lock))
return -ERESTARTSYS;
}
add_wait_queue(&tty->read_wait, &wait);
while (nr) {
/* First test for status change. */
if (tty->packet && tty->link->ctrl_status) {
unsigned char cs;
if (b != buf)
break;
cs = tty->link->ctrl_status;
tty->link->ctrl_status = 0;
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;
}
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
continue;
}
__set_current_state(TASK_RUNNING);
/* Deal with packet mode. */
if (tty->packet && b == buf) {
if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (tty->icanon) {
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol;
eol = test_and_clear_bit(tty->read_tail,
tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &
(N_TTY_BUF_SIZE-1));
tty->read_cnt--;
if (eol) {
/* this test should be redundant:
* we shouldn't be reading data if
* canon_data is 0
*/
if (--tty->canon_data < 0)
tty->canon_data = 0;
}
spin_unlock_irqrestore(&tty->read_lock, flags);
if (!eol || (c != __DISABLED_CHAR)) {
if (tty_put_user(tty, c, b++)) {
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
int uncopied;
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
if (uncopied) {
retval = -EFAULT;
break;
}
}
/* If there is enough space in the read buffer now, let the
* low-level driver know. We use n_tty_chars_in_buffer() to
* check the buffer, as it now knows about canonical mode.
* Otherwise, if the driver is throttled and the line is
* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
* we won't get any more characters.
*/
if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
n_tty_set_room(tty);
check_unthrottle(tty);
}
if (b - buf >= minimum)
break;
if (time)
timeout = time;
}
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait);
if (!waitqueue_active(&tty->read_wait))
tty->minimum_to_wake = minimum;
__set_current_state(TASK_RUNNING);
size = b - buf;
if (size) {
retval = size;
if (nr)
clear_bit(TTY_PUSH, &tty->flags);
} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
goto do_it_again;
n_tty_set_room(tty);
return retval;
}
四、 TTY设备的写
首先调用tty_write.
drivers/tty/tty_io.c
static ssize_t tty_write(struct kiocb *iocb, struct iov_iter *from)
{
return file_tty_write(iocb->ki_filp, iocb, from);
}
static ssize_t file_tty_write(struct file *file, struct kiocb *iocb, struct iov_iter *from)
{
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;
if (!tty || !tty->ops->write || tty_io_error(tty))
return -EIO;
/* Short term debug to catch buggy drivers */
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(iocb, from);
if (!ld->ops->write)
ret = -EIO;
else
ret = iterate_tty_write(ld, tty, file, from);
tty_ldisc_deref(ld);
return ret;
}
TTY设备写涉及写进程、中断ISR、即tasklet_action三部分的配合:
应用APP函数调用:
tty_write()---------------------------------------------------------------------------------------------------tty_io.c
do_tty_write()
copy_from_user() //将用户空间数据copy至tty->write_buf
write () //对于N_TTY,即tty_ldisc_N_TTY.write()=write_chan()
add_wait_queue()-------------------------------------------------------------------------n_tty.c
//添加等待队列
tty->driver->flush_chars(tty); //struct tty_operations uart_ops. flush_chars=uart_flush_chars()
uart_start()---------------------------------------------------------------------------serial_core.c
__uart_start()
uart_port->ops->start_tx() //uart_ops serial8250_pops.start_tx = serial8250_start_tx
transmit_chars() //启动真正发送------------------------------8250.c
serial_out(up, UART_TX, xmit->buf[xmit->tail]) //copy至芯片发送buffer
uart_write_wakeup(struct uart_port *port) //调度tasklet_schedule()
tasklet_schedule(&info->tlet);
schedule(); //进程睡眠
中断函数调用:
serial8250_interrupt()-----------------------------------------------------------------------------------处理中断
serial8250_handle_port()------------------------------------------------------------------------8250.c
if(Transmit-hold-register empty)
transmit_chars()
serial_out(up, UART_TX, xmit->buf[xmit->tail]) //copy至芯片发送缓冲
uart_write_wakeup()---------------------------------------------------serial_core.c
tasklet_schedule() //调度tasklet_schedule()
tasklet在tty_open()-->……-->uart_open()-->uart_get()
-->tasklet_init(&state->info->tlet, uart_tasklet_action,state)中进行初始化。
【tty_open()-->tty_struct.tty_driver.open()=uart_open()-->uart_get()-->tasklet_init()】
tasklet_action()调用:
经过tasklet_schedule ()后执行uart_tasklet_action。
uart_tasklet_action ()
tty_wakeup()
ld->write_wakeup(tty)//即n_tty_write_wakeup():发送信号SIGIO给fasync_struct所描述的PID
wake_up_interruptible(&tty->write_wait); //唤醒写进程
五、TTY驱动总结
从进程、vfs、tty_core、serial_core到uart驱动各个数据结构之间的相互关系图:
+------------------+ +-------------------------+
| 进程 (Process) | | VFS (Virtual FileSystem) |
| | | |
| - 文件描述符 |<----->| - tty设备文件(/dev/tty*)|
+------------------+ +-------------------------+
| |
| |
v v
+----------------+ +----------------------+
| tty_core |<-------->| struct tty_struct |
| | | - 终端设备状态 |
| - tty设备管理 | | - 控制终端关联 |
| - 设备分配 | | - 输入/输出队列 |
+----------------+ +----------------------+
| |
v v
+----------------+ +------------------------+
| serial_core |<-------->| struct uart_driver |
| | | - 串口驱动操作接口 |
| - 串口管理 | | - 驱动初始化、操作 |
+----------------+ +------------------------+
| |
v v
+--------------------+ +-------------------------+
| UART 驱动 (UART) |<----->| struct uart_port |
| | | - 硬件串口接口 |
| - 硬件操作接口 | | - 状态、寄存器等硬件信息|
+--------------------+ +-------------------------+
TTY 驱动程序
TTY 驱动程序是内核中负责实现具体终端设备操作的模块。不同类型的终端设备有不同的驱动程序,常见的 TTY 驱动程序包括:
- 控制台驱动程序:负责管理物理控制台设备。
- 串口驱动程序(如
uart_driver
):处理串口设备的输入输出。 - 虚拟终端驱动程序:管理多个虚拟终端设备。
- 伪终端驱动程序:实现终端仿真,允许通过主从设备进行通信。
关键数据结构
-
struct tty_struct
:表示一个终端设备的结构体,管理终端设备的状态、输入/输出队列、控制信息等。
-
struct tty_struct { struct file_operations *ops; // 终端操作函数 unsigned int flags; // 终端状态标志 struct queue *read_q; // 输入队列 struct queue *write_q; // 输出队列 // 其他终端相关信息 };
-
struct tty_driver
:表示一个终端类型(如串口、控制台等)的驱动程序结构体,提供设备操作接口。 -
struct tty_driver { struct file_operations *driver_ops; // 驱动的文件操作函数 unsigned int num_ports; // 设备端口数量 // 其他驱动程序相关信息 };
-
struct uart_port
:表示一个硬件串口设备,包含硬件资源和配置。
-
struct uart_port { unsigned int irq; // 中断号 void __iomem *membase; // 内存映射的硬件地址 unsigned int type; // 串口类型(例如 16550A) // 其他硬件相关信息 };
TTY 驱动的工作流程
-
初始化:在内核启动时,TTY 驱动程序会初始化并注册各种终端设备(如串口设备、虚拟终端设备等)。
-
打开设备:当用户进程打开一个终端设备文件(例如
/dev/ttyS0
),TTY 驱动会创建并初始化一个tty_struct
结构体,关联到该终端设备。 -
读写操作:当用户进程对终端设备进行读写时,内核通过相应的文件操作函数将数据从用户空间传递到终端设备(例如串口、虚拟终端等)。这些数据被缓存在
tty_struct
中的输入队列和输出队列中。 -
信号处理:TTY 系统还负责处理与终端相关的信号,如终止信号、暂停信号等。
-
关闭设备:当用户进程关闭终端设备时,TTY 驱动会释放相关资源,清理状态,并注销设备。