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

17 Linux 中断

一、Linux 中断简介

1. Linux 中断 API 函数

① 中断号

  每个中断都有一个中断号,通过中断号可以区分出不同的中断。在 Linux 内核中使用一个 int 变量表示中断号。

② request_irq 函数

  在 Linux 中想要使用某个中断是需要申请的,request_irq 函数就是用来申请中断的,并且 request_irq 函数会激活(使能)中断,但 request_irq 函数会导致睡眠,所以不能在中断上下文或者其他禁止睡眠的代码段中使用  request_irq 函数。

/** @description : 申请内核中断,并使能中断函数* @param - irq : 要申请中断的中断号* @param - handler : 中断处理函数,当中断发生以后就会执行此中断处理函数* @param - flags : 中断标志* @param - name : 中断名字* @param - dev : 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断。
一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数* @return : 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了*/
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)
中断标志描述
IRQF_SHARED多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志。
IRQF_ONESHOT单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE无触发。
IRQF_TRIGGER_RISING上升沿触发。
IRQF_TRIGGER_FALLING下降沿触发。
IRQF_TRIGGER_HIGH高电平触发。
IRQF_TRIGGER_LOW低电平触发。

③ free_irq 函数 

  使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 

/** @description : 释放中断* @param - irq : 要释放的中断* @param - dev : 如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉* @return : 无*/
void free_irq(unsigned int irq, void *dev);

④ 中断处理函数 

  使用 request_irq 函数申请中断的时候,需要设置中断处理函数:

irqreturn_t (*irq_handler_t) (int, void *);/*第一个参数是要中断处理函数相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。*//* irqreturn_t 结构体 */
enum irqreturn 
{IRQ_NONE = (0 << 0),IRQ_HANDLED = (1 << 0),IRQ_WAKE_THREAD = (1 << 1),
};typedef enum irqreturn irqreturn_t;/* 其实一般中断服务函数返回值使用:*/
return IRQ_RETVAL(IRQ_HANDLED)

⑤ 中断使能与禁止函数 

void enable_irq(unsigned int irq);        // 使能指定中断
void disable_irq(unsigned int irq);       // 禁止指定中断// 其实他们的参数 irq 都是要使能/禁止的中断号
// disable_irq 函数有个缺点是,使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出

  但我们不能确保的情况下,使用这个中断禁止函数(推荐使用):

// 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。
void disable_irq_nosync(unsigned int irq);

  当我们需要关闭当前处理器整个中断系统的时候,使用以下函数:

local_irq_enable();        // 使能当前处理器的中断系统
local_irq_disable();       // 禁止当前处理器的中断系统

  这里也有一个缺点是,如果在中断禁止的时候使能中断,这时候可能会任务崩溃,所以使用(推荐使用):

local_irq_save(flags);        // 禁止中断,并且将中断状态保存在 flags 中
local_irq_restore(flags);     // 恢复中断,将中断到 flags 状态

2. 上半部与下半部 

  上半部: 中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。

  下半部: 如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。 

   Linux 内核将中断分为上半部和下半部的目的就是为了实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行。

  关于哪些工作放上半部,哪些放下半部,可以参考:

  ① 如果要处理的内容不希望被其他中断打扰,放在上半部;

  ② 如果要处理的任务对时间敏感,放到上半部;

  ③ 如果要处理的任务与硬件有关,放到上半部;

  ④ 除了以上三点,其他任务都可以放到下半部。

   上半部其实就是编写中断处理函数,下半部 Linux 提供了许多机制:

① 软中断

  软中断常用于处理需要及时响应的事件,优先级较高的任务。它可以根据优先级和中断处理队列的情况来确定哪个软中断被优先处理。

  Linux 内核使用 softirq_action 结构体表示软中断,并且在 kernel/softirq.c 文件中一共定义了 10 个软中断:

static struct softirq_action softirq_vec[NR_SOFTIRQS];
// NR_SOFTIRQS 是枚举类型,定义如下:
enum
{HI_SOFTIRQ=0, /* 高优先级软中断 */TIMER_SOFTIRQ, /* 定时器软中断 */NET_TX_SOFTIRQ, /* 网络数据发送软中断 */NET_RX_SOFTIRQ, /* 网络数据接收软中断 */BLOCK_SOFTIRQ,IRQ_POLL_SOFTIRQ,TASKLET_SOFTIRQ, /* tasklet 软中断 */SCHED_SOFTIRQ, /* 调度软中断 */HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */RCU_SOFTIRQ, /* RCU 软中断 */NR_SOFTIRQS
};

   10 个软中断,所以 NR_SOFTIRQS 元素有 10 个。

  softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组。

  如果要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数:

/** @description : 注册软中断处理函数* @param - nr : 要开启的软中断,选择 NR_SOFTIRQS 其中一个元素* @param - action : 软中断对应的处理函数* @return : 没有返回值*/
void open_softirq(int nr, void (*action)(struct softirq_action *));

   但是软中断必须必须在编译的时候静态注册(在编译时期将组件与系统进行绑定的配置方式)。内核使用 softirq_init 函数进行初始化软中断:

void __init softirq_init(void) 
{int cpu;for_each_possible_cpu(cpu) {per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head;per_cpu(tasklet_hi_vec, cpu).tail = &per_cpu(tasklet_hi_vec, cpu).head;}open_softirq(TASKLET_SOFTIRQ, tasklet_action);open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

   从 softirq_init函数可以看出,当使用软中断的时候,这个函数会自动打开:

HI_SOFTIRQ, /* 高优先级软中断 */
TASKLET_SOFTIRQ, /* tasklet 软中断 */

② tasklet

  tasklet 适用于低优先级的、需要延迟处理的事件。如果事件需要尽快得到处理并具有不同的优先级,那么软中断更适合;如果事件可以在稍后的时间点进行处理,并且没有特定的优先级要求,那么 tasklet 更适合。但我们已经在下半部分了,所以 tasklet 更适合使用。

  tasklet_struct 结构体如下:

struct tasklet_struct
{struct tasklet_struct *next; /* 下一个 tasklet */unsigned long state; /* tasklet 状态 */atomic_t count; /* 计数器,记录对 tasklet 的引用数 */void (*func)(unsigned long); /* tasklet 执行的函数 */    // 这里相当于中断处理函数unsigned long data; /* 函数 func 的参数 */
};

  如果要使用 tasklet,必须先定义一个 tasklet_struct 变量,然后使用 tasklet_init 函数对其进行初始化:

/** @description :tasklet初始化函数* @param - t : 要初始化的 tasklet* @param - func : tasklet 的处理函数* @param - data : 要传递给 func 函数的参数* @return : 没有返回值*/
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

  当然也可以使用宏 DECLARE_TASKLET 一次性来完成 tasklet 的定义和初始化。

/** @description : 定义和初始化tasklet* @param - name : 要定义的tasklet名字* @param - func : tasklet的处理函数* @param - data : 传递给 func 函数的参数*/
DECLARE_TASKLET(name, func, data);

  除此之外,在上半部分的中断处理函数需要调用 tasklet_schedule 函数,这就可以让 tasklet 在合适的时间运行:

// 这里的形参指针 t:要调度的tasklet
void tasklet_schedule(struct tasklet_struct *t);

   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 结构体表示工作者线程,就是管理工作队列的结构体。

  在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程基本不用管,因为这两者都是由内核自动管理的。

  创建工作其实就直接定义一个 work_struct 结构体变量:

#define INIT_WORK(_work, _func)
/*_work:要初始化的工作_func:工作对应的要处理函数*/// 也可以用使用 DECLARE_WORK 宏一次性完成工作的创建和初始化
#define DECLARE_WORK(n, f)
/*n:要初始化的工作(work_struct)f:工作对应的要处理函数*/

   和 tasklet 一样,工作也是需要调度才能工作,它的调度函数为 schedule_work

/** @description : 调度工作(wrok_struct)的函数* @param - work : 要调度的工作* @return : 0 成功,其他值 失败*/
bool schedule_work(struct work_struct *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);......
}

  这里的 工作 的使用流程其实跟 tasklet 一模一样。但他们区别还是蛮大的,例如 工作 是在进程上下文中执行,tasklet 是在软中断上下文执行等等。

3. 设备树中断信息节点

① GIC 中断控制器

  STM32MP1 有三个与中断有关的控制器: GIC、EXTI 和 NVIC 。因为 NVIC 是 Cortex-M4 内核的中断控制器,暂时不考虑。

  GIC 有 4 个版本:V1~V4,V1 被淘汰,V3 和 V4 是 64 位芯片使用,这次使用的是  GIC V2。

  GIC 中断控制器是用来管理中断的优先级、中断分发、中断控制等。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是 ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和 IRQ,他们之间的关系如下:

  GIC 接受很多的外部中断,然后对其进行处理,最终通过4个信号报给 ARM 内核:

  VFIQ:虚拟快速 FIQ。

  VIRQ:虚拟快速 IRQ。 

  FIQ:快速中断 IRQ。 

  IRQ:外部中断 IRQ。 

  虚拟 FIQ 是专门虚拟化环境设计的中断机制,与传统的 FIQ 相互独立,VIRQ 也是有虚拟化环境机制。

  FIQ 必须尽快处理,处理结束后离开这个 FIQ。IRQ 可以被 FIQ 中断,但 IRQ 不能中断 FIQ,因此 FIQ 响应更快。GIC V2的逻辑图如下:

  左边就是中断源,中间是 GIC 控制器,右边是中断控制器向处理器内核发送的中断信息。

  重点看中间部分,GIC 将中断源分为三类:

  ① SPI(Shared Peripheral Interrupt),共享中断,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于共享中断 。比如 GPIO 中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。

  ② PPI(Private Peripheral Interrupt),私有中断,GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。

  ③ SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。 

