经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
【驱动】串口驱动分析(二)-tty core
来源:cnblogs  作者:嵌入式与Linux那些事  时间:2023/12/1 9:09:39  对本文有异议

前言

tty这个名称源于电传打字节的简称,在linux表示各种终端,终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标,输出设备显示器的控制终端和串口终端。也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题。

tty驱动概貌

tty架构如下所示:

如上图所示,用户空间主要是通过系统调用与tty core交互。tty core根据用空间操作的类型再选择跟line disciplinetty driver交互。

例如,设置硬件的ioctl指令就直接交给tty_driver处理。read和write操作就会交给 line discipline处理。

Line discipline是线路规程的意思。正如它的名字一样,它表示的是这条终端”线程”的输入与输出规范设置。主要用来进行输入/输出数据的预处理。

处理之后,就会将数据交给tty driver ,它将字符转换成终端可以理解的字串。将其传给终端设备。

值得注意的是,这个架构没有为tty driver 提供read操作。也就是说tty coreline discipline都没有办法从tty driver里直接读终端信息。这是因为tty driver对应的hardware并不一定是输入数据和输出 数据的共同负载者。

例如控制终端,输出设备是显示器,输入设备是键盘。基于这样的原理。在line discipline中有一个输入缓存区,并提供了一个名叫receive_buf()的接口函数。对应的终端设备只要调用line discipinereceiver_buf函数,将数据写入到输入缓存区就可以了。如果一个设备同时是输入设备又是输出设备。那在设备的中断处理中调用receive_buf()将数据写入即可.

tty驱动接口分析

tty_init()

  1. /*
  2. * Ok, now we can initialize the rest of the tty devices and can count
  3. * on memory allocations, interrupts etc..
  4. */
  5. int __init tty_init(void)
  6. {
  7. tty_sysctl_init();
  8. cdev_init(&tty_cdev, &tty_fops);
  9. if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
  10. register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
  11. panic("Couldn't register /dev/tty driver\n");
  12. device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
  13. cdev_init(&console_cdev, &console_fops);
  14. if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
  15. register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
  16. panic("Couldn't register /dev/console driver\n");
  17. consdev = device_create_with_groups(tty_class, NULL,
  18. MKDEV(TTYAUX_MAJOR, 1), NULL,
  19. cons_dev_groups, "console");
  20. if (IS_ERR(consdev))
  21. consdev = NULL;
  22. #ifdef CONFIG_VT
  23. vty_init(&console_fops);
  24. #endif
  25. return 0;
  26. }

tty_init主要做了以下工作:

  1. 初始化 tty 子系统的 sysctl 相关设置,包括注册 sysctl 参数、创建 sysctl 目录等。
  2. 初始化 tty 设备的字符设备对象,并将其与 tty 设备操作函数 tty_fops 绑定。同时,创建一个名为 "tty" 的 tty 设备节点,并将其设备号设置为 MKDEV(TTYAUX_MAJOR, 0)
  3. 初始化控制台设备的字符设备对象,并将其添加到字符设备系统中。同时,创建一个名为 "console" 的控制台设备节点,并将其设备号设置为 MKDEV(TTYAUX_MAJOR, 1)。该控制台设备节点还将在 sysfs 中创建一个名为 "console" 的目录,并在该目录下创建多个属性文件,用于控制控制台的一些属性。
  4. 如果内核支持虚拟终端,则初始化虚拟终端。

这里我们看到了熟悉的cdev_init(),device_create()之类的函数,这正是字符设备的创建流程。因此,我们说串口驱动也是一个字符设备驱动。

而在serial8250_init()中,会调用platform_driver_register()去注册serial8250_isa_driver,在设备树节点和serial8250_isa_driver name匹配的时候,就会进入probe流程。因此,也可以说串口驱动是总线设备驱动模型。

tty_alloc_driver

  1. /* Use TTY_DRIVER_* flags below */
  2. #define tty_alloc_driver(lines, flags) __tty_alloc_driver(lines, THIS_MODULE, flags)

