Skip to content

LV070-应用操作硬件

一、应用层操控硬件的两种方式

在 Linux 系统下,一切皆文件。应用层如何操控底层硬件,同样也是通过文件 I/O 的方式来实现,前面我们学习设备文件,包括字符设备文件和块设备文件,为啥叫设备文件?其实设备文件便是各种硬件设备向应用层提供的一个接口,应用层通过对设备文件的 I/O 操作来操控硬件设备,譬如 LCD 显示屏、串口、按键、摄像头等等,所以设备文件其实是与硬件设备相互对应的。

设备文件通常在/dev/目录下,我们也把/dev 目录下的文件称为设备节点。设备节点并不是操控硬件设备的唯一途径,除此之外,我们还可以通过 sysfs 文件系统对硬件设备进行操控, 接下来将进行学习!

1. sysfs 文件系统

简单的说, sysfs 是一个基于内存的文件系统, 同 devfs、 proc 文件系统一样,称为虚拟文件系统; 它的作用是将内核信息以文件的方式提供给应用层使用。 前面中我们学习过 proc 文件系统, 应用层可以通过 proc 文件系统得到系统信息和进程相关信息,与 proc 文件系统类似, sysfs 文件系统的主要功能便是对系统设备进行管理,它可以产生一个包含所有系统硬件层次的视图。

sysfs 文件系统把连接在系统上的设备和总线组织成为一个分级的文件、 展示设备驱动模型中各组件的层次关系。 sysfs 提供了一种机制,可以显式的描述内核对象、对象属性及对象间关系, 用来导出内核对象(kernel object,譬如一个硬件设备)的数据、属性到用户空间,以文件目录结构的形式为用户空间提供对这些数据、属性的访问支持。下表描述了内核对象、对象属性及对象间关系在用户空间 sysfs 中的的表现:

内核中的组成要素sysfs 中的表现
内核对象(例如一个硬件设备)目录
对象属性(例如设备属性)文件
对象关系链接文件

2. sysfs 与/sys

sysfs 文件系统挂载在/sys 目录下,启动 ALPHA/Mini I.MX6U 开发板,进入 Linux 系统(开发板出厂系统) 之后,我们进入到/sys 目录下查看,如下所示:

image-20240902200341919

上图显示的便是 sysfs 文件系统中的目录,包括 block、 bus、 class、 dev、 devices、 firmware、 fs、 kernel、modules、 power 等,每个目录下又有许多文件或子目录,对这些目录的说明如所示:

/sys 子目录 说明
/sys/devices 这是系统中所有设备存放的目录, 也就是系统中的所有设备在 sysfs 中的呈现、表达,也是 sysfs 管理设备的最重要的目录结构。
/sys/block 块设备的存放目录,这是一个过时的接口,按照 sysfs 的设计理念,系统所有的设备都存放在/sys/devices 目录下,所以/sys/block 目录下的文件通常是链接到/sys/devices 目录下的文件。
/sys/bus 这是系统中的所有设备按照总线类型分类放置的目录结构,/sys/devices 目录下每一种设备都是挂在某种总线下的,例如 i2c 设备挂在 I2C 总线下。同样, /sys/bus 目录下的文件通常也是链接到了/sys/devices 目录。
/sys/class 这是系统中的所有设备按照其功能分类放置的目录结构,同样该目录下的文件也是链接到了/sys/devices 目录。 按照设备的功能划分组织在/sys/class 目录下,例如/sys/class/leds 目录中存放了所有的 LED 设备,/sys/class/input 目录中存放了所有的输入类设备。
/sys/dev 这是按照设备号的方式放置的目录结构,同样该目录下的文件也是链接到了/sys/devices 目录。该目录下有很多以主设备号: 次设备号(major: minor)命名的文件,这些文件都是链接文件,链接到/sys/devices 目录下对应的设备。
/sys/firmware 描述了内核中的固件。
/sys/fs 用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点。
/sys/kernel 这里是内核中所有可调参数的位置。
/sys/module 这里有系统中所有模块的信息。
/sys/power 这里是系统中电源选项,有一些属性可以用于控制整个系统的电源状态。