② 中断 ID

  因为有很多中断,为了区分他们必须给他们分配唯一一个 ID 号。这个 ID 号就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、 SPI 和 SGI,这 1020 个 ID 分配如下:

  D0~ID15:这 16 个 ID 分配给 SGI(软件中断)。

  ID16~ID31:这 16 个 ID 分配给 PPI(私有中断)。 

  D32~ID1019:这 988 个 ID 分配给 SPI(共享中断)。

  STM32MP157 总共分配了 265 个中断 ID,加上 SGI 和 PPI,就有 288 个中断ID。从 ID 32 开始的 SPI 中断:

③ EXTI

  EXTI 称为 外部中断和事件控制器, EXTI 通过可配置的事件输入和直接事件输入来管理唤醒。它可以针对电源控制提供唤醒请求、针对 CPU 事件输入生成事件。 EXTI 唤醒请求可让系统从停止模式唤醒,以及让 CPU 从 CSTOP 和 CSTANDBY 模式唤醒。此外, EXTI 还可以在运行模式下生成中断请求和事件请求。在实际使用中 EXTI 主要是为 STM32 的 GPIO 中断服务的。 

  EXTI 的异步输入事件可以分为 2 组:

  ① 可配置事件(来自能够生成脉冲的 I/O 或外设的信号),这类事件具有以下特性:
  – 可选择的有效触发边沿。
  – 中断挂起状态寄存器位。
  – 单独的中断和事件生成屏蔽。
  – 支持软件触发。

  ② 直接事件(来自其他外设的中断和唤醒源,需要在外设中清除),这类事件具有以下特性:
  – 固定上升沿有效触发。
  – EXTI 中无中断挂起状态寄存器位(中断挂起状态由生成事件的外设提供)。
  – 单独的中断和事件生成屏蔽。
  – 不支持软件触发。

  对于 GPIO 中断来说,就是可配置事件,EXIT 和 GIC 关系如下:

  从上图中可以看出中断方式:

  ① 外设直接产生中断到 GIC,然后 GIC 通知 CPU 内核。

  ② GPIO 或外设产生中断到 EXTI,EXTI 将信号提交给 GIC,最终再将中断信号提交给 CPU。

  ③ GPIO 或外设产生中断到 EXTI,EXTI 直接将中断信号提交给 CPU。

  Linux 系统会用到这三种中断方式,一个外设最多可以有两种中断方式。GPIO 中断是我们最常用的。STM32 每一组 GPIO 最多 16 个 IO,比如 PA0~PA15,因此每组 GPIO 就有 16 个中断,这 16 个 GPIO 事件输入对应 EXTI0~15,其中 PA0、PB0,只要是 PX0 的都是对应的是 EXTI0(其实跟学习STM32裸机的时候一样):

  如果要在 Linux 系统中使用中断,那么就需要在设备树中设置好中断信息,Linux 内核通过读取设备树中的中断属性信息来配置中断。

④ GIC 控制节点

  首先进入 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 目录下,打开 stm32mp151.dtsi 文件。

