Linux驱动开发(13):输入子系统–按键输入实验

计算机的输入设备繁多,有按键、鼠标、键盘、触摸屏、游戏手柄等等,Linux内核为了能够将所有的输入设备进行统一的管理, 设计了输入子系统。为上层应用提供了统一的抽象层,各个输入设备的驱动程序只需上报产生的输入事件即可。

下面以按键中断为例讲解输入子系统的使用。 本章配套源码和设备树插件位于“ ~/linux_driver/input_sub_system ”目录下。

1. 输入子系统简介

linux为了统一各个输入设备,将输入子系统分为了Drivers(驱动层)、Input Core(输入子系统核心层)、 handlers(事件处理层)三部分。

1

  • Drivers主要实现对硬件设备的读写访问,设置中断,并将硬件产生的事件转为Input Core定义的规范提交给Handlers;

  • Input Core起到承上启下的作用,为Drivers提供了规范及接口,并通知Handlers对事件进行处理;

  • Handlers并不涉及硬件方面的具体操作,是一个纯软件层,包含了不同的解决方案,如按键、键盘、鼠标、游戏手柄等。

最终所有输入设备的输入信息将被抽象成以下结构体:

struct input_event结构体 (内核源码/include/uapi/linux/input.h):

//输入事件
struct input_event{struct timeval time;   //事件产生的时间__u16 type;            //输入设备的类型,鼠标、键盘、触摸屏__u16 code;__s16 value;
}
  • time :事件产生的时间。

  • type :输入设备的类型。

  • code :根据设备类型的不同而含义不同,如果设备类型是按键,code表示为按键值(如第几个按键等)。

  • value:根据设备类型的不同而含义不同。如果设备类型是按键,value表示的是松开或者按下。

本章目的是编写一个基于输入子系统和中断的按键驱动程序,重点在于了解Input Core为我们提供了哪些接口,并了解如何将 按键信息以事件上报。

input子系统Input Core实现代码是“ 内核源码/drivers/input/input.c ”以及“ 内核源码/include/linux/input.h ”两个文件 为我们提供了注册输入子系统的API,通过操作这些API就可以实现输入事件的注册、初始化、上报、注销等等工作。 下面我们介绍输入子系统常用的API接口及数据结构。

1.1. input_dev结构体

在输入子系统中input_dev代表一个具体的输入设备,后面将会根据具体的设备来初始化这个结构体,结构体成员介绍如下: (input_dev参数很多,有些不需要我们手动配置,所以这里只列出和介绍常用的参数,完整内容位于input.h文件)。

input_dev结构体 (内核源码/include/linux/input.h):

struct input_dev {const char *name;  //提供给用户的输入设备的名称const char *phys;  //提供给编程者的设备节点的名称const char *uniq;   //指定唯一的ID号struct input_id id; //输入设备标识IDunsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];unsigned long evbit[BITS_TO_LONGS(EV_CNT)];  //指定设备支持的事件类型unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //记录支持的键值unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //记录支持的相对坐标位图unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //记录支持的绝对坐标位图/*----------以下结构体成员省略----------------*/
};

结构体成员中最重要的是 evbitkeybitrelbit 等数组,这些数组设置了设备输入事件的类型和键值。

  • evbit:用于指定支持的事件类型,这要根据实际输入设备能够产生的事件来选择,可选选项如下所示。

输入子系统事件类型 (内核源码/include/uapi/linux/input-event-codes.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
#define EV_SND                      0x12
#define EV_REP                      0x14
#define EV_FF                       0x15
#define EV_PWR                      0x16
#define EV_FF_STATUS                0x17
#define EV_MAX                      0x1f
#define EV_CNT                      (EV_MAX+1)

上面代码中前几个宏定义较为常用的输入事件类型,介绍如代码后面所示。 完整的介绍可以参考内核源码目录下的“ 内核源码/Documentation/input/event-codes.rst ”内核文档。 很明显,我们这章节要使用的按键的事件类型应该使用 EV_KEY 。

  • keybit:记录支持的键值,“键值”在程序中用于区分不同的按键,可选“键值”如下所示。

输入子系统—按键键值 (内核源码/include/uapi/linux/input-event-codes.h):

#define KEY_RESERVED                0
#define KEY_ESC                     1
#define KEY_1                       2
#define KEY_2                       3
#define KEY_3                       4
#define KEY_4                       5
/*-----------以下内容省略-------------*/

可以看出“键值”就是一些数字。只要实际设备与按键对应即可。例如本章的按键可以使用KEY_1、也可以使用KEY_4等。

  • relbit、absbit:这两个参数和上面的keybit都和参数evbit有关,如果evbit中只选择了EV_KEY, 那么我们就不需要设置relbit(相对坐标)和absbit(绝对坐标)以及后面省略成员的内容。这些内容使用到时再具体介绍。 使用不同的输入事件类型需要设备不同的

总之,input_dev结构体成员很多,但是对应到一个具体的输入设备,只需要设置自己用到的其中一两个属性。

1.2. input_dev结构体的申请和释放

一个input_dev结构体代表了一个输入设备,它实际会占输入子系统的一个次设备号。 input子系统为我们提供了申请和释放input_dev结构体的函数。 由于input_dev结构体的成员很多,初始化过程也相对麻烦,一般都使用input子系统为我们提供的接口函数来 申请和释放input_dev结构体,如下所示。

input_dev申请函数 (内核源码/drivers/input/input.c):

struct input_dev *input_allocate_device(void)
{static atomic_t input_no = ATOMIC_INIT(-1);struct input_dev *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);        //动态内存申请if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class;                      //dev->dev为struct device类型结构体device_initialize(&dev->dev);                       //初始化dev->dev结构体内部成员mutex_init(&dev->mutex);                            //初始化互斥锁spin_lock_init(&dev->event_lock);           //初始化自旋锁timer_setup(&dev->timer, NULL, 0);          //初始化定时器INIT_LIST_HEAD(&dev->h_list);                       //初始化handle链表节点INIT_LIST_HEAD(&dev->node);                         //初始化输入设备链表节点dev_set_name(&dev->dev, "input%lu",(unsigned long)atomic_inc_return(&input_no));  //设置设备名称__module_get(THIS_MODULE);}return dev;
}
EXPORT_SYMBOL(input_allocate_device);

我们只需要知道如何调用这个函数来申请input_dev即可,想要更深入学习的同学们可以尝试去分析整个输入子系统的实现源码, 对于输入子系统的源码分析就可以写一篇很长的文章了,这里并不展开详细的源码分析。

input_dev释放函数 (内核源码/drivers/input/input.c):

void input_free_device(struct input_dev *dev)

申请和释放函数接口比较简单。申请函数input_allocate_device执行成功后会返回申请得到的input_dev结构体的地址, 如果失败,返回NULL。释放函数input_free_device只有一个参数dev,用于指定要释放的input_dev结构体。

1.3. 注册和注销input_dev结构体

input_dev申请成功后,我们需要根据自己的实际输入设备配置input_dev结构体,具体配置在实验代码编写部分会详细说明, 配置完成后还要使用注册和注销函数将input_dev注册到输入子系统。注册和注销函数如下:

input_dev注册函数(内核源码/drivers/input/input.c):

int input_register_device(struct input_dev *dev)

参数: dev:struct input_dev类型指针 

返回值:

  • 成功: 0

  • 失败: 返回非0值

input_register_device函数将输入设备(input_dev)注册到输入子系统的核心层。 该函数使用需要注意以下几点

  • 使用该函数注册的input_dev必须是使用input_allocate_device函数申请得到的。

  • 注册之前需要根据实际输入设备配置好input_dev结构体。

  • 如果注册失败必须调用input_free_device函数释放input_dev结构体。

  • 如果注册成功,在函数退出时只需要使用input_unregister_device函数注销input_dev结构体不需要再调用 input_free_device函数释放input_dev结构体。

input_dev注销函数 (内核源码/drivers/input/input.c):

void input_unregister_device(struct input_dev *dev)

input_unregister_device是注销函数,输入子系统的资源是有限的,不使用是应当注销。 调用input_unregister_device注销函数之后就不必调用input_free_device函数释放input_dev。

1.4. 上报事件函数和上报结束函数

以按键为例,按键按下后需要使用上报函数向输入子系统核心层上报按键事件,并且上报后还要发送上报结束信息。函数定义如下所示。

通用的上报事件函数 (内核源码include/linux/input.h):

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

参数

  • dev,指定输入设备(input_dev结构体)。

  • type,事件类型。我们在根据实际输入设备配置input_dev结构体时会设置input_dev-> evbit参数, 用于设置输入设备能够产生的事件类型(可能是多个)。上报事件时要从“能够产生”的这些事件类型中选择。

  • code,编码。以按键为例,按键的编码就是我们设置的按键键值。

  • value,指定事件的值。

返回值: 

上报按键事件及发送上报结束事件 (内核源码include/linux/input.h):

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

input子系统为不同的输入事件函数提供了不同的函数接口,这些函数接口只是对input_event函数进行简单的封装, 具体的参数参照input_event函数。input_report_key用于上报按键事件,input_sync用于发送同步信号,表示上报结束。

2. 输入子系统实验

本小节以按键为例介绍输入子系统的具体使用方法。本实验在上一章“中断实验”基础上完成。结合源码介绍如下。

2.1. 设备树插件实现

设备树插件与上一章的“按键中断实验”使用的设备树插件几乎相同,我们只需要将中断类型修改为“上升和下降沿触发”。 修改部分如下所示。

/dts-v1/;
/plugin/;
#include "../imx6ul-pinfunc.h"
#include "dt-bindings/interrupt-controller/irq.h"
#include "dt-bindings/gpio/gpio.h"/ {fragment@0 {target-path = "/";__overlay__ {button_interrupt {compatible = "button_interrupt";pinctrl-names = "default";pinctrl-0 = <&pinctrl_button>;button_gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;status = "okay";interrupt-parent = <&gpio5>;interrupts = <1 IRQ_TYPE_EDGE_BOTH>;    //设备树修改内容,将上升沿触发修改为双边沿触发};};};fragment@1 {target = <&iomuxc>;__overlay__ {pinctrl_button: buttongrp {fsl,pins = <MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01  0x10b0>;};};};
};

修改内容很简单只是将原来中断的触发方式修改为双边沿触发,其他的设备树内容不变。

2.2. 驱动程序实现

2.2.1. 驱动入口函数

驱动入口函数如下所示。

static int __init button_driver_init(void)
{int error;printk(KERN_ERR "button_driver_init \n");/*-----------第一部分-------------*//*获取按键 设备树节点*/button_device_node = of_find_node_by_path("/button_interrupt");if (NULL == button_device_node){printk(KERN_ERR "of_find_node_by_path error!");return -1;}/*获取按键使用的GPIO*/button_GPIO_number = of_get_named_gpio(button_device_node, "button_gpio", 0);if (0 == button_GPIO_number){printk(KERN_ERR"of_get_named_gpio error");return -1;}/*申请GPIO  , 记得释放*/error = gpio_request(button_GPIO_number, "button_gpio");if (error < 0){printk(KERN_ERR "gpio_request error");gpio_free(button_GPIO_number);return -1;}error = gpio_direction_input(button_GPIO_number); //设置引脚为输入模式/*获取中断号*/interrupt_number = irq_of_parse_and_map(button_device_node, 0);printk(KERN_ERR "\n interrupt_number =  %d \n", interrupt_number);/*申请中断, 记得释放*/error = request_irq(interrupt_number, button_irq_hander, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_interrupt", NULL);if (error != 0){printk(KERN_ERR "request_irq error");gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);return -1;}/*-----------第二部分-------------*//*申请输入子系统结构体*/button_input_dev = input_allocate_device();if (NULL == button_input_dev){printk(KERN_ERR "input_allocate_device error");return -1;}button_input_dev->name = BUTTON_NAME;/*-----------第三部分-------------*//*设置要使用的输入事件类型*/button_input_dev->evbit[0] = BIT_MASK(EV_KEY);input_set_capability(button_input_dev, EV_KEY, KEY_1); //标记设备能够触发的事件/*-----------第四部分-------------*//*注册输入设备*/error = input_register_device(button_input_dev);if (0 != error){printk(KERN_ERR "input_register_device error");gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);input_unregister_device(button_input_dev);return -1;}return 0;
}

驱动入口函数完成基本的初始化工作,结合代码各部分介绍如下:

  • 第9-49行:这部分和“中断实验”相同,依次执行获取设备树节点、获取GPIO、申请GPIO、获取中断号、申请中断, 需要注意的是这里中断类型为“上升和下降沿触发”。

  • 第50-58行:申请输入子系统结构体,申请得到的input_dev结构体代表了一个输入设备, 下面要根据实际的输入设备设置这个结构体。

  • 第62行:设置输入事件类型。input_dev参数很多,其中最主要的是事件类型和事件对应的code。 evbit每一位代表了一种事件类型,为1则表示支持,0表示不支持。例如我们这里要支持“按键”事件, 那么就要将EV_KEY(等于0x01)位置1。内核提供了帮助宏BIT_MASK帮助我们开启某一“事件”。

  • 第63行:设置支持的事件类型之后还要设置与之对应的“事件值”,内核文档中称为code。以按键为例,就是为按键选择键值 (在程序中通过键值区分不同的按键),input_dev->keybit参数用于选择键值,例如在驱动中有6个按键,那么就要使能6个键值, 同样input_dev->keybit每一位代表一个键值,我们可以直接设置某一位使能对应的键值, 不过内核提供了很多帮助宏或函数帮助我们设置键值(也可用于设置其他类型事件的code), 我们在程序中使用的是input_set_capability函数。:

  • 第67-75行:注册输入设备。注册成功后,输入设备被添加到输入子系统内核层,系统能够接受来自该设备的输入事件。 需要注意的是如果注册失败需要注销之前申请的资源然后退出

input_set_capability函数,原型如下:

input_set_capability函数 (内核源码/drivers/input/input.c):

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{switch (type) {case EV_KEY:__set_bit(code, dev->keybit);break;case EV_REL:__set_bit(code, dev->relbit);break;case EV_ABS:input_alloc_absinfo(dev);if (!dev->absinfo)return;__set_bit(code, dev->absbit);break;case EV_MSC:__set_bit(code, dev->mscbit);break;case EV_SW:__set_bit(code, dev->swbit);break;case EV_LED:__set_bit(code, dev->ledbit);break;case EV_SND:__set_bit(code, dev->sndbit);break;case EV_FF:__set_bit(code, dev->ffbit);break;case EV_PWR:/* do nothing */break;default:pr_err("input_set_capability: unknown type %u (code %u)\n",type, code);dump_stack();return;}__set_bit(type, dev->evbit);
}

参数

  • dev:指定要设置的input_dev结构体,也就是要设置的输入设备,

  • type:设置输入类型,可以看到,函数实现中根据type设置不同的input_dev结构体参数。例如type =EV_KEY, 那么设置的是input_dev->keybit,也就是键值。

  • code:不同类型的输入信号含义不同,如果是按键,则表示的是要设置的按键的键值。

返回值: 

2.2.2. 驱动出口函数

出口函数主要完成驱动退出前的清理工作,很简单,代码如下:

static void __exit button_driver_exit(void)
{pr_info("button_driver_exit\n");/*释放申请的引脚,和中断*/gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);/*释放输入子系统相关内容*/input_unregister_device(button_input_dev);
}
  • 第6-7行:释放申请的引脚和中断

  • 第10行:释放申请的输入子系统

2.2.3. 中断服务函数

中断服务函数中我们读取按键输入引脚的状态判断按键是按下还是松开。代码如下所示。

static irqreturn_t button_irq_hander(int irq, void *dev_id)
{int button_satus = 0;/*读取按键引脚的电平,根据读取得到的结果输入按键状态*/button_satus = gpio_get_value(button_GPIO_number);if(0 == button_satus){input_report_key(button_input_dev, KEY_1, 0);input_sync(button_input_dev);}else{input_report_key(button_input_dev, KEY_1, 1);input_sync(button_input_dev);}return IRQ_HANDLED;
}
  • 第7行:读取按键对应引脚的电平。

  • 第8-18行:根据按键引脚状态向系统上报按键事件。

2.3. 测试应用程序实现

测试应用程序中读取按键键值,打印按键状态。具体代码如下所示。

struct input_event button_input_event;int main(int argc, char *argv[])
{int error = -20;/*打开文件*/int fd = open("/dev/input/event1", O_RDONLY);if (fd < 0){printf("open file : /dev/input/event1 error!\n");return -1;}printf("wait button down... \n");printf("wait button down... \n");do{/*读取按键状态*/error = read(fd, &button_input_event, sizeof(button_input_event));if (error < 0){printf("read file error! \n");}/*判断并打印按键状态*/if((button_input_event.type == 1) && (button_input_event.code == 2)){if(button_input_event.value == 0){printf("button up\n");}else if(button_input_event.value == 1){printf("button down\n");}}} while (1);printf("button Down !\n");/*关闭文件*/error = close(fd);if (error < 0){printf("close file error! \n");}return 0;
}
  • 第1行:申请一个input_event类型的结构体变量,如我们在本章开头前所说的所有的输入设备传递的信息都会以事件的形式上报。

  • 第8行:这里的打开的文件 /dev/input/event1 是输入子系统为我们生成的输入设备设备,即我们使用的按键。

  • 第21行:读取按键信息,read函数没有读取到上报输入事件则将一直等待。

  • 第27-37行:根据获取读取到的信息判断按键的状态。

测试应用程序的内容很简单,基本是按照打开文件、读取状态、判断状态并打印状态。

2.4. 实验准备

在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。

如本节实验中,可能在鲁班猫系统中默认使能了 KEY 的设备功能, 用在了GPIO子系统。引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。

方法参考如下:

broken

取消 KEY 设备树插件,以释放系统对应KEY资源,操作如下:

broken

dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-button-interrupt.dtbo

如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

2.4.1. 编译设备树插件

将 linux_driver/input_sub_system/imx-fire-button-interrupt-overlay.dts 拷贝到 内核源码/arch/arm/boot/dts/overlays 目录下, 并修改同级目录下的Makefile,追加 imx-fire-button-interrupt.dtbo 编译选项。然后执行如下命令编译设备树插件:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfigmake ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

编译成功后生成同名的设备树插件文件(imx-fire-button-interrupt.dtbo)位于 内核源码/arch/arm/boot/dts/overlays 目录下。

2.4.2. 编译驱动程序

将 linux_driver/input_sub_system 拷贝到内核源码同级目录,执行里面的MakeFile,生成input_sub_system.ko。

5|

2.4.3. 编译应用程序

将 linux_driver/input_sub_system/test_app 目录中执行里面的MakeFile,生成test_app。

编译应用程序

2.5. 下载验证

编译驱动和应用程序并拷贝到开发板,这里就不再赘述如何拷贝文件了。 在加载模块之前,先查看 /boot/uEnv.txt 文件是否加载了板子上原有的与按键相关设备树插件。 如果之前开启了按键相关的设备树插件,记得先屏蔽掉。并添加输入子系统的设备树插件。

1

在上图所示代码前添加’#’以注销掉与按键相关的设备树插件,并 reboot 重启开发板。

使用insmod命令加载驱动,如下所示:

2

此时会在“/dev/input”目录下生成设备节点文件。

3

驱动加载成功后直接运行测试应用程序命令“./test_app”.测试程序运行后等待按键按下,此时按下开发板的“KEY”按键, 终端会输出按键状态,如下所示。

4

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/490357.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于Postgresql旧版本安装

抛出问题 局点项目现场&#xff0c;要求对如下三类资产做安全加固&#xff0c;需要在公司侧搭建测试验证环境&#xff0c;故有此篇。 bclinux 8.2 tomcat-8.5.59 postgrel -11 随着PG迭代&#xff0c;老旧版本仅提供有限维护。如果想安装老版本可能就要费劲儿一些。现在&…

继电器控制与C++编程:实现安全开关控制的技术分享

在现代生活中,继电器作为一种重要的电气控制元件,在电气设备的安全控制中起到了至关重要的作用。通过低电流控制高电流,继电器能够有效地隔离控制电路与被控设备,从而保障使用者的安全。本项目将介绍如何通过树莓派Pico与继电器模块结合,使用C++编程实现继电器的控制。 一…

时序论文31|NIPS24自注意力机制真的对时序预测任务有效吗?

论文标题&#xff1a;Are Self-Attentions Effective for Time Series Forecasting? 论文链接&#xff1a;https://arxiv.org/pdf/2409.18696 代码链接&#xff1a;https://github.com/dongbeank/CATS 前言 本文将重点转向探究自注意力机制在其中的有效性&#xff0c;提出…

ip_done

文章目录 路由结论 IP分片 数据链路层重谈Mac地址MAC帧报头局域网的通信原理MSS&#xff0c;以及MAC帧对上层的影响ARP协议 1.公司是不是这样呢? 类似的要给运营商交钱&#xff0c;构建公司的子网&#xff0c;具有公司级别的入口路由器 2&#xff0e;为什么要这样呢?? IP地…

计算机网络错题

文章目录 码分复用透明传输差错检测停止-等待协议回退N帧协议CSMA/CD协议以太网交换机Vlanip地址的无分类编制方法ip地址的应用规划ip数据包的发送和转发过程路由信息协议IPI2016201720202022 2.5信道 码分复用 透明传输 差错检测 停止-等待协议 回退N帧协议 CSMA/CD协议 以太网…

2024 年 9 月区块链游戏研报:行业回暖,Telegram 游戏引发热潮

作者&#xff1a;Stella L (stellafootprint.network) 数据来源&#xff1a;Footprint Analytics Games Research Page 9 月份&#xff0c;区块链游戏代币的市场总值增长了 29.2%&#xff0c;达到 232 亿美元&#xff0c;日活跃用户&#xff08;DAU&#xff09;数量上升了 1…

Https身份鉴权(小迪网络安全笔记~

附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;若有错误欢迎指正&#xff01; 5.2 Https&身份鉴权 引子&#xff1a;上一篇主要对Http数据包结构、内容做了介绍&#xff0c;本篇则聊聊Https、身份鉴权等技术。 …

ORACLE逗号分隔的字符串字段,关联表查询

使用场景如下&#xff1a; oracle12 以前的写法&#xff1a; selectt.pro_ids,wm_concat(t1.name) pro_names from info t,product t1 where instr(,||t.pro_ids|| ,,,|| t1.id|| ,) > 0 group by pro_ids oracle12 以后的写法&#xff1a; selectt.pro_ids,listagg(DIS…

MySQL八股文

MySQL 自己学习过程中的MySQL八股笔记。 主要来源于 小林coding 牛客MySQL面试八股文背诵版 以及b站和其他的网上资料。 MySQL是一种开放源代码的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;使用最常用的数据库管理语言–结构化查询语言&#xff08;SQL&…

使用echarts实现3d柱状图+折线图

以下代码有问题请直接问国内直连GPT/Claude HTML 需要注意threeDchart一定要设置宽度高度&#xff0c;不然图不显示,然后echarts版本不要太低&#xff0c;不然也不显示 <div id"threeDchart" class"threeDchart"></div>js set3DBarChart2(dat…

基地址和偏移地址的理解

在之前的一篇博客介绍了怎么找阳光地址&#xff1a;CE和Ollydbg简单介绍&#xff0c;但是那个地址在重启游戏后会变化&#xff0c;这次会讲解为什么这个阳光的地址会变化&#xff0c;以及对于变化的地址怎么处理。 推荐博客&#xff1a;CE找基址原理 1.阳光的地址为什么会变化…

C语言:详解指针最终篇(3)

一.字符指针变量 在指针的类型中我们知道有一种指针类型为字符指针char*。一般我们这样使用&#xff1a; 我们来看另一种使用方式&#xff1a; 这个常量字符串就相当于它本身首字符的地址&#xff0c;收地址加上方括号下标就可以访问该表达式中对应下标的元素。可以把该表达式…

【深度学习】 零基础介绍卷积神经网络(CNN)

零基础介绍 卷积神经网络&#xff08;CNN&#xff0c;Convolutional Neural Network&#xff09;是深度学习中的一种神经网络&#xff0c;特别擅长处理图像和视频等有空间结构的数据。 假设我们在做一个“照片分类”的任务&#xff0c;比如判断一张照片中是猫还是狗。下面用一…

Dual-Write Problem 双写问题(微服务)

原文链接https://www.confluent.io/blog/dual-write-problem/ 双写问题发生于当两个外部系统必须以原子的方式更新时。 问题 说有人到银行存了一笔钱&#xff0c;触发 DepositFunds 命令&#xff0c;DepositFunds 命令被发送到Account microservice。 Account microservice需…

ReactPress最佳实践—搭建导航网站实战

Github项目地址&#xff1a;https://github.com/fecommunity/easy-blog 欢迎Star。 近期&#xff0c;阮一峰在科技爱好者周刊第 325 期中推荐了一款开源工具——ReactPress&#xff0c;ReactPress一个基于 Next.js 的博客和 CMS 系统&#xff0c;可查看 demo站点。&#xff08;…

什么叫ip地址一样?网络ip地址一样说明什么

在探索网络世界的奥秘中&#xff0c;IP地址作为网络设备的唯一身份标识&#xff0c;其重要性不言而喻。然而&#xff0c;当我们遇到“IP地址一样”的情况时&#xff0c;不禁会产生诸多疑问&#xff1a;这究竟意味着什么&#xff1f;是否会对网络产生影响&#xff1f;虎观代理小…

C# 探险之旅:第三十二节 - 类型class之(方法重载Overloading):魔法技能的大变身!

嘿&#xff0c;各位勇敢的探险家们&#xff01;欢迎再次踏上C#的奇幻旅程。今天&#xff0c;我们要一起探索一个超级有趣的魔法技巧——方法重载&#xff08;Overloading&#xff09;&#xff01;想象一下&#xff0c;你有一个超级技能&#xff0c;但是这个技能可以根据不同的情…

kubervirt使用与运行策略

三、KubeVirt基本命令 3.1查看virtctl版本&#xff0c;说明安装成功 [rootk8s-master ~]# virtctl version 3.2创建和管理虚拟机 列出所有可用的虚拟机实例 [rootmaster ~]# kubectl get vmi -n <namespace> 参数-n用于指定命名空间 查看特定虚拟机实例的详细信息 […

[Pro Git#3] 远程仓库 | ssh key | .gitignore配置

目录 1. 分布式版本控制系统的概念 2. 实际使用中的“中央服务器” 3. 远程仓库的理解 4. 新建远程仓库 5. 克隆远程仓库 6. 设置SSH Key 实验 一、多用户协作与公钥管理 二、克隆后的本地与远程分支对应 三、向远程仓库推送 四、拉取远程仓库更新 五、配置Git忽略…

【python因果库实战2】使用银行营销数据集研究营销决策的效果2

目录 联系方式的效应 逆概率加权&#xff1a;首次尝试 联系方式的效应 我们已经完成了大部分艰苦的工作&#xff0c;即理解数据并识别处理变量和混杂因素。现在我们可以开始使用 Causal Inference 360 的工具了。 我们将首先研究联系方式 contact 的因果效应。具体来说&…