linux设备模型之tty驱动架构分析,Linux设备模型之tty驱动架构分析

一:前言

Tty这个名称源于电传打字节的简称。在linux表示各种终端。终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标。输出设备显示器的控制

终端和串口终端.也有对应于不存在设备的pty驱动。在如此众多的终端模型之中,linux是怎么将它们统一建模的呢?这就是我们今天要讨论的问题

二:tty驱动概貌

Tty架构如下所示:

eb75591c09b9f97ab80614d98e962cff.png

如上图所示,用户空间主要是通过设备文件同tty_core交互.tty_core根据用空间操作的类型再选择跟line

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

line discipline处理.

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

Tty_driver就是终端对应的驱动了。它将字符转换成终端可以理解的字串.将其传给终端设备。

值得注意的是,这个架构没有为tty_drivero提供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()接口代码如下所示:

01.struct tty_driver *alloc_tty_driver(int lines)

02.{

03. struct tty_driver *driver;

04. driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);

05. if (driver) {

06. driver->magic = TTY_DRIVER_MAGIC;

07. driver->num = lines;

08. /* later we'll move allocation of tables here */

09. }

10. return driver;

11.}

这个函数只有一个参数。这个参数的含义为line的个数。也即次设备号的个数。注意每个设备文件都会对应一个line.

在这个接口里为tty_driver分配内存,然后将driver->mage.driver->num初始化之后就返回了.

tty_register_driver()用来注册一个tty_driver。代码如下:

01.int tty_register_driver(struct tty_driver *driver)

02.{

03. int error;

04. int i;

05. dev_t dev;

06. void **p = NULL;

07. //TTY_DRIVER_INSTALLED:已安装的

08. if (driver->flags & TTY_DRIVER_INSTALLED)

09. return 0;

10. //TTY_DRIVER_DEVPTS_MEM:使用devpts进行动态内存映射

11. if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {

12. p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);

13. if (!p)

14. return -ENOMEM;

15. }

16. //注册字符设备号

17. //如果没有指定driver->major

18. if (!driver->major) {

19. error = alloc_chrdev_region(&dev, driver->minor_start,

20. driver->num, driver->name);

21. if (!error) {

22. driver->major = MAJOR(dev);

23. driver->minor_start = MINOR(dev);

24. }

25. } else {

26. dev = MKDEV(driver->major, driver->minor_start);

27. error = register_chrdev_region(dev, driver->num, driver->name);

28. }

29. if (error

30. kfree(p);

31. return error;

32. }

33. if (p) {

34. driver->ttys = (struct tty_struct **)p;

35. driver->termios = (struct ktermios **)(p + driver->num);

36. driver->termios_locked = (struct ktermios **)

37. (p + driver->num * 2);

38. } else {

39. driver->ttys = NULL;

40. driver->termios = NULL;

41. driver->termios_locked = NULL;

42. }

43. //注册字符设备

44. cdev_init(&driver->cdev, &tty_fops);

45. driver->cdev.owner = driver->owner;

46. error = cdev_add(&driver->cdev, dev, driver->num);

47. if (error) {

48. unregister_chrdev_region(dev, driver->num);

49. driver->ttys = NULL;

50. driver->termios = driver->termios_locked = NULL;

51. kfree(p);

52. return error;

53. }

54. //指定默认的put_char

55. if (!driver->put_char)

56. driver->put_char = tty_default_put_char;

57. mutex_lock(&tty_mutex);

58. list_add(&driver->tty_drivers, &tty_drivers);

59. mutex_unlock(&tty_mutex);

60. //如果没有指定TTY_DRIVER_DYNAMIC_DEV.即动态设备管理

61. if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {

62. for (i = 0; i num; i++)

63. tty_register_device(driver, i, NULL);

64. }

65. proc_tty_register_driver(driver);

66. return 0;

67.}

这个函数操作比较简单。就是为tty_driver创建字符设备。然后将字符设备的操作集指定为tty_fops.并且将tty_driver

挂载到tty_drivers链表中.其实这个链表的作用跟我们之前分析的input子系统中的input_dev[

]数组类似。都是以设备号为关键字找到对应的driver.

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

