最近在深入研究我的香橙派,不可避免的遇到了怎么认识和使用Linux内核的问题。 我给自己留了一个简单的任务:使用原生C来监听内核,实现读取键盘的消息。
CSDN上也有其他文章来解决这个问题,不过要么是技术不达标(直接和终端交互显然不是我们想要的,这只是一个取巧的方案),要么是收费文章🥲 。楼主也是查阅了很多资料才解决了这个问题。如果这个方案解决了你的问题,还请你点赞,评论,收藏。您的支持是我的动力
如果你要学习知识,那可以从头读下去,如果你要快速使用,可以从 evtest 这个部分开始读,源代码贴在后面
接口在哪里?
在linux中,一切都是文件。哪怕你是一个物理设备,在内核中,你也得有个文件接口!
两个关键文件夹
/dev/input/
所有的设备,都是在dev 目录下统一管理的。让我们看看
/dev/input/
orangepi@orangepi3b:/dev/input$ ls
by-id event1 event12 event15 event18 event4 event7
by-path event10 event13 event16 event2 event5 event8
event0 event11 event14 event17 event3 event6 event9
orangepi@orangepi3b:/dev/input$ ls -l
total 0
drwxr-xr-x 2 root root 220 Apr 11 11:34 by-id
drwxr-xr-x 2 root root 280 Apr 11 11:34 by-path
crw-rw---- 1 root input 13, 64 Apr 6 12:37 event0
crw-rw---- 1 root input 13, 65 Apr 6 12:37 event1
crw-rw---- 1 root input 13, 74 Apr 11 11:34 event10
crw-rw---- 1 root input 13, 75 Apr 11 11:34 event11
crw-rw---- 1 root input 13, 76 Apr 11 11:34 event12
crw-rw---- 1 root input 13, 77 Apr 11 11:34 event13
crw-rw---- 1 root input 13, 78 Apr 11 11:34 event14
crw-rw---- 1 root input 13, 79 Apr 11 11:34 event15
crw-rw---- 1 root input 13, 80 Apr 11 11:34 event16
crw-rw---- 1 root input 13, 81 Apr 11 11:34 event17
crw-rw---- 1 root input 13, 82 Apr 11 11:34 event18
crw-rw---- 1 root input 13, 66 Apr 6 12:37 event2
crw-rw---- 1 root input 13, 67 Apr 11 11:34 event3
crw-rw---- 1 root input 13, 68 Apr 11 11:34 event4
crw-rw---- 1 root input 13, 69 Apr 11 11:34 event5
crw-rw---- 1 root input 13, 70 Apr 11 11:34 event6
crw-rw---- 1 root input 13, 71 Apr 11 11:34 event7
crw-rw---- 1 root input 13, 72 Apr 11 11:34 event8
crw-rw---- 1 root input 13, 73 Apr 11 11:34 event9
每个event代表一个事件,那么,怎么知道事件和设备的对应呢? 这要借助/proc/bus
(这个部分的知识点参考了蓝天居士的博客,他是一位水平非常高的专家,大家可以学习一下)
/proc/bus/input/devices
让我们转到这个目录
orangepi@orangepi3b:/proc$ cd bus
orangepi@orangepi3b:/proc/bus$ ls
input pci
orangepi@orangepi3b:/proc/bus$ cd input
orangepi@orangepi3b:/proc/bus/input$ ls
devices handlers
orangepi@orangepi3b:/proc/bus/input$ cd devices
bash: cd: devices: Not a directory
orangepi@orangepi3b:/proc/bus/input$ cd devices
bash: cd: devices: Not a directory
orangepi@orangepi3b:/proc/bus/input$ tree
.
├── devices
└── handlers
0 directories, 2 files
看看设备信息,信息比较长。我们要拆开来看
event0是什么
orangepi@orangepi3b:/proc/bus/input$ cat devices
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="hdmi_cec_key"
P: Phys=hdmi_cec_key/input0
S: Sysfs=/devices/platform/fe0a0000.hdmi/dw-hdmi-cec.1.auto/input/input0
U: Uniq=
H: Handlers=event0
B: PROP=0
B: EV=3
B: KEY=10000000000000 0
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
这个信息:设备挂在总线19下,厂商ID(vendor=0001) 产品ID(Product=0001)
产品版本Version=0100
N: Name="hdmi_cec_key"
标识设备名称和用途
P: Phys=hdmi_cec_key/input0
设备的物理路径,标识设备在系统中的物理位置
S: Sysfs=/devices/platform/fe0a0000.hdmi/dw-hdmi-cec.1.auto/input/input0
设备早sysfs 文件系统中的路径,用于内核和用户空间交互
U: Uniq=
设备唯一标识符号,这里为空
H: Handlers=event0
设备对应的事件处理程序是event0 标识该设备的输入事件通过events0暴露 这个信息是关键信息
B: PROP=0
设备属性标识,这里为0 标识无特殊属性
B: EV=3
标识设备支持的事件类型
B: KEY=10000000000000 0
标识设备支持的按键键值
鼠标
可见,一个设备可以有多个输入
I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse"
P: Phys=usb-fd800000.usb-1.1/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.0/0003:046D:C08B.0001/input/input3
U: Uniq=0470323E3232
H: Handlers=event3
B: PROP=0
B: EV=17
B: KEY=ffff0000 0 0 0 0
B: REL=1943
B: MSC=10
I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse Keyboard"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input4
U: Uniq=0470323E3232
H: Handlers=sysrq kbd event4
B: PROP=0
B: EV=100013
B: KEY=1000000000007 ff800000000007ff febeffdfffefffff fffffffffffffffe
B: MSC=10
I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse Consumer Control"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input5
U: Uniq=0470323E3232
H: Handlers=kbd event5
B: PROP=0
B: EV=1f
B: KEY=306ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17c000 677bfad9415fed 9ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10
I: Bus=0003 Vendor=046d Product=c08b Version=0111
N: Name="Logitech G502 HERO Gaming Mouse System Control"
P: Phys=usb-fd800000.usb-1.1/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.1/1-1.1:1.1/0003:046D:C08B.0002/input/input6
U: Uniq=0470323E3232
H: Handlers=kbd event6
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10
键盘
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87"
P: Phys=usb-fd800000.usb-1.3/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.0/0003:320F:5055.0003/input/input9
U: Uniq=2020-12-15
H: Handlers=sysrq kbd leds event7
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Keyboard"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input10
U: Uniq=2020-12-15
H: Handlers=kbd event8
B: PROP=0
B: EV=100013
B: KEY=ff80000000000000 80b0ffcd01cfffff febffbffdffffffe
B: MSC=10
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 System Control"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input11
U: Uniq=2020-12-15
H: Handlers=kbd event9
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Consumer Control"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input12
U: Uniq=2020-12-15
H: Handlers=kbd event10
B: PROP=0
B: EV=1f
B: KEY=3f000307ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17d000 677bfad9415fed 19ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input13
U: Uniq=2020-12-15
H: Handlers=event11
B: PROP=0
B: EV=9
B: ABS=10000000000
I: Bus=0003 Vendor=320f Product=5055 Version=0110
N: Name="VXE V87 VXE V87 Mouse"
P: Phys=usb-fd800000.usb-1.3/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.3/1-1.3:1.1/0003:320F:5055.0004/input/input14
U: Uniq=2020-12-15
H: Handlers=event12
B: PROP=0
B: EV=17
B: KEY=1f0000 0 0 0 0
B: REL=1943
B: MSC=10
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle"
P: Phys=usb-fd800000.usb-1.4/input0
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.0/0003:320F:5088.0007/input/input21
U: Uniq=
H: Handlers=sysrq kbd leds event13
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Keyboard"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input22
U: Uniq=
H: Handlers=kbd event14
B: PROP=0
B: EV=100013
B: KEY=ff80000000000000 80b0ffcd01cfffff febffbffdffffffe
B: MSC=10
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle System Control"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input23
U: Uniq=
H: Handlers=kbd event15
B: PROP=0
B: EV=13
B: KEY=c000 10000000000000 0
B: MSC=10
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Consumer Control"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input24
U: Uniq=
H: Handlers=kbd event16
B: PROP=0
B: EV=1f
B: KEY=3f000307ff 0 0 483ffff17aff32d bfd4444600000000 1 130ff38b17d000 677bfad9415fed 19ed68000004400 10000002
B: REL=1040
B: ABS=100000000
B: MSC=10
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input25
U: Uniq=
H: Handlers=event17
B: PROP=0
B: EV=9
B: ABS=10000000000
I: Bus=0003 Vendor=320f Product=5088 Version=0111
N: Name="Telink VXE V87 2.4G Dongle Mouse"
P: Phys=usb-fd800000.usb-1.4/input1
S: Sysfs=/devices/platform/fd800000.usb/usb1/1-1/1-1.4/1-1.4:1.1/0003:320F:5088.0008/input/input26
U: Uniq=
H: Handlers=event18
B: PROP=0
B: EV=17
B: KEY=1f0000 0 0 0 0
B: REL=1943
B: MSC=10
看起来有些复杂。不过我们很容易看出event* 和设备的对应关系。你觉得有些麻烦? 已经有人为我们制作了工具!
使用evtest来测试你的输入设备
orangepi@orangepi3b:~/bop_code$ sudo apt install evtest
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
evtest is already the newest version (1:1.34-1).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
扫描设备
orangepi@orangepi3b:~/bop_code$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: hdmi_cec_key
/dev/input/event1: rk805 pwrkey
/dev/input/event2: rockchip-rk809 Headset
/dev/input/event3: Logitech G502 HERO Gaming Mouse
/dev/input/event4: Logitech G502 HERO Gaming Mouse Keyboard
/dev/input/event5: Logitech G502 HERO Gaming Mouse Consumer Control
/dev/input/event6: Logitech G502 HERO Gaming Mouse System Control
/dev/input/event7: VXE V87 VXE V87
/dev/input/event8: VXE V87 VXE V87 Keyboard
/dev/input/event9: VXE V87 VXE V87 System Control
/dev/input/event10: VXE V87 VXE V87 Consumer Control
/dev/input/event11: VXE V87 VXE V87
/dev/input/event12: VXE V87 VXE V87 Mouse
/dev/input/event13: Telink VXE V87 2.4G Dongle
/dev/input/event14: Telink VXE V87 2.4G Dongle Keyboard
/dev/input/event15: Telink VXE V87 2.4G Dongle System Control
/dev/input/event16: Telink VXE V87 2.4G Dongle Consumer Control
/dev/input/event17: Telink VXE V87 2.4G Dongle
/dev/input/event18: Telink VXE V87 2.4G Dongle Mouse
选取并监听
这里以键盘为例子
Select the device event number [0-18]: 7
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x320f product 0x5055 version 0x110
Input device name: "VXE V87 VXE V87"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 1 (KEY_ESC)
Event code 2 (KEY_1)
Event code 3 (KEY_2)
Event code 4 (KEY_3)
....
Event code 190 (KEY_F20)
Event code 191 (KEY_F21)
Event code 192 (KEY_F22)
Event code 193 (KEY_F23)
Event code 194 (KEY_F24)
Event code 240 (KEY_UNKNOWN)
Event type 4 (EV_MSC)
Event code 4 (MSC_SCAN)
Event type 17 (EV_LED)
Event code 0 (LED_NUML) state 0
Event code 1 (LED_CAPSL) state 0
Event code 2 (LED_SCROLLL) state 0
Event code 3 (LED_COMPOSE) state 0
Event code 4 (LED_KANA) state 0
Key repeat handling:
Repeat type 20 (EV_REP)
Repeat code 0 (REP_DELAY)
Value 250
Repeat code 1 (REP_PERIOD)
Value 33
Properties:
Testing ... (interrupt to exit)
Event: time 1744374523.592185, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70028
Event: time 1744374523.592185, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0
Event: time 1744374523.592185, -------------- SYN_REPORT ------------
Event: time 1744374526.435125, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70013
Event: time 1744374526.435125, type 1 (EV_KEY), code 25 (KEY_P), value 1
Event: time 1744374536.039071, -------------- SYN_REPORT ------------
Event: time 1744374536.207057, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1744374536.207057, type 1 (EV_KEY), code 46 (KEY_C), value 1
Event: time 1744374536.207057, -------------- SYN_REPORT ------------
Event: time 1744374536.295063, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70006
Event: time 1744374536.295063, type 1 (EV_KEY), code 46 (KEY_C), value 0
Event: time 1744374536.295063, -------------- SYN_REPORT ------------
使用代码实现内核监听
我们在刚刚,已经获得了键盘输入在event7的信息
!!!注意! 你必须使用evtest来观察你的键盘的正确位置!!! 否则代码会失败!
下面按照这个来写,这是源代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <linux/input.h>
int main(int argc, char **argv) {
const char* devloc = "/dev/input/event7";
int fd = open(devloc, O_RDONLY) ;
if (fd == -1) {
perror("无法打开输入设备");
return 1;
}
struct input_event ev;
while(true)
{
ssize_t n = read(fd , &ev ,sizeof(ev));
if(n ==-1)
{
perror("this error \n");
break;
}
if(n!= sizeof(ev))
{
continue;
}
if(ev.type ==EV_KEY && ev.value ==1)
{
printf(" key!!!\n");
switch ((ev.code))
{
case KEY_P:
printf("hello");
break;
case KEY_K:
printf(" keyboard");
break;
default:
break;
}
}
}
close(fd);
return 0;
}
让我们把代码封装成类吧
#include <iostream>
#include <thread>
#include <unordered_map>
#include <functional>
#include <linux/input.h>
#include <fcntl.h> //open函数在这个库
#include <unistd.h> //read在这个库
class bop_keylistener
{
private:
int fd;
std::unordered_map<int, std::function<void()>> keyCallbacks;
std::thread listenerThread;
struct input_event ev;
bool running = true;
int millsec_wait ;
void listenkey()
{
while(running)
{
ssize_t n = read(fd , &ev ,sizeof(ev));
if(n ==-1)
{
std::cerr << "this event message error "<<std::endl;
}
if(n!= sizeof(ev))
{
continue;
}
if(ev.type ==EV_KEY && ev.value ==1)
{
for(const auto& pair :keyCallbacks)
{
auto keyi=pair.first;
if(keyi == ev.code)
{
pair.second();
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(millsec_wait));
}
}
}
public:
bop_keylistener(const char* devloc , int millsecwait = 50)
{
millsec_wait =millsecwait;
fd = open(devloc, O_RDONLY) ;
if (fd == -1) {
perror("无法打开输入设备");
}
listenerThread = std::thread(&bop_keylistener::listenkey, this);
}
~bop_keylistener()
{
running = false;
if (listenerThread.joinable()) {
listenerThread.join();
}
}
// 注册按键和对应的回调函数
void registerKeyCallback(int key, const std::function<void()>& callback) {
keyCallbacks[key] = callback;
}
};
// 示例回调函数
void exampleCallback() {
std::cout << "Example callback executed!" << std::endl;
}
int main() {
bop_keylistener mkeyl("/dev/input/event7" );
// 注册按键 'A' 和对应的回调函数
mkeyl.registerKeyCallback(KEY_1, exampleCallback);
// 保持主线程运行
std::this_thread::sleep_for(std::chrono::hours(1));
return 0;
}
创作不易,还请多多支持