乾坤合一:Linux设备驱动之终端设备驱动

1 终端设备

终端是一种字符型设备,通常使用tty简称各种类型的设备

1.1 串行端口终端(/dev/ttySn)

串行端口终端 (Serial Port Terminal )是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。在命令行上把标准输出重定向到端口对应的设备文件名上就可以通过端口发送数据。

1.2 伪终端(/dev/pty/)

伪终端 (Pseudo Terminal )是成对的逻辑终端设备,并存在成对的设备文件。以telnet 为例,如果某人在使用telnet 程序连接到Linux 系统,则telnet 程序就可能会开始连接到设备ptyp2 上,而此时一个getty 程序会运行在对应的ttyp2 端口上。 当telnet 从远端获取了一个字符时, 字符就会通过ptyp2 、ttyp2 传递给getty 程序,而getty 程序则会通过ttyp2 、ptyp2 和telnet 程序返回“login: ”字符串信息。这样,登录程序与telnet 程序就通过伪终端进行通信。通过使用适当的软件,可以把两个或多个伪终端设备连接到同一个物理串行端口上。

1.3 控制台终端(/dev/ttyn,/dev/console)

如果当前进程有控制终端 (Controlling Terminal ),那么/dev/tty 就是当前进程的控 制终端的设备特殊文件。可以使用命令“ps ax ”来查看进程与哪个控制终端相连,使用命令 “tty ”可以查看它具体对应哪个实际终端设备。/dev/tty 有些类似于到实际所 使用终端设备的一个连接。

在Linux 系统中,可以在系统启动命令行里指定当前的输出终端,格式如下:

2 终端设备驱动结构

终端设备驱动都围绕tty_driver 结构体而展开,一般而言,终端设备驱动应包含如下组成:

  • 终端设备驱动模块加载函数和卸载函数:完成注册和注销 tty_driver,初始化 和释放终端设备对应的tty_driver 结构体成员及硬件资源。
  • 实现tty_operations 结构体中的一系列成员函数:主要是实现open()、close()、 write()、tiocmget()、tiocmset()等函数

2.1 tty层次结构

Linux内核tty驱动结构包括tty 核心、tty 线路规程和tty 驱动,从用户获取数据之后,其发送数据的结构图如下:

接收数据的流程图如下:

2.2 tty_dirver结构体

特定tty 设备驱动的主体工作是填充tty_driver 结构体中的成员,实现其中的成员函数,tty_driver 结构体的定义如下:

2.3 tty_std_termos结构体

驱动会使用一个标准的数值集初始化这个成员,这个数值集来源于tty_std_termios 变量,tty_std_termos 在tty 核心中的定义如下:

2.4 tty_driver 结构体及tty 设备的操作

3 设备的初始化和释放

3.1 模块加载和卸载函数

tty 驱动的模块加载函数中通常需要分配、初始化tty_driver 结构体并申请必要的硬件资源,下面举一个终端设备驱动的模块加载函数的例子,代码如下:

3.2 打开与关闭函数

当用户对tty 驱动所分配的设备节点进行open()系统调用时,tty_driver 中的open() 成员函数将被 tty 核心调用。tty 驱动必须设置open()成员,否则,ENODEV 将被返回给调用open() 的用户。tty_struct 结构体被 tty 核心用来保存当前tty 端口的状态,它的大多数成员只被tty核心使用,驱动中可以定义一个设备相关的结体,并在open()函数中将其赋值给tty_struct的driver_data 成员,其代码如下:

4 数据发送和接收

用户在有数据发送给终端设备时,通过 “write()系统调用—tty 核心—线路规程”的层层调用,最终调用tty_driver 结构体中的write()函数完成发送。

4.1 tty_driver 的write()函数

tty_driver 的write()函数接受3 个参数:tty_struct、发送数据指针及要发送的字节数,一般首先会通过tty_struct 的driver_data 成员得到设备私有信息结构体,然后依次进行必要的硬件操作开始发送,相关的代码如下:

4.2 put_char()函数的write()替代

当tty 子系统自己需要发送数据到tty 设备时,如果没有实现put_char() 函数,write()函数将被调用,此时传入的count 参数为1,通过对以下代码的分析即可知。

4.3 tty_flip_buffer_push()范例

tty 核心在一个称为struct tty_flip_buffer的结构体中缓冲数据直到它被用户请求。因为tty 核心提供了缓冲逻辑,因此每个tty驱动并非一定要实现它自身的缓冲逻辑。如果其count 字段大于或等于 TTY_ FLIP BUF_SIZE ,这个 flip 缓冲区就需要被刷新到用户,刷新通过对 tty_flip_buffer_push()函数的调用来完成,其相关代码如下:

5 tty线路设置

5.1 线路设置用户空间接口

1) 调用用户空间的termios 库函数

用户空间的应用程序需引用termios.h 头文件, 头文件包含了终端设备的I/O 接口,实际是由POSIX 定义的标准方法。对终端设备操作模式的描述由termios 结体完成,通过tcgetattr() 、tcsetattr() 函数即可完成对终端设备的操作模式的设置和获取,这两个函数的原型如下:

2) 对tty 设备节点进行ioctl()调用

大部分termios 库函数会被转化为对tty 设备节点的ioctl()调用,例如tcgetattr() 、 tcsetattr() 函数对应着TCGETS、TCSETS IO 控制命令。 TIOCMGET(获得 MODEM 状态位)、TIOCMSET (设置 MODEM 状态位)、TIOCMBIC(清除指示MODEM 位)、TIOCMBIS (设置指示MODEM 位)这 4 个I/O 控制命令用于获取和设置MODEM 握手,如RTS、CTS、DTR、DSR、RI、 CD 等。

5.2 tty 驱动的set_termios 函数

大部分 termios 用户空间函数被库转换为对驱动节点的 ioctl()调用,而 tty ioctl中的大部分命令会被tty 核心转换为对tty 驱动的set_termios()函数的调set_termios()函数需要根据用户对termios 的设置(termios 设置包括字长、奇偶校验位、停止位、波特等)完成实际的硬件设置。tty_operations 中set_termios()函数原型为:

5.3 tty 驱动的tiocmget 和tiocmset 函数

对TIOCMGET、TIOCMSET、TIOCMBIC 和TIOCMBIS IO 控制命令的调用将被tty 核心转换为对 tty 驱动 tiocmget()函数和tiocmset()函数的调,TIOCMGET 对应tiocmget()函数,TIOCMSET、TIOCMBIC 和TIOCMBIS 对应tiocmset()函数,分别用于取Modem 控制的设置和进行Modem 的设置,tty 驱动程序的tiocmget()函数范例如下:

5.4 tty 驱动的ioctl 函数

当用户在tty 设备节点上进行ioctl(2)调用时,tty_operations 中的 ioctl()函数会被 tty 核心调用。如果 tty 驱动不知道如何处理传递给它的 ioctl 值,它返回 ENOIOCTLCMD ,之后tty核心会 行一个通用的操作。tty 驱动程序的ioctl()函数范例代码如下:

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 1 收藏 评论

关于作者:李辉

湖南省天杰信息技术有限公司创始人之一,主要从事基站智能门禁锁、智能蓝牙锁、智慧商城等等的研发。在2015年1月完成《24小时学通Linux内核》的写作,目前其关于嵌入式Linux 驱动的写作仍在进行中。 个人主页 · 我的文章 · 2 ·    

可能感兴趣的话题



直接登录
跳到底部
返回顶部