深入解析GIC中断处理与内核初始化:基于Linux 4.9.88内核的详细分析

embedded/2024/11/19 23:21:51/

往期内容

本专栏往期内容,interrtupr子系统:

  1. 深入解析Linux内核中断管理:从IRQ描述符到irq domain的设计与实现
  2. Linux内核中IRQ Domain的结构、操作及映射机制详解
  3. 中断描述符irq_desc成员详解
  4. Linux 内核中断描述符 (irq_desc) 的初始化与动态分配机制详解
  5. 中断的硬件框架
  6. GIC介绍
  7. GIC寄存器介绍
  8. ARM架构中断与异常向量表机制解析

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

目录

  • 往期内容
  • 前言
  • 1.GIC中断处理流程
    • 1.1 一级中断控制器处理流程
    • 1.2 多级中断控制器处理流程
  • 2.GIC中的重要函数和结构体
  • 3.GIC初始化过程
    • 3.1 内核支持多种GIC
    • 3.2 初始化调用
    • 3.3 在设备树里指定GIC
    • 3.4 gic_of_init分析
      • 3.4.1`gic_of_setup`
      • 3.4.2 `__gic_init_bases`

前言

建议先看一下往期文章关于中断相关结构体的讲解:中断描述符irq_desc成员详解-CSDN博客

  • Linux 4.9.88内核源码

    • Linux-4.9.88\drivers\irqchip\irq-gic.c
    • Linux-4.9.88/arch/arm/boot/dts/imx6ull.dtsi

本文以Linux 4.9.88内核为基础,系统解析了通用中断控制器(GIC)的中断处理与初始化机制。介绍了一级和多级中断控制器的处理流程,包括中断号映射(hwirq与virq)和中断服务函数的调用。梳理了GIC驱动的关键结构体与核心函数,阐述了GIC的功能及其内核表示方法。以设备树为线索,详细剖析了GIC初始化过程,解读了IRQCHIP_DECLARE宏及其如何与设备树节点匹配并触发初始化。通过源码分析与注释,逐步讲解了GIC的域操作和分层中断处理流程。

1.GIC中断处理流程

1.1 一级中断控制器处理流程

对于irq_desc,内核有两种分配方法:

  • 一次分配完所有的irq_desc
  • 按需分配(用到某个中断才分配它的irq_desc

现在的内核基本使用第1种方法。

img

  • 假设GIC可以向CPU发出161019号中断,这些数字被称为hwirq。015用于Process之间通信,比较特殊。

  • 假设要使用UART模块,它发出的中断连接到GIC的32号中断,分配的irq_desc序号为16

  • 在GIC domain中会记录(32, 16)

  • 那么注册中断时就是:request_irq(16, ...) //虚拟中断号or软件中断号

  • 发生UART中断时

    • 程序从GIC中读取寄存器知道发生了32号中断,通过GIC irq_domain可以知道virq为16
    • 调用irq_desc[16]中的handleA函数(GIC提供的handle函数),它的作用是屏蔽中断、调用action链表中用户注册的函数handler和thread_fn、清除中断

1.2 多级中断控制器处理流程

img

  • 假设GPIO模块下有4个引脚,都可以产生中断,都连接到GIC的33号中断

  • GPIO也可以看作一个中断控制器,对于它的4个中断

  • 对于GPIO模块中0~3这四个hwirq,一般都会一下子分配四个irq_desc

  • 假设这4个irq_desc的序号为100~103,在GPIO domain中记录(0,100) (1,101)(2,102) (3,103)

  • 对于KEY,注册中断时就是:request_irq(102, ...)

  • 按下KEY时:

    • 程序从GIC中读取寄存器知道发生了33号中断,通过GIC irq_domain可以知道virq为17

    • 调用irq_desc[17]中的handleB函数

    • mask GIC33号中断

      • handleB读取GPIO寄存器,确定是GPIO里2号引脚发生中断
      • 通过GPIO irq_domain可以知道virq为102
      • 调用irq_desc[102]中的handleA函数,它的作用是mask GPIO2号中断、调用action链表中用户注册的函数、clear GPIO2号中断
      • clear GIC33 号中断

2.GIC中的重要函数和结构体

沿着中断的处理流程,GIC涉及这4个重要部分:

  • CPU从异常向量表中调用handle_arch_irq,这个函数指针是有GIC驱动设置的

    • GIC才知道怎么判断发生的是哪个GIC中断
  • 从GIC获得hwirq后,要转换为virq:需要有GIC Domain

  • 调用irq_desc[virq].handle_irq函数:这也应该由GIC驱动提供

  • 处理中断时,要屏蔽中断、清除中断等:这些函数保存在irq_chip里,由GIC驱动提供

从硬件上看,GIC的功能是什么?

  • 可以使能、屏蔽中断
  • 发生中断时,可以从GIC里判断是哪个中断

在内核里,使用gic_chip_data结构体表示GIC,gic_chip_data里有什么?

  • irq_chip:中断使能、屏蔽、清除,放在irq_chip中的各个函数里实现

在这里插入图片描述

  • irq_domain
    • 申请中断时

      • 在设备树里指定hwirq、flag,可以使用irq_domain的函数来解析设备树
      • 根据hwirq可以分配virq,把(hwirq, virq)存入irq_domain中
    • 发生中断时,从GIC读出hwirq,可以通过irq_domain找到virq,从而找到处理函数

所以,GIC用gic_chip_data来表示,gic_chip_data中重要的成员是:irq_chip、irq_domain。

3.GIC初始化过程

Linux-4.9.88\drivers\irqchip\irq-gic.c📎irq-gic.c

(将dtb文件反汇编为dts后,在里面搜索Interrupt-contorller找到中断控制器的节点,复制其compatible属性,在内核源码中grep该属性搜索一下就可以找到该文件)

3.1 内核支持多种GIC

按照设备树的套路:

  • 驱动程序注册platform_driver
  • 它的of_match_table里有多个of_device_id,表示能支持多个设备
  • 有多种版本的GIC,在内核为每一类GIC定义一个结构体of_device_id,并放在一个段里:
// drivers\irqchip\irq-gic.c
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

把宏IRQCHIP_DECLARE展开:

// include\linux\irqchip.h
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)#define OF_DECLARE_2(table, name, compat, fn) \_OF_DECLARE(table, name, compat, fn, of_init_fn_2)#define _OF_DECLARE(table, name, compat, fn, fn_type)			\static const struct of_device_id __of_table_##name		\__used __section(__irqchip_of_table)			\= { .compatible = compat,				\.data = (fn == (fn_type)NULL) ? fn : fn  }

