LinuxTTY 子系统5

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;
}

read_chan()

  • 初始化延迟工作队列: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 驱动的工作流程

  1. 初始化:在内核启动时,TTY 驱动程序会初始化并注册各种终端设备(如串口设备、虚拟终端设备等)。

  2. 打开设备:当用户进程打开一个终端设备文件(例如 /dev/ttyS0),TTY 驱动会创建并初始化一个 tty_struct 结构体,关联到该终端设备。

  3. 读写操作:当用户进程对终端设备进行读写时,内核通过相应的文件操作函数将数据从用户空间传递到终端设备(例如串口、虚拟终端等)。这些数据被缓存在 tty_struct 中的输入队列和输出队列中。

  4. 信号处理:TTY 系统还负责处理与终端相关的信号,如终止信号、暂停信号等。

  5. 关闭设备:当用户进程关闭终端设备时,TTY 驱动会释放相关资源,清理状态,并注销设备。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值