__tty_alloc_driver()用于分配一个 tty 驱动程序的数据结构 struct tty_driver,并对其一些常用字段进行初始化。

  1. /**
  2. * __tty_alloc_driver -- allocate tty driver
  3. * @lines: count of lines this driver can handle at most
  4. * @owner: module which is repsonsible for this driver
  5. * @flags: some of TTY_DRIVER_* flags, will be set in driver->flags
  6. *
  7. * This should not be called directly, some of the provided macros should be
  8. * used instead. Use IS_ERR and friends on @retval.
  9. */
  10. struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
  11. unsigned long flags)
  12. {
  13. struct tty_driver *driver;
  14. unsigned int cdevs = 1;
  15. int err;
  16. if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1))
  17. return ERR_PTR(-EINVAL);
  18. /*分配一个 struct tty_driver 结构体,并对其中的一些字段进行初始化,包括 num、owner、flags 等*/
  19. driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
  20. if (!driver)
  21. return ERR_PTR(-ENOMEM);
  22. kref_init(&driver->kref);
  23. driver->magic = TTY_DRIVER_MAGIC;
  24. driver->num = lines;
  25. driver->owner = owner;
  26. driver->flags = flags;
  27. /*如果 TTY_DRIVER_DEVPTS_MEM 标志位没有被设置,那么函数会分配 driver->ttys 和 driver->termios,否则不需要分配*/
  28. if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
  29. driver->ttys = kcalloc(lines, sizeof(*driver->ttys),
  30. GFP_KERNEL);
  31. driver->termios = kcalloc(lines, sizeof(*driver->termios),
  32. GFP_KERNEL);
  33. if (!driver->ttys || !driver->termios) {
  34. err = -ENOMEM;
  35. goto err_free_all;
  36. }
  37. }
  38. /*如果 TTY_DRIVER_DYNAMIC_ALLOC 标志位没有被设置,那么函数会分配 driver->ports,否则不需要分配*/
  39. if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
  40. driver->ports = kcalloc(lines, sizeof(*driver->ports),
  41. GFP_KERNEL);
  42. if (!driver->ports) {
  43. err = -ENOMEM;
  44. goto err_free_all;
  45. }
  46. cdevs = lines;
  47. }
  48. /*函数会根据 lines 的值分配相应数量的 driver->cdevs*/
  49. driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
  50. if (!driver->cdevs) {
  51. err = -ENOMEM;
  52. goto err_free_all;
  53. }
  54. return driver;
  55. err_free_all:
  56. kfree(driver->ports);
  57. kfree(driver->ttys);
  58. kfree(driver->termios);
  59. kfree(driver->cdevs);
  60. kfree(driver);
  61. return ERR_PTR(err);
  62. }

tty_register_driver

