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

嵌入式驱动开发详解7(并发、竞争、中断)

文章目录

  • 前言
  • 并发和竞争
    • 原子操作
    • 自旋锁
    • 信号量
    • 互斥体
  • 中断
    • 中断简介
    • 中断API
    • 上半部和下半部
    • 设备树分析
    • 中断号获取
    • 源码
  • 后续
  • 参考文献

前言

中断会引起线程的切换,并发和竞争也是对线程切换的一种灵活保护和处理,因此这里将中断和并发与竞争放在一块讲解说明。

并发和竞争

为保护共享资源,防止进行并发访问,Linux 内核提供的几种并发和竞争的处理方法,以下是对这几种处理方法的详细分析

原子操作

起因:当运行如下图的两个线程时,正常情况是线程 A 将 ro 地址的变量设置为 10,线程 B 将将 ro 地址的变量设置为 20。
在这里插入图片描述
但是实际上的执行流程可能如下图所示,要解决这个问题就要保证的三行汇编指令作为一个整体运行,也就是作为一个原子存在。
在这里插入图片描述

常用API函数
在这里插入图片描述
在这里插入图片描述
使用方法非常简单,如下所示:

atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */ 
atomic_set(&v, 10); /* 设置 v=10 */ 
atomic_read(&v); /* 读取 v 的值,肯定是 10 */ 
atomic_inc(&v); /* v 的值加 1,v=11 */

适用场景:两组原子操作 API 函数,一组是对整形变量进行操作的,一组是对位进行操作。
弊端:仅仅用于对变量的保护。

自旋锁

起因:原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形 变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于 结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的 线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,在 Linux 内核中就是自旋锁。
原理:当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有, 只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。对于自旋锁而言,如果自旋锁正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态,线程 B 不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待锁可用。
API函数
在这里插入图片描述除了上表第一个方法外,也可以直接用下面这个方式定义:

spinlock_t lock; //定义自旋锁

死锁现象:如下图所示,线程 A 先运行,并且获取到了 lock 这个锁,当线程 A 运行 functionA 函 数的时候中断发生了,中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁, 但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。但是在中断服务函数执行完之前,线程 A 是不可能执行的,线程 A 说“你先放手”,中断说“你先放手”,场面就这么僵持着, 死锁发生!最好的解决方法就是获取锁之前关闭本地中断,Linux 内核提供了相应的 API 函数,如下表所示。
在这里插入图片描述在这里插入图片描述
内核很庞大,运行也是“千变万化”,我们是很难确定某个时刻的中断状态,因此不推荐使用 spin_lock_irq/spin_unlock_irq。建议使用 spin_lock_irqsave/ spin_unlock_irqrestore,因为这一组函 数会保存中断状态,在释放锁的时候会恢复中断状态。一般在线程中使用 spin_lock_irqsave/ spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock
其它衍生锁:在自旋锁的基础上衍生出来了读写自旋锁,顺序锁等。
适用场景:自旋锁适合用于短时期的轻量级加锁
缺点:1、自旋锁的线程会一直处于自旋状态;2、临界区比较大,运行时间比较长的话要选择其他的并发处理方式,比如信号量和互斥体;3、自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,不能递归申请自旋锁,否则可能导致死锁。

信号量

起因自选锁在获取锁时会将本线程一直处于忙碌状态,直到锁被释放,相比于自旋锁,信号量可以使该线程进入休眠状态,当信号量被释放时就会提醒该线程退出休眠态执行接下来的程序。
工作原理:信号量有一个信号量值,相当于一个房子有 10 把钥匙,这 10 把钥匙就相当于信号量值为 10。因此,可以通过信号量来控制访问共享资源的访问数量,如果要想进房间,那就要先获取 一把钥匙,信号量值减 1,直到 10 把钥匙都被拿走,信号量值为 0,这个时候就不允许任何人 进入房间了,因为没钥匙了。如果有人从房间出来,那他要归还他所持有的那把钥匙,信号量 值加 1,此时有 1 把钥匙了,那么可以允许进去一个人。
注意事项:通过信号量控制访问资源的线程数,在初始化的时候将信号量值设置得大于 1,那么这个信号量就是计数型信号量,计数型信号量不能用于互斥访问,因为它允许多个线程同时访问共享资源。如果要互斥的访问共享资源那么信号量的值就不能大于 1,此时的信号量就是一个二值信号量。
API函数:在选择API函数时要注意区分是否会进入休眠态,具体函数如下:
在这里插入图片描述
使用方法:Linux 内核使用 semaphore 结构体表示信号量,因此使用时要先定义一个结构体变量或者上述的第一个API函数,具体方法如下所示

struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */ 
down(&sem); /* 申请信号量 */ 
/* 临界区 */ 
up(&sem); /* 释放信号量 */

适用场景:因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
缺点:信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠;如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。

互斥体

起因:虽然信号量的值设置为 1 就可以使用信号量进行互斥访问了,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体 mutex。
工作原理:工作原理类似信号量,只是信号量设置为1的一种特殊形式,比如 A 与 B、C 合租了一套房子,这个 房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕 所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着, 等 A 出来,这个时候就相当于自旋锁。B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉。
API函数:具体函数如下:
在这里插入图片描述
使用方法:Linux 内核使用 mutex 结构体表示互斥体,因此使用时要先定义一个结构体变量或者上述的第一个API函数,具体方法如下

struct mutex lock; /* 定义一个互斥体 */ 
mutex_init(&lock); /* 初始化互斥体 */ 
mutex_lock(&lock); /* 上锁 */ 
/* 临界区 */ 
mutex_unlock(&lock); /* 解锁 */

适用场景需要互斥访问的地方建议使用 mutex,比信号量设置为1更加专业,等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
缺点:mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁;一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。

中断

不管是裸机MCU开发还是 Linux 下的驱动开发,中断都是频繁使用的功能,接下来将讲解在linux驱动下中断的实现。

中断简介

中断控制器:GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,类似 Cortex-M 内核中的 NVIC。目前 GIC 有 4 个版本:V1~V4,V1 是最老的版本,已经被废弃了。V2~V4 目前正在大 量的使用。GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、Cortex-A9、Cortex-A15 等, V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。
在这里插入图片描述
左侧部分就是中断源,中间部分就是 GIC 控制器,最右侧就是中断控制器向处理器内核发送中断信息通过四个信号报给 ARM 内核。

  • 这四个信号的含义如下: VFIQ:虚拟快速 FIQ。 VIRQ:虚拟外部 IRQ。 FIQ:快速中断 IRQ。 IRQ:外部中断 IRQ。
  • 中断源分为分为三类:
    ①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最 常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口 中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
    ②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定 有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
    ③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
  • 为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是 中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。
    ID0 ~ ID15:这 16 个 ID 分配给 SGI。
    ID16 ~ ID31:这 16 个 ID 分配给 PPI。
    ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断。

中断流程:①、使能中断,初始化相应的寄存器。 ②、注册中断服务函数,也就是向 irqTable 数组的指定标号处写入中断服务函数 ②、中断发生以后进入 IRQ 中断服务函数,在 IRQ 中断服务函数在数组 irqTable 里面查找 具体的中断处理函数,找到以后执行相应的中断处理函数。

中断API

关于中断的API函数这里只做简要说明,关于具体的形参返回值等信息用的时候再去百度学习更方便更详细。

  • request_irq 函数用于申请中断
  • free_irq 函数释放 掉相应的中断
  • irqreturn_t (*irq_handler_t) (int, void *),使用 request_irq 函数申请中断的时候需要设置中断处理函数,此函数是中断处理函数格式
  • 还有一些中断的使能和禁止函数

上半部和下半部

原理:中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理 函数的执行时间。比如电容触摸屏通过中断通知 SOC 有触摸事件发生,SOC 响应中断,然后 通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有 400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。我们可以将通过 IIC 读取触摸数据 的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理 过程就分为了两部分:
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可 以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部 去执行,这样中断处理函数就会快进快出。

关于什么放在上半部什么放在下半部有一定的讲究,以下是一些参考办法:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