展开示例:

IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
展开后得到:
static const struct of_device_id __of_table_cortex_a7_gic		\__used __section(__irqchip_of_table)			\= { .compatible = "arm,cortex-a7-gic",				\.data = gic_of_init  }

3.2 初始化调用

// drivers\irqchip\irq-gic.c
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
IRQCHIP_DECLARE(pl390, "arm,pl390", gic_of_init);

像5.3.1中上述的宏,表示支持的GIC的种类,比如要使用IRQCHIP_DECLARE(cortex_a7_gic, “arm,cortex-a7-gic”, gic_of_init);,那它是如何去选择和初始化的?如下:

start_kernel (init\main.c)init_IRQ (arch\arm\kernel\irq.c)irqchip_init (drivers\irqchip\irqchip.c)of_irq_init (drivers\of\irq.c)desc->irq_init_cb = match->data;ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);

如何知道of_irq_init在哪里被调用的:
在这里插入图片描述

选择某一GIC和进行初始化:

img

\Linux-4.9.88\drivers\irqchip\irq-gic.c
void __init of_irq_init(const struct of_device_id *matches)
{const struct of_device_id *match;         // 用于存放匹配的设备节点的指针struct device_node *np, *parent = NULL;   // np 指向当前中断控制器节点,parent 指向父节点struct of_intc_desc *desc, *temp_desc;    // 中断控制器描述符指针,用于保存控制器相关信息struct list_head intc_desc_list, intc_parent_list; // 初始化中断控制器列表和父节点列表INIT_LIST_HEAD(&intc_desc_list);          // 初始化描述符列表,存储扫描到的中断控制器INIT_LIST_HEAD(&intc_parent_list);        // 初始化父节点列表,存储待初始化的父节点// 1. 遍历设备树中与 `matches` 匹配的节点for_each_matching_node_and_match(np, matches, &match) {// 检查节点是否有 "interrupt-controller" 属性且可用if (!of_find_property(np, "interrupt-controller", NULL) || !of_device_is_available(np))continue;// 如果匹配项的 `data` 字段为空,发出警告并跳过if (WARN(!match->data, "of_irq_init: no init function for %s\n", match->compatible))continue;// 2. 为每个匹配的中断控制器分配并填充一个 `of_intc_desc` 结构体desc = kzalloc(sizeof(*desc), GFP_KERNEL);if (WARN_ON(!desc)) {of_node_put(np);goto err;}desc->irq_init_cb = match->data;                 // 初始化回调函数desc->dev = of_node_get(np);                     // 保存节点信息desc->interrupt_parent = of_irq_find_parent(np); // 查找中断父节点if (desc->interrupt_parent == np)                // 若节点为根中断控制器,设置为 NULLdesc->interrupt_parent = NULL;list_add_tail(&desc->list, &intc_desc_list);     // 添加到中断控制器描述符列表}/** 3. 逐层初始化中断控制器,优先初始化根节点(即无父节点的控制器)*/while (!list_empty(&intc_desc_list)) {list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {int ret;// 跳过非当前父节点的子节点if (desc->interrupt_parent != parent)continue;list_del(&desc->list);                       // 从描述符列表中移除当前描述符of_node_set_flag(desc->dev, OF_POPULATED);   // 标记节点已初始化pr_debug("of_irq_init: init %s (%p), parent %p\n",desc->dev->full_name,desc->dev, desc->interrupt_parent);// 调用中断控制器的初始化回调函数ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);if (ret) {of_node_clear_flag(desc->dev, OF_POPULATED); // 若初始化失败,清除标记kfree(desc);continue;}/** 初始化成功,将此节点加入到父节点列表中,* 以便后续初始化其子节点*/list_add_tail(&desc->list, &intc_parent_list);}// 获取下一个待处理的父节点desc = list_first_entry_or_null(&intc_parent_list,typeof(*desc), list);if (!desc) {pr_err("of_irq_init: children remain, but no parents\n");break;}list_del(&desc->list);parent = desc->dev;kfree(desc);}// 释放未使用的描述符节点list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {list_del(&desc->list);kfree(desc);}
err:// 清理和释放资源,释放未初始化的描述符节点list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {list_del(&desc->list);of_node_put(desc->dev);kfree(desc);}
}
  1. 匹配中断控制器节点:遍历设备树中所有符合 matches 条件的节点,通过检查节点是否有 interrupt-controller 属性,筛选出所有中断控制器节点,并分配 of_intc_desc 结构体保存节点相关信息(包括初始化回调函数、节点指针、父节点指针等)。
  2. 初始化中断控制器:从根中断控制器(无父节点)开始,每次处理当前父节点的所有子节点,并递归初始化,确保初始化顺序由根至叶子。
  3. 清理和释放:初始化完成后,释放未使用的描述符,确保资源不被浪费。