系统中所有的设备(对象)都会在/sys/devices 体现出来,是 sysfs 文件系统中最重要的目录结构;而/sys/bus、 /sys/class、 /sys/dev 分别将设备按照挂载的总线类型、功能分类以及设备号的形式将设备组织存放在这些目录中,这些目录下的文件都是链接到了/sys/devices 中。

设备的一些属性、数据通常会通过设备目录下的文件体现出来,也就是说设备的数据、属性会导出到用户空间,以文件形式为用户空间提供对这些数据、属性的访问支持, 可以把这些文件称为属性文件; 读这些属性文件就表示读取设备的属性信息,相反写属性文件就表示对设备的属性进行设置、以控制设备的状态。

3. 总结

应用层想要对底层硬件进行操控,通常可以通过两种方式:

  • /dev 目录下的设备文件(设备节点) ;
  • /sys/ 目录下设备的属性文件。

具体使用哪种方式需要根据不同功能类型设备进行选择,有些设备只能通过设备节点进行操控,而有些设备只能通过 sysfs 方式进行操控;当然跟设备驱动具体的实现方式有关,通常情况下,一般简单地设备会使用 sysfs 方式操控,其设备驱动在实现时会将设备的一些属性导出到用户空间 sysfs 文件系统,以属性文件的形式为用户空间提供对这些数据、属性的访问支持,譬如 LED、 GPIO 等。但对于一些较复杂的设备通常会使用设备节点的方式, 譬如 LCD 等、触摸屏、摄像头等。

二、标准接口与非标准接口

Linux 内核中为了尽量降低驱动开发者难度以及接口标准化,就出现了设备驱动框架的概念; Linux 针对各种常见的设备进行分类,例如 LED 类设备、输入类设备、 FrameBuffer 类设备、 video 类设备、 PWM 设备等等,并为 每一种类型的设备设计了一套成熟的、标准的、典型的驱动实现的框架, 这个就叫做 设备驱动框架

设备驱动框架为驱动开发和应用层提供了一套统一的接口规范, 例如对 LED 类设备来说, 内核提供了 LED 设备驱动框架,驱动工程师编写 LED 驱动时,使用 LED 驱动框架来开发自己的 LED 驱动程序,这样做的好处就在于,能够对上层应用层提供统一、标准化的接口、 同时又降低了驱动开发工程师的难度。

编写 LED 驱动程序并不仅仅只能使用内核设计的 LED 设备驱动框架,不用内核的 LED 驱动框架也是可以开发出 LED 驱动程序的,但如果这样写,使用这个驱动程序注册的 LED 那就不是标准设备了, 因为该驱动程序向应用层提供的接口并不是统一、 标准化接口。

除此之外,还有很多硬件外设,尤其是嵌入式系统中所使用到的这些硬件外设,它们可能并不属于 Linux 系统所规划的设备分类当中的任何一种设备类型,例如在 Linux 系统中,有一种设备类型叫杂散/杂项类设备(misc device),可以想一想为啥叫杂散类设备,说明这种设备既不属于这种设备类型、又不属于另一种设备类型,无奈只能把它归为杂项类。

因为一个计算机系统所能够连接、使用的外设实在太多了,不可能每一种外设都能够精准地分类到某一个设备类型中,通常把这些无法进行分类的外设就称为杂项设备,杂项设备驱动程序向应用层提供的接口通常都不是标准化接口、它是一种非标准接口,具体如何去操控这个设备通常只有驱动工程师知道。所以在嵌入式系统中,很多硬件外设的驱动程序都是定制的。

三、LED 硬件控制方式

1. sysfs 控制 LED

1.1 控制说明

ALPHA I.MX6U 开发板底板上有一颗可被用户控制的 LED 灯,如下所示:

image-20240902201340398