上半部处理很简单,直接编写中断处理函数就行了,Linux 核提供了多种下半部机制:软中断、tasklet、工作队列。
软中断

  • 先使用 open_softirq 函数注册对应的软中断处理函数,nr:要开启的软中断,在 kernel/softirq.c 文件中一共定义了 10 个软中断,action:软中断对应的处理函数。
    void open_softirq(int nr, void (*action)(struct softirq_action *))
    
  • 定义好之后只需要通过 raise_softirq 函数触发中断即可进入处理函数。
    void raise_softirq(unsigned int nr)
    

tasklet:tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用 tasklet_struct 结构体来表示 tasklet:

struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};
  • func 函数就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数,
  • 如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet(也可以使用宏 DECLARE_TASKLET 来一次性完成 tasklet 的定义和初始化)
  • 在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行。
/* 定义 taselet */ 
struct tasklet_struct testtasklet; 
/* tasklet 处理函数 */ 
void testtasklet_func(unsigned long data) { /* tasklet 具体处理内容 */ 
} 
/* 中断处理函数 */ 
irqreturn_t test_handler(int irq, void *dev_id) { ...... /* 调度 tasklet */ tasklet_schedule(&testtasklet); ...... 
} 
/* 驱动入口函数 */ 
static int __init xxxx_init(void) 
{ ...... /* 初始化 tasklet */tasklet_init(&testtasklet, testtasklet_func, data); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);...... 
}

工作队列:工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的 工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet
Linux 内核使用 work_struct 结构体表示一个工作,这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示。Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用 worker 结构体表示工作者线程,每个 worker 都有一个工作队列,工作者线程处理自己工 作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作 队列和工作者线程我们基本不用去管。

  • 定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作(也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化)
  • 和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work。
/* 定义工作(work) */ 
struct work_struct testwork; 
/* work 处理函数 */
void testwork_func_t(struct work_struct *work); { /* work 具体处理内容 */ 
} 
/* 中断处理函数 */ 
irqreturn_t test_handler(int irq, void *dev_id) { ...... /* 调度 work */ schedule_work(&testwork); ...... 
} 
/* 驱动入口函数 */ 
static int __init xxxx_init(void) { ...... /* 初始化 work */ INIT_WORK(&testwork, testwork_func_t); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... 
}

设备树分析

如果使用设备树的话就需要在设备树中设置好中断属性信息,Linux 内核通过读取设备树 中的中断属性信息来配置中断。对于中断控制器而言,设备树绑定信息参考文档 Documentation/devicetree/bindings/arm/gic.txt。中断涉及到设备树的内容比较多,这里就详细分析一下,便于掌握设备树的相关知识。

中断控制器节点的设备树属性如下所示:

	intc: interrupt-controller@00a01000 {compatible = "arm,cortex-a7-gic";#interrupt-cells = <3>;interrupt-controller;reg = <0x00a01000 0x1000>,<0x00a02000 0x100>;};
  • compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7gic”即可找到 GIC 中断控制器驱动文件
  • #interrupt-cells 和#address-cells、#size-cells 一样。表示此中断控制器下设备的 cells 大小,对于设备而言,会使用 interrupts 属性描述中断信息,#interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的 GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:
    • 第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。
    • 第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0 ~ 987,对于 PPI 中断来说中断 号的范围为 0~15。
    • 第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候 表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中 断的 CPU 掩码。
  • interrupt-controller 节点为空,表示当前节点是中断控制器。

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如 gpio5 节点内容如下所示:

gpio5: gpio@020ac000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x020ac000 0x4000>;interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;
};
  • interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI, 触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,《IMX6ULL 参考手册》的“Chapter 3 Interrupts and DMA Events”章节,可以看出,GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对 应 GPIO5_IO00 ~ GPIO5_IO15 这低 16 个 IO,75 对应 GPIO5_IO16 ~ GPIOI5_IO31 这高 16 位 IO。
  • interrupt-controller 表明了 gpio5 节点也是个中断控制器
  • #interrupt-cells 修改为 2,因为在后面对gpio5的某一个引脚进行 interrupts 属性配置时,就只需要配置两个
    cells即可,下面以gpio1为例。