IRQCHIP_DECLARE 宏如何完成与设备树的挂钩:

IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init); 的作用是声明并注册一个中断控制器,这样在调用 of_irq_init 时能够识别设备树中的 "arm,cortex-a9-gic" 中断控制器节点。具体过程如下:

  1. IRQCHIP_DECLARE 定义了一个 of_device_id 类型的结构体实例,其中包含 "arm,cortex-a9-gic" 兼容字符串与 gic_of_init 初始化函数的关联。
  2. of_irq_init 遍历设备树时,识别出符合 "arm,cortex-a9-gic" 的节点,匹配后调用 gic_of_init 进行中断控制器初始化,从而与设备树节点挂钩。

3.3 在设备树里指定GIC

在设备树中指定GIC,内核驱动程序根据设备树来选择、初始化GIC。

drivers\irqchip\irqchip.c中并没有定义一个platform_driver,但是套路是一样的。

img

调用过程:img

of_irq_init:

  • 内核有一个__irqchip_of_table数组,里面有多个of_device_id,表示多种GIC

  • 要使用哪类GIC?在设备树里指定

  • 根据设备树,找到__irqchip_of_table树组中对应的项,调用它的初始化函数

    • IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);

3.4 gic_of_init分析

请添加图片描述

先记住上面的中断处理流程,再来分析gic_of_init:

申请中断的过程(irq_domain_ops域操作结构体):

static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {

​ .translate = gic_irq_domain_translate,

​ .alloc = gic_irq_domain_alloc,

​ .free = irq_domain_free_irqs_top,

};

  • 在设备树中声明要使用哪个GIC控制器下的哪个中断
  • 用gic_irq_domain_translate来解析设备树,知道其hwirq以及flag
  • 为hwirq在irq_desc数组中找到空闲的位置(数组下标就是其virq)
  • 记录hwirq和virq在domain中(建立联系)
  • 使用gic_irq_domain_alloc处理,提供handle中断函数等

img

``📎irq-gic.c📎entry-armv.S

关键词:gic_of_setup、__gic_init_bases、gic_init_chip、gic_init_bases、irq_domain_create_linear