以流程图的方式将上述操作表示如下:

85edec6ae33f99cc5193144691994926.png

四:设备文件的操作

设备文件的操作是本节分析的重点。它的主要操作是将各项操作对应到ldsic或者是tty_driver.

4.1:打开tty设备的操作

从注册的过程可以看到,所有的操作都会对应到tty_fops中。Open操作对应的操作接口是tty_open()。代码如下:

01.static int tty_open(struct inode *inode, struct file *filp)

02.{

03. struct tty_struct *tty;

04. int noctty, retval;

05. struct tty_driver *driver;

06. int index;

07. dev_t device = inode->i_rdev;

08. unsigned short saved_flags = filp->f_flags;

09. nonseekable_open(inode, filp);

10.retry_open:

11. //O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端

12. //noctty:需不需要更改当前进程的控制终端

13. noctty = filp->f_flags & O_NOCTTY;

14. index = -1;

15. retval = 0;

16. mutex_lock(&tty_mutex);

17. //设备号(5,0) 即/dev/tty.表示当前进程的控制终端

18. if (device == MKDEV(TTYAUX_MAJOR, 0)) {

19. tty = get_current_tty();

20. //如果当前进程的控制终端不存在,退出

21. if (!tty) {

22. mutex_unlock(&tty_mutex);

23. return -ENXIO;

24. }

25. //取得当前进程的tty_driver

26. driver = tty->driver;

27. index = tty->index;

28. filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */

29. /* noctty = 1; */

30. goto got_driver;

31. }

32.#ifdef CONFIG_VT

33. //设备号(4,0).即/dev/tty0:表示当前的控制台

34. if (device == MKDEV(TTY_MAJOR, 0)) {

35. extern struct tty_driver *console_driver;

36. driver = console_driver;

37. //fg_console: 表示当前的控制台

38. index = fg_console;

39. noctty = 1;

40. goto got_driver;

41. }

42.#endif

43. //设备号(5,1).即/dev/console.表示外接的控制台. 通过regesit_console()

44. if (device == MKDEV(TTYAUX_MAJOR, 1)) {

45. driver = console_device(&index);

46. if (driver) {

47. /* Don't let /dev/console block */

48. filp->f_flags |= O_NONBLOCK;

49. noctty = 1;

50. goto got_driver;

51. }

52. mutex_unlock(&tty_mutex);

53. return -ENODEV;

54. }

55. //以文件的设备号为关键字,到tty_drivers中搜索所注册的driver

56. driver = get_tty_driver(device, &index);

57. if (!driver) {

58. mutex_unlock(&tty_mutex);

59. return -ENODEV;

60. }

61.got_driver:

62. //index表示它的次设备号

63. retval = init_dev(driver, index, &tty);

64. mutex_unlock(&tty_mutex);

65. if (retval)

66. return retval;

67. filp->private_data = tty;

68. file_move(filp, &tty->tty_files);

69. check_tty_count(tty, "tty_open");

70. if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&

71. tty->driver->subtype == PTY_TYPE_MASTER)

72. noctty = 1;

73.#ifdef TTY_DEBUG_HANGUP

74. printk(KERN_DEBUG "opening %s...", tty->name);

75.#endif

76. if (!retval) {

77. if (tty->driver->open)

78. retval = tty->driver->open(tty, filp);

79. else

80. retval = -ENODEV;

81. }

82. filp->f_flags = saved_flags;

83. if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&

84. !capable(CAP_SYS_ADMIN))

85. retval = -EBUSY;

86. if (retval) {

87.#ifdef TTY_DEBUG_HANGUP

88. printk(KERN_DEBUG "error %d in opening %s...", retval,

89. tty->name);

90.#endif

91. release_dev(filp);

92. if (retval != -ERESTARTSYS)

93. return retval;

94. if (signal_pending(current))

95. return retval;

96. schedule();

97. /*

98. * Need to reset f_op in case a hangup happened.

99. */

100. if (filp->f_op == &hung_up_tty_fops)

101. filp->f_op = &tty_fops;

102. goto retry_open;

103. }

104. mutex_lock(&tty_mutex);