下面这个是 key 按键的设备树配置信息。

key{#address-cells = <1>;#size-cells = <1>;compatible = "hbb-key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;interrupt-parent = <&gpio1>;interrupts = <18 IRQ_TYPE_EDGE_BOTH>;status = "okay";
};
  • interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。
  • interrupts 设置中断信息,0 表示 GPIO5_IO00,8 表示低电平触发。

以上便是设备树对一个中断信息配置的全过程。

中断号获取

编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因 此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号

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

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号

int gpio_to_irq(unsigned int gpio)

通过上面两种方式即可获得中断号。

源码

源码大家可以参考参考文献github链接,都已经上传上去了, 其中实验11讲解了按键的具体中断实现,没有用下半部的概念去实现,但是用定时器进行了消抖。先初始化io口,然后配置中断获取中断号,并申请中断,当中断发生时进入中断服务函数,在中断服务函数中开启定时器,定时10ms后进入定时服务函数,读取按键是否按下的信息,最终在用户态通过read读取内核态的数据,内核态通过read函数里面的copy_to_user到用户态read函数的buf里面,用户态read函数的返回值reg即为内核态read函数的返回值。

后续

后续对原子操作,自旋锁,信号量,互斥体以及中断有了更深入一步的理解之后会继续更新。

参考文献

  1. 个人专栏系列文章
  2. 正点原子嵌入式驱动开发指南
  3. 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

相关文章:

嵌入式驱动开发详解7(并发、竞争、中断)

文章目录 前言并发和竞争原子操作自旋锁信号量互斥体 中断中断简介中断API上半部和下半部设备树分析中断号获取源码 后续参考文献 前言 中断会引起线程的切换&#xff0c;并发和竞争也是对线程切换的一种灵活保护和处理&#xff0c;因此这里将中断和并发与竞争放在一块讲解说明…...

@repository注解

Repository 是 Spring 框架中的一个注解&#xff0c;主要用于标记类作为数据访问层&#xff08;Data Access Layer, DAL&#xff09;的组件。在分层架构中&#xff0c;Repository 注解标识的类负责与数据库或其他持久化存储进行交互&#xff0c;如执行查询、保存或更新数据等操…...

Linux top指令

top指令概述 top 是 Linux 系统中用于实时监控系统性能和进程信息的命令&#xff0c;功能强大且灵活。它提供了系统资源的动态视图&#xff0c;包括 CPU、内存、运行中的进程等。 这个指令可以说是Linux中最基本的工具了&#xff0c;用来监视系统的实时运行状态&#xff0c;类…...

Anaconda 安装与虚拟环境创建完整指南

Anaconda 安装与虚拟环境创建完整指南 Anaconda 是目前最流行的 Python 和数据科学工具集之一&#xff0c;它不仅可以轻松管理 Python 包&#xff0c;还能提供强大的虚拟环境功能&#xff0c;避免项目之间的依赖冲突。如果你是机器学习、数据科学或计算机视觉的开发者&#xf…...

基于自然语言处理(NLP)的智能客服系统

基于自然语言处理&#xff08;NLP&#xff09;的智能客服系统是现代客户服务领域的一项重要技术&#xff0c;它通过模拟人类对话的方式&#xff0c;为用户提供及时、准确和个性化的服务。以下是关于基于NLP的智能客服系统的一些关键要素和功能&#xff1a; 1. 自然语言理解&am…...

C语言期末复习题(PTA)

去字符串中的重复字符与空格 dele函数的功能&#xff1a;对给定的字符串&#xff0c;去掉重复的字符与字符串中的空格后&#xff0c;按照字符ASCII码顺序从小到大排序后输出。 输入样例&#xff1a; abcd efg abc fH 3 2 1 输出样例&#xff1a; 123Habcdefg 测试程序样例&am…...

Ubuntu 中安装 RabbitMQ 教程

