Linux INPUT 子系统实验

2023-11-15

按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息,input 核心层负责处理这些事件。
本章我们就来学习一下Linux 内核中的input 子系统。

input 子系统

input 子系统简

input 就是输入的意思,因此input 子系统就是管理输入的子系统,和pinctrl、gpio 子系统一样,都是Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心
应用层的事情,我们只需要按照要求上报这些输入事件即可。为此input 子系统分为input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图58.1.1.1 所示:
在这里插入图片描述
图58.1.1.1 中左边就是最底层的具体设备,比如按键、USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。

input 驱动编写流程

input 核心层会向Linux 内核注册一个字符设备,大家找到drivers/input/input.c 这个文件,input.c 就是input 输入子系统的核心层,此文件里面有如下所示代码:

1767 struct class input_class = {
1768 .name = "input",
1769 .devnode = input_devnode,
1770 };
......
2414 static int __init input_init(void)
2415 {
2416 int err;
2417
2418 err = class_register(&input_class);
2419 if (err) {
2420 pr_err("unable to register input_dev class\n");
2421 return err;
2422 }
2423
2424 err = input_proc_init();
2425 if (err)
2426 goto fail1;
2427
2428 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2429 INPUT_MAX_CHAR_DEVICES, "input");
2430 if (err) {
2431 pr_err("unable to register char major %d", INPUT_MAJOR);
2432 goto fail2;
2433 }
2434
2435 return 0;
2436
2437 fail2: input_proc_exit();
2438 fail1: class_unregister(&input_class);
2439 return err;
2440 }

第2418 行,注册一个input 类,这样系统启动以后就会在/sys/class 目录下有一个input 子目录,如图58.1.2.1 所示:
在这里插入图片描述

第2428~2429 行,注册一个字符设备,主设备号为INPUT_MAJOR,INPUT_MAJOR 定义在include/uapi/linux/major.h 文件中,定义如下:

#define INPUT_MAJOR 13

因此,input 子系统的所有设备主设备号都为13,我们在使用input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个input_device 即可。
1、注册input_dev
在使用input 子系统的时候我们只需要注册一个input 设备即可,input_dev 结构体表示input设备,此结构体定义在include/linux/input.h 文件中,定义如下(有省略):

121 struct input_dev {
122 const char *name;
123 const char *phys;
124 const char *uniq;
125 struct input_id id;
126
127 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
128
129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图*/
130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图*/
131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图*/
132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图*/
133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图*/
134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED相关的位图*/
135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound有关的位图*/
136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图*/
137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图*/
......
189 bool devres_managed;
190 };

第129 行,evbit 表示输入事件类型,可选的事件类型定义在include/uapi/linux/input.h 文件中,事件类型如下:

#define EV_SYN 0x00 /* 同步事件*/
#define EV_KEY 0x01 /* 按键事件*/
#define EV_REL 0x02 /* 相对坐标事件*/
#define EV_ABS 0x03 /* 绝对坐标事件*/
#define EV_MSC 0x04 /* 杂项(其他)事件*/
#define EV_SW 0x05 /* 开关事件*/
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件*/
#define EV_FF 0x15 /* 压力事件*/
#define EV_PWR 0x16 /* 电源事件*/
#define EV_FF_STATUS 0x17 /* 压力状态事件*/

比如本章我们要使用到按键,那么就需要注册EV_KEY 事件,如果要使用连按功能的话还需要注册EV_REP 事件。

继续回到示例代码58.1.2.2 中,第129 行~137 行的evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在include/uapi/linux/input.h 文件中,按键值如下:

215 #define KEY_RESERVED 0
216 #define KEY_ESC 1
217 #define KEY_1 2
218 #define KEY_2 3
219 #define KEY_3 4
220 #define KEY_4 5
221 #define KEY_5 6
222 #define KEY_6 7
223 #define KEY_7 8
224 #define KEY_8 9
225 #define KEY_9 10
226 #define KEY_0 11
......
794 #define BTN_TRIGGER_HAPPY39 0x2e6
795 #define BTN_TRIGGER_HAPPY40 0x2e7

我们可以将开发板上的按键值设置为示例代码58.1.2.4 中的任意一个,比如我们本章实验会将I.MX6U-ALPHA 开发板上的KEY 按键值设置为KEY_0。

在编写input 设备驱动的时候我们需要先申请一个input_dev 结构体变量,使用input_allocate_device 函数来申请一个input_dev,此函数原型如下所示:

struct input_dev *input_allocate_device(void)

函数参数和返回值含义如下:
参数:无。
返回值:申请到的input_dev。
如果要注销的input 设备的话需要使用input_free_device 函数来释放掉前面申请到的input_dev,input_free_device 函数原型如下:

void input_free_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要释放的input_dev。
返回值:无。
申请好一个input_dev 以后就需要初始化这个input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。input_dev 初始化完成以后就需要向Linux 内核注册input_dev了,需要用到input_register_device 函数,此函数原型如下:

int input_register_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:要注册的input_dev 。
返回值:0,input_dev 注册成功;负值,input_dev 注册失败。
同样的,注销input 驱动的时候也需要使用input_unregister_device 函数来注销掉前面注册的input_dev,input_unregister_device 函数原型如下:

void input_unregister_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:要注销的input_dev 。
返回值:无。
综上所述,input_dev 注册过程如下:

①、使用input_allocate_device 函数申请一个input_dev。
②、初始化input_dev 的事件类型以及事件值。
③、使用input_register_device 函数向Linux 系统注册前面初始化好的input_dev。
④、卸载input 驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev,然后使用input_free_device 函数释放掉前面申请的input_dev。input_dev 注册过程示例代码如下所示:

1 struct input_dev *inputdev; /* input结构体变量*/
2
3 /* 驱动入口函数*/
4 static int __init xxx_init(void)
5 {
6 ......
7 inputdev = input_allocate_device(); /* 申请input_dev */
8 inputdev->name = "test_inputdev"; /* 设置input_dev名字*/
9
10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件*/
12 __set_bit(EV_REP, inputdev->evbit); /* 重复事件*/
13 __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值*/
14 /************************************************/
15
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/
25
26 /* 注册input_dev */
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
31
32 /* 驱动出口函数*/
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销input_dev */
36 input_free_device(inputdev); /* 删除input_dev */
37 }

第1 行,定义一个input_dev 结构体指针变量。

第4~ 30 行,驱动入口函数,在此函数中完成input_dev 的申请、设置、注册等工作。第7行调用input_allocate_device 函数申请一个input_dev。第10~23 行都是设置input 设备事件和按键值,这里用了三种方法来设置事件和按键值。第27 行调用input_register_device 函数向Linux内核注册inputdev。

第33~37 行,驱动出口函数,第35 行调用input_unregister_device 函数注销前面注册的input_dev,第36 行调用input_free_device 函数删除前面申请的input_dev。

2、上报输入事件
当我们向Linux 内核注册好input_dev 以后还不能高枕无忧的使用input 设备,input 设备都是具有输入功能的,但是具体是什么样的输入值Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给Linux 内核,这样Linux 内核才
能获取到正确的输入值。不同的事件,其上报事件的API 函数不同,我们依次来看一下一些常用的事件上报API 函数。

首先是input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下:

void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value)

函数参数和返回值含义如下:
dev:需要上报的input_dev。
type: 上报的事件类型,比如EV_KEY。
code:事件码,也就是我们注册的按键值,比如KEY_0、KEY_1 等等。
value:事件值,比如1 表示按键按下,0 表示按键松开。
返回值:无。
input_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了input_event 函数。比如上报按键所使用的input_report_key 函数,此函数内容如下:

static inline void input_report_key(struct input_dev *dev,
unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}

从示例代码58.1.2.6 可以看出,input_report_key 函数的本质就是input_event 函数,如果要上报按键事件的话还是建议大家使用input_report_key 函数。
同样的还有一些其他的事件上报函数,这些函数如下所示:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

当我们上报事件以后还需要使用input_sync 函数来告诉Linux 内核input 子系统上报结束,input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

void input_sync(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要上报同步事件的input_dev。
返回值:无。
综上所述,按键的上报事件的参考代码如下所示:

1 /* 用于按键消抖的定时器服务函数*/
2 void timer_function(unsigned long arg)
3 {
4 unsigned char value;
5
6 value = gpio_get_value(keydesc->gpio); /* 读取IO值*/
7 if(value == 0){ /* 按下按键*/
8 /* 上报按键值*/
9 input_report_key(inputdev, KEY_0, 1); /* 最后一个参数1,按下*/
10 input_sync(inputdev); /* 同步事件*/
11 } else { /* 按键松开*/
12 input_report_key(inputdev, KEY_0, 0); /* 最后一个参数0,松开*/
13 input_sync(inputdev); /* 同步事件*/
14 }
15 }

第6 行,获取按键值,判断按键是否按下。

第9~10 行,如果按键值为0 那么表示按键被按下了,如果按键按下的话就要使用input_report_key 函数向Linux 系统上报按键值,比如向Linux 系统通知KEY_0 这个按键按下了。
第12~13 行,如果按键值为1 的话就表示按键没有按下,是松开的。向Linux 系统通知KEY_0 这个按键没有按下或松开了。

input_event 结构体

Linux 内核使用input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };

我们依次来看一下input_event 结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为timeval 结构体类型,timeval 结构体定义如下:

1 typedef long __kernel_long_t;
2 typedef __kernel_long_t __kernel_time_t;
3 typedef __kernel_long_t __kernel_suseconds_t;
4
5 struct timeval {
6 __kernel_time_t tv_sec; /* 秒*/
7 __kernel_suseconds_t tv_usec; /* 微秒*/
8 };

从示例代码58.1.3.2 可以看出,tv_sec 和tv_usec 这两个成员变量都为long 类型,也就是32位,这个一定要记住,后面我们分析event 事件上报数据的时候要用到。
type:事件类型,比如EV_KEY,表示此次事件为按键事件,此成员变量为16 位。
code:事件码,比如在EV_KEY 事件中code 就表示具体的按键码,如:KEY_0、KEY_1等等这些按键。此成员变量为16 位。
value:值,比如EV_KEY 事件中value 就是按键值,表示按键有没有被按下,如果为1 的话说明按键按下,如果为0 的话说明按键没有被按下或者按键松开了。
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照input_event 结构体呈现给用户的,用户应用程序可以通过input_event 来获取到具体的输入事件或相关的值,比如按键值等。关于input 子系统就讲解到这里,接下来我们就以开发板上的KEY0 按键为例,讲解一下如何编写input 驱动。

硬件原理图分析

本章实验硬件原理图参考15.2 小节即可。

实验程序编写

本实验对应的例程路径为:开发板光盘-> 2、Linux 驱动例程-> 20_input。

修改设备树文件

直接使用49.3.1 小节创建的key 节点即可。

按键input 驱动程序编写

新建名为“20_input”的文件夹,然后在20_input 文件夹里面创建vscode 工程,工作区命名为“keyinput”。工程创建好以后新建keyinput.c 文件,在keyinput.c 里面输入如下内容:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: keyinput.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: Linux按键input子系统实验
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/8/21 左忠凯创建
***************************************************************/
#define KEYINPUT_CNT		1			/* 设备号个数 	*/
#define KEYINPUT_NAME		"keyinput"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* keyinput设备结构体 */
struct keyinput_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键描述数组 */
	unsigned char curkeynum;				/* 当前的按键号 */
	struct input_dev *inputdev;		/* input结构体 */
};

struct keyinput_dev keyinputdev;	/* key input设备 */

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct keyinput_dev *dev = (struct keyinput_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];
	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		/* 上报按键值 */
		//input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
		input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */
		input_sync(dev->inputdev);
	} else { 									/* 按键松开 */
		//input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
		input_report_key(dev->inputdev, keydesc->value, 0);
		input_sync(dev->inputdev);
	}	
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	char name[10];
	int ret = 0;
	
	keyinputdev.nd = of_find_node_by_path("/key");
	if (keyinputdev.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,"key-gpio", i);
		if (keyinputdev.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */
		sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
		gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);	
		keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);
	}
	/* 申请中断 */
	keyinputdev.irqkeydesc[0].handler = key0_handler;
	keyinputdev.irqkeydesc[0].value = KEY_0;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, &keyinputdev);
		if(ret < 0){
			printk("irq %d request failed!\r\n", keyinputdev.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
	init_timer(&keyinputdev.timer);
	keyinputdev.timer.function = timer_function;

	/* 申请input_dev */
	keyinputdev.inputdev = input_allocate_device();
	keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0
	/* 初始化input_dev,设置产生哪些事件 */
	__set_bit(EV_KEY, keyinputdev.inputdev->evbit);	/* 设置产生按键事件          */
	__set_bit(EV_REP, keyinputdev.inputdev->evbit);	/* 重复事件,比如按下去不放开,就会一直输出信息 		 */

	/* 初始化input_dev,设置产生哪些按键 */
	__set_bit(KEY_0, keyinputdev.inputdev->keybit);	
#endif

#if 0
	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif

	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

	/* 注册输入设备 */
	ret = input_register_device(keyinputdev.inputdev);
	if (ret) {
		printk("register input device failed!\r\n");
		return ret;
	}
	return 0;
}

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init keyinput_init(void)
{
	keyio_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit keyinput_exit(void)
{
	unsigned int i = 0;
	/* 删除定时器 */
	del_timer_sync(&keyinputdev.timer);	/* 删除定时器 */
		
	/* 释放中断 */
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
	}
	/* 释放input_dev */
	input_unregister_device(keyinputdev.inputdev);
	input_free_device(keyinputdev.inputdev);
}

module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

keyinput.c 文件内容其实就是实验“13_irq”中的imx6uirq.c 文件中修改而来的,只是将其中与字符设备有关的内容进行了删除,加入了input_dev 相关的内容,我们简单来分析一下示例代码58.3.2.1 中的程序。

第57 行,在设备结构体中定义一个input_dev 指针变量。

第93~102 行,在按键消抖定时器处理函数中上报输入事件,也就是使用input_report_key函数上报按键事件以及按键值,最后使用input_sync 函数上报一个同步事件,这一步一定得做!

第156~180 行,使用input_allocate_device 函数申请input_dev,然后设置相应的事件以及事件码(也就是KEY 模拟成那个按键,这里我们设置为KEY_0)。最后使用input_register_device函数向Linux 内核注册input_dev。

第211~212 行,当注销input 设备驱动的时候使用input_unregister_device 函数注销掉前面注册的input_dev,最后使用input_free_device 函数释放掉前面申请的input_dev。

编写测试APP

新建keyinputApp.c 文件,然后在里面输入如下所示内容:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: keyinputApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: input子系统测试APP。
其他	   	: 无
使用方法	 :./keyinputApp /dev/input/event1
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/8/26 左忠凯创建
***************************************************************/

/* 定义一个input_event变量,存放输入事件信息 */
static struct input_event inputevent;

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	int err = 0;
	char *filename;

	filename = argv[1];

	if(argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		err = read(fd, &inputevent, sizeof(inputevent));
		if (err > 0) { /* 读取数据成功 */
			switch (inputevent.type) {


				case EV_KEY:
					if (inputevent.code < BTN_MISC) { /* 键盘键值 */
						printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
					} else {
						printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
					}
					break;

				/* 其他类型的事件,自行处理 */
				case EV_REL:
					break;
				case EV_ABS:
					break;
				case EV_MSC:
					break;
				case EV_SW:
					break;
			}
		} else {
			printf("读取数据失败\r\n");
		}
	}
	return 0;
}

第58.1.3 小节已经说过了,Linux 内核会使用input_event 结构体来表示输入事件,所以我们要获取按键输入信息,那么必须借助于input_event 结构体。第28 行定义了一个inputevent 变量,此变量为input_event 结构体类型。

第56 行,当我们向Linux 内核成功注册input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的input 设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的。使用read 函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照input_event 结构体组织起来。获取到输入事件以后(input_event 结构体类型)使用switch case 语句来判断事件类型,本章实验我们设置的事件类型为EV_KEY,因此只需要处理EV_KEY 事件即可。比如获取按键编号(KEY_0 的编号为11)、获取按键状态,按下还是松开的?

运行测试

编译驱动程序和测试APP

1、编译驱动程序
编写Makefile 文件,本章实验的Makefile 文件和第四十章实验基本一样,只是将obj-m 变量的值改为“keyinput.o”,Makefile 内容如下所示:

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)

obj-m := keyinput.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4 行,设置obj-m 变量的值为“keyinput.o”。
输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyinputApp.c 这个测试程序:

arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp

编译成功以后就会生成keyinputApp 这个应用程序。

运行测试

将上一小节编译出来keyinput.ko 和keyinputApp 这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15 中。在加载keyinput.ko 驱动模块之前,先
看一下/dev/input 目录下都有哪些文件,结果如图58.4.2.1 所示:
在这里插入图片描述

从图58.4.2.1 可以看出,当前/dev/input 目录只有event0 和mice 这两个文件。接下来输入如下命令加载keyinput.ko 这个驱动模块。

depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块

当驱动模块加载成功以后再来看一下/dev/input 目录下有哪些文件,结果如图58.4.2.2 所示:
在这里插入图片描述

从图58.4.2.2 可以看出,多了一个event1 文件,因此/dev/input/event1 就是我们注册的驱动所对应的设备文件。keyinputApp 就是通过读取/dev/input/event1 这个文件来获取输入事件信息的,输入如下测试命令:

./keyinputApp /dev/input/event1

然后按下开发板上的KEY 按键,结果如图58.4.2.3 所示:
在这里插入图片描述
从图58.4.2.3 可以看出,当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在Linux 内核中KEY_0 为11。
另外,我们也可以不用keyinputApp 来测试驱动,可以直接使用hexdump 命令来查看/dev/input/event1 文件内容,输入如下命令:

hexdump /dev/input/event1

然后按下按键,终端输出如图58.4.2.4 所示信息:
在这里插入图片描述
图58.4.2.4 就是input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

/*****************input_event类型********************/
/* 编号*/ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000 0c41 0000 d7cd 000c 0001 000b 0001 0000
0000010 0c41 0000 d7cd 000c 0000 0000 0000 0000
0000020 0c42 0000 54bb 0000 0001 000b 0000 0000
0000030 0c42 0000 54bb 0000 0000 0000 0000 0000

type 为事件类型,查看示例代码58.1.2.3 可知,EV_KEY 事件值为1,EV_SYN 事件值为0。因此第1 行表示EV_KEY 事件,第2 行表示EV_SYN 事件。code 为事件编码,也就是按键号,查看示例代码58.1.2.4 可以,KEY_0 这个按键编号为11,对应的十六进制为0xb,因此第1 行表示KEY_0 这个按键事件,最后的value 就是按键值,为1 表示按下,为0 的话表示松开。

综上所述,示例代码58.4.2.1 中的原始事件值含义如下:
第1 行,按键(KEY_0)按下事件。
第2 行,EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个EV_SYN 事件。
第3 行,按键(KEY_0)松开事件。
第4 行,EV_SYN 同步事件,和第2 行一样。

Linux 自带按键驱动程序的使用

自带按键驱动程序源码简析

Linux 内核也自带了KEY 驱动,如果要使用内核自带的KEY 驱动的话需要配置Linux 内核,不过Linux 内核一般默认已经使能了KEY 驱动,但是我们还是要检查一下。按照如下路径找到相应的配置选项:

-> Device Drivers
	-> Input device support
		-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
			-> Keyboards (INPUT_KEYBOARD [=y])
				->GPIO Buttons

选中“GPIO Buttons”选项,将其编译进Linux 内核中,如图58.5.1.1 所示:

在这里插入图片描述
选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”这一行,Linux 内核就会根据这一行来将KEY 驱动文件编译进Linux 内核。Linux 内核自带的KEY 驱动文件为drivers/input/keyboard/gpio_keys.c,gpio_keys.c 采用了platform 驱动框架,在KEY 驱动上使用了input 子系统实现。在gpio_keys.c 文件中找到如下所示内容:

673 static const struct of_device_id gpio_keys_of_match[] = {
674 { .compatible = "gpio-keys", },
675 { },
676 };
......
842 static struct platform_driver gpio_keys_device_driver = {
843 .probe = gpio_keys_probe,
844 .remove = gpio_keys_remove,
845 .driver = {
846 .name = "gpio-keys",
847 .pm = &gpio_keys_pm_ops,
848 .of_match_table = of_match_ptr(gpio_keys_of_match),
849 }
850 };
851
852 static int __init gpio_keys_init(void)
853 {
854 return platform_driver_register(&gpio_keys_device_driver);
855 }
856
857 static void __exit gpio_keys_exit(void)
858 {
859 platform_driver_unregister(&gpio_keys_device_driver);
860 }

从示例代码58.5.1.1 可以看出,这就是一个标准的platform 驱动框架,如果要使用设备树来描述KEY 设备信息的话,设备节点的compatible 属性值要设置为“gpio-keys”。当设备和驱动匹配以后gpio_keys_probe 函数就会执行,gpio_keys_probe 函数内容如下(为了篇幅有缩减):

689 static int gpio_keys_probe(struct platform_device *pdev)
690 {
691 struct device *dev = &pdev->dev;
692 const struct gpio_keys_platform_data *pdata =
dev_get_platdata(dev);
693 struct gpio_keys_drvdata *ddata;
694 struct input_dev *input;
695 size_t size;
696 int i, error;
697 int wakeup = 0;
698
699 if (!pdata) {
700 pdata = gpio_keys_get_devtree_pdata(dev);
701 if (IS_ERR(pdata))
702 return PTR_ERR(pdata);
703 }
......
713 input = devm_input_allocate_device(dev);
714 if (!input) {
715 dev_err(dev, "failed to allocate input device\n");
716 return -ENOMEM;
717 }
718
719 ddata->pdata = pdata;
720 ddata->input = input;
721 mutex_init(&ddata->disable_lock);
722
723 platform_set_drvdata(pdev, ddata);
724 input_set_drvdata(input, ddata);
725
726 input->name = pdata->name ? : pdev->name;
727 input->phys = "gpio-keys/input0";

第700 行,调用gpio_keys_get_devtree_pdata 函数从设备树中获取到KEY 相关的设备节点信息。
第713 行,使用devm_input_allocate_device 函数申请input_dev。
第726~735,初始化input_dev。
第739 行,设置input_dev 事件,这里设置了EV_REP 事件。
第745 行,调用gpio_keys_setup_key 函数继续设置KEY,此函数会设置input_dev 的EV_KEY 事件已经事件码(也就是KEY 模拟为哪个按键)。
第760 行,调用input_register_device 函数向Linux 系统注册input_dev。
我们接下来再来看一下gpio_keys_setup_key 函数,此函数内容如下:

437 static int gpio_keys_setup_key(struct platform_device *pdev,
438 struct input_dev *input,
439 struct gpio_button_data *bdata,
440 const struct gpio_keys_button *button)
441 {
442 const char *desc = button->desc ? button->desc : "gpio_keys";
443 struct device *dev = &pdev->dev;
444 irq_handler_t isr;
445 unsigned long irqflags;
446 int irq;
447 int error;
448
449 bdata->input = input;
450 bdata->button = button;
451 spin_lock_init(&bdata->lock);
452
453 if (gpio_is_valid(button->gpio)) {
454
455 error = devm_gpio_request_one(&pdev->dev, button->gpio,
456 GPIOF_IN, desc);
457 if (error < 0) {
458 dev_err(dev, "Failed to request GPIO %d, error %d\n",
459 button->gpio, error);
460 return error;
......
488 isr = gpio_keys_gpio_isr;
489 irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
490
491 } else {
492 if (!button->irq) {
493 dev_err(dev, "No IRQ specified\n");
494 return -EINVAL;
495 }
496 bdata->irq = button->irq;
......
506
507 isr = gpio_keys_irq_isr;
508 irqflags = 0;
509 }
510
511 input_set_capability(input, button->type ?: EV_KEY,
button->code);
......
540 return 0;
541 }

第511 行,调用input_set_capability 函数设置EV_KEY 事件以及KEY 的按键类型,也就是KEY 作为哪个按键?我们会在设备树里面设置指定的KEY 作为哪个按键。
一切都准备就绪以后剩下的就是等待按键按下,然后向Linux 内核上报事件,事件上报是在gpio_keys_irq_isr 函数中完成的,此函数内容如下:

392 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
393 {
394 struct gpio_button_data *bdata = dev_id;
395 const struct gpio_keys_button *button = bdata->button;
396 struct input_dev *input = bdata->input;
397 unsigned long flags;
398
399 BUG_ON(irq != bdata->irq);
400
401 spin_lock_irqsave(&bdata->lock, flags);
402
403 if (!bdata->key_pressed) {
404 if (bdata->button->wakeup)
405 pm_wakeup_event(bdata->input->dev.parent, 0);
406
407 input_event(input, EV_KEY, button->code, 1);
408 input_sync(input);
409
410 if (!bdata->release_delay) {
411 input_event(input, EV_KEY, button->code, 0);
412 input_sync(input);
413 goto out;
414 }
415
416 bdata->key_pressed = true;
417 }
418
419 if (bdata->release_delay)
420 mod_timer(&bdata->release_timer,
421 jiffies + msecs_to_jiffies(bdata->release_delay));
422 out:
423 spin_unlock_irqrestore(&bdata->lock, flags);
424 return IRQ_HANDLED;
425 }

gpio_keys_irq_isr 是按键中断处理函数,第407 行向Linux 系统上报EV_KEY 事件,表示按键按下。第408 行使用input_sync 函数向系统上报EV_REP 同步事件。

综上所述,Linux 内核自带的gpio_keys.c 驱动文件思路和我们前面编写的keyinput.c 驱动文件基本一致。都是申请和初始化input_dev,设置事件,向Linux 内核注册input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。

自带按键驱动程序的使用

要使用Linux 内核自带的按键驱动程序很简单,只需要根据
Documentation/devicetree/bindings/input/gpio-keys.txt 这个文件在设备树中添加指定的设备节点即可,节点要求如下:
①、节点名字为“gpio-keys”。
②、gpio-keys 节点的compatible 属性值一定要设置为“gpio-keys”。
③、所有的KEY 都是gpio-keys 的子节点,每个子节点可以用如下属性描述自己:
gpios:KEY 所连接的GPIO 信息。
interrupts:KEY 所使用GPIO 中断信息,不是必须的,可以不写。
label:KEY 名字
linux,code:KEY 要模拟的按键,也就是示例代码58.1.2.4 中的这些按键。
④、如果按键要支持连按的话要加入autorepeat。
打开imx6ull-alientek-emmc.dts,根据上面的要求创建对应的设备节点,设备节点内容如下所示:

1 gpio-keys {
2 compatible = "gpio-keys";
3 #address-cells = <1>;
4 #size-cells = <0>;
5 autorepeat;
6 key0 {
7 label = "GPIO Key Enter";
8 linux,code = <KEY_ENTER>;
9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
10 };
11 };

第5 行,autorepeat 表示按键支持连按。
第6~10 行,ALPHA 开发板KEY 按键信息,名字设置为“GPIO Key Enter”,这里我们将开发板上的KEY 按键设置为“EKY_ENTER”这个按键,也就是回车键,效果和键盘上的回车键一样。后面学习LCD 驱动的时候需要用到此按键,因为Linux 内核设计的10 分钟以后LCD关闭,也就是黑屏,就跟我们用电脑或者手机一样,一定时间以后关闭屏幕。这里将开发板上的KEY 按键注册为回车键,当LCD 黑屏以后直接按一下KEY 按键即可唤醒屏幕,就跟当电
脑熄屏以后按下回车键即可重新打开屏幕一样。

最后设置KEY 所使用的IO 为GPIO1_IO18,一定要检查一下设备树看看此GPIO 有没有被用到其他外设上,如果有的话要删除掉相关代码!

重新编译设备树,然后用新编译出来的imx6ull-alientek-emmc.dtb 启动Linux 系统,系统启动以后查看/dev/input 目录,看看都有哪些文件,结果如图58.5.2.1 所示:
在这里插入图片描述
从图58.5.2.1 可以看出存在event1 这个文件,这个文件就是KEY 对应的设备文件,使用hexdump 命令来查看/dev/input/event1 文件,输入如下命令:

hexdump /dev/input/event1

然后按下ALPHA 开发板上的按键,终端输出图58.5.2.2 所示内容:
在这里插入图片描述

如果按下KEY 按键以后会在终端上输出图58.5.2.2 所示的信息那么就表示Linux 内核的按键驱动工作正常。至于图58.5.2.2 中内容的含义大家就自行分析,这个已经在58.4.2 小节详细的分析过了,这里就不再讲解了。
大家如果发现按下KEY 按键以后没有反应,那么请检查一下三方面:
①、是否使能Linux 内核KEY 驱动。
②、设备树中gpio-keys 节点是否创建成功。
③、在设备树中是否有其他外设也使用了KEY 按键对应的GPIO,但是我们并没有删除掉这些外设信息。检查Linux 启动log 信息,看看是否有类似下面这条信息:

gpio-keys gpio_keys:Failed to request GPIO 18, error -16

上述信息表示GPIO 18 申请失败,失败的原因就是有其他的外设正在使用此GPIO。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux INPUT 子系统实验 的相关文章

  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 如何通过替换为空页映射来取消映射 mmap 文件

    Linux 用户空间有没有办法用空页面 映射自 dev null 或者可能是一个空页面 重复映射到从文件映射的页面的顶部 对于上下文 我想找到这个 JDK bug 的修复 https bugs openjdk java net browse
  • 跟踪 Linux 程序中活跃使用的内存

    我想跟踪各种程序在特定状态下接触了多少内存 例如 假设我有一个图形程序 最小化时 它可能会使用更少的内存 因为它不会重新绘制窗口 这需要读取图像和字体并执行大量库函数 这些对象仍然可以在内存中访问 但实际上并没有被使用 类似的工具top它们
  • 如何使用 bash 锁定文件

    我有一个任务从远程服务器同步目录 rsync av email protected cdn cgi l email protection srv data srv data 为了使其定期运行并避免脚本 reEnter 问题 我使用 rsyn
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • nginx 上的多个网站和可用网站

    通过 nginx 的基本安装 您的sites available文件夹只有一个文件 default 怎么样sites available文件夹的工作原理以及如何使用它来托管多个 单独的 网站 只是为了添加另一种方法 您可以为您托管的每个虚拟
  • Jenkins中找不到环境变量

    我想在詹金斯中设置很多变量 我试过把它们放进去 bashrc bash profile and profile of the jenkins用户 但 Jenkins 在构建发生时找不到它们 唯一有效的方法是将所有环境变量放入Jenkinsf
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • 如何在 shell 脚本中并行运行多个实例以提高时间效率[重复]

    这个问题在这里已经有答案了 我正在使用 shell 脚本 它读取 16000 行的输入文件 运行该脚本需要8个多小时 我需要减少它 所以我将其划分为 8 个实例并读取数据 其中我使用 for 循环迭代 8 个文件 并在其中使用 while
  • 为什么内核需要虚拟寻址?

    在Linux中 每个进程都有其虚拟地址空间 例如 32位系统为4GB 其中3GB为进程保留 1GB为内核保留 这种虚拟寻址机制有助于隔离每个进程的地址空间 对于流程来说这是可以理解的 因为有很多流程 但既然我们只有 1 个内核 那么为什么我
  • Linux 中的动态环境变量?

    Linux 中是否可以通过某种方式拥有动态环境变量 我有一个网络服务器 网站遵循以下布局 site qa production 我想要一个环境变量 例如 APPLICATION ENV 当我在 qa 目录中时设置为 qa 当我在生产目录中时
  • 如何在Linux内核源代码中打印IP地址或MAC地址

    我必须通过修改 Linux 内核源代码来稍微改变 TCP 拥塞控制算法 但为了检查结果是否正确 我需要记录 MAC 或 IP 地址信息 我使用 PRINTK 函数来打印内核消息 但我感觉很难打印出主机的MAC IP地址 printk pM
  • CentOS:无法安装 Chromium 浏览器

    我正在尝试在 centOS 6 i 中安装 chromium 以 root 用户身份运行以下命令 cd etc yum repos d wget http repos fedorapeople org repos spot chromium
  • 有谁知道在哪里定义硬件、版本和序列号。 /proc/cpuinfo 的字段?

    我想确保我的 proc cpuinfo 是准确的 目前它输出 Hardware am335xevm Revision 0000 Serial 0000000000000000 我可以在代码中的哪里更改它以给出实际值 这取决于 Linux 的
  • ubuntu:升级软件(cmake)-版本消歧(本地编译)[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我的机器上安装了 cmake 2 8 0 来自 ubuntu 软件包 二进制文件放置在 usr bin cmake 中 我需要将 cmake 版本至少
  • 为arm构建WebRTC

    我想为我的带有arm926ej s处理器的小机器构建webrtc 安装 depot tools 后 我执行了以下步骤 gclient config http webrtc googlecode com svn trunk gclient s
  • PHP 无法打开流:是一个目录

    非常简单的 PHP 脚本 我在我亲自设置的 Ubuntu Web 服务器上的 EE 模板中运行 我知道这与权限有关 并且我已经将我尝试写入的目录的所有者更改为 Apache 用户 我得到的错误是 遇到 PHP 错误 严重性 警告 消息 fi
  • 查找哪些页面不再与写入时复制共享

    假设我在 Linux 中有一个进程 我从中fork 另一个相同的过程 后forking 因为原始进程将开始写入内存 Linux写时复制机制将为进程提供与分叉进程使用的不同的唯一物理内存页 在执行的某个时刻 我如何知道原始进程的哪些页面已被写
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 查找哪个程序运行另一个程序

    我有一个 NAS 运行在 Redhat Linux 的有限版本上 我按照指示破解了它 这样我就可以访问 shell 这很有帮助 我还做了一些修改 其他人也做过修改 除了一个问题之外 它们似乎都工作得很好 不知何故 每隔 22 天 系统就会关

随机推荐

  • 2023蓝桥杯 试题E:接龙数列

    include
  • PCB设计基础概念

    芯片电源 VCC 即接入电路的电压 VDD 即器件内部的工作电压 VSS 即电路公共接地端电压 GND 即电压参考基点 VEE 负电压供电 VPP 编程 擦除电压 V 与 V A的区别是 数字与模拟的区别 型滤波设计 晶体电路设计多采用 型
  • 国产开源大模型: 百亿参数“伶荔”,填补中文基础模型空白!

    Datawhale开源 团队 深圳大学沈琳琳教授团队 Linly 伶荔说 中文语言大模型来啦 大数据系统计算技术国家工程实验室副主任 深圳大学计算机与软件学院沈琳琳教授团队主持的人工智能项目 伶荔 Linly 于今天隆重推出 伶荔说 系列中
  • HashMap源码分析

    目录 hashmap1 8源码大纲 那么问题来了 hashmap的数据结构 为什么扩容长度必须是2的指数次幂也就是2的n次方 为什么加载因子是0 75 为什么数组转链表阈值是8 key能否为空 hashmap为什么线程不安全 hashmap
  • js与移动端交互

    1 js 调用移动端ios与android方法 2 移动端ios与android调用js方法 3 demo如下 div div
  • 使用vlc显示海康网络摄像机的视频

    通过博主的另外一篇博客https blog csdn net u014552102 article details 86700057 配置完海康网络摄像机后 我们就可以使用vlc显示摄像机的视频了 在下图所示的浏览器页面中 我们可以知道摄像
  • redis集群主从复制bug:从机出现master_link_status:down提示,显示主机是down的状态,主机显示没有从机挂载

    bug 从机出现master link status down 原因分析 这里主要是因为redis设置了密码 可以在redis conf文件里面配从不配主 也就是 将master和slave的密码配置相同 然后将slave的配置文件中的ma
  • matlab的一些基本矩阵函数总结

    单位矩阵的生成 A eye 3 3 生成一个3 3的单位矩阵 随机矩阵的生成 A rand 4 5 生成一个4 5的随机矩阵 对角矩阵的生成 d diag A 若A是一个矩阵 则d为取A对角线元素组成的一个向量 如果A为一个向量 则d是一个
  • 归并排序与基数排序

    你好 我是史丰源 欢迎你的来访 希望我的博客能给你带来一些帮助 我的Gitee 代码仓库 归并排序与基数排序 归并排序 概念 来自Wikipedia 实现算法 来自Wikipedia 基数排序 概念 来自Wikipedia 举例 来自Wik
  • 第十七讲:神州三层交换机DHCP服务器配置

    DHCP是基于Client Server模式的协议 DHCP客户机向DHCP服务器索取网络地址及配置参数 服务器为客户机提供网络地址及配置参数 当DHCP客户机和DHCP服务器不在同一子网时 需要由DHCP中继为DHCP客户机和DHCP服务
  • 2021年南京市高考成绩查询,2021年南京各高中高考成绩排名及放榜最新消息

    一 2020年南京各高中高考成绩排名及放榜最新消息 南师附中 理科最高分431分 裸分400分及以上147人 据统计 南师附中在本届已有9位同学2019年被中国科技大学创新班提前录取 5位同学被清华 北大保送的情况下 共563人参加高考 理
  • java中的流的分类

    java中的流的分类 按照流是否直接与特定的地方 如磁盘 内存 设备等 相连 分为节点流和处理流两类 节点流 可以从或向一个特定的地方 节点 读写数据 如FileReader 处理流 是对一个已存在的流的连接和封装 通过所封装的流的功能调用
  • 它来了!Flutter3.0新特性全接触

    点击上方蓝字关注我 知识会给你力量 又到了Flutter稳定版发布的时候了 我们非常自豪地宣布Flutter 3 仅仅三个月前 我们宣布Flutter支持Windows 今天 我们很高兴地宣布 除了Windows之外 Flutter现在在m
  • svpwm之先把电机转起来

    学习FOC一段时间 怎是没有长进 一直看书 FOC框架比较复杂 我在想可不可以输出一个固定频率的SVPWM先把电机转动起来 FOC框架如上图 我先实现SVPWM部分 如下图框选的部分 生成7段式SVPWM 1 硬件平台选择 硬件平台 MCU
  • 解决在本地连接不上阿里云服务器mysql服务的问题

    先得在阿里云上把3306端口添加到安全组 首先先进入mysql的服务 选择mysql这个库 然后查看user用户的host属性 会发现其host属性值是localhost 意思是只准许本地的连接访问 此时我们要对他修改为谁都可以访问的 修改
  • Albumentations 对 PIL 图像进行数据增强

    要使用 Albumentations 对 PIL 图像进行数据增强 你需要将 PIL 图像转换为 NumPy 数组 并使用 Albumentations 库中的转换函数来进行数据增强 以下是一个示例代码 import albumentati
  • dhcp协议配置练习

    路由器配置 配置接口ip地址 配置地址池 开启dhcp全局映射 结果 Pc1 Pc2 Pc3 Pc4
  • postman中post请求正常,但是利用postman生成C#后台http模拟代码之后调用失败问题记录

    postman中post请求正常 但是利用postman中code功能生成C 后台代码之后 填入C 后台失败 postman中code生成的代码引用的是RestSharp Restful Client开发 RestSharp帮助类 post
  • vue特性 is ref

    is属性 使用is标签解决页面中出现的小bug 例如下面的例子 div table tbody tbody table div
  • Linux INPUT 子系统实验

    目录 input 子系统 input 子系统简 input 驱动编写流程 input event 结构体 硬件原理图分析 实验程序编写 修改设备树文件 按键input 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测