tty_register_driver用于注册 tty 驱动程序的,被 tty 驱动程序调用以将自己注册到内核中。

  1. /*
  2. * Called by a tty driver to register itself.
  3. */
  4. int tty_register_driver(struct tty_driver *driver)
  5. {
  6. int error;
  7. int i;
  8. dev_t dev;
  9. struct device *d;
  10. /*确认是否要内核动态分配主设备号*/
  11. if (!driver->major) {
  12. /*函数调用 alloc_chrdev_region 函数来动态分配主设备号,并将分配的主设备号和次设备号保存在 driver->major 和 driver->minor_start 字段中*/
  13. error = alloc_chrdev_region(&dev, driver->minor_start,
  14. driver->num, driver->name);
  15. if (!error) {
  16. driver->major = MAJOR(dev);
  17. driver->minor_start = MINOR(dev);
  18. }
  19. } else {
  20. /*已经预先分配了主设备号,函数调用 register_chrdev_region 函数来注册设备号*/
  21. dev = MKDEV(driver->major, driver->minor_start);
  22. error = register_chrdev_region(dev, driver->num, driver->name);
  23. }
  24. if (error < 0)
  25. goto err;
  26. /*判断是否设置了 TTY_DRIVER_DYNAMIC_ALLOC 标志位*/
  27. if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
  28. /*需要动态分配 tty 设备号,函数调用 tty_cdev_add 函数来添加 tty 设备号,并将每个 tty 设备的字符设备注册到内核中*/
  29. error = tty_cdev_add(driver, dev, 0, driver->num);
  30. if (error)
  31. goto err_unreg_char;
  32. }
  33. mutex_lock(&tty_mutex);
  34. /*将 driver 添加到链表 tty_drivers 中*/
  35. list_add(&driver->tty_drivers, &tty_drivers);
  36. mutex_unlock(&tty_mutex);
  37. /*判断 TTY_DRIVER_DYNAMIC_DEV 标志位是否设置*/
  38. if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
  39. for (i = 0; i < driver->num; i++) {
  40. /*需要注册固定的 tty 设备号,函数在循环中调用 tty_register_device 函数来注册每个 tty 设备号,并将每个 tty 设备注册到内核中*/
  41. d = tty_register_device(driver, i, NULL);
  42. if (IS_ERR(d)) {
  43. error = PTR_ERR(d);
  44. goto err_unreg_devs;
  45. }
  46. }
  47. }
  48. /*注册 /proc/tty/drivers 目录中的信息*/
  49. proc_tty_register_driver(driver);
  50. /*将 driver 结构体中的 flags 字段设置为 TTY_DRIVER_INSTALLED,表示该驱动程序已经被成功注册到内核中*/
  51. driver->flags |= TTY_DRIVER_INSTALLED;
  52. return 0;
  53. err_unreg_devs:
  54. for (i--; i >= 0; i--)
  55. tty_unregister_device(driver, i);
  56. mutex_lock(&tty_mutex);
  57. list_del(&driver->tty_drivers);
  58. mutex_unlock(&tty_mutex);
  59. err_unreg_char:
  60. unregister_chrdev_region(dev, driver->num);
  61. err:
  62. return error;
  63. }

tty_register_driver()函数操作比较简单。就是为tty_driver创建字符设备。然后将字符设备的操作集指定为tty_fops。并且将tty_driver 挂载到tty_drivers链表中。这个链表中是以设备号为关键字找到对应的driver。

特别的。如果没有定义TTY_DRIVER_DYNAMIC_DEV。还会在sysfs中创建一个类设备。这样主要是为了udev管理设备。

tty_unregister_device

tty_unregister_device用于注销一个 tty 设备。该函数的作用是销毁设备节点和字符设备,以便于释放与该 tty 设备相关的资源,例如内存和设备文件等.

  1. /**
  2. * tty_unregister_device - unregister a tty device
  3. * @driver: the tty driver that describes the tty device
  4. * @index: the index in the tty driver for this tty device
  5. *
  6. * If a tty device is registered with a call to tty_register_device() then
  7. * this function must be called when the tty device is gone.
  8. *
  9. * Locking: ??
  10. */
  11. void tty_unregister_device(struct tty_driver *driver, unsigned index)
  12. {
  13. device_destroy(tty_class,
  14. MKDEV(driver->major, driver->minor_start) + index);
  15. if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
  16. cdev_del(driver->cdevs[index]);
  17. driver->cdevs[index] = NULL;
  18. }
  19. }

tty_unregister_device所做工作如下:

  1. 调用 device_destroy 函数来销毁 tty 设备对应的设备节点。接受两个参数:第一个参数 tty_class 表示 tty 类,第二个参数是 tty 设备的设备号,其中 MKDEV(driver->major, driver->minor_start) + index 表示 tty 设备的设备号,driver->major 表示 tty 设备的主设备号,driver->minor_start 表示 tty 设备的次设备号的起始值,index 表示 tty 设备的索引
  2. 如果该 tty 驱动程序不是动态分配的,则调用 cdev_del 函数来注销该 tty 设备对应的字符设备。

get_tty_driver

