本教程操作环境:linux5.9.8系统、Dell G3电脑。
什么是设备节点
人和人之间沟通桥梁是语言。同样,应用程序和设备驱动程序沟通也需要一个桥梁。这个桥梁就是设备节点。
对于Linux系统,所有的IO资源都是文件,包括文件、目录、硬盘、设备等。那么,键盘作为计算机系统中的一款输入设备,操作系统同样也把它抽象了文件,要想获取用户从键盘上输入的数据时,只需要读取键盘提供的设备节点即可。
在Linux系统中,键盘作为输入设备,其对应的设备节点位于”/dev/input“下。在这个文件夹下有很多以event打头的文件,这些就是所有input设备的设备节点。如何确定哪个是键盘的设备节点呢?将键盘连接到树莓派上,打开终端,执行“sudo cat /dev/input/event0”,敲击键盘,如果没有输出,就换下一个节点,直到找到有输出的节点,那这个节点就是键盘对应的设备节点。
设备节点被创建在/dev下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个ID 上。 相当于硬盘的inode一样的东西,记录了硬件设备的位置和信息
在Linux中,所有设备都以文件的形式存放在/dev目录下,都是通过文件的方式进行访问,设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。
设备节点的作用
设备节点使得用户可以与内核进行硬件的沟通,读写设备以及其他的操作
在linux里面设备就像是普通文件一样的存在,访问一个设备就好像是访问一个文件一样
主设备号代表着一类设备,次设备号代表着同一类设备的不同个体,说到这里也许并不知道设备节点的存在形式
设备节点的存在形式
另外在linux里面还有一个概念,就是inode与block,也就是硬盘一面的块与节点,硬盘里面的inode就相当于一个文件或者文件夹,它记录下此文件下面的文件位置所在,文件的位置是以block大小对齐的,例如有些系统就是4K的大小,而inode的大小是有限的,所以就有了单个文件不能超过4G的说法。而在linux的驱动程序里面的节点在我个人的理解也可以看做是一个类似于硬盘的inode一样的东西,里面可以记录硬件设备的位置以及别的一些信息,在用户需要进行访问的时候就参照到设备节点所记录的信息进行设备的访问
如何从设备节点中获取数据
操作系统之所以把IO都抽象成了文件,最大的好处就是可以通过统一的接口来访问这个文件,从而和不同的设备沟通。这些统一的接口就是操作系统针对文件操作对外提供的一组系统调用:open函数、read函数、write函数等。比如,如果需要从一个设备中获取数据,只需要调用read函数去读取该设备对应的设备节点就可以了,当然在read之前,要先调用open函数打开。现在以获取键盘输入为例来介绍。
1、打开设备节点
在读取设备节点的数据之前,要先调用open函数打开设备节点。open函数的具体用法可以参考链接。简单描述如下:
函数声明:
int open(const char *pathname, int flags);
需要包含的头文件:
#include
参数:
* 第一个参数(const char *pathname):表示需要打开的文件路径
* 第二个参数(int flags):表示打开文件的方式,比如,”O_RDONLY” ——只读打开;”O_WRONLY”——只写打开;”O_RDWR”——读、写打开,等。
返回值:
如果打开成功,则返回该文件的文件描述符,以供read,write等函数使用。否则,返回-1。
那么,要打开键盘的设备文件(假设是”/dev/input/even10“),则需要以下代码:
int keys_fd; keys_fd = open("/dev/input/even10", O_RDONLY); if(keys_fd <= 0) { printf("open /dev/input/event10 device error!\n"); return -1; }
2 、读取设备节点的数据
读取设备节点需要使用read函数,具体使用方法可以参考链接。简单介绍如下:
函数声明:
ssize_t read(int fd, void *buf, size_t count);
需要包含的头文件:
#include
参数:
* 第一个参数(int fd):要打开文件的文件描述符,来源一般是上述open函数的返回值。
* 第二个参数(void *buf):读取到的数据存放的起始位置指针
* 第三个参数(size_t count):要读取的数据字节数
返回值:
* 如果读取成功,则返回实际读取到的字节数
* 如果读取失败,则返回-1
* 如果返回值小于第三个参数count,则表示已经读取到文件结尾,返回值表示实际读取的字节数。
在读取键盘的例子中,我们循环读取键盘设备的文件节点,并将设备保存到一个char buf[24]的数组中去。具体代码如下:
char buf[24];while(1){ if(read(keys_fd, buf, 24) == 24) { // 成功的从设备节点中获取到了24个字节 ... }}
根据read函数用法,当要读取24个字节,且read函数的返回值是24时,表示成功的从设备节点中获取到了24个字节。
3、分析从设备节点获取的数据
为什么这里要从键盘的设备驱动获取24个字节呢?这是因为正常情况下,从键盘设备节点获取的数据实际上是一个struct input_event结构。其定义为:
struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value;};
显然,上述结构体的大小为24。
这里需要理解的是:设备节点是设备驱动程序提供的,且设备节点的数据是设备驱动写入的,而且写入时,是以上述结构的规则写入的,这是双方通过
* struct timeval time:其大小为16个字节,具体意义暂时不考虑。
* __u16 type:其大小为2个字节,表示input设备的类型,比如:EV_KEY表示上报的是键盘类型的数据,EV_REL表示相对路径,鼠标就属于这种类型,还是其他等等。
* __u16 code:其大小为2个字节,表示事件的代码。比如,如果type为EV_KEY,那么该代码code为设备键盘代码。code值实际上是应用程序和驱动程序约定好的一些固定的值,它可取的值位于include/uapi/linux/input-event-codes.h中。举例来讲,根据Linux源码下的include/uapi/linux/input-event-codes.h文件的第91行#define KEY_Q 16,如果键盘上按下或松开了Q键,那么键盘的驱动程序上报的code值应该是16;反之,如果应用程序获取到的值是19,那么,表示用户按下或松开了键盘上的Q键。
* __s32 value:其大小为4个字节,事件的值。如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;
根据上述解释,我们可以添加以下代码来解析从设备节点中获取的数据。
if(t.type == EV_KEY) // 我们只关心input event类型为EV_KEY(按键)的消息 if(t.value == 0 || t.value == 1) { printf("key %d %s\n", t.code, // t.code表示按下或松开了哪个按键 (t.value) ? "Pressed" : "Released"); // t.value表示按下还是松开了相应的按键 }
4、关闭设备节点
在从设备节点获取数据完成后,务必调用close函数,来关闭设备节点。即
close(keys_fd);
相关推荐:《Linux视频教程》
以上就是什么是linux设备节点的详细内容,更多请关注php中文网其它相关文章!