int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{struct gic_chip_data *gic;  // 用于存储当前GIC的配置信息int irq, ret;// 检查设备树节点的有效性,如果为空则返回错误码 -ENODEVif (WARN_ON(!node))return -ENODEV;// 检查当前GIC控制器数量是否已达到最大允许数量,若超限则返回错误码 -EINVALif (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR))return -EINVAL;// 获取用于当前GIC的数据结构,并使用 gic_data 数组存储多实例的 GICgic = &gic_data[gic_cnt];// 调用 gic_of_setup 函数根据设备树节点 `node` 初始化 GIC 的基本信息ret = gic_of_setup(gic, node);if (ret)return ret;/** 如果 hypervisor 模式(HYP)不可用或 CPU 接口寄存器太小,禁用分离的* End Of Interrupt/Deactivate (EOI/Deactivate) 模式。*/if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base))static_key_slow_dec(&supports_deactivate);// 初始化 GIC 的寄存器基地址、GIC 中断域等基本配置ret = __gic_init_bases(gic, -1, &node->fwnode);if (ret) {// 若初始化失败,调用 gic_teardown 清理当前 GIC 配置gic_teardown(gic);return ret;}// 若是第一个 GIC 控制器,进行物理地址初始化并设置 KVM (Kernel-based Virtual Machine) 信息if (!gic_cnt) {gic_init_physaddr(node);gic_of_setup_kvm_info(node);}// 若存在父节点,则解析并映射第一个中断号并进行级联中断配置if (parent) {irq = irq_of_parse_and_map(node, 0); // 解析并映射设备树中第一个中断gic_cascade_irq(gic_cnt, irq);       // 配置级联中断}// 如果启用了 GIC V2M(GIC Version 2 Memory Mapped)支持,初始化 GIC V2Mif (IS_ENABLED(CONFIG_ARM_GIC_V2M))gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);// 增加 GIC 控制器计数器,表明一个新的 GIC 已初始化完成gic_cnt++;return 0;
}

3.4.1gic_of_setup

gic_of_init函数中调用的,主要是初始化 GIC 的基本信息,和设备树挂钩。

img

img

reg中,第一行就是分发器Distributro所对应寄存器的地址,第二行则是cpu interface所对应寄存器的地址,既然是物理地址,那就得映射,就是由该函数实现的:

static int gic_of_setup(struct gic_chip_data *gic, struct device_node *node)
{// 检查传入参数 gic 和 node 是否为空,以确保有效性if (!gic || !node)return -EINVAL;  // 如果为空,返回 -EINVAL 表示无效参数// 使用设备树节点 `node` 映射分发器(distributor)寄存器基地址gic->raw_dist_base = of_iomap(node, 0);// 如果映射失败,输出错误信息,并跳转到 error 标签清理已分配资源if (WARN(!gic->raw_dist_base, "unable to map gic dist registers\n"))goto error;// 使用设备树节点 `node` 映射 CPU 接口(CPU interface)寄存器基地址gic->raw_cpu_base = of_iomap(node, 1);// 如果映射失败,输出错误信息,并跳转到 error 标签清理已分配资源if (WARN(!gic->raw_cpu_base, "unable to map gic cpu registers\n"))goto error;// 读取设备树节点中的 `cpu-offset` 属性,用于定义 CPU 接口偏移量// 如果未定义 `cpu-offset` 属性,默认偏移量设为0if (of_property_read_u32(node, "cpu-offset", &gic->percpu_offset))gic->percpu_offset = 0;return 0;  // 成功初始化时返回 0 表示无错误error:// 如果映射失败,调用 gic_teardown 释放已分配的 GIC 资源gic_teardown(gic);return -ENOMEM;  // 返回 -ENOMEM 表示内存分配失败
}

3.4.2 __gic_init_bases

要目的是为 GIC(Generic Interrupt Controller)初始化基础数据结构和相关属性,为后续中断处理和分发提供支持。它包含了对第一个 GIC 实例的特殊处理(主要是主 GIC 的初始化),并设置了对 SMP(对称多处理)系统的支持。