get_tty_driver作用是在用户空间的应用程序使用 tty 设备时,获取对应的 tty 驱动程序的信息。

  1. /**
  2. * get_tty_driver - find device of a tty
  3. * @dev_t: device identifier
  4. * @index: returns the index of the tty
  5. *
  6. * This routine returns a tty driver structure, given a device number
  7. * and also passes back the index number.
  8. *
  9. * Locking: caller must hold tty_mutex
  10. */
  11. static struct tty_driver *get_tty_driver(dev_t device, int *index)
  12. {
  13. struct tty_driver *p;
  14. /**/
  15. list_for_each_entry(p, &tty_drivers, tty_drivers) {
  16. dev_t base = MKDEV(p->major, p->minor_start);
  17. if (device < base || device >= base + p->num)
  18. continue;
  19. *index = device - base;
  20. return tty_driver_kref_get(p);
  21. }
  22. return NULL;
  23. }

首先使用 list_for_each_entry 循环遍历全局链表 tty_drivers,该链表中保存了所有已经注册的 tty 驱动程序。对于每个 tty 驱动程序,函数将其设备号的起始值和结束值计算出来,如果给定设备号不在这个范围内,则继续遍历下一个 tty 驱动程序。

如果给定设备号在某个 tty 驱动程序的范围内,则计算出该设备号对应的 tty 设备的索引值,并调用 tty_driver_kref_get 函数来获取该 tty 驱动程序的引用计数。函数返回该 tty 驱动程序的结构体指针,并将找到的 tty 设备的索引值保存到 index 参数中。

需要注意的是,函数在访问全局链表 tty_drivers 时,需要持有互斥锁 tty_mutex。因为多个应用程序可能同时访问同一个 tty 驱动程序,如果没有互斥锁保护,可能会导致并发问题。

tty_open