上图中箭头所指的 LED 便是开发板上唯一一个可以被用户所控制的 LED, 另外一颗 LED 则(名称为 PWR)是底板上的电源指示灯。

对于 ALPHA I.MX6U 开发板出厂系统来说, 此 LED 设备使用的是 Linux 内核标准 LED 驱动框架注册而成, 在/dev 目录下并没有其对应的设备节点,其实现使用 sysfs 方式控制。 进入到/sys/class/leds 目录下,如下所示:

image-20240902201451385

上小节介绍了/sys/class 目录,系统中的所有设备根据其功能分类组织到了/sys/class 目录下,所以/sys/class/leds 目录下便存放了所有的 LED 类设备。从上图可以看到该目录下有一个 sys-led 文件夹, 这个便是底板上的用户 LED 设备文件夹,进入到该目录下,如下所示:

image-20240902201548602

这里我们主要关注便是 brightness、 max_brightness 以及 trigger 三个文件,这三个文件都是 LED 设备的属性文件:

  • brightness: 翻译过来就是亮度的意思, 该属性文件可读可写; 所以这个属性文件是用于设置 LED 的亮度等级或者获取当前 LED 的亮度等级,譬如 brightness 等于 0 表示 LED 灭, brightness 为正整数表示 LED 亮,其值越大、 LED 越亮; 对于 PWM 控制的 LED 来说, 这通常是适用的,因为它存在亮度等级的问题,不同的亮度等级对应不同的占空比,自然 LED 的亮度也是不同的; 但对于 GPIO 控制(控制 GPIO 输出高低电平)的 LED 来说,通常不存在亮度等级这样的说法,只有 LED 亮(brightness 等于 0)和 LED 灭(brightness 为非 0 值的正整数)两种状态,ALPHA I.MX6U 开发板上的这颗 LED 就是如此,所以自然就不存在亮度等级一说,只有亮和灭两种亮度等级。
  • max_brightness: 该属性文件只能被读取,不能写,用于获取 LED 设备的最大亮度等级。
  • trigger: 触发模式,该属性文件可读可写,读表示获取 LED 当前的触发模式,写表示设置 LED 的触发模式。 不同的触发模式其触发条件不同, LED 设备会根据不同的触发条件自动控制其亮、灭状态, 通过 cat 命令查看该属性文件,可获取 LED 支持的所有触发模式以及 LED 当前被设置的触发模式:
image-20240902201728489

方括号([heartbeat])括起来的表示当前 LED 对应的触发模式, none 表示无触发,常用的触发模式包括 none(无触发)、 mmc0(当对 mmc0 设备发起读写操作的时候 LED 会闪烁)、 timer(LED 会有规律的一亮一灭,被定时器控制住)、 heartbeat(心跳呼吸模式, LED 模仿人的心跳呼吸那样亮灭变化)。

通常系统启动之后,会将板子上的一颗 LED 设置为 heartbeat 触发模式,将其作为系统正常运行的指示灯,例如 ALPHA I.MX6U 开发板系统启动之后,底板上的用户 LED 就会处于心跳呼吸模式,这个我们自己观察便可知道。

1.2 相关命令

shell
cd /sys/class/leds/sys-led
echo timer > trigger # 将 LED 触发模式设置为 timer
echo none > trigger  # 将 LED 触发模式设置为 none
echo 1 > brightness  # 点亮 LED 
echo 0 > brightness  # 熄灭 LED

我们可以自己动手使用 echo 或 cat 命令进行测试、控制 LED 状态; 除了使用 echo 或 cat 命令之后,同样我们编写应用程序,使用 write()、read()函数对这些属性文件进行 I/O 操作以达到控制 LED 的效果。

Tips:命令 cat 读取以及 echo 写入到属性文件中的均是字符串,所以如果在应用程序中通过 write()向属性文件写入数据,同样也要是字符串形式;同理,使用 read()读取的数据也是字符串 ASCII 编码的。

2. I/O 控制

这一部分就是通过 open、close、read、write、ioctl 等函数来操作 LED 驱动,进而控制 LED 灯。