当前位置: 首页 > news >正文

Linux驱动开发(12):中断子系统–按键中断实验

本章我们以按键为例讲解在驱动程序中如何使用中断, 在学习本章之前建议先回顾一下关于中断相关的裸机部分相关章节, 这里主要介绍在驱动中如何使用中断,对于中断的概念及GIC中断控制器相关内容不再进行讲解。

本章配套源码和设备树插件位于“~/linux_driver/button_interrupt”目录下。

1. 在设备树中添中断信息以及中断基本函数介绍

1.1. 设备树中的中断相关内容

让我们先来了解一下设备树是如何描述整个中断系统信息的。

1.1.1. 顶层中断控制器

打开 ./arch/arm/boot/dts/ 目录下的 imx6ull.dtsi 设备树文件, 找到“interrupt-controller”节点,如下所示。

中断interrupt-controller节点:

intc: interrupt-controller@a01000 {compatible = "arm,cortex-a7-gic";#interrupt-cells = <3>;interrupt-controller;reg = <0xa01000 0x1000>,<0xa02000 0x100>;
};
  • compatible:compatible属性用于平台设备驱动的匹配

  • reg:reg指定中断控制器相关寄存器的地址及大小

  • interrupt-controller:声明该设备树节点是一个中断控制器。

  • #interrupt-cells :指定它的“子”中断控制器用几个cells来描述一个中断,可理解为用几个参数来描述一个中断信息。 在这里的意思是在intc节点的子节点将用3个参数来描述中断。

学过裸机章节的同学们想必对GIC中断控制器并不陌生,GIC架构分为了:分发器(Distributor)CPU接口(CPU Interface),上面设备树节点就是用来描述整个GIC控制器的。

(1) Distributor

  • 作用
    • 负责中断的全局管理和分发。
    • 确定中断需要发送到哪个 CPU 核心。
  • 功能
    • 配置中断优先级。
    • 设置中断目标 CPU。
    • 启用或禁用中断。
  • 接口:通常挂载在 SoC 的外设总线(如 AXI 或 APB)。

(2) CPU Interface

  • 作用
    • 为每个 CPU 核心提供接口,用于处理中断。
    • 确保中断到达正确的 CPU 核心。
  • 功能
    • 向 CPU 发起中断信号(如 IRQ 或 FIQ)。
    • 提供当前活动中断的 ID 和状态。
    • 确认中断已处理(EOI)。

1.1.2. gpc一级子中断控制器

在imx6ull.dtsi文件中直接搜索节点标签“intc”即可找到“一级子中断控制器”

一级子中断控制器:

gpc: gpc@20dc000 {compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";reg = <0x20dc000 0x4000>;interrupt-controller;#interrupt-cells = <3>;interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;interrupt-parent = <&intc>;fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
};

结合以上代码介绍如下:

  • interrupt-controller:声明该设备树节点是一个中断控制器,只要是中断控制器都要用该标签声明。

  • #interrupt-cells:用于规定该节点的“子”中断控制器将使用三个参数来描述子控制器的信息。

  • interrupt-parent:指定该中断控制器的“父”中断控制器。除了“顶层中断控制器”其他中断控制器都要声明“父”中断控制器。

  • interrupts:具体的中断描述信息,在该节点的中断控制器的“父”中断控制器,规定了使用三个cells来描述子控制器的信息。 三个参数表示的含义如下:

    第一个参数用于指定中断类型,在GIC中中断的类型有三种(SPI共享中断、PPI私有中断、SGI软件中断), 我们使用的外部中断均属于SPI中断类型。

    第二个参数用于设定中断编号,范围和第一个参数有关。PPI中断范围是[0-15],SPI中断范围是[0-987]。

    第三个参数指定中断触发方式,参数是一个u32类型,其中后四位[0-3]用于设置中断触发类型。 每一位代表一个触发方式,可进行组合,系统提供了相对的宏顶义我们可以直接使用,如下所示:

中断触发方式设置:

#define IRQ_TYPE_NONE           0
#define IRQ_TYPE_EDGE_RISING    1
#define IRQ_TYPE_EDGE_FALLING   2
#define IRQ_TYPE_EDGE_BOTH      (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH     4
#define IRQ_TYPE_LEVEL_LOW      8

[8-15]位在PPI中断中用于设置“CPU屏蔽”。在多核系统中这8位用于设置PPI中断发送到那个CPU,一位代表一个CPU, 为1则将PPI中断发送到CPU,否则屏蔽。imx6ull是单核CPU,所以我们不用设置这些位。

1.1.3. 二级子中断控制器

同样在imx6ull.dtsi文件中直接搜索节点标签“gpc”即可找到“二级子中断控制器”如下所示。

中断触发方式设置:

soc {#address-cells = <1>;#size-cells = <1>;compatible = "simple-bus";interrupt-parent = <&gpc>;ranges;//busfreq子节点busfreq {................  //表示省略}...............     //表示省略

soc 节点即片上外设“总节点”,翻阅源码可以发现该节点很长,我们使用的外设大多包含在里面。 具体外设(例如GPIO)也可作为中断控制器,这里声明了它们的“父”中断控制器是 <&gpc>节点。

soc节点内包含的中断控制器很多,几乎用到中断的外设都是中断控制器,我们使用的是开发板上的按键, 使用的是GPIO5_1,所以这里以GPIO5为例介绍。在imx6ull.dtsi文件中直接搜索GPIO5,找到GPIO5对应的设备树节点,如下所示。

gpio5:

gpio5: gpio@20ac000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x20ac000 0x4000>;interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clks IMX6UL_CLK_GPIO5>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;
};

以上是gpio5节点的全部内容,这里主要介绍和中断相关的节点信息。

  • interrupts:用来描述GPIO5能产生中断类型及中断编号、触发方式,查看imx6ull的数据手册我们可以知道, GPIO5能产生的中断只有两个,分配的中断ID为106、107,对于SPI中断它们的编号是74(106-32),75(107-32)。

  • interrupt-controller:声明该节点是一个中断控制器

  • #interrupt-cells:声明该节点的子节点将用多少个参数来描述中断信息。

1.1.4. 按键设备树节点

以上三部分的内容是内核为我们提供的,我们要做的内容很简单, 只需要在我们编写的设备树节点中 引用已经写好的中断控制器父节点以及配置中断信息即可,如下所示。