从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open(),用于打开一个 tty 设备。函数的作用是在用户空间的应用程序使用 tty 设备时,打开对应的 tty 设备,并初始化相应的数据结构。

  1. /**
  2. * tty_open - open a tty device
  3. * @inode: inode of device file
  4. * @filp: file pointer to tty
  5. *
  6. * tty_open and tty_release keep up the tty count that contains the
  7. * number of opens done on a tty. We cannot use the inode-count, as
  8. * different inodes might point to the same tty.
  9. *
  10. * Open-counting is needed for pty masters, as well as for keeping
  11. * track of serial lines: DTR is dropped when the last close happens.
  12. * (This is not done solely through tty->count, now. - Ted 1/27/92)
  13. *
  14. * The termios state of a pty is reset on first open so that
  15. * settings don't persist across reuse.
  16. *
  17. * Locking: tty_mutex protects tty, tty_lookup_driver and tty_init_dev.
  18. * tty->count should protect the rest.
  19. * ->siglock protects ->signal/->sighand
  20. *
  21. * Note: the tty_unlock/lock cases without a ref are only safe due to
  22. * tty_mutex
  23. */
  24. static int tty_open(struct inode *inode, struct file *filp)
  25. {
  26. struct tty_struct *tty;
  27. int noctty, retval;
  28. struct tty_driver *driver = NULL;
  29. int index;
  30. dev_t device = inode->i_rdev;
  31. unsigned saved_flags = filp->f_flags;
  32. nonseekable_open(inode, filp);
  33. retry_open:
  34. /*分配一个 tty 结构体*/
  35. retval = tty_alloc_file(filp);
  36. if (retval)
  37. return -ENOMEM;
  38. /*检查文件的标志位,如果包含 O_NOCTTY 标志,则禁止将该 tty 设备设置为控制终端*/
  39. noctty = filp->f_flags & O_NOCTTY;
  40. index = -1;
  41. retval = 0;
  42. /*尝试打开当前的 tty 设备*/
  43. tty = tty_open_current_tty(device, filp);
  44. if (!tty) {
  45. mutex_lock(&tty_mutex);
  46. /*根据设备号来查找对应的 tty 驱动程序,并初始化该 tty 设备,将找到的 tty 驱动程序保存到 driver 变量中*/
  47. driver = tty_lookup_driver(device, filp, &noctty, &index);
  48. if (IS_ERR(driver)) {
  49. retval = PTR_ERR(driver);
  50. goto err_unlock;
  51. }
  52. /* check whether we're reopening an existing tty */
  53. /*查找对应的 tty 设备,并将找到的 tty 设备结构体指针保存到 tty 变量中*/
  54. tty = tty_driver_lookup_tty(driver, inode, index);
  55. if (IS_ERR(tty)) {
  56. retval = PTR_ERR(tty);
  57. goto err_unlock;
  58. }
  59. if (tty) {
  60. /*如果找到了该 tty 设备,则需要重新打开该 tty 设备*/
  61. mutex_unlock(&tty_mutex);
  62. retval = tty_lock_interruptible(tty);
  63. tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */
  64. if (retval) {
  65. if (retval == -EINTR)
  66. retval = -ERESTARTSYS;
  67. goto err_unref;
  68. }
  69. retval = tty_reopen(tty);
  70. if (retval < 0) {
  71. tty_unlock(tty);
  72. tty = ERR_PTR(retval);
  73. }
  74. } else { /* Returns with the tty_lock held for now */
  75. /*需要初始化该 tty 设备*/
  76. tty = tty_init_dev(driver, index);
  77. /*为该 tty 设备分配一个 tty 结构体,并对其进行初始化*/
  78. mutex_unlock(&tty_mutex);
  79. }
  80. tty_driver_kref_put(driver);
  81. }
  82. if (IS_ERR(tty)) {
  83. retval = PTR_ERR(tty);
  84. if (retval != -EAGAIN || signal_pending(current))
  85. goto err_file;
  86. tty_free_file(filp);
  87. schedule();
  88. goto retry_open;
  89. }
  90. /*将该 tty 设备与文件结构体相关联*/
  91. tty_add_file(tty, filp);
  92. check_tty_count(tty, __func__);
  93. /*如果该 tty 设备是一个伪终端主设备,则需要将 noctty 标志设置为 1*/
  94. if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
  95. tty->driver->subtype == PTY_TYPE_MASTER)
  96. noctty = 1;
  97. tty_debug_hangup(tty, "(tty count=%d)\n", tty->count);
  98. /*调用 tty 设备的 open 函数*/
  99. if (tty->ops->open)
  100. retval = tty->ops->open(tty, filp);
  101. else
  102. retval = -ENODEV;
  103. filp->f_flags = saved_flags;
  104. if (retval) {
  105. tty_debug_hangup(tty, "error %d, releasing...\n", retval);
  106. tty_unlock(tty); /* need to call tty_release without BTM */
  107. tty_release(inode, filp);
  108. if (retval != -ERESTARTSYS)
  109. return retval;
  110. if (signal_pending(current))
  111. return retval;
  112. schedule();
  113. /*
  114. * Need to reset f_op in case a hangup happened.
  115. */
  116. if (tty_hung_up_p(filp))
  117. filp->f_op = &tty_fops;
  118. goto retry_open;
  119. }
  120. clear_bit(TTY_HUPPED, &tty->flags);
  121. read_lock(&tasklist_lock);
  122. spin_lock_irq(&current->sighand->siglock);
  123. if (!noctty &&
  124. current->signal->leader &&
  125. !current->signal->tty &&
  126. tty->session == NULL) {
  127. /*
  128. * Don't let a process that only has write access to the tty
  129. * obtain the privileges associated with having a tty as
  130. * controlling terminal (being able to reopen it with full
  131. * access through /dev/tty, being able to perform pushback).
  132. * Many distributions set the group of all ttys to "tty" and
  133. * grant write-only access to all terminals for setgid tty
  134. * binaries, which should not imply full privileges on all ttys.
  135. *
  136. * This could theoretically break old code that performs open()
  137. * on a write-only file descriptor. In that case, it might be
  138. * necessary to also permit this if
  139. * inode_permission(inode, MAY_READ) == 0.
  140. */
  141. if (filp->f_mode & FMODE_READ)
  142. __proc_set_tty(tty);
  143. }
  144. spin_unlock_irq(&current->sighand->siglock);
  145. read_unlock(&tasklist_lock);
  146. tty_unlock(tty);
  147. return 0;
  148. err_unlock:
  149. mutex_unlock(&tty_mutex);
  150. err_unref:
  151. /* after locks to avoid deadlock */
  152. if (!IS_ERR_OR_NULL(driver))
  153. tty_driver_kref_put(driver);
  154. err_file:
  155. tty_free_file(filp);
  156. return retval;
  157. }