122     intc: interrupt-controller@a0021000 {123         compatible = "arm,cortex-a7-gic";    // compatible属性为"arm,cortex-a7-gic",那么内核就会去找""里的内容,即可找到GIC中断驱动文件124         #interrupt-cells = <3>;    // #interrupt-cells 和#address-cells、 #size-cells 一样。125         interrupt-controller;    // 表示当前节点为中断控制器,类似于gpio-controller;126         reg = <0xa0021000 0x1000>,127               <0xa0022000 0x2000>;128     };/*详细了解 #interrupt-cells = <3>;表示此中断控制器下设备的 cells 大小,对于设备而言,会使用 interrupts 属性描述中断信息。#interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个cells都是32位整型值。这三个cells含义如下:第一个 cells:中断类型, 0 表示 SPI 中断, 1 表示 PPI 中断。第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 32~287(256 个),对于 PPI 中断来说中断号的范围为 16~31,但是该 cell 描述的中断号是从 0 开始。第三个 cells:标志, bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。 bit[15:8]为 PPI 中断的 CPU 掩码。*/

  首先来看一下 SPI6 如何在设备树节点中描述中断信息的,找到 SPI6 对应的中断号:

  第一列的 Num 是 86 号,但是注意,这里并没有算上前面的 PPI + SGI = 32,所以这里应该是 32 + 86 = 118,就跟第二列的 ID 号所对应。

  打开stm32mp151.dtsi,找到 SPI6 节点内容:

1712         spi6: spi@5c001000 {
1713             #address-cells = <1>;
1714             #size-cells = <0>;
1715             compatible = "st,stm32h7-spi";
1716             reg = <0x5c001000 0x400>;
1717             interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;    
1718             clocks = <&scmi0_clk CK_SCMI0_SPI6>;
1719             resets = <&scmi0_reset RST_SCMI0_SPI6>;
1720             dmas = <&mdma1 34 0x0 0x40008 0x0 0x0 0x0>,
1721                    <&mdma1 35 0x0 0x40002 0x0 0x0 0x0>;
1722             dma-names = "rx", "tx";
1723             power-domains = <&pd_core>;
1724             status = "disabled";/*interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;第一个表示中断类型,为 GIC_SPI,也就是共享中断。第二个表示中断号为86。第三个表示中断出发类型,高电平触发*/

⑤ EXTI 控制节点

  打开 stm32mp151.dtsi,其中的 exti 节点就是 EXTI 中断控制器的节点:

exti: interrupt-controller@5000d000 {compatible = "st,stm32mp1-exti", "syscon";interrupt-controller;    // 表示exti节点是中断控制器#interrupt-cells = <2>;  // 第一个cell表示中断号,第二个cell表示中断标志位reg = <0x5000d000 0x400>;hwlocks = <&hsem 1 1>;    // 硬件锁,指向hsem节点,数字 "1 1" 是传递给硬件锁节点的参数
};

   在 GPIO 中其实也用到了 EXIT,所以 GPIO 节点里面也有 EXTI 相关内容:

pinctrl: pin-controller@50002000 {
1815             #address-cells = <1>;
1816             #size-cells = <1>;
1817             compatible = "st,stm32mp157-pinctrl";
1818             ranges = <0 0x50002000 0xa400>;
1819             interrupt-parent = <&exti>;    // 指定pinctrl所有子节点的中断父节点为exti,这样就可以将GPIO和EXTI联系起来
1820             st,syscfg = <&exti 0x60 0xff>;
1821             hwlocks = <&hsem 0 1>;
1822             pins-are-numbered;
1823 
1824             gpioa: gpio@50002000 {
1825                 gpio-controller;
1826                 #gpio-cells = <2>;
1827                 interrupt-controller;    // 表示gpioa节点也是中断控制器
1828                 #interrupt-cells = <2>;  // 这里的第一个cell表示某个IO所处组的编号(类似PA0),第二个cell表示中断触发方式,每个#interrupt-cells在EXTI、GPIO和GIC含义都不一样
1829                 reg = <0x0 0x400>;
1830                 clocks = <&rcc GPIOA>;
1831                 st,bank-name = "GPIOA";// 比如现在要设置PA1引脚为下降沿触发,interrupts=<1 IRQ_TYPE_EDGE_FALLING>
1832                 status = "disabled";
1833             };...
/* 由于GPIOA-GPIOK是连续的,GPIOZ对应的寄存器地址不是连续的,所以单独使用pinctrl_z来描述GPIOZ */
pinctrl_z: pin-controller-z@54004000 {
1947             #address-cells = <1>;
1948             #size-cells = <1>;
1949             compatible = "st,stm32mp157-z-pinctrl";
1950             ranges = <0 0x54004000 0x400>;
1951             pins-are-numbered;
1952             interrupt-parent = <&exti>;
1953             st,syscfg = <&exti 0x60 0xff>;
1954             hwlocks = <&hsem 0 1>;
1955 
1956             gpioz: gpio@54004000 {
1957                 gpio-controller;
1958                 #gpio-cells = <2>;
1959                 interrupt-controller;
1960                 #interrupt-cells = <2>;
1961                 reg = <0 0x400>;
1962                 clocks = <&scmi0_clk CK_SCMI0_GPIOZ>;
1963                 st,bank-name = "GPIOZ";
1964                 st,bank-ioport = <11>;
1965                 status = "disabled";
1966             };
1967         };
1968     };

  来看一个具体应用:

hdmi-transmitter@39 {compatible = "sil,sii9022";    // sii9022是ST开发板上的HDMI芯片reg = <0x39>;iovcc-supply = <&v3v3_hdmi>;cvcc12-supply = <&v1v2_hdmi>;reset-gpios = <&gpioa 10 GPIO_ACTIVE_LOW>;    interrupts = <1 IRQ_TYPE_EDGE_FALLING>;    // 这个芯片是连接到PG1,下降沿触发中断。interrupt-parent = <&gpiog>;    // 指定中断节点的父节点为 gpiog#sound-dai-cells = <0>;status = "okay";
};// 其实在实际开发过程中,只需要通过interrupts和interrupt-parent就可以指定引脚和触发方式。

   stm32mp157f-ev1-a7-examples.dts 文件,再来看一个应用:

 16     test_keys {17         compatible = "gpio-keys";18         #address-cells = <1>;19         #size-cells = <0>;20         autorepeat;21         status = "okay";22         /* gpio needs vdd core in retention for wakeup */23         power-domains = <&pd_core_ret>;24 25         button@1 {26             label = "PA13";27             linux,code = <BTN_1>;28             interrupts-extended = <&gpioa 13 IRQ_TYPE_EDGE_FALLING>;    // 新出现的interrupts-extended29             status = "okay";30             wakeup-source;31         };32     };/*上述代码来描述一个按键,此按键采用中断方式并且使用到PA13引脚。直接通过 interrupts-extended 一个属性描述了所有中断信息,如果要用普通方式来描述的话:interrupt-parent = <&gpioa>;interrupts = <13 IRQ_TYPE_EDGE_FALLING>;这种 interrupts-extended 更加简介。*/

⑥ 获取中断号

  编写驱动的时候就需要中断号,用到的中断号,这个信息都已经写到了设备树里面。

  一个是 interrupt 属性提取对应设备号:

/** @description : 从interrupt属性提取到对应的设备号* @param - dev : 设备节点* @param - index : 索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息* @return : 中断号*/
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

   一个是从 gpio 属性里提取设备号:

/** @description : 从 GPIO 属性提取到对应的设备号* @param - gpio : 要获取的GPIO编号* @return : GPIO 对应的中断号*/
int gpio_to_irq(unsigned int gpio);

二、实验程序编写

  这次使用的是案件来触发中断。

  首先修改按键中的设备树,打开 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 目录下的 stm32mp157d-atk.dts 文件,修改 key 节点内容:

52     key {53         compatible = "alientek,key";54         status = "okay";55         key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;56         interrupts-extended = <&gpiog 3 IRQ_TYPE_EDGE_BOTH>;    // IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿同时有效,相当于按下KEY0和释放的时候同时有效57         // 也可以这样写58         // interrupt-parent = <&gpiog>;59         // interrupts = <3 IRQ_TYPE_EDGE_BOTH>;60     };

   之后编译设备树:

cd
cd linux/atk-mpl/linux/my_linux/linux-5.4.31/
make dtbs

  将编译好的设备树复制:

cd arch/arm/boot/dts/
sudo cp stm32mp157d-atk.dtb /home/alientek/linux/tftpboot/ -f

   在 ~/linux/atk-mpl/Drivers 目录下创建 13_irq,并在里面创建 Vscode 工作区,新建一个 keyirq.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/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define KEY_CNT			1		/* 设备号个数 	*/
#define KEY_NAME		"key"	/* 名字 		*//* 定义按键状态 */
enum key_status {KEY_PRESS = 0,      /* 按键按下 */ KEY_RELEASE,        /* 按键松开 */ KEY_KEEP,           /* 按键状态保持 */ 
};/* key设备结构体 */
struct key_dev{dev_t devid;				/* 设备号 	 */struct cdev cdev;			/* cdev 	*/struct class *class;		/* 类 		*/struct device *device;		/* 设备 	 */struct device_node	*nd; 	/* 设备节点 */int key_gpio;				/* key所使用的GPIO编号		*/struct timer_list timer;	/* 按键值 		*/int irq_num;				/* 中断号 		*/spinlock_t spinlock;		/* 自旋锁		*/
};static struct key_dev key;          /* 按键设备 */
static int status = KEY_KEEP;   	/* 按键状态 *//* 中断进入定时器,定时时间是把按键抖动给延时掉 */
static irqreturn_t key_interrupt(int irq, void *dev_id)	// 中断处理函数
{/* 按键防抖处理,开启定时器延时15ms */mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));	// 为什么需要周期性的定时器,是因为每当检测到按下一次就需要定时器延时return IRQ_HANDLED;		// IRQ_HANDLED是一个预定义的常量,表示中断已经得到处理,并且处理程序成功执行了必要的操作
}/** @description	: 初始化按键IO,open函数打开驱动的时候* 				  初始化按键所使用的GPIO引脚。* @param 		: 无* @return 		: 无*/
static int key_parse_dt(void)
{int ret;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点:key */key.nd = of_find_node_by_path("/key");if(key.nd == NULL) {printk("key node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(key.nd, "status", &str);if(ret < 0) return -EINVAL;if (strcmp(str, "okay"))return -EINVAL;/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(key.nd, "compatible", &str);if(ret < 0) {printk("key: Failed to get compatible property\n");return -EINVAL;}if (strcmp(str, "alientek,key")) {printk("key: Compatible match failed\n");return -EINVAL;}/* 4、 获取设备树中的gpio属性,得到KEY0所使用的KYE编号 */key.key_gpio = of_get_named_gpio(key.nd, "key-gpio", 0);if(key.key_gpio < 0) {printk("can't get key-gpio");return -EINVAL;}/* 5 、获取GPIO对应的中断号 */key.irq_num = irq_of_parse_and_map(key.nd, 0);if(!key.irq_num){return -EINVAL;}printk("key-gpio num = %d\r\n", key.key_gpio);return 0;
}/* 主要进行GPIO和中断的初始化 */
static int key_gpio_init(void)
{int ret;unsigned long irq_flags;/* 使用GPIO就要申请GPIO使用权 */ret = gpio_request(key.key_gpio, "KEY0");if (ret) {printk(KERN_ERR "key: Failed to request key-gpio\n");return ret;}	/* 将GPIO设置为输入模式 */gpio_direction_input(key.key_gpio);/* 获取设备树中指定的中断触发类型 */irq_flags = irq_get_trigger_type(key.irq_num);		// 获得定义的中断触发类型if (IRQF_TRIGGER_NONE == irq_flags)irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/* 申请中断(使用中断必须申请中断) */ret = request_irq(key.irq_num, key_interrupt, irq_flags, "Key0_IRQ", NULL);	// request_irq会默认使能中断,所以不需要enable_irq使能中断if (ret) {gpio_free(key.key_gpio);return ret;}// 建议申请成功后先用disbale_irq函数禁止中断,等所有工作完成之后再来使能中断return 0;
}/* 定时器处理函数 */
static void key_timer_function(struct timer_list *arg)
{static int last_val = 1;	// 保存按键上一次读取到的值unsigned long flags;int current_val;		// 存放当前按键读取的值/* 自旋锁上锁 */spin_lock_irqsave(&key.spinlock, flags);/* 读取按键值并判断按键当前状态 */current_val = gpio_get_value(key.key_gpio);if (0 == current_val && last_val)       /* 按下 */ 	// 读取的值为0,上次的值为1,则是按下status = KEY_PRESS;else if (1 == current_val && !last_val)status = KEY_RELEASE;  	 			/* 松开 */ elsestatus = KEY_KEEP;              	/* 状态保持 */ last_val = current_val;/* 自旋锁解锁 */spin_unlock_irqrestore(&key.spinlock, flags);
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int key_open(struct inode *inode, struct file *filp)
{return 0;
}/** @description     : 从设备读取数据 * @param – filp        : 要打开的设备文件(文件描述符)* @param – buf     : 返回给用户空间的数据缓冲区* @param – cnt     : 要读取的数据长度* @param – offt        : 相对于文件首地址的偏移* @return          : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{unsigned long flags;int ret;/* 自旋锁上锁 */spin_lock_irqsave(&key.spinlock, flags);/* 将按键状态信息发送给应用程序 */ret = copy_to_user(buf, &status, sizeof(int));	// 当前的status保存了按键当前的状态/* 状态重置 */status = KEY_KEEP;/* 自旋锁解锁 */spin_unlock_irqrestore(&key.spinlock, flags);return ret;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int key_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations key_fops = {.owner = THIS_MODULE,.open = key_open,.read = key_read,.write = key_write,.release = 	key_release,
};/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init mykey_init(void)
{int ret;/* 初始化自旋锁 */spin_lock_init(&key.spinlock);/* 设备树解析 */ret = key_parse_dt();if(ret)return ret;/* GPIO 中断初始化 */ret = key_gpio_init();if(ret)return ret;/* 注册字符设备驱动 *//* 1、创建设备号 */ret = alloc_chrdev_region(&key.devid, 0, KEY_CNT, KEY_NAME);	/* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", KEY_NAME, ret);goto free_gpio;}/* 2、初始化cdev */key.cdev.owner = THIS_MODULE;cdev_init(&key.cdev, &key_fops);/* 3、添加一个cdev */ret = cdev_add(&key.cdev, key.devid, KEY_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */key.class = class_create(THIS_MODULE, KEY_NAME);if (IS_ERR(key.class)) {goto del_cdev;}/* 5、创建设备 */key.device = device_create(key.class, NULL, key.devid, NULL, KEY_NAME);if (IS_ERR(key.device)) {goto destroy_class;}/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */timer_setup(&key.timer, key_timer_function, 0);return 0;destroy_class:class_destroy(key.class);
del_cdev:cdev_del(&key.cdev);
del_unregister:unregister_chrdev_region(key.devid, KEY_CNT);
free_gpio:free_irq(key.irq_num, NULL);gpio_free(key.key_gpio);return -EIO;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit mykey_exit(void)
{/* 注销字符设备驱动 */cdev_del(&key.cdev);/*  删除cdev */unregister_chrdev_region(key.devid, KEY_CNT); /* 注销设备号 */del_timer_sync(&key.timer);		/* 删除timer */device_destroy(key.class, key.devid);/*注销设备 */class_destroy(key.class); 		/* 注销类 */free_irq(key.irq_num, NULL);	/* 释放中断 */gpio_free(key.key_gpio);		/* 释放IO */
}module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  新建一个 keyirqApp 测试文件, 通过不断的读取/dev/key 设备文件来获取按键值来判断当前按键的状态,从按键驱动上传到应用程序的数据可以有 3 个值,分别为 0、 1、 2; 0 表示按键按下时的这个状态, 1 表示按键松开时对应的状态,而 2 表示按键一直被按住或者松开。编写测试 APP:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>/** @description		: main主程序* @param – argc		: argv数组元素个数* @param – argv		: 具体参数* @return			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, ret;int key_val;/* 判断传参个数是否正确 */if(2 != argc) {printf("Usage:\n""\t./keyApp /dev/key\n");return -1;}/* 打开设备 */fd = open(argv[1], O_RDONLY);if(0 > fd) {printf("ERROR: %s file open failed!\n", argv[1]);return -1;}/* 循环读取按键数据 */while(1) {read(fd, &key_val, sizeof(int));if (0 == key_val)printf("Key Press\n");else if (1 == key_val)printf("Key Release\n");}/* 关闭设备 */close(fd);return 0;
}

  编写 Makefile 文件:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)obj-m := keyirq.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  编译 keyirq.c 和 keyirqApp.c 文件:

make -j32
arm-none-linux-gnueabihf-gcc keyirqApp.c -o keyirqApp

  将编译好的 keyirqApp 和 keyirq.ko 文件复制:

sudo cp keyirqApp keyirq.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/

  开启开发板,输入以下命令:

cd lib/modules/modules/5.4.31/
depmod     # 第一次加载驱动需要运行此命令
modprobe keyirq.ko     # 加载驱动

  可以查看 /proc/interrputs 文件来检查对应的中断是否注册上了:

cd
cat /proc/interrupts

  从上图可以看出,keyirq.c 驱动文件里面的 KYE0 中断已经存在,触发方式为跳边沿(Edge)。

  接下来测试中断:

cd lib/modules/5.4.31/
./keyirqApp /dev/key

  按键值成功获取,并且不会有抖动的误判发生,说明消抖工作正常。

  卸载驱动:

rmmod keyirq.ko

总结

  概念:

  首先,我们学习了 Linux 中断号,并且了解了中断是如何开启的。每当使用到了中断,必须去申请中断(request_irq),在驱动出口再释放中断(free_irq),如果使用了 request_irq 函数,那么就不用使用使能中断 (enable_irq)。建议在申请成功后先用 disbale_irq 函数禁止中断,等所有工作完成之后再来使能中断。

  其次,学习了上半部和下半部,上半部其实就是对哪些时间敏感、执行速度快的操作放在中断处理函数中,也就是上半部,其他的就放在下半部。下半部里面我们学习了三个东西:

  ① 软中断:它是处理需要及时响应的事件,一般这个了解即可。

  ② tasklet:是利用软中断来实现,这个是适用于低优先级,需要延迟的事件。这个需要掌握概念和使用方法,定义->处理函数->中断处理函数里写调度->驱动入口函数里写初始化和注册中断处理函数。

  ③ 工作队列:工作队列在进程上下文执行,如果你的工作可以睡眠,那么选择工作队列。掌握概念及使用方法,使用方法和 tasklet 极为相似。

  最后,学习了设备树的中断信息节点,这里面有 GIC 中断控制(重点了解SPI(共享中断))、中断ID(需要查手册)和 EXTI。后面又了解到了 GIC 控制节点和 EXTI 控制节点,这两者 compatible 里面的元素和 #interrupt-cells 信息稍许不一样外,其他类似。并且在设备树里加入中断信息的方式有两种:一种是 interrupts-extended、另一种是 interrupt-parent 和 interrupts,前者是后者的结合体。

  程序:

  ① 在初始化阶段分开了设备树信息设置和 GPIO 初始化设置;

  ② 在中断处理函数中增减定时器消除按键抖动;

  ③ 定时器处理函数(也就是回调函数)中去判断按键的值,并打印出按键的值。

相关文章:

17 Linux 中断

一、Linux 中断简介 1. Linux 中断 API 函数 ① 中断号 每个中断都有一个中断号&#xff0c;通过中断号可以区分出不同的中断。在 Linux 内核中使用一个 int 变量表示中断号。 ② request_irq 函数 在 Linux 中想要使用某个中断是需要申请的&#xff0c;request_irq 函数就是…...

微信小程序真机调试连接状态一直在正常和未链接之间反复横跳?

背景&#xff1a;小程序真机调试的时候&#xff0c;发现真机的network不显示接口调用情况&#xff0c;控制台也没有输出内容。具体如下所示&#xff1b; 解决方法&#xff1a; 1、确保手机端连接的网络和微信开发者工具网络一致&#xff0c;比如用同一个WiFi 2、真机自动调试…...

最新Next 14快速上手基础部分

最新Next 14快速上手基础部分 最新的NEXT快速上手文档&#xff0c;2023.10.27 英文官网同步&#xff0c;版本Next14.0.0 本项目案例&#xff1a;GitHub地址&#xff0c;可以根据git回滚代码到对应知识&#xff0c;若有错误&#xff0c;欢迎指正&#xff01; 一、介绍 1.什么是…...

【uniapp/uview】Collapse 折叠面板更改右侧小箭头图标

最终效果是这样的&#xff1a; 官方没有给出相关配置项&#xff0c;后来发现小箭头不是 uview 的图标&#xff0c;而是 unicode 编码&#xff0c;具体代码&#xff1a; // 箭头图标 ::v-deep .uicon-arrow-down[data-v-6e20bb40]:before {content: \1f783; }附一个查询其他 u…...

企业如何落地搭建商业智能BI系统

随着新一代信息化、数字化技术的应用&#xff0c;引发了新一轮的科技革命&#xff0c;现代化社会和数字化的联系越来越紧密&#xff0c;数据也变成继土地、劳动力、资本、技术之后的第五大生产要素&#xff0c;这一切都表明世界已经找准未来方向&#xff0c;前沿科技也与落地并…...

RedisTemplate连接密码设置教程

最近在一个项目中使用Redis保存Token时&#xff0c;出现连接Redis报错的情况 org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhos…...

基于SSM的二手车交易网站的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…...

温故知新:探究Android UI 绘制刷新流程

一、说明&#xff1a; 基于之前的了解知道ui的绘制最终会走到Android的ViewRootImpl中scheduleTraversals进行发送接收vsync信号绘制&#xff0c;在ViewRootImpl中还会进行主线程检测&#xff0c;也就是我们所谓子线程更新ui会抛出异常。 像我们常用的刷新ui&#xff0c;inval…...

设计模式-命令模式(Command)

设计模式-命令模式&#xff08;Command&#xff09; 一、命令模式概述1.1 什么是命令模式1.2 简单实现命令模式1.3 使用命令模式的注意事项 二、命令模式的用途三、命令模式实现方式3.1 使用匿名内部类实现命令模式3.2 使用Lambda表达式实现命令模式3.3 使用Java内置的函数式接…...

linux批量解压zip

方法一 1&#xff0c;创建unzip.sh #!/bin/bashwhile read line do unzip $linedone < filelist.txt #!/bin/bashwhile read line dounzip "$line" >& log & done < filelist.txt3. 在终端中执行以下命令 $ chmod x unzip.sh $ ./unzip.sh 这…...

HBase导出建表语句

HBase导出建表语句 HBase是一个面向大数据的分布式列存数据库&#xff0c;它以Hadoop作为底层存储和计算平台。在HBase中&#xff0c;数据以表的形式存储&#xff0c;每个表由行和列组成。本文将介绍如何使用HBase导出建表语句&#xff0c;并提供相应的代码示例。 HBase建表语…...

Linux环境配置(云服务器)

目录 1.第一步&#xff1a;购买云服务器 2.第二步&#xff1a;下载Xshell 7 3.第三步&#xff1a;打开Xshell&#xff0c;登录云服务器 4.第四步&#xff1a;更加便捷的云服务器登录方式 1.第一步&#xff1a;购买云服务器 &#xff08;推荐&#xff1a;阿里云、华为云、腾…...

【性能测试】Linux下Docker安装与docker-compose管理容器(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Linux下Docker…...

陪玩2.0升级版源码/价值18500元的最新商业版游戏陪玩语音聊天系统源码

陪玩2.0升级版源码&#xff0c;价值18500元的最新商业版游戏陪玩语音聊天系统源码。 修复部分逻辑以及bug 修复bug&#xff1a;店员拒单后&#xff0c;退款会退到店员账号里而不是用户账户里。 修复bug&#xff1a;客户在盲盒下单后&#xff0c;马上取消了订单&#xff0c;但…...

读程序员的制胜技笔记08_死磕优化(上)

1. 过早的优化是万恶之源 1.1. 著名的计算机科学家高德纳(Donald Knuth)的一句名言 1.2. 原话是&#xff1a;“对于约97%的微小优化点&#xff0c;我们应该忽略它们&#xff1a;过早的优化是万恶之源。而对于剩下的关键的3%&#xff0c;我们则不能放弃优化的机会。” 2. 过早…...

【gltf-pipeline】安装gltf-pipeline 进行文件格式转换

问题 想使用gltf-pipeline进行gltf和glb格式转换。简单记录一下安装过程。 解决 1、安装Node.js Node.js下载路径&#xff1a;https://nodejs.org/en 建议默认设置安装。 添加系统环境变量&#xff1a; 测试安装是否成功&#xff1a; 在cmd.exe中运行&#xff1a; no…...

Android OpenGL ES踩坑记录

因为项目中的一个自定义绘图控件性能不行&#xff0c;改用OpenGL实现&#xff0c;也是第一次使用OpenGL&#xff0c;由于只是绘制2D图形&#xff0c;参考官方以及网上的教程&#xff0c;实现起来还是比较顺畅的&#xff0c;开发时只用了两个手机测试&#xff0c;运行良好&#…...

Vue3 项目完整配置

目录 一、配置简述二、创建项目1、使用包管理工具 pnpm2、新增目录 三、配置 ESLint1、添加代码2、修改 VSCode 配置 四、husky 工具配置五、暂存区 eslint 校验六、axios 配置1、安装创建2、测试 七、导入 Element Plus八、Pinia 持久化实现九、其他导入 .scss 文件需要安装 s…...

二十三种设计模式全面解析-从线程安全到创新应用:探索享元模式的进阶之路

在软件开发领域&#xff0c;线程安全和设计模式都是我们经常遇到的话题。线程安全保证了多线程环境下的数据一致性和可靠性&#xff0c;而设计模式则提供了一套经验丰富的解决方案。在前文中&#xff0c;我们已经了解了线程安全的处理和享元模式的基本概念。但是&#xff0c;如…...

Qt之qobject_cast使用

描述 qobject_cast是Qt中的一个转换函数&#xff0c;主要用于在QObject子类之间进行转换&#xff0c;实现父类指针向子类指针的转换。其语法为&#xff1a; qobject_cast<T>(object);其中&#xff0c;T表示目标类型&#xff0c;object表示要转换的QObject对象指针。 q…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...

算法250609 高精度

加法 #include<stdio.h> #include<iostream> #include<string.h> #include<math.h> #include<algorithm> using namespace std; char input1[205]; char input2[205]; int main(){while(scanf("%s%s",input1,input2)!EOF){int a[205]…...