105. spin_lock_irq(&current->sighand->siglock);

106. //设置当前进程的终端

107. if (!noctty &&

108. current->signal->leader &&

109. !current->signal->tty &&

110. tty->session == NULL)

111. __proc_set_tty(current, tty);

112. spin_unlock_irq(&current->sighand->siglock);

113. mutex_unlock(&tty_mutex);

114. tty_audit_opening();

115. return 0; 116.}

注意在这里有个容易忽略的操作: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))的操作:

01.Tty_ldisc_get():

02.struct tty_ldisc *tty_ldisc_get(int disc)

03.{

04. unsigned long flags;

05. struct tty_ldisc *ld;

06. if (disc = NR_LDISCS)

07. return NULL;

08. spin_lock_irqsave(&tty_ldisc_lock, flags);

09. ld = &tty_ldiscs[disc];

10. /* Check the entry is defined */

11. if (ld->flags & LDISC_FLAG_DEFINED) {

12. /* If the module is being unloaded we can't use it */

13. if (!try_module_get(ld->owner))

14. ld = NULL;

15. else /* lock it */

16. ld->refcount++;

17. } else

18. ld = NULL;

19. spin_unlock_irqrestore(&tty_ldisc_lock, flags);

20. return ld;

21.}

这个函数的操作为到tty_ldiscs[ ]找到对应项.这个数组中的成员是调用tty_register_ldisc()将其设置进去的.

tty_ldisc_assign操作如下:

01.static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)

02.{

03. tty->ldisc = *ld;

04. tty->ldisc.refcount = 0;

05.}

即将取出来的idisc作为tty->ldisc字段.

在这段代码中涉及到了tty_driver,tty_struct, struct

tty_ldisc.这三者之间的关系用下图表示如下:

364d861c44b530b02c3cf021de2c8e12.png

在这里,为tty_struct的ldisc是默认指定为tty_ldiscs[N_TTY].该ldisc对应的是控制终端的线路规范。可以在用空间用带TIOCSETD的ioctl调用进行更改.

将上述open用流程图的方式表示如下:

d4bc82aa03de1a7ad10724e7171f2f8b.png

4.2:设备文件的write操作

设备文件的write操作对应tty_fops->write即tty_write().代码如下:

01.static ssize_t tty_write(struct file *file, const char __user *buf,

02. size_t count, loff_t *ppos)

03.{

04. struct tty_struct *tty;

05. struct inode *inode = file->f_path.dentry->d_inode;

06. ssize_t ret;

07. struct tty_ldisc *ld;

08. tty = (struct tty_struct *)file->private_data;

09. if (tty_paranoia_check(tty, inode, "tty_write"))

10. return -EIO;

11. if (!tty || !tty->driver->write ||

12. (test_bit(TTY_IO_ERROR, &tty->flags)))

13. return -EIO;

14. ld = tty_ldisc_ref_wait(tty);

15. if (!ld->write)

16. ret = -EIO;

17. else

18. ret = do_tty_write(ld->write, tty, file, buf, count);

19. tty_ldisc_deref(ld);

20. return ret;

21.}

在open的过程中,将tty_struct存放在file的私有区。在write中,从file的私有区中就可以取到要操作的tty_struct.

如果tty_driver中没有write.如果tty有错误都会有效性判断失败返回。如果一切正常,递增ldsic的引用计数。将用do_tty_wirte()再行写操作。写完之后,再递减ldsic的引用计数.

Do_tty_write代码分段分析如下:

01.static inline ssize_t do_tty_write(

02. ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),

03. struct tty_struct *tty,

04. struct file *file,

05. const char __user *buf,

06. size_t count)

07.{

08. ssize_t ret, written = 0;

09. unsigned int chunk;

10. ret = tty_write_lock(tty, file->f_flags & O_NDELAY);

11. if (ret

12. return ret;

13. /*

14. * We chunk up writes into a temporary buffer. This

15. * simplifies low-level drivers immensely, since they

16. * don't have locking issues and user mode accesses.

17. *

18. * But if TTY_NO_WRITE_SPLIT is set, we should use a

19. * big chunk-size..

20. *

21. * The default chunk-size is 2kB, because the NTTY

22. * layer has problems with bigger chunks. It will

23. * claim to be able to handle more characters than

24. * it actually does.

25. *

26. * FIXME: This can probably go away now except that 64K chunks

27. * are too likely to fail unless switched to vmalloc...

28. */

29. chunk = 2048;

30. if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))