函数所作工作如下:

  1. 在打开 tty 设备时,该函数会检查文件的标志位,如果包含 O_NOCTTY 标志,则禁止将该 tty 设备设置为控制终端。这是因为如果一个进程打开一个 tty 设备并将其设置为控制终端,其他进程就无法再将该 tty 设备设置为控制终端,这可能会导致一些问题。

  2. 如果打开当前的 tty 设备失败,则需要根据设备号来查找对应的 tty 驱动程序,并初始化该 tty 设备。在查找 tty 驱动程序时,需要调用 tty_lookup_driver 函数来查找对应的 tty 驱动程序,并将找到的 tty 驱动程序保存到 driver 变量中。如果找不到对应的 tty 驱动程序,则返回错误码。

  3. 如果找到了对应的 tty 驱动程序,则调用 tty_driver_lookup_tty 函数来查找对应的 tty 设备,并将找到的 tty 设备结构体指针保存到 tty 变量中。如果找到了该 tty 设备,则需要重新打开该 tty 设备。否则,需要初始化该 tty 设备。在初始化 tty 设备时,需要调用 tty_init_dev 函数来为该 tty 设备分配一个 tty 结构体,并对其进行初始化。

  4. 在打开 tty 设备之后,函数会调用 tty_add_file 函数将该 tty 设备与文件结构体相关联。此外,如果该 tty 设备是一个伪终端主设备,则需要将 noctty 标志设置为 1。

  5. 最后,函数会调用 tty 设备的 open 函数,如果存在的话,来进行一些特定的操作。如果 open 函数返回错误码,则需要释放该 tty 设备并返回错误码。如果 open 函数返回 -ERESTARTSYS,则需要重新打开该 tty 设备。如果有中断发生,也需要重新打开该 tty 设备。

tty_write

tty_write()作用是将用户数据写入 tty 设备,并通过线路规则(line discipline)进行处理。

线路规则是 tty 设备的一种机制,用于处理和转换从用户进程到内核和设备的数据流。在写入 tty 设备之前,需要获取该 tty 设备的线路规则,并调用其 write 方法进行处理。

  1. /**
  2. * tty_write - write method for tty device file
  3. * @file: tty file pointer
  4. * @buf: user data to write
  5. * @count: bytes to write
  6. * @ppos: unused
  7. *
  8. * Write data to a tty device via the line discipline.
  9. *
  10. * Locking:
  11. * Locks the line discipline as required
  12. * Writes to the tty driver are serialized by the atomic_write_lock
  13. * and are then processed in chunks to the device. The line discipline
  14. * write method will not be invoked in parallel for each device.
  15. */
  16. static ssize_t tty_write(struct file *file, const char __user *buf,
  17. size_t count, loff_t *ppos)
  18. {
  19. struct tty_struct *tty = file_tty(file);
  20. struct tty_ldisc *ld;
  21. ssize_t ret;
  22. if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
  23. return -EIO;
  24. if (!tty || !tty->ops->write ||
  25. (test_bit(TTY_IO_ERROR, &tty->flags)))
  26. return -EIO;
  27. /* Short term debug to catch buggy drivers */
  28. if (tty->ops->write_room == NULL)
  29. printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
  30. tty->driver->name);
  31. ld = tty_ldisc_ref_wait(tty);
  32. if (!ld->ops->write)
  33. ret = -EIO;
  34. else
  35. ret = do_tty_write(ld->ops->write, tty, file, buf, count);
  36. tty_ldisc_deref(ld);
  37. return ret;
  38. }