static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start,struct fwnode_handle *handle)
{char *name;int i, ret;// 检查传入的 gic 是否为空或已经初始化,若是则返回 -EINVAL 表示无效参数if (WARN_ON(!gic || gic->domain))return -EINVAL;// 检查当前是否为主 GIC,即 gic_data[0]if (gic == &gic_data[0]) {/** 初始化 CPU 接口映射,将其默认设置为所有 CPU。* 每个 CPU 在探测其 ID 时将此映射进一步细化。* 仅主 GIC 需要该操作。*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff; // 0xff 表示所有 CPU#ifdef CONFIG_SMP// 设置跨 CPU 的中断调用函数set_smp_cross_call(gic_raise_softirq);
#endif// 设置 CPU 热插拔的回调状态,用于在 CPU 启动时初始化 GICcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"AP_IRQ_GIC_STARTING",gic_starting_cpu, NULL);// 注册 GIC 的中断处理函数set_handle_irq(gic_handle_irq);// 如果支持 EOI/Deactivate 模式,输出提示信息if (static_key_true(&supports_deactivate))pr_info("GIC: Using split EOI/Deactivate mode\n");}// 初始化 GIC 芯片名称和类型if (static_key_true(&supports_deactivate) && gic == &gic_data[0]) {// 主 GIC 使用分离的 EOI/Deactivate 模式,名称设置为 "GICv2"name = kasprintf(GFP_KERNEL, "GICv2");gic_init_chip(gic, NULL, name, true); // EOI/Deactivate 为 true} else {// 其他 GIC 使用默认名称格式 "GIC-x",x 是 GIC 索引name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic - &gic_data[0]));gic_init_chip(gic, NULL, name, false); // EOI/Deactivate 为 false}// 调用 gic_init_bases 初始化 GIC 基础结构ret = gic_init_bases(gic, irq_start, handle);if (ret)kfree(name); // 若初始化失败,释放为名称分配的内存return ret; // 返回初始化结果
}

set_handle_irq

img

gic_init_chip

img

gic_init_bases

请添加图片描述


http://www.ppmy.cn/embedded/138898.html

相关文章

IPv6基础知识

IPv6是由IEIF提出的互聯網協議第六版&#xff0c;用來替代IPv4的下一代協議&#xff0c;它的提出不僅解決了網絡地址資源匱乏問題&#xff0c;也解決了多種接入設備接入互聯網的障礙。IPv6的地址長度為128位&#xff0c;可支持340多萬億個地址。如下圖&#xff0c;3ffe:1900:fe…

网络安全、Web安全、渗透测试之笔经面经总结(三)

本篇文章涉及的知识点有如下几方面&#xff1a; 1.什么是WebShell? 2.什么是网络钓鱼&#xff1f; 3.你获取网络安全知识途径有哪些&#xff1f; 4.什么是CC攻击&#xff1f; 5.Web服务器被入侵后&#xff0c;怎样进行排查&#xff1f; 6.dll文件是什么意思&#xff0c;有什么…

静默绑定推广人方法修复

问题表现为&#xff1a;原方法缺少对自己和两个用户互为上下级的拦截 修复方案&#xff1a; 找到文件app/services/user/UserServices.php 将方法spread整个代码替换为 /*** 静默绑定推广人* param Request $request* return mixed */ public function spread(int $uid, in…

Node高级进阶四-http模块

1、Web服务器 什么是Web服务器&#xff1f; 当应用程序&#xff08;客户端&#xff09;需要某一个资源时&#xff0c;可以向一台服务器&#xff0c;通过Http请求获取到这个资源&#xff1b;提供资源的这个服务器&#xff0c;就是一个Web服务器&#xff1b; 目前有很多开源的We…

比特币前景再度不明,剧烈波动性恐即将回归

比特币市场降温&#xff0c;波动性增加 自特朗普赢得美国总统大选以来&#xff0c;比特币市场的投机狂热有所降温&#xff0c;现货和衍生品市场的活跃度开始减弱。比特币在上周五跌破87000美元&#xff0c;较之前创下的历史高点低了约6500美元。这一变化受到美联储主席鲍威尔讲…

SpringBoot总结

一、Spring\SpringBoot\SpringCloud Spring&#xff1a;Spring是SpringBoot和SpringCloud的基础。Spring是一个广泛使用的企业级 Java 应用程序框架&#xff0c;提供了应用开发的核心功能&#xff0c;如依赖注入、AOP&#xff08;面向切面编程&#xff09; 等&#xff0c;旨在简…

【ChatGPT】让ChatGPT生成特定时间段或主题的文章

让ChatGPT生成特定时间段或主题的文章 ChatGPT能够根据设定的时间段或主题生成文章&#xff0c;但这需要通过设计精准的Prompt来实现。通过结合上下文信息、明确的写作目标和格式要求&#xff0c;您可以高效地引导ChatGPT生成符合预期的内容。本文将详细介绍如何实现这一目标&…

创建游戏云存档功能的完整指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…