31. chunk = 65536;

32. if (count

33. chunk = count;

34. /* write_buf/write_cnt is protected by the atomic_write_lock mutex */

35. if (tty->write_cnt

36. unsigned char *buf;

37. if (chunk

38. chunk = 1024;

39. buf = kmalloc(chunk, GFP_KERNEL);

40. if (!buf) {

41. ret = -ENOMEM;

42. goto out;

43. }

44. kfree(tty->write_buf);

45. tty->write_cnt = chunk;

46. tty->write_buf = buf;

47. }

默认一次写数据的大小为2K.如果设置了TTY_NO_WRITE_SPLIT.则将一次写的数据量扩大为65536.

Tty->write_buf是写操作的临时缓存区。即将用户空的数据暂时存放到这里

Tty->write_cnt是临时缓存区的大小。

在这里,必须要根据一次写的数据量对这个临时缓存区做调整

01. /* Do the write .. */

02. for (;;) {

03. size_t size = count;

04. if (size > chunk)

05. size = chunk;

06. ret = -EFAULT;

07. if (copy_from_user(tty->write_buf, buf, size))

08. break;

09. lock_kernel();

10. ret = write(tty, file, tty->write_buf, size);

11. unlock_kernel();

12. if (ret

13. break;

14. written += ret;

15. buf += ret;

16. count -= ret;

17. if (!count)

18. break;

19. ret = -ERESTARTSYS;

20. if (signal_pending(current))

21. break;

22. cond_resched();

23. }

24. if (written) {

25. struct inode *inode = file->f_path.dentry->d_inode;

26. inode->i_mtime = current_fs_time(inode->i_sb);

27. ret = written;

28. }

29.out:

30. tty_write_unlock(tty);

31. return ret;

32.}

后面的操作就比较简单了。先将用户空间的数据copy到临时缓存区,然后再调用ldisc->write()完成这次写操作.最后再更新设备结点的时间戳.

Write操作的流程图如下示:

7ca62672a1fa0a2205c13660dd380712.png

在这里,我们只看到将数据写放到了ldisc->write().没有看到与tty_driver相关的部份。实际上在ldisc中对写入的数据做预处理过后,还是会调用tty_driver->write()将其写入硬件.

4.3:设备文件的read操作

01.static ssize_t tty_read(struct file *file, char __user *buf, size_t count,

02. loff_t *ppos)

03.{

04. int i;

05. struct tty_struct *tty;

06. struct inode *inode;

07. struct tty_ldisc *ld;

08. tty = (struct tty_struct *)file->private_data;

09. inode = file->f_path.dentry->d_inode;

10. if (tty_paranoia_check(tty, inode, "tty_read"))

11. return -EIO;

12. if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))

13. return -EIO;

14. /* We want to wait for the line discipline to sort out in this

15. situation */

16. ld = tty_ldisc_ref_wait(tty);

17. lock_kernel();

18. if (ld->read)

19. i = (ld->read)(tty, file, buf, count);

20. else

21. i = -EIO;

22. tty_ldisc_deref(ld);

23. unlock_kernel();

24. if (i > 0)

25. inode->i_atime = current_fs_time(inode->i_sb);

26. return i;

27.}

这个read操作就更简单。直接调用ldsic->read()完成工作

流程图如下:

ca023cef69fb5e06e950c2c59235fa99.png

五:小结

在tty设备文件的操作中。Open操作会进行一系统初始化。然后调用ldsic->open

tty_driver->open。在write和read调用中只tty_core只会用到ldisc->wirte/ldisc-

>read.除了上面分析的几个操作之外,还有一个ioctl操作,以及它封装的几个termios。这些ioctl类的操作会直接和

tty_driver相关联.

在这一节里,只对tty的构造做一个分析,具体ldisc的操作我们之后以控制终端为例进行分析.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值