tty_write()所作工作如下:

  1. 首先从文件指针中获取 tty_struct 数据结构的指针,表示要写入的 tty 设备。
  2. 检查传入的 tty_struct 指针是否有效,以及是否有其他进程正在访问该 tty 设备。如果出现问题,返回输入/输出错误码 -EIO
  3. 检查 tty_struct 指针是否有效、tty 设备是否支持写操作,以及是否已经出现了输入/输出错误。如果出现问题,返回输入/输出错误码 -EIO
  4. 检查 tty 设备是否实现了 write_room 方法,如果没有,则输出错误信息。
  5. 获取 tty 设备的线路规则(line discipline),并等待获取成功。
  6. 检查线路规则的 write 方法是否存在,如果不存在,返回输入/输出错误码 -EIO。否则,调用 do_tty_write 函数,将数据写入 tty 设备。
  7. 释放线路规则引用计数器。
  8. 返回写入操作的结果,如果写入成功,则返回写入的字节数;否则,返回相应的错误码。

tty_read

  1. /**
  2. * tty_read - read method for tty device files
  3. * @file: pointer to tty file
  4. * @buf: user buffer
  5. * @count: size of user buffer
  6. * @ppos: unused
  7. *
  8. * Perform the read system call function on this terminal device. Checks
  9. * for hung up devices before calling the line discipline method.
  10. *
  11. * Locking:
  12. * Locks the line discipline internally while needed. Multiple
  13. * read calls may be outstanding in parallel.
  14. */
  15. static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
  16. loff_t *ppos)
  17. {
  18. int i;
  19. struct inode *inode = file_inode(file);
  20. struct tty_struct *tty = file_tty(file);
  21. struct tty_ldisc *ld;
  22. if (tty_paranoia_check(tty, inode, "tty_read"))
  23. return -EIO;
  24. if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
  25. return -EIO;
  26. /* We want to wait for the line discipline to sort out in this
  27. situation */
  28. ld = tty_ldisc_ref_wait(tty);
  29. if (ld->ops->read)
  30. i = ld->ops->read(tty, file, buf, count);
  31. else
  32. i = -EIO;
  33. tty_ldisc_deref(ld);
  34. if (i > 0)
  35. tty_update_time(&inode->i_atime);
  36. return i;
  37. }

tty_read()实现终端设备文件读操作的函数 。

  1. 获取 tty_struct 结构体、inodeline discipline 对象的指针。
  2. 调用 tty_paranoia_check() 函数检查 tty_struct 结构体是否可用。如果检查失败,返回 -EIO。
  3. 检查 tty_struct 结构体是否为空或者 TTY_IO_ERROR 标志位已经设置。如果是,则返回 -EIO。
  4. 获取 line discipline 对象的引用,确保它不会在 tty_read() 函数执行期间被卸载。
  5. 检查 line disciplineread() 方法是否可用。如果可用,则调用该方法进行读取操作,并将返回的字节数保存在变量 i 中。如果不可用,返回 -EIO。
  6. 释放 line discipline 的引用。
  7. 如果读取操作成功,调用 tty_update_time() 函数更新 inode 的访问时间。
  8. 返回读取的字节数。

小结

在这一节里,只对tty的构造做一个分析,具体的比如线路规程的内容我们了解知道就好,这里不做深入分析。

本文参考

https://blog.csdn.net/pan0755/article/details/51693178

https://blog.csdn.net/qq_43286311/article/details/117824804

https://www.jianshu.com/p/09e87a725ed4

https://blog.csdn.net/weixin_40407893/article/details/117956968

https://blog.csdn.net/pan0755/article/details/51693178

原文链接:https://www.cnblogs.com/dongxb/p/17868508.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号