简介 RabbitMq作为一款消息队列产品&#xff0c;它由Erlang语言开发&#xff0c;实现AMQP&#xff08;高级消息队列协议&#xff09;的开源消息中间件。 应用场景 异步处理 场景说明&#xff1a;用户注册后&#xff0c;注册信息写入数据库&#xff0c;再发邮件、短信通知。 …...

如何测试模型推理精度:Python初学者指南

如何测试模型推理精度&#xff1a;Python初学者指南 什么是模型推理精度&#xff1f;使用工具包&#xff1a;lm-evaluation-harness安装工具包 测试模型推理精度的步骤1. 加载模型和分词器2. 使用 lm-evaluation-harness 进行测试3. 运行脚本4. 查看结果 总结 在机器学习和深度…...

人工智能与区块链的碰撞:双剑合璧的创新前景

引言 人工智能&#xff08;AI&#xff09;与区块链技术&#xff0c;这两项曾经各自独立发展的前沿科技&#xff0c;如今正逐步走向融合。人工智能通过强大的数据处理能力和智能决策能力&#xff0c;在各个领域掀起了革命性的变革&#xff1b;而区块链凭借其去中心化、不可篡改的…...

安装Visual studio2022后,没法新建.net 4.5.2的项目

在Visual Studio 2022中无法新建.NET Framework 4.5.2的项目&#xff0c;通常是因为Visual Studio 2022默认不再支持较旧的.NET Framework版本&#xff0c;如4.5或4.5.2。不过&#xff0c;你可以通过一些手动步骤来使Visual Studio 2022支持.NET Framework 4.5.2项目。以下是一…...

小程序配置文件 —— 12 全局配置 - pages配置

全局配置 - pages配置 在根目录下的 app.json 文件中有一个 pages 字段&#xff0c;这里我们介绍一下 pages 字段的具体用法&#xff1b; pages 字段&#xff1a;用来指定小程序由哪些页面组成&#xff0c;用来让小程序知道由哪些页面组成以及页面定义在哪个目录&#xff0c;…...

Flink Data Source详解

注意在高版本中SourceFunction以及其子类RichSourceFunction、ParallelSourceFunction等已经被标记为废弃,所以我们要看数据源的实现只需要关注Source接口(org.apache.flink.api.connector.source.Source)。了解Source背后的架构和运行原理有助于我们更好的使用Source,或者…...

Spring Boot 介绍与应用

什么是 Spring Boot&#xff1f; Spring Boot 是一个用于简化 Spring 应用程序开发和部署的框架&#xff0c;它建立在 Spring 框架的基础之上&#xff0c;但去除了繁琐的配置。Spring Boot 采用“约定优于配置”的原则&#xff0c;默认启用了大量自动配置&#xff0c;使得开发…...

并行编程实战——TBB框架的应用之五Supra中IGTL的应用

一、OpenIGTLink OpenIGTLink&#xff0c;可以简称为IGTL。它是一个专供医疗应用的网络通信库。IGTL可以应用各种传感器、手术机器人和成像仪等的数据传输。OpenIGTLink是有一系列的协议在底层支持的&#xff0c;它是跨平台的&#xff0c;应用也非常简单。 OpenIGTLink迭代还是…...