button按键设备节点:

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_RISING>;     // 指定中断,触发方式为上升沿触发。
};

这里主要介绍和中断相关部分的内容。

  • interrupt-parent:指定“父控制器节点 ”。我们按键所在的引脚是gpio5_1,故我们按键所在的中断控制父节点 为gpio5。

  • interrupts:在gpio5节点中定义使用两个cells来描述我们的按键信息,‘1’表示的是我们按键GPIO5中引脚编号, “IRQ_TYPE_EDGE_RISING”表示的是触发方式。触发方式宏定义如下:

中断触发类型设置:

#define IRQ_TYPE_NONE           0
#define IRQ_TYPE_EDGE_RISING    1
#define IRQ_TYPE_EDGE_FALLING   2
#define IRQ_TYPE_EDGE_BOTH      (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH     4
#define IRQ_TYPE_LEVEL_LOW      8

需要注意的是,我们编写的这个节点并不是个中断控制器,所以没有“interrupt-controller”标签。

1.2. 中断相关函数

1.2.1. request_irq中断注册函数

申请中断:

static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler,unsigned long flags, const char *name, void *dev)

参数

  • irq:用于指定“内核中断号”,这个参数我们会从设备树中获取或转换得到。在内核空间中它代表一个唯一的中断编号。

  • handler:用于指定中断处理函数,中断发生后跳转到该函数去执行。

  • flags:中断触发条件,也就是我们常说的上升沿触发、下降沿触发等等 触发方式通过“|”进行组合(注意,这里的设置会覆盖设备树中的默认设置),宏定义如下所示:

中断触发方式:

#define IRQF_TRIGGER_NONE       0x00000000
#define IRQF_TRIGGER_RISING     0x00000001
#define IRQF_TRIGGER_FALLING        0x00000002
#define IRQF_TRIGGER_HIGH       0x00000004
#define IRQF_TRIGGER_LOW        0x00000008
#define IRQF_TRIGGER_MASK       (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE      0x00000010#define IRQF_SHARED         0x00000080 ---------①
/*-----------以下宏定义省略------------*/
  • name:中断的名字,中断申请成功后会在“/proc/interrupts”目录下看到对应的文件。

  • dev: 如果使用了**IRQF_SHARED** 宏,则开启了共享中断。“共享中断”指的是多个驱动程序共用同一个中断。 开启了共享中断之后,中断发生后内核会依次调用这些驱动的“中断服务函数”。 这就需要我们在中断服务函数里判断中断是否来自本驱动,这里就可以用dev参数做中断判断。 即使不用dev参数判断中断来自哪个驱动,在申请中断时也要加上dev参数 因为在注销驱动时内核会根据dev参数决定删除哪个中断服务函数。

返回值

  • 成功:返回0

  • 失败:返回负数。

1.2.2. 中断注销函数free_irq

申请中断:

void free_irq(unsigned int irq, void *dev);

参数

  • irq:从设备树中得到或者转换得到的中断编号。

  • dev:与request_irq函数中dev传入的参数一致。

返回值

1.2.3. 中断处理函数

在中断申请时需要指定一个中断处理函数,书写格式如下所示。

中断服务函数格式:

irqreturn_t (*irq_handler_t)(int irq, void * dev);

参数

  • irq:用于指定“内核中断号”。

  • dev:在共享中断中,用来判断中断产生的驱动是哪个,具体介绍同上中断注册函数。 不同的是dev参数是内核“带回”的。如果使用了共享中断还得根据dev带回的硬件信息判断中断是否来自本驱动,或者不使用dev, 直接读取硬件寄存器判断中断是否来自本驱动。如果不是,应当立即跳出中断服务函数,否则正常执行中断服务函数。

返回值

  • irqreturn_t类型:枚举类型变量,如下所示。

中断服务函数返回值类型:

enum irqreturn {IRQ_NONE                = (0 << 0),IRQ_HANDLED             = (1 << 0),IRQ_WAKE_THREAD         = (1 << 1),
};typedef enum irqreturn irqreturn_t;

如果是“共享中断”并且在中断服务函数中发现中断不是来自本驱动则应当返回 IRQ_NONE , 如果没有开启共享中断或者开启了并且中断来自本驱动则返回 IRQ_HANDLED,表示中断请求已经被正常处理。 第三个参数涉及到我们后面会讲到的中断服务函数的“上半部分”和“下半部分”, 如果在中断服务函数是使用“上半部分”和“下半部分”实现,则应当返回IRQ_WAKE_THREAD。

1.2.4. 中断的使能和禁用函数

通过函数使能、禁用某一个中断。

中断的使能和禁用函数:

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

参数

  • irq:指定的“内核中断号”

1.2.5. 关闭和开启全局中断相关函数(宏定义)

关闭和开启全局中断相关函数:

local_irq_enable()
local_irq_disable()
local_irq_save(flags)
local_irq_restore(flags)

由于“全局中断”的特殊性,通常情况下载关闭之前要使用local_irq_save保存当前中断状态, 开启之后使用local_irq_restore宏恢复关闭之前的状态。flags是一个unsigned long 类型的数据。

了解了以上函数的作用,我们就可以编写中断的驱动程序了, 如有遗漏的内容我们将会在代码介绍中,驱动程序介绍如下。

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_RISING>;};};};fragment@1 {target = <&iomuxc>;__overlay__ {pinctrl_button: buttongrp {fsl,pins = <MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01  0x10b0>;};};};};
  • 第4-6行:我们在设备树插件中用了几个宏定义,这里需要包含相应头文件,

  • 第10-24行,新增的button_interrupt节点,

  • 第12行,指定设备节点插入位置,这里是根节点。

  • 第18行,定义button使用的GPIO。

  • 第20-21行,添加中断相关信息。

  • 第27-35行,配置按键所在引脚。

2.2. 按键中断驱动程序实现

这里不再介绍有关字符设备的内容,重点放在驱动程序中如何使用中断。 完整的代码请参考本章配套例程。

虽然使用了设备树(设备树插件)但是驱动程序是一个简单的字符设备驱动,不会和设备树中的节点匹配。 无论是否匹配与我们“读设备树”无关,驱动源码大致分为驱动入口和出口函数实现、字符设备操作函数实现两部分内容, 结合源码介绍如下:

2.2.1. 驱动入口和出口函数实现

在驱动的入口函数中实现字符设备的注册, 在出口函数中注销字符设备,部分代码如下所示:

驱动入口和出口函数实现:

 /*
*驱动初始化函数
*/
static int __init button_driver_init(void)
{int error = -1;/*采用动态分配的方式,获取设备编号,次设备号为0,*/error = alloc_chrdev_region(&button_devno, 0, DEV_CNT, DEV_NAME);if (error < 0){printk("fail to alloc button_devno\n");goto alloc_err;}/*关联字符设备结构体cdev与文件操作结构体file_operations*/button_chr_dev.owner = THIS_MODULE;cdev_init(&button_chr_dev, &button_chr_dev_fops);/*添加设备至cdev_map散列表中*//*------------一下代码省略---------------*/
}/*
*驱动注销函数
*/
static void __exit button_driver_exit(void)
{pr_info("button_driver_exit\n");/*删除设备*/device_destroy(class_button, button_devno);                //清除设备class_destroy(class_button);                                       //清除类cdev_del(&button_chr_dev);                                             //清除设备号unregister_chrdev_region(button_devno, DEV_CNT);   //取消注册字符设备
}module_init(button_driver_init);
module_exit(button_driver_exit);MODULE_LICENSE("GPL");

字符设备注册于注销已经使用n次了,为方便阅读这里将它的部分代码列出来了。完整的内容请参考本小节配套代码。

2.2.2. .open函数实现

open函数实现button的初始化工作,代码如下:

static int button_open(struct inode *inode, struct file *filp)
{int error = -1;/*添加初始化代码*/// printk_green("button_open");/*获取按键 设备树节点*/button_device_node = of_find_node_by_path("/button_interrupt");if(NULL == button_device_node){printk("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("of_get_named_gpio error");return -1;}/*申请GPIO  , 记得释放*/error = gpio_request(button_GPIO_number, "button_gpio");if(error < 0){printk("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("\n irq_of_parse_and_map! =  %d \n",interrupt_number);/*申请中断, 记得释放*/error = request_irq(interrupt_number,button_irq_hander,IRQF_TRIGGER_RISING,"button_interrupt",device_button); ---------------⑥if(error != 0){printk("request_irq error");free_irq(interrupt_number, device_button);return -1;}/*申请之后已经开启了,切记不要再次打开,否则运行时报错*/// // enable_irq(interrupt_number);return 0;
}
  • 第10行,获取button的设备树节点,我们之前说过,虽然驱动没有采用与设备树节点匹配的方式, 但这不影响我们获取设备树节点,只要节点路径正确即可获取其他设备树节点。

  • 第18行,获取使用的GPIO。详细说明可参考“GPIO子系统章节”。

  • 第26行,注册GPIO。

  • 第34行,设置GPIO为输入模式。

  • 第37行,使用函数irq_of_parse_and_map解析并映射(map)中断函数。函数原型如下:

  • 第41行,申请中断,这个函数在本章的开始已经介绍,需要注意的是,这里虽然没有使用共享中断, 但是仍然将dev参数设置为字符设备结构体指针。当然你也可以设置为NULL或其他值。

解析并映射中断函数:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

该函数的功能是从设备树中获取某一个中断,并且将中断ID转化为linux内核虚拟IRQ number。 IRQ number用于区别中断ID。

参数

  • dev:用于指定设备节点

  • index:指定解析、映射第几个中断, 一个设备树节点可能包含多个中断,这里指定第几个,标号从0开始。

返回值

  • 成功:解析、映射得到的内核中断号

  • 失败:返回0

2.2.3. 中断服务函数实现

在open函数申请中断时要指定中断服务函数,一个简单的中断服务函数如下。

中断服务函数实现:

atomic_t   button_status = ATOMIC_INIT(0);  //定义整型原子变量,保存按键状态 ,设置初始值为0
static irqreturn_t button_irq_hander(int irq, void *dev_id)
{// printk("button on \n");/*按键状态加一*/atomic_inc(&button_status);return IRQ_HANDLED;
}

从以上代码可以看到我们定义了一个整型原子变量用于保存按键状态,中断发送后,整型原子变量自增一。 整型原子变量大于0表示有按键按下。

2.2.4. .read和.release函数实现

.read函数的工作是向用户空间返回按键状态值,.release函数实现退出之前的清理工作。函数实现源码如下:

.read 和.release函数实现:

static int button_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int error = -1;int button_countervc = 0;/*读取按键状态值*/button_countervc = atomic_read(&button_status);/*结果拷贝到用户空间*/error = copy_to_user(buf, &button_countervc, sizeof(button_countervc));if(error < 0){printk_red("copy_to_user error");return -1;}/*清零按键状态值*/atomic_set(&button_status,0);return 0;
}/*字符设备操作函数集,.release函数实现*/
static int button_release(struct inode *inode, struct file *filp)
{/*释放申请的引脚,和中断*/gpio_free(button_GPIO_number);free_irq(interrupt_number, device_button);return 0;
}
  • 第1-20行,在button_read函数中我们读取按键状态值,然后使用copy_to_user拷贝到用户空间, 最后设置按键状态为0。

  • 第23-29行,button_release函数很简单,它只是释放.open函数中申请的中断和GPIO.

2.3. 测试应用程序实现

测试应用程序工作是读取按键状态然后打印状态,就这么简单,源码如下:

测试应用程序:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{int error = -20;int button_status = 0;/*打开文件*/int fd = open("/dev/button", O_RDWR);if (fd < 0){printf("open file : /dev/button error!\n");return -1;}printf("wait button down... \n");printf("wait button down... \n");do{/*读取按键状态*/error = read(fd, &button_status, sizeof(button_status));if (error < 0){printf("read file error! \n");}usleep(1000 * 100); //延时100毫秒} while (0 == button_status);printf("button Down !\n");/*关闭文件*/error = close(fd);if (error < 0){printf("close file error! \n");}return 0;
}

测试应用程序仅仅是测试驱动是否正常,我们只需要打开、读取状态、关闭文件即可。 需要注意的是打开之后需要关闭才能再次打开,如果连续打开两次由于第一次打开申请的GPIO和中断还没有释放打开会失败。

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/button_interrupt/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/button_interrupt/interrupt 拷贝到内核源码同级目录,执行里面的MakeFile,生成interrupt.ko。

5|

2.4.3. 编译应用程序

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

编译应用程序

2.5. 程序运行结果

将前面生成的设备树插件、驱动程序、应用程序通过scp等方式拷贝到开发板。

2.5.1. 加载设备树和驱动文件

将设备树插件拷贝到开发板 /usr/lib/linux-image-4.19.35-imx6/overlays/ 目录下,然后 reboot 重启开发板。

加载设备树

在加载驱动程序之前,先查看设备树是否加载了对应的设备节点,然后再加载驱动陈程序 insmod interrupt.ko 。

驱动程序

2.5.2. 测试效果

驱动加载成功后直接运行测试应用程序 ./test_app,如下所示。

应用程序测试效果

3. 中断的上半部分和下半部分

在linux中断我们需要知道以下两点:

  • 1、Linux中断与中断之间不能嵌套

  • 2、中断服务函数运行时间应当尽量短,做到快进快出。

然而一些中断的产生之后需要较长的时间来处理,如由于网络传输产生的中断, 在产生网络传输中断后需要比较长的时间来处理接收或者发送数据,因为在linux中断并不能被嵌套 如果这时有其他中断产生就不能够及时的响应,为了解决这个问题,linux对中断的处理引入了“中断上半部”和 “中断下半部”的概念,在中断的上半部中只对中断做简单的处理,把需要耗时处理的部分放在中断下半部中,使得能够 对其他中断作为及时的响应,提供系统的实时性。这一概念又被称为中断分层。

  • “上半部分”是指在中断服务函数中执行的那部分代码,

  • “下半部分”是指那些原本应当在中断服务函数中执行但通过某种方式把它们放到中断服务函数外执行。

并不是所有的中断处理都需要用到“上半部分”和“下半部分”,如果像我们上面编写的按键中断程序一样并不需要用到 相对耗时的处理,对中断的处理只需放在中断“上半部分”即可。

为了学习如何使用中断分层,这里模拟一个耗时操作,加上中断分层的“下半部分”。

中断分层实现方法常用的有三种,分别为软中断、tasklet、和工作队列,下面分别介绍这三种方式。

3.1. 软中断和tasklet

tasklet是基于软中断实现,它们有很多相似之处,我们把它两个放到一块介绍。

3.1.1. 软中断

软中断由软件发送中断指令产生,Linux4.xx支持的软中断非常有限,只有10个(不同版本的内核可能不同) 在Linux内核中使用一个枚举变量列出所有可用的软中断,如下所示。

软中断中断编号:

enum
{HI_SOFTIRQ=0,TIMER_SOFTIRQ,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ,SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */NR_SOFTIRQS
};

类比硬中断,这个枚举类型列出了软中断的中断编号,我们“注册”软中断以及触发软中断都会用到软中断的中断编号。

软中断“注册”函数如下所示:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{softirq_vec[nr].action = action;
}

参数

  • nr:用于指定要“注册”的软中断中断编号

  • action:指定软中断的中断服务函数

返回值

我们再看函数实现,这里只有一个赋值语句, 重点是softirq_vec变量,在内核源码中找到这个变量如下所示:

软中断“中断向量表”:

static struct softirq_action softirq_vec[NR_SOFTIRQS]

这是一个长度为NR_SOFTIRQS的softirq_action类型数组,长度NR_SOFTIRQS在软中断的“中断编号”枚举类型中有定义, 长度为10。这个数组是一个全局的数组,作用等同于硬中断的中断向量表。接着来看数组的类型“struct softirq_action”如下所示。

 软中断结构体:

struct softirq_action
{void    (*action)(struct softirq_action *);
};

它只有一个参数,就是注册软中断函数的参数open_softirq。至此我们知道数组softirq_vec就是软中断的中断向量表, 所谓的注册软中断函数就是根据中断号将中断服务函数的地址写入softirq_vec数组的对应位置。

软中断注册之后还要调用“触发”函数触发软中断,进而执行软中断中断服务函数,函数如下所示:

中断interrupt-controller节点:

void raise_softirq(unsigned int nr);

参数

  • nr:要触发的软中断

返回值

3.1.2. tasklet

tasklet是基于软中断实现,如果对效率没有特殊要求推荐是用tasklet实现中断分层。为什么这么说, 根据之前讲解软中断的中断服务函数是一个全局的数组,在多CPU系统中,所有CPU都可以访问, 所以在多CPU系统中需要用户自己考虑并发、可重入等问题,增加编程负担。 软中断资源非常有限一些软中断是为特定的外设准备的(不是说只能用于特定外设)例如“NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,” 从名字可以看出它们用于网络的TX和RX。像网络这种对效率要求较高的场合还是会使用软中断实现中断分层的。

相比软中断,tasklet使用起来更简单,最重要的一点是在多CPU系统中同一时间只有一个CPU运行tasklet, 所以并发、可重入问题就变得很容易处理(一个tasklet甚至不用去考虑)。而且使用时也比较简单,介绍如下。

tasklet_struct结构体

在驱动中使用tasklet_struct结构体表示一个tasklet,结构体定义如下所示:

触发软中断:

struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

参数介绍如下:

  • next:指向链表的下一个tasklet_struct,这个参数我们不需要自己去配置。

  • state:保存tasklet状态,等于0表示tasklet还没有被调度,等于TASKLET_STATE_SCHED表示tasklet被调度正准备运行。 等于TASKLET_STATE_RUN表示正在运行。

  • count:引用计数器,如果为0表示tasklet可用否则表示tasklet被禁止。

  • func:指定tasklet处理函数

  • data:指定tasklet处理函数的参数。

tasklet初始化函数:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
{t->next = NULL;t->state = 0;atomic_set(&t->count, 0);t->func = func;t->data = data;
}

参数

  • t:指定要初始化的tasklet_struct结构体

  • func:指定tasklet处理函数,等同于中断中的中断服务函数

  • data:指定tasklet处理函数的参数。函数实现就是根据设置的参数填充tasklet_struct结构体结构体。

返回值

和软中断一样,需要一个触发函数触发tasklet,函数定义如下所示:

tasklet触发函数:

static inline void tasklet_schedule(struct tasklet_struct *t)
{if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))__tasklet_schedule(t);
}

3.1.3. tasklet实现中断分层实验