【Golang 面试题】每日 3 题(八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…...

11. 日常算法

1. 141. 环形链表 题目来源 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接…...

FPGA三模冗余TMR工具(二)

学术和商业领域有许多自动化的三模冗余TMR工具&#xff0c;本文介绍当前主流的基于寄存器传输级的三模冗余工具&#xff08;Register-Transfer Level&#xff0c;RTL&#xff09;&#xff0c;基于重要软核资源的三模冗余工具&#xff0c;以及新兴的基于高层次综合的三模冗余工具…...

springboot499基于javaweb的城乡居民基本医疗信息管理系统(论文+源码)_kaic

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…...

MF248:复制工作表形状到Word并调整多形状位置

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…...

微信流量主挑战:用户破16!新增文档转换(新纪元3)

朋友们&#xff0c;报告好消息&#xff01;我的小程序用户数量已经涨到16个了&#xff01;没错&#xff0c;真没拉朋友圈亲戚好友来撑场子&#xff0c;全靠实力&#xff08;和一点点运气&#xff09;吸引了16位陌生小伙伴光临&#xff01;这波进步&#xff0c;连我自己都感动了…...

NoSQL 之 Redis 配置与优化

目录 一、 前置知识点 1. 关系数据库与非关系型数据库 &#xff08;1&#xff09;关系型数据库 &#xff08;2&#xff09;非关系型数据库 &#xff08;3&#xff09;非关系型数据库产生背景 &#xff08;4&#xff09;两者对比 2. Redis 基础 &#xff08;1&#xff0…...

echarts柱状图实现动态展示时报错

echarts柱状图实现动态展示时报错 1、问题&#xff1a; 在使用Echarts柱状图时&#xff0c;当数据量过多&#xff0c;x轴展示不下的时候&#xff0c;可以使用dataZoom实现动态展示。如下图所示&#xff1a; 但是当鼠标放在图上面滚动滚轮时或拖动滚动条时会报错&#xff0c;…...

OpenCV CUDA模块图像处理------图像融合函数blendLinear()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数执行 线性融合&#xff08;加权平均&#xff09; 两个图像 img1 和 img2&#xff0c;使用对应的权重图 weights1 和 weights2。 融合公式…...

art-pi2 上手记录(二)

功能比较庞杂&#xff0c;写得不好&#xff0c;抛砖引玉 预备知识 stm32 默认从主闪存0x08000000启动 art-pi2的psram 映射0x90000000 art-pi2的8线ospi flash 映射0x70000000 stm32h7比较灵活&#xff0c;通过修改选项字节&#xff0c;可以实现从 0x0000 0000 到 0x3FFF 0…...

K8s基础一

Kubernetes 架构 Kubernetes 背后的架构概念。 Kubernetes 集群由一个控制平面和一组用于运行容器化应用的工作机器组成&#xff0c; 这些工作机器称作节点&#xff08;Node&#xff09;。每个集群至少需要一个工作节点来运行 Pod。 工作节点托管着组成应用负载的 Pod。控制平…...

Vue 3 弹出式计算器组件(源码 + 教程)

&#x1f9ee; Vue 3 弹出式计算器组件&#xff08;源码 教程&#xff09; &#x1f4cc; 建议收藏 点赞 关注&#xff0c;本组件支持加减乘除、双向绑定、计算过程展示&#xff0c;适用于表单辅助输入场景。 &#x1f527; 一、完整源码&#xff08;复制即用&#xff09; …...

文件上传漏洞深度解析:检测与绕过技术矩阵

文件上传漏洞深度解析&#xff1a;检测与绕过技术矩阵 引言&#xff1a;无处不在的文件上传风险 在当今的Web应用生态系统中&#xff0c;文件上传功能几乎无处不在。从社交媒体分享图片到企业文档管理系统&#xff0c;用户上传文件已成为现代Web应用的核心功能之一。然而&…...

固态继电器与驱动隔离器:电力系统的守护者

在电力系统中&#xff0c; 固态继电器合驱动隔离器像两位“电力守护神”&#xff0c;默默地确保电力设备的安全与稳定运行。它们通过高效、可靠的性能&#xff0c;保障了电力设备在各种环境下的正常工作。 固态继电器是电力控制中的关键组成部分&#xff0c;利用半导体器件来实…...

React源码阅读-fiber核心构建原理

React源码阅读(2)-fiber核心构建原理 好的&#xff0c;我明白了。您提供的文本主要介绍了 React 源码中 Fiber 核心的构建原理&#xff0c;涵盖了从执行上下文到构建、提交、调度等关键阶段&#xff0c;以及相关的代码实现。 您提出的关联问题也很重要&#xff0c;它们深入探讨…...

【多线程初阶】阻塞队列 生产者消费者模型

文章目录 一、阻塞队列二、生产者消费者模型(一)概念(二)生产者消费者的两个重要优势(阻塞队列的运用)1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)2) 削峰填谷 (三)生产者消费者模型付出的代价 三、标准库中的阻塞队列(一)观察模型的运行效果(二)观察阻塞效果1) 队…...