实验在按键中断程序基础上完成,按键中断原本不需要使用中断分层,这里只是以它为例简单介绍tasklet的具体使用方法。 tasklet使用非常简单,主要包括定义tasklet结构体、初始化定义的tasklet结构体、实现tasklet中断处理函数、触发tasklet中断。

下面结合源码介绍如下。注意,源码是在“按键中断程序”基础上添加tasklet相关代码,这里只列出了tasklet相关代码。

tasklet相关代码 (位于linux_driver/button_interrupt/interrupt_tasklet/interrupt.c):

/*--------------第一部分--------------- */
struct tasklet_struct button_tasklet;  //定义全局tasklet_struct类型结构体/*--------------第二部分-----------------*/
void button_tasklet_hander(unsigned long data)
{int counter = 1;mdelay(200);printk(KERN_ERR "button_tasklet_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "button_tasklet_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "button_tasklet_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "button_tasklet_hander counter = %d \n", counter++);mdelay(200);printk(KERN_ERR "button_tasklet_hander counter = %d \n", counter++);
}/*--------------第三部分-----------------*/
static int button_open(struct inode *inode, struct file *filp)
{/*----------------以上代码省略----------------*//*初始化button_tasklet*/tasklet_init(&button_tasklet,button_tasklet_hander,0);/*申请之后已经开启了,切记不要再次打开,否则运行时报错*/// // enable_irq(interrupt_number);return 0;
}/*--------------第四部分-----------------*/
static irqreturn_t button_irq_hander(int irq, void *dev_id)
{printk(KERN_ERR "button_irq_hander----------inter");/*按键状态加一*/atomic_inc(&button_status);tasklet_schedule(&button_tasklet);printk(KERN_ERR "button_irq_hander-----------exit");return IRQ_RETVAL(IRQ_HANDLED)

结合代码各部分介绍如下:

  • 第2行:定义tasklet_struct类型结构体。

  • 第5-18行:定义tasklet的“中断服务函数”可以看到我们在tasklet的中断服务函数中使用延时 和printk语句模拟一个耗时的操作。

  • 第21-31行:在原来的代码基础上调用tasklet_init函数初始化tasklet_struct类型结构体。

  • 第40行:在中断服务函数中调用tasklet_schedule函数触发tasklet中断。 在按键中断服务函数中的开始处和结束处添加打印语句,正常情况下程序会先执行按键中断的中短发服务函数, 退出中断服务函数后再执行中断的下半部分,既tasklet的“中断服务函数”。

3.1.4. 下载验证

本实验在在按键中断驱动程序基础上修改,实验方法与按键中断程序相同,测试应用程序以及设备树插件不用修改。

将修改后的驱动程序编译、下载到开发板,加载驱动然后运行测试应用程序如下所示。

3.2. 工作队列

与软中断和tasklet不同,工作队列运行在内核线程,允许被重新调度和睡眠。 如果中断的下部分能够接受被重新调度和睡眠,推荐使用工作队列。

和tasklet类似,从使用角度讲主要包括定义工作结构体、初始化工作、触发工作。

3.2.1. 工作结构体

“工作队列”是一个“队列”,但是对于用户来说不必关心“队列”以及队列工作的内核线程,这些内容由内核帮我们完成, 我们只需要定义一个具体的工作、初始化工作即可,在驱动中一个工作结构体代表一个工作,工作结构体如下所示:

work_struct结构体:

struct work_struct {atomic_long_t data;struct list_head entry;work_func_t func;
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};

重点关心参数“work_func_t func;”该参数用于指定“工作”的处理函数。

work_func_t如下所示:

void (*work_func_t)(struct work_struct *work);

3.2.2. 工作初始化函数

内核提初始化宏定义如下所示。

#define INIT_WORK(_work, _func)

 该红顶共有两个参数,_work用于指定要初始化的工作结构体,_func用于指定工作的处理函数。

3.2.3. 启动工作函数

驱动工作函数执行后相应内核线程将会执行工作结构体指定的处理函数,驱动函数如下所示。

static inline bool schedule_work(struct work_struct *work)
{return queue_work(system_wq, work);
}

 启动工作函数只有一个工作结构体参数。

3.2.4. 工作队列实验

工作队列实验同样在按键中断程序基础上实现,这里只列出了工作队列相关代码, 完整内容请参考本小节配套驱动程序。(这里只修改驱动程序,其他内容保持不变)

工作队列相关函数 (位于linux_driver/button_interrupt/interrupt_work/interrupt.c):

/*--------------第一部分-----------------*/
struct work_struct button_work;/*--------------第二部分-----------------*/
void work_hander(struct work_struct  *work)
{int counter = 1;mdelay(200);printk(KERN_ERR "work_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "work_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "work_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "work_hander counter = %d  \n", counter++);mdelay(200);printk(KERN_ERR "work_hander counter = %d  \n", counter++);
}/*--------------第三部分-----------------*/
static int button_open(struct inode *inode, struct file *filp)
{/*----------------以上代码省略----------------*//*初始化button_work*/INIT_WORK(&button_work, work_hander);return 0;
}/*--------------第四部分-----------------*/
static irqreturn_t button_irq_hander(int irq, void *dev_id)
{/*按键状态加一*/atomic_inc(&button_status);schedule_work(&button_work);return IRQ_HANDLED;
}
  • 第2行:定义work_struct类型结构体。

  • 第5-18行:定义工作队列中的“中断服务函数”,使用延时和printk语句模拟一个耗时的操作。

  • 第21-27行:在原来的代码基础上调用INIT_WORK宏初始化work_struct类型结构体与中断下半部分函数。

  • 第34行:在中断服务函数中调用schedule_work函数触发中断下半部。

与tasklet实现中断分层类似,使用方法几乎一样,这里不进行详细描述。

应用程序测试效果

 

核心区别总结

特性直接中断处理Tasklet工作队列
处理复杂任务不适合较适合(非阻塞、短任务)非常适合(支持阻塞、长任务)
上下文中断上下文软中断上下文进程上下文
是否允许睡眠
执行延迟最低较低较高
适用场景简单任务(计数、状态变更)简单非阻塞的延迟任务复杂、阻塞、长时间任务

 

推荐使用场景

  • 直接中断处理: 如果按键逻辑简单,直接用第一种方案即可。
  • Tasklet: 如果需要按键中断后执行少量非阻塞逻辑,例如短暂延迟打印,可以使用第二种方案。
  • 工作队列: 如果按键触发后需要执行较复杂的任务逻辑(如 I/O 操作、文件操作),建议采用第三种方案。

 

相关文章:

Linux驱动开发(12):中断子系统–按键中断实验

本章我们以按键为例讲解在驱动程序中如何使用中断&#xff0c; 在学习本章之前建议先回顾一下关于中断相关的裸机部分相关章节&#xff0c; 这里主要介绍在驱动中如何使用中断&#xff0c;对于中断的概念及GIC中断控制器相关内容不再进行讲解。 本章配套源码和设备树插件位于“…...

C语言(函数指针与指针函数)

函数指针 定义&#xff1a;函数指针本质上是指针&#xff0c;它是函数的指针&#xff08;定义了一个指针变量&#xff0c;变量中存储了函数的地 址&#xff09;。函数都有一个入口地址&#xff0c;所谓指向函数的指针&#xff0c;就是指向函数的入口地址。这里函数名就代 表入…...

中国计算机学会计算机视觉专委会携手合合信息举办企业交流活动,为AI安全治理打开“新思路”

近期&#xff0c;《咬文嚼字》杂志发布了2024年度十大流行语&#xff0c;“智能向善”位列其中&#xff0c;过去一年时间里&#xff0c;深度伪造、AI诈骗等话题屡次登上热搜&#xff0c;AI技术“野蛮生长”引发公众担忧。今年9月&#xff0c;全国网络安全标准化技术委员会发布了…...

MacOs 日常故障排除troubleshooting

1. 关闭开机自启动 app X macOs 15.1 System settings -> General -> Login Items & Extensions->Open at Login -> Select app X and click -...

ArcGIS字符串补零与去零

我们有时候需要 对属性表中字符串的补零与去零操作 我们下面直接视频教学 下面看视频教学 ArcGIS字符串去零与补零 推荐学习 ArcGIS全系列实战视频教程——9个单一课程组合 ArcGIS10.X入门实战视频教程&#xff08;GIS思维&#xff09; ArcGIS之模型构建器&#xff08;Mod…...

【FLASH、SRAM和DRAM、CISC和RISC、冯诺依曼和哈佛】单片机内存结构的了解

【FLASH、SRAM和DRAM、CISC和RISC、冯诺依曼和哈佛】单片机内存结构的了解 一、单片机概念 单片机&#xff1a;Single-Chip Microcomputer&#xff0c;单片微型计算机&#xff0c;是一种集成电路芯片 1.1RAM里的SRAM和DRAM SRAM&#xff08;Static Random Access Memory&…...

ionic capacitor JSValueEncodingContainer报错

try to clean the build folder. exit Xcode. upgrade your capacitor core libraries update cocoapods to 1.13.0 do “pod install --repo-udpdate” after that: ionic build --prodnpx cap updatenpx cap syncnpx cap open ios capacitor ios最低版本要求13 [Bug]:…...

pdf merge

在 Ubuntu 22.04 上&#xff0c;你可以使用以下命令行工具来合并多个 PDF 文件&#xff1a; 1. pdftk pdftk 是一个强大的 PDF 工具&#xff0c;支持合并、拆分和其他操作。安装和使用方法如下&#xff1a; sudo apt install pdftk pdftk file1.pdf file2.pdf cat output me…...

【Trouble Shooting】Oracle ADG hung,出现ORA-04021

异常问题&#xff1a; 突然收到告警&#xff0c;ADG实例状态异常。 环境&#xff1a; 版本&#xff1a;Oracle 11.2.0.4.201020 状态&#xff1a;Active Dataguard 问题&#xff1a; 查看Oracle实例alert日志&#xff0c;发现有异常报错&#xff1a; Thu Dec 12 22:15:23 …...

奇怪的知识又增加了:ESP32下的Lisp编程=>ULisp--Lisp for microcontrollers

ESP32下有MicroPython&#xff0c;那么我就在想&#xff0c;有Lisp语言支持吗&#xff1f;答案是果然有&#xff01;有ULisp&#xff0c;专门为MCU设计的Lisp&#xff01; 网址&#xff1a;uLisp - Lisp for microcontrollers 介绍&#xff1a;用于微控制器的 Lisp 适用于 Ar…...

什么是CRM系统?CRM系统的功能、操作流程、生命周期

CRM系统作为企业管理和维护客户关系的重要工具&#xff0c;在商业活动中扮演着越来越重要的角色。今天&#xff0c;就让我们一起揭开它的神秘面纱&#xff0c;看看这个“幕后英雄”到底是怎么工作的。 什么是CRM系统&#xff1f; 首先&#xff0c;我们要了解什么是CRM。简单来…...

[每日一练]转换日期格式

#该题目来源于力扣&#xff1a; 1853. 转换日期格式 - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 表: Days------------------- | Column Name | Type | ------------------- | day | date | ------------------- day 是这个表的主键。给定一个Da…...

LSM Tree 底层设计理念

场景&#xff1a;设计一个海量读写的的kv数据库&#xff0c;优先保证写入速度&#xff0c;但是读取速度也不能很慢 因为海量数据存储&#xff0c;不能使用内存&#xff0c;得存到文件里。 Q&#xff1a;对已经落盘的文件&#xff0c;怎么根据key修改value A&#xff1a;读取文件…...

面向对象设计规则和各类设计模式

面向对象设计&#xff08;Object-Oriented Design, OOD&#xff09;是一种软件设计方法论&#xff0c;它使用对象、类、继承、封装、多态等概念来组织代码。面向对象设计的核心目标是提高软件的可维护性、可扩展性和复用性。在面向对象设计中&#xff0c;遵循一定的设计原则和模…...

Artec Leo3D扫描仪在重型机械设备定制中的应用【沪敖3D】

挑战&#xff1a;一家加拿大制造商需要有效的方法&#xff0c;为富于变化且难度较高的逆向工程&#xff0c;快速、安全、准确地完成重型机械几何采集。 解决方案&#xff1a;Artec Leo, Artec Studio, Geomagic for SOLIDWORKS 效果&#xff1a;Artec Leo三维扫描代替过去的手动…...

Linux下socket广播通讯的实现

概念大家都很清楚&#xff0c;不赘述。 广播必然用UDP这套东西。 setsockopt() 函数及其在广播中的应用&#xff1a; 在 C 网络编程中&#xff0c;setsockopt() 函数用于设置套接字选项&#xff0c;这些选项可以控制套接字的各种行为。对于广播通信&#xff0c;我们特别关心…...

Tiptap,: 富文本编辑器入门与案例分析

Tiptap 是一个现代的富文本编辑器&#xff0c;基于 ProseMirror 打造&#xff0c;旨在提供一个灵活且功能强大的文本编辑解决方案。它具有开箱即用的能力&#xff0c;同时也允许开发者根据业务需求进行高度定制化扩展。与传统的富文本编辑器相比&#xff0c;Tiptap 提供了更精细…...

数智读书笔记系列002 埃隆·马斯克传

书名&#xff1a;埃隆马斯克传 作者&#xff1a;【美】沃尔特艾萨克森 译者&#xff1a;孙思远&#xff1b;刘家琦 出版社&#xff1a;中信出版集团 出版时间&#xff1a;2023年9月 ISBN&#xff1a;9787521758399 这本书是关于特斯拉CEO埃隆马斯克的传记&#xff0c;作者…...

linux环境一句话后门

原文地址&#xff1a;linux环境一句话后门 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 注意&#xff1a;本文章只做网络安全技术交流使用&#xff0c;切莫用来做坏事。 也可以叫一句话木马&#xff0c;一个意思。 设置监听 回连端口可以…...

django——admin后台管理1

一、admin后台管理 访问url进入&#xff1a; http://127.0.0.1:8000/admin ​ 创建超级管理用户 终端输入以下命令&#xff1a; python manage.py createsuperuser (py36_pingping) E:\django学习\day03-django入门\demo>python manage.py createsuperuser Username: mo…...

QT图形/视图架构详解(一)

场景、视图与图形项 图形/视图架构主要由 3 个部分组成&#xff0c;即场景、视图和图形项&#xff0c;三者的关系如图所示&#xff1a; 场景、视图和图形项的关系 场景&#xff08;QGraphicsScene 类&#xff09; 场景不是界面组件&#xff0c;它是不可见的。场景是一个抽象的…...

h5 区分ios和安卓

h5 区分ios和安卓 const systemInfo uni.getSystemInfoSync(); if (systemInfo.platform "ios" || systemInfo.platform "android") {}h5 区分微信小程序与app用条件编译条件编译 js #ifdef MP-WEIXIN #endif...

爬虫基础知识点

最近看了看爬虫相关知识点&#xff0c;做了记录&#xff0c;具体代码放到了仓库&#xff0c;本文仅学习使用&#xff0c;如有违规请联系博主删除。 这个流程图是我使用在线AI工具infography生成的&#xff0c;这个网站可以根据url或者文本等数据自动生成流程图&#xff0c;挺…...

golang 实现简单redis服务3(实现多类型数据结构支持)

redis各种数据类型的工作原理stringlisthashset(集合)zset(有序集合)(思考1):为什么redis使用跳跃表而不是红黑树?(思考2): 都可以范围取值,为什么mysql使用b树不用跳跃表,为什么redis使用跳跃表不用b树? 之前的redis只实现了基本数据string类型的操作,那能不能实现多种数据类…...

【硬件测试】基于FPGA的4ASK调制解调通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.Verilog核心程序 4.开发板使用说明和如何移植不同的开发板 5.完整算法代码文件获得 1.算法仿真效果 本文是之前写的文章: 《基于FPGA的4ASK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR》 的…...

配置mysqld(读取选项内容,基本配置),数据目录(配置的必要性,目录下的内容,具体文件介绍,修改配置)

目录 配置mysqld 读取选项内容 介绍 启动脚本 基本配置 内容 端口号 数据目录的路径 配置的必要性 配置路径 mysql数据目录 具体文件 修改配置时 权限问题 配置mysqld 读取选项内容 介绍 会从[mysqld] / [server] 节点中读取选项内容 优先读取[server] 虽然服务…...

【roadMap】我转行软件测试的经历

软件测试这行咋样&#xff1f; 如果你简单了解过「软件测试工程师」这个岗位&#xff0c;就会知道它的基本特点&#xff1a; 待遇比开发低&#xff0c;比其他行业高入门丝滑&#xff0c;算是技术岗最简单的一类测试行业有细分领域&#xff1a;功能、性能、自动化… 每个行业…...

回归任务与分类任务应用及评价指标

能源系统中的回归任务与分类任务应用及评价指标 一、回归任务应用1.1 能源系统中的回归任务应用1.1.1 能源消耗预测1.1.2 负荷预测1.1.3 电池健康状态估计&#xff08;SOH预测&#xff09;1.1.4 太阳能发电量预测1.1.5 风能发电量预测 1.2 回归任务中的评价指标1.2.1 RMSE&…...

半导体制造全流程

半导体制造是一个极其复杂且精密的过程&#xff0c;主要涉及将硅片加工成功能强大的芯片。以下是半导体制造的全流程概述&#xff1a; 1. 硅材料制备 硅提纯&#xff1a; 使用冶金级硅&#xff0c;进一步提纯为高纯度硅&#xff08;电子级硅&#xff09;&#xff0c;纯度可达 …...

Mac m2电脑上安装单机Hadoop(伪集群)

1. 引言 本教程旨在介绍在Mac 电脑上安装Hadoop 2. 前提条件 2.1 安装JDK Mac电脑上安装Hadoop&#xff0c;必须首先安装JDK&#xff0c;并配置环境变量&#xff08;此处不做详细描述&#xff09; 2.2 配置ssh环境 关闭防火墙 在Mac下配置ssh环境&#xff0c;防止后面启…...

React 第十六节 useCallback 使用详解注意事项

useCallback 概述 1、useCallback 是在React 中多次渲染缓存函数的 Hook&#xff0c;返回一个函数的 memoized的值&#xff1b; 2、如果多次传入的依赖项不变&#xff0c;那么多次定义的时候&#xff0c;返回的值是相同的,防止频繁触发更新&#xff1b; 3、多应用在 父组件为函…...

悬赏任务源码(悬赏发布web+APP+小程序)开发附源码

悬赏任务源码是指一个软件或网站的源代码&#xff0c;用于实现悬赏任务的功能。悬赏任务是指发布方提供一定的奖励&#xff0c;希望能够找到解决特定问题或完成特定任务的人。悬赏任务源码通常包括任务发布、任务接受、任务完成和奖励发放等功能的实现。搭建悬赏任务源码是一个…...

Collection接口

目录 一. Collection基本介绍 二. Collection中的方法及其使用 1. 添加元素 (1) 添加单个元素 (2) 添加另一集合中的所有元素 2. 删除元素 (1) 删除单个元素 (2) 删除某个集合中包含在其他集合中的元素 (3) 保留两个集合中的交集部分, 删除其他元素. 3. 遍历元素 (1) …...

电机驱动模块L9110S详解

电机驱动模块是一种用于控制和驱动电机的设备&#xff0c;它能够将控制信号转化为适合电机操作的电流和电压。通过电机驱动模块&#xff0c;可以实现对电机的速度、方向等参数进行精确控制。 今天我们要介绍的 L9110S 电机驱动适合大学生、工程师、个人DIY、电子爱好者们学习和…...

路由之间是怎么跳转的?有哪些方式?

1. React 路由跳转方式&#xff08;React Router&#xff09; 在 React 中&#xff0c;路由跳转通常使用 React Router 来管理。React Router 提供了不同的跳转方式。 <Link> 组件跳转 使用 <Link> 组件来进行路由跳转&#xff0c;它会渲染为一个 HTML <a> …...

AudioSegment 将音频分割为指定长度时间片段 - python 实现

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” -------------------------------------------------------------…...

双目摄像头标定方法

打开matlab 找到这个标定 将双目左右目拍的图像上传&#xff08;左右目最好不少于20张&#xff09; 等待即可 此时已经完成标定&#xff0c;左下角为反投影误差&#xff0c;右边为外参可视化 把这些误差大的删除即可。 点击导出 此时回到主页面&#xff0c;即可看到成功导出 Ca…...

题解 - 莱布尼茨三角形

题目描述 世界上著名的莱布尼茨三角形如图所示&#xff0c;请编程输出图中排在第n行从左边数第m个位置上的数。 输入 共一行&#xff0c;有二个整数N 和M(N<15)&#xff0c;两数间用空格隔开。 输出 共一行&#xff0c;有二个整数&#xff0c;两数间用“/”隔开&#xff…...

低代码开发:企业数智化转型中的关键角色与实践案例分析

引言 随着信息技术的飞速发展&#xff0c;企业数智化转型已成为提升竞争力和实现可持续发展的必经之路。在这一过程中&#xff0c;低代码开发平台作为一种新兴的开发模式&#xff0c;正逐渐成为企业数智化转型的关键角色。本文将探讨低代码开发在企业数智化转型中的作用&#x…...

【ubuntu24.04】PDFMathTranslate 本地PDF翻译GPU部署

https://huggingface.co/spaces/reycn/PDFMathTranslate-Docker排不上号官方都是要安装包,感觉可以本地试着源码部署一下, http://localhost:7860/官方是这个端口,但是我本地启动是:5000IDEA 里本地 backend启动效果 GUI 是监听7860的...

基于Spring Boot + Vue的摄影师分享交流社区的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…...

高空坠物识别

目录 一、回答二、其他参考 prompt 有没有识别高空坠物的github项目一、回答 根据您的请求&#xff0c;以下是一些在GitHub上可以找到的关于识别高空坠物的项目&#xff1a; 1、overthecity 这是一个监控高空抛物的云端服务项目&#xff0c;它利用Edison的计算能力对高空抛物…...

22. 正则表达式

一、概述 正则表达式&#xff08;regular expression&#xff09;又称 规则表达式&#xff0c;是一种文本模式&#xff08;pattern&#xff09;。正则表达式使用一个字符串来描述、匹配具有相同规格的字符串&#xff0c;通常被用来检索、替换那些符合某个模式&#xff08;规则&…...

阿里云数据库MongoDB版助力极致游戏高效开发

客户简介 成立于2010年的厦门极致互动网络技术股份有限公司&#xff08;以下简称“公司”或“极致游戏”&#xff09;&#xff0c;是一家集网络游戏产品研发与运营为一体的重点软件企业&#xff0c;公司专注于面向全球用户的网络游戏研发与运营。在整个产业链中&#xff0c;公…...

leetcode230.二叉搜索树中第k小的元素

标签&#xff1a;二叉搜索树&#xff1b;中序遍历 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1…...

深入了解C++中const的用法

文章目录 一、C中的const如何理解&#xff1f;二、C中的const与C语言中的const有何区别&#xff1f;三、const与指针、引用的结合使用 一、C中的const如何理解&#xff1f; 在C中&#xff0c;const是一个关键字&#xff0c;用来表示常量性&#xff0c;意在告诉编译器某些变量或…...

adb连接逍遥安卓模拟器失败的问题解决方案

1、逍遥安卓模拟器进入系统应用&#xff0c;设置-关于平板电脑-版本号&#xff0c;连续点击3次以上&#xff0c;直到提示进入开发者模式&#xff0c;返回设置界面&#xff0c;进入【开发者选项】-【USB调试】开启&#xff0c;之后重启模拟器再次adb尝试连接。 2、android stud…...

【Go基础】Go算法常用函数整理

Go算法常用函数整理 使用 Go 语言编写算法题时&#xff0c;掌握一些常用的函数和用法可以大大提高效率。 1. 排序 (slices 包)&#xff1a; slices.Sort(x []T)&#xff1a; 对切片 x 进行升序排序。需要 Go 1.18 版本。T 需要实现 constraints.Ordered 接口&#xff0c;例如…...

【rust杂乱笔记】

code . 打开vscode fn main() {println!("hello world!") }loop{}循环; break跳出循环 // 引入三方库 use rand::Rng; // 引入标准库中的输入输出 use std::cmp::Ordering; use std::io;// main函数 先执行main函数 fn main() {// 打印的宏方法// 打印提示信息print…...

BFS算法题

目录 1.BFS 2.树里的宽搜 题目一——429. N 叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 题目二——103. 二叉树的锯齿形层序遍历 - 力扣&#xff08;LeetCode&#xff09; 题目三——662. 二叉树最大宽度 - 力扣&#xff08;LeetCode&#xff09; 题目四——…...