[嵌入式系统-63]:RT-Thread-内核:内核在不同CPU架构上的移植和不同硬件板BSP上的移植

目录

内核移植

1. CPU 架构移植:由CPU厂家提供

1.1 实现全局中断开关:汇编语言实现

(1)关闭全局中断

(2)打开全局中断

1.2 实现线程栈初始化

1.3 实现上下文切换

(1)实现 rt_hw_context_switch_to()

(2)实现 rt_hw_context_switch()/ rt_hw_context_switch_interrupt()

(3)实现 PendSV 中断

1.4 实现时钟节拍

2. BSP 移植:由开发板厂家提供


内核移植

经过前面内核章节的学习,大家对 RT-Thread 也有了不少的了解,但是如何将 RT-Thread 内核移植到不同的硬件平台上,很多人还不一定熟悉。RT Thread操作系统必然要在特定的硬件系统上运行,内核移植就是指将 RT-Thread 内核在不同的CPU芯片架构不同的硬件电路板卡上运行起来,使得运行后的软件系统能够具备:线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。移植可分为 :CPU 架构移植和 BSP(Board support package,板级支持包)移植两部分。

本章将展开介绍 CPU 架构移植和 BSP 移植,CPU 架构移植部分会结合 Cortex-M CPU 架构进行介绍,因此有必要回顾下上一章《中断管理》介绍的 “Cortex-M CPU 架构基础” 的内容,本章最后以实际移植到一个开发板的示例展示 RT-Thread 内核移植的完整过程,读完本章,我们将了解如何完成 RT-Thread 的内核移植。

1. CPU 架构移植:由CPU厂家提供

在嵌入式领域有多种不同 CPU 架构,例如 ARM Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构

RT-Thread 的 libcpu 抽象层向上:对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。

RT-Thread 的 libcpu 抽象层向下:提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等与硬件强相关的内容或需要硬件支持的功能。

下表是 CPU 架构移植需要实现的接口和变量。

libcpu 移植相关 API

函数和变量描述
rt_base_t rt_hw_interrupt_disable(void);关闭全局中断
void rt_hw_interrupt_enable(rt_base_t level);打开全局中断
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit);线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数,每个线程有自己独立的线程栈,线程栈的压栈和出栈需要硬件支持。
void rt_hw_context_switch_to(rt_uint32_t to);没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用
void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);从 from 线程切换到 to 线程,用于线程和线程之间的切换
void rt_hw_context_switch_interrupt(rt_uint32_t from, rt_uint32_t to);从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用
rt_uint32_t rt_thread_switch_interrupt_flag;表示需要在中断里进行切换的标志
rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread;在线程进行上下文切换时候,用来保存 from 和 to 线程

在C语言程序中,上下文切换是一个重要的概念,尤其是在多线程或多任务环境中。

这些上下文切换通常涉及CPU硬件和操作系统的支持。

以下是针对您提到的三种上下文切换的详细说明:

  1. 函数切换上下文:不需要CPU硬件支持,编译器实现

    • 在C语言中,函数切换上下文主要涉及函数调用栈(Call Stack)的操作。当调用一个函数时,当前函数的局部变量、返回地址等信息会被压入栈中,然后跳转到被调用函数的入口点执行当函数返回时,这些信息会从栈中弹出,以恢复调用者的上下文
    • 函数切换上下文主要关注于函数调用栈的管理和局部变量、返回地址等信息的保存和恢复。这个过程主要由编译器在编译时处理,而不需要CPU硬件的特别支持(除了函数调用和返回指令),并通过执行汇编语言指令实现函数上下文涉及到的局部变量的切换的压栈和出栈操作。
  2. 线程切换上下文需要操作系统和硬件提供支持,提升切换效率

    • 线程切换上下文在多线程操作系统中非常重要。当CPU从一个线程切换到另一个线程时,它需要保存当前线程的状态(如寄存器值、程序计数器、函数栈指针-切换线程的函数栈等),然后加载新线程的状态。每个线程都有自己独立的栈,用于线程的函数调用和线程自身上下文的独占性。
    • 线程切换上下文涉及操作系统内核的参与,因为操作系统需要管理线程的状态和调度。CPU硬件提供了对寄存器值、程序计数器等关键信息的保存和加载的支持,操作系统则负责在切换时更新这些信息
    • 在C语言中,线程的创建、管理和切换通常是通过调用操作系统提供的API来实现的,这些API会利用CPU硬件提供的支持来快速执行实际的上下文切换
  3. 中断切换上下文

    • 中断切换上下文发生在硬件中断发生时。当硬件设备(如键盘、磁盘等)需要CPU的注意时,它会发送一个中断信号给CPU。CPU在接收到中断信号后,会暂停当前正在执行的线程,跳转到中断处理程序执行。
    • 在中断处理过程中,CPU需要保存当前线程的上下文(包括寄存器值、程序计数器等),然后加载中断处理程序的上下文。当中断处理程序执行完毕后,CPU需要恢复被中断线程的上下文,并继续执行。
    • 中断切换上下文同样需要CPU硬件的支持,因为CPU需要能够保存和加载线程上下文,并且能够处理中断信号和跳转到中断处理程序。操作系统则负责提供中断处理程序,并在中断发生时调用这些程序。

        总结来说,C语言程序执行中的函数切换上下文主要由编译器处理,而线程切换上下文和中断切换上下文则需要CPU硬件和操作系统的共同支持。在这些上下文切换过程中,关键信息的保存和恢复对于确保程序的正确性和安全性至关重要。

1.1 实现全局中断开关:汇编语言实现

无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。RT-Thread 里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到 libcpu 里提供的全局中断开关函数。它们分别是:

/* 关闭全局中断 */
rt_base_t rt_hw_interrupt_disable(void);/* 打开全局中断 */
void rt_hw_interrupt_enable(rt_base_t level);复制错误复制成功

下面介绍在 Cortex-M 架构上如何实现这两个函数,前文中曾提到过,Cortex-M 为了快速开关中断,实现了 CPS 指令,可以用在此处。

CPSID I ;PRIMASK=1, ; 关中断
CPSIE I ;PRIMASK=0, ; 开中断复制错误复制成功
(1)关闭全局中断

在 rt_hw_interrupt_disable() 函数里面需要依序完成的功能是:

1). 保存当前的全局中断状态,并把状态作为函数的返回值。

2). 关闭全局中断。

基于 MDK,在 Cortex-M 内核上实现关闭全局中断,如下代码所示:

关闭全局中断

;/*
; * rt_base_t rt_hw_interrupt_disable(void);
; */
rt_hw_interrupt_disable    PROC      ;PROC 伪指令定义函数EXPORT  rt_hw_interrupt_disable  ;EXPORT 输出定义的函数,类似于 C 语言 externMRS     r0, PRIMASK              ; 读取 PRIMASK 寄存器的值到 r0 寄存器CPSID   I                        ; 关闭全局中断BX      LR                       ; 函数返回ENDP                             ;ENDP 函数结束复制错误复制成功

上面的代码首先是使用 MRS 指令将 PRIMASK 寄存器的值保存到 r0 寄存器里,然后使用 “CPSID I” 指令关闭全局中断,最后使用 BX 指令返回。r0 存储的数据就是函数的返回值。中断可以发生在 “MRS r0, PRIMASK” 指令和 “CPSID I” 之间,这并不会导致全局中断状态的错乱。

关于寄存器在函数调用的时候和在中断处理程序里是如何管理的,不同的 CPU 架构有不同的约定。在 ARM 官方手册《Procedure Call Standard for the ARM ® Architecture》里可以找到关于 Cortex-M 的更详细的介绍寄存器使用的约定。

(2)打开全局中断

rt_hw_interrupt_enable(rt_base_t level) 里,将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态。

基于 MDK,在 Cortex-M 内核上的实现打开全局中断,如下代码所示:

打开全局中断

;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC      ; PROC 伪指令定义函数EXPORT  rt_hw_interrupt_enable  ; EXPORT 输出定义的函数,类似于 C 语言 externMSR     PRIMASK, r0             ; 将 r0 寄存器的值写入到 PRIMASK 寄存器BX      LR                      ; 函数返回ENDP                            ; ENDP 函数结束复制错误复制成功

上面的代码首先是使用 MSR 指令将 r0 的值寄存器写入到 PRIMASK 寄存器,从而恢复之前的中断状态。

1.2 实现线程栈初始化

在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init() 函数会调用栈初始化函数 rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。不同的CPU需要保存的CPU寄存器的现场是不相同的。

上下文在栈里的排布如下图所示:

栈里的上下文信息

下代码是栈初始化的代码:

在栈里构建上下文

rt_uint8_t *rt_hw_stack_init(void       *tentry,void       *parameter,rt_uint8_t *stack_addr,void       *texit)
{struct stack_frame *stack_frame;rt_uint8_t         *stk;unsigned long       i;/* 对传入的栈指针做对齐处理 */stk  = stack_addr + sizeof(rt_uint32_t);stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);stk -= sizeof(struct stack_frame);/* 得到上下文的栈帧的指针 */stack_frame = (struct stack_frame *)stk;/* 把所有寄存器的默认值设置为 0xdeadbeef */for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++){((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}/* 根据 ARM  APCS 调用标准,将第一个参数保存在 r0 寄存器 */stack_frame->exception_stack_frame.r0  = (unsigned long)parameter;/* 将剩下的参数寄存器都设置为 0 */stack_frame->exception_stack_frame.r1  = 0;                 /* r1 寄存器 */stack_frame->exception_stack_frame.r2  = 0;                 /* r2 寄存器 */stack_frame->exception_stack_frame.r3  = 0;                 /* r3 寄存器 *//* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */stack_frame->exception_stack_frame.r12 = 0;                 /* r12 寄存器 *//* 将线程退出函数的地址保存在 lr 寄存器 */stack_frame->exception_stack_frame.lr  = (unsigned long)texit;/* 将线程入口函数的地址保存在 pc 寄存器 */stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;/* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */stack_frame->exception_stack_frame.psr = 0x01000000L;/* 返回当前线程的栈地址       */return stk;
}复制错误复制成功

1.3 实现上下文切换

在不同的 CPU 架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。但是为了能适应不同的 CPU 架构,RT-Thread 的 libcpu 抽象层还是需要实现三个线程切换相关的函数:

1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。

2) rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。

3) rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。

在线程环境下进行切换和在中断环境进行切换是存在差异的。线程环境下,如果调用 rt_hw_context_switch() 函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换。

由于这种差异,在 ARM9 等平台,rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 的实现并不一样。在中断处理程序里如果触发了线程的调度,调度函数里会调用 rt_hw_context_switch_interrupt() 触发上下文切换。中断处理程序里处理完中断事务之后,中断退出之前,检查 rt_thread_switch_interrupt_flag 变量,如果该变量的值为 1,就根据 rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换。

在 Cortex-M 处理器架构里,基于自动部分压栈和 PendSV 的特性,上下文切换可以实现地更加简洁。

线程之间的上下文切换,如下图表示:

线程之间的上下文切换

硬件在进入 PendSV 中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后 PendSV 里保存 from 线程的 R11~R4 寄存器,以及恢复 to 线程的 R4~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0~R3、R12、LR、PC、PSR 寄存器。

中断到线程的上下文切换可以用下图表示:

中断到线程的切换

硬件在进入中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后触发了 PendSV 异常。在 PendSV 异常处理函数里保存 from 线程的 R11~R4 寄存器,以及恢复 to 线程的 R4~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0~R3、R12、PSR、PC、LR 寄存器。

显然,在 Cortex-M 内核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在 PendSV 里完成剩余上下文的保存和回复。所以我们仅仅需要实现一份代码,简化移植的工作。

(1)实现 rt_hw_context_switch_to()

rt_hw_context_switch_to() 只有目标线程,没有来源线程。这个函数里实现切换到指定线程的功能,下图是流程图:

rt_hw_context_switch_to() 流程图

在 Cortex-M3 内核上的 rt_hw_context_switch_to() 实现(基于 MDK),如下代码所示:

MDK 版 rt_hw_context_switch_to() 实现

;/*
; * void rt_hw_context_switch_to(rt_uint32_t to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to    PROCEXPORT rt_hw_context_switch_to; r0 的值是一个指针,该指针指向 to 线程的线程控制块的 SP 成员; 将 r0 寄存器的值保存到 rt_interrupt_to_thread 变量里LDR     r1, =rt_interrupt_to_threadSTR     r0, [r1]; 设置 from 线程为空,表示不需要保存 from 的上下文LDR     r1, =rt_interrupt_from_threadMOV     r0, #0x0STR     r0, [r1]; 设置标志为 1,表示需要切换,这个变量将在 PendSV 异常处理函数里切换的时被清零LDR     r1, =rt_thread_switch_interrupt_flagMOV     r0, #1STR     r0, [r1]; 设置 PendSV 异常优先级为最低优先级LDR     r0, =NVIC_SYSPRI2LDR     r1, =NVIC_PENDSV_PRILDR.W   r2, [r0,#0x00]       ; readORR     r1,r1,r2             ; modifySTR     r1, [r0]             ; write-back; 触发 PendSV 异常 (将执行 PendSV 异常处理程序)LDR     r0, =NVIC_INT_CTRLLDR     r1, =NVIC_PENDSVSETSTR     r1, [r0]; 放弃芯片启动到第一次上下文切换之前的栈内容,将 MSP 设置启动时的值LDR     r0, =SCB_VTORLDR     r0, [r0]LDR     r0, [r0]MSR     msp, r0; 使能全局中断和全局异常,使能之后将进入 PendSV 异常处理函数CPSIE   FCPSIE   I; 不会执行到这里ENDP复制错误复制成功
(2)实现 rt_hw_context_switch()/ rt_hw_context_switch_interrupt()

函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。下图是具体的流程图:

rt_hw_context_switch()/ rt_hw_context_switch_interrupt() 流程图

在 Cortex-M3 内核上的 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 实现(基于 MDK),如下代码的所示:

rt_hw_context_switch()/rt_hw_context_switch_interrupt() 实现

;/*
; * void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interruptEXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch    PROCEXPORT rt_hw_context_switch; 检查 rt_thread_switch_interrupt_flag 变量是否为 1; 如果变量为 1 就跳过更新 from 线程的内容LDR     r2, =rt_thread_switch_interrupt_flagLDR     r3, [r2]CMP     r3, #1BEQ     _reswitch; 设置 rt_thread_switch_interrupt_flag 变量为 1MOV     r3, #1STR     r3, [r2]; 从参数 r0 里更新 rt_interrupt_from_thread 变量LDR     r2, =rt_interrupt_from_threadSTR     r0, [r2]_reswitch; 从参数 r1 里更新 rt_interrupt_to_thread 变量LDR     r2, =rt_interrupt_to_threadSTR     r1, [r2]; 触发 PendSV 异常,将进入 PendSV 异常处理函数里完成上下文切换LDR     r0, =NVIC_INT_CTRLLDR     r1, =NVIC_PENDSVSETSTR     r1, [r0]BX      LR复制错误复制成功
(3)实现 PendSV 中断

在 Cortex-M3 里,PendSV 中断处理函数是 PendSV_Handler()。在 PendSV_Handler() 里完成线程切换的实际工作,下图是具体的流程图:

PendSV 中断处理

如下代码是 PendSV_Handler 实现:

; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler   PROCEXPORT PendSV_Handler; 关闭全局中断MRS     r2, PRIMASKCPSID   I; 检查 rt_thread_switch_interrupt_flag 变量是否为 0; 如果为零就跳转到 pendsv_exitLDR     r0, =rt_thread_switch_interrupt_flagLDR     r1, [r0]CBZ     r1, pendsv_exit         ; pendsv already handled; 清零 rt_thread_switch_interrupt_flag 变量MOV     r1, #0x00STR     r1, [r0]; 检查 rt_interrupt_from_thread 变量是否为 0; 如果为 0,就不进行 from 线程的上下文保存LDR     r0, =rt_interrupt_from_threadLDR     r1, [r0]CBZ     r1, switch_to_thread; 保存 from 线程的上下文MRS     r1, psp                 ; 获取 from 线程的栈指针STMFD   r1!, {r4 - r11}       ; 将 r4~r11 保存到线程的栈里LDR     r0, [r0]STR     r1, [r0]                ; 更新线程的控制块的 SP 指针switch_to_threadLDR     r1, =rt_interrupt_to_threadLDR     r1, [r1]LDR     r1, [r1]                ; 获取 to 线程的栈指针LDMFD   r1!, {r4 - r11}       ; 从 to 线程的栈里恢复 to 线程的寄存器值MSR     psp, r1                 ; 更新 r1 的值到 psppendsv_exit; 恢复全局中断状态MSR     PRIMASK, r2; 修改 lr 寄存器的 bit2,确保进程使用 PSP 堆栈指针ORR     lr, lr, #0x04; 退出中断函数BX      lrENDP复制错误复制成功

1.4 实现时钟节拍

有了开关全局中断上下文切换功能的基础,RTOS 就可以进行:线程的创建、运行、调度等功能了。有了时钟节拍支持,RT-Thread 可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现 rt_thread_delay() 延时函数等等。

libcpu 的移植需要完成的工作,就是确保 rt_tick_increase() 函数会在时钟节拍的中断里被周期性的调用,调用周期取决于 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。

在 Cortex M 中,实现 SysTick 的中断处理函数即可实现时钟节拍功能。

void SysTick_Handler(void)
{/* enter interrupt */rt_interrupt_enter();rt_tick_increase();/* leave interrupt */rt_interrupt_leave();
}复制错误复制成功

2. BSP 移植:由开发板厂家提供

相同的 CPU 架构在实际项目中,不同的板卡上可能使用相同的 CPU 架构,搭载不同的外设资源,完成不同的产品,所以我们也需要针对板卡做适配工作。RT-Thread 提供了 BSP 抽象层来适配常见的板卡。如果希望在一个板卡上使用 RT-Thread 内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的 BSP。

主要任务是建立让操作系统运行的基本环境,需要完成的主要工作是:

1)CPU: 初始化 CPU 内部寄存器,设定 RAM 工作时序。 

2)时钟与中断:实现时钟驱动及中断控制器驱动完善中断管理

3)内存堆:初始化动态内存堆,实现动态堆内存管理。

4)调试外设:实现串口和 GPIO 驱动。

备注:

RT-Thread BSP(Board Support Package)是RT-Thread实时操作系统针对特定硬件板卡提供的一套完整的板级支持软件包,它通常包括板卡初始化硬件抽象层(HAL)实现、驱动程序等。

在编写RT-Thread BSP时,不需要为所有的外设硬件编写驱动程序,但需要关注那些影响操作系统内核功能和内核调试相关的硬件。

以下是一些通常需要考虑的、与操作系统内核功能和内核调试相关的硬件和驱动:

  1. CPU 和内存管理:BSP 需要为 CPU 提供初始化代码,并配置内存管理单元(MMU,如果可用),以便操作系统内核能够正确管理内存和进程。
  2. 中断控制器:中断控制器是处理硬件中断的关键组件。BSP 需要为中断控制器编写驱动程序,以便操作系统内核能够响应和处理各种硬件中断。
  3. 系统时钟:系统时钟是操作系统内核调度任务、管理时间和提供定时服务的基础。BSP 需要确保系统时钟的准确性和稳定性,并为其编写驱动程序。
  4. 串口通信:串口通信通常用于调试和日志输出。BSP 需要为串口提供驱动程序,以便操作系统内核能够通过串口发送和接收数据。
  5. 调试接口:如果硬件板卡提供了调试接口(如JTAG、SWD等),BSP 需要为这些接口提供驱动程序,以便开发者能够使用调试工具进行内核调试和故障排查。
  6. 其他关键外设:除了上述硬件外,BSP 还需要关注其他与操作系统内核功能和内核调试相关的关键外设,如GPIO、PWM、定时器等。这些外设的驱动程序需要根据具体硬件板卡的特性和需求进行编写。

在编写 RT-Thread BSP 时,可以根据具体硬件板卡的特性和需求来确定需要编写哪些驱动程序。通常,可以先关注那些影响操作系统内核功能和内核调试的关键硬件,然后再逐步完善对其他外设的支持,其他外设的支持是由实时内核之上的组件与服务中的设备框架来提供的,如下图所示,设备驱动框架层也可以通过HAL层(硬件抽象层)来访问底层的硬件。

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

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

相关文章

零代码编程:用Kimichat从PDF文件中批量提取图片

一个PDF文件中&#xff0c;有很多图片&#xff0c;想批量提取出来&#xff0c;可以借助kimi智能助手。 在借助kimi智能助手中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个网页爬取Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹…

基于深度学习检测恶意流量识别框架(80+特征/99%识别率)

基于深度学习检测恶意流量识别框架 目录 基于深度学习检测恶意流量识别框架简要示例a.检测攻击类别b.模型训练结果输出参数c.前端检测页面d.前端训练界面e.前端审计界面&#xff08;后续更新了&#xff09;f.前端自学习界面&#xff08;自学习模式转换&#xff09;f1.自学习模式…

数据结构与算法之经典排序算法

一、简单排序 在我们的程序中&#xff0c;排序是非常常见的一种需求&#xff0c;提供一些数据元素&#xff0c;把这些数据元素按照一定的规则进行排序。比如查询一些订单按照订单的日期进行排序&#xff0c;再比如查询一些商品&#xff0c;按照商品的价格进行排序等等。所以&a…

021、Python+fastapi,第一个Python项目走向第21步:ubuntu 24.04 docker 安装mysql8、redis(二)

系列文章目录 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 安装redis 我会以三种方式安装&#xff0c; 第一、直接最简单安装&#xff0c;适用于测试环境玩玩 第二、conf配置安装 第三、集群环境安装 一…

电脑崩溃了,之前备份的GHO文件怎么恢复到新硬盘?

前言 之前咱们说到用WinPE系统给电脑做一个GHO镜像备份&#xff0c;这个备份可以用于硬盘完全崩溃换盘的情况下使用。 那么这个GHO镜像文件怎么用呢&#xff1f; 咱们今天详细来讲讲&#xff01; 如果你的电脑系统硬盘崩溃了或者是坏掉了&#xff0c;那么就需要使用之前备份…

ElementUI从unpkg.com完整下载到本地的方法 - 解决unpkg.com不稳定的问题 - 自建镜像站 - 不想打包只想cdn一下

方法 方法1&#xff09;随便弄个文件夹&#xff0c;根据官网npm方法下载包&#xff0c;提取即可 npm i element-ui -S cd /node_modules/element-ui/ ls src 安装npm方法&#xff1a;https://nodejs.org/en 方法2&#xff09;不推荐 - 在github中搜索对应的库zip包&#xff0…

C++仿函数周边及包装器

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

Vue Vant 移动端如何禁止手机调起自带的输入键盘

前言 前不久在公司用Vue2开发了一个手机充值项目&#xff0c;键盘组件用的vant2的NumberKeyboard 数字键盘组件&#xff1b;上线后在IOS端只有一个vant数字键盘组件&#xff0c;但到了Android端&#xff0c;输入框一获取焦点不仅vant数字键盘弹出&#xff0c;连手机自带的键盘…

39 死锁

目录 1.死锁 2.线程同步 3.条件变量 4.案例 死锁 概念 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态 四个必要条件 互斥条件&#xff1a;一个资源每次只能被一个执行流使用 请求…

如何使用提示测试为LLMs构建单元测试?

原文地址&#xff1a;how-to-build-unit-tests-for-llms-using-prompt-testing 确保您的人工智能交付&#xff1a;快速测试完美生成应用程序的基本指南 2024 年 4 月 26 日 如果你曾经编写过软件&#xff0c;你就会知道测试是开发过程中必不可少的一部分。特别是单元测试&#…

设计模式——保护性暂停

同步模式之保护性暂停 文章目录 同步模式之保护性暂停定义实现应用带超时版 GuardedObject扩展——原理之join扩展——多任务版 GuardedObject 定义 即 Guarded Suspension&#xff0c;用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程&…

【Linux】进程的控制①之进程创建与进程退出

目录 ​编辑 一 、进程的创建 1、fork函数 2.函数的返回值 ①return 可以返回两次&#xff0c;使得父子进程读到的id有两个值 ②写实拷贝&#xff0c;使得父子进程读到的值都对自身有意义 a.为什么要写实拷贝&#xff0c;而不是在创建子进程的时候直接就将空间给子进程开辟好&a…

Linux系统安装Redis7(详细版)

Linux系统安装Redis7 一、windows安装redis二、Linux安装Redis下载redis编辑redis7.conf文件启动redis-server服务如何关闭redis服务设置Redis开机自启动 一、windows安装redis Window 下安装 下载地址&#xff1a;https://github.com/dmajkic/redis/downloads 下载到的Redi…

【Qt之·控件·样式表】

系列文章目录 文章目录 前言一、Qt样式表的基础知识1.1 Qt样式表的定义和语法规则1.2 Qt样式表中的选择器和属性1.2.1 盒子模型1.2.2 border 1.3 Qt样式表中的伪类和伪元素 二、编写基本的Qt样式表2.1 在Qt应用程序中引入样式表文件的方式2.2 设置基本的背景色、字体样式等 三、…

MATLAB中功率谱密度计算pwelch函数使用详解

MATLAB中功率谱密度计算pwelch函数使用详解 目录 前言 一、pwelch函数简介 二、pwelch函数参数说明 三、pxx pwelch(x)示例 四、[pxx,f]pwelch(x,window,noverlap,nfft,fs)示例 四、[pxx,f] pwelch(x,window,noverlap,nfft,fs,freqrange,spectrumtype)示例 五、多通道功…

SpringCloud整合Gateway结合Nacos

目录 一、引入依赖 二、开启两个测试项目 2.1 order service ​编辑 2.2 user service 三、gateway项目 3.1 新建一个bootstrap.yml文件 3.2 将我们的的网关配置写道nacos里的配置里 3.3 测试&#xff1a;看能够根据网关路由到两个测试的项目 四、 优化 4.1 将项目打包…

低代码技术在构建质量管理系统中的应用与优势

引言 在当今快节奏的商业环境中&#xff0c;高效的质量管理系统对于组织的成功至关重要。质量管理系统帮助组织确保产品或服务符合客户的期望、符合法规标准&#xff0c;并持续改进以满足不断变化的需求。与此同时&#xff0c;随着技术的不断进步&#xff0c;低代码技术作为一…

Windows系统启动Redis

一、下载windows版本Redis 1.1 选择一个使用的版本 在以下地址中选择一个Windows系统可以使用的版本 https://github.com/microsoftarchive/redis/tags 1.2 下载 1.3 解压到文件夹 二、启动Redis 双击redis-server.exe启动Redis 出现以下界面即启动成功 三、测试是否可以使…

hive-row_number() 和 rank() 和 dense_rank()

row_number() 是无脑排序 rank() 是相同的值排名相同&#xff0c;相同值之后的排名会继续加&#xff0c;是我们正常认知的排名&#xff0c;比如学生成绩。 dense_rank()也是相同的值排名相同&#xff0c;接下来的排名不会加。不会占据排名的坑位。

DB-GPT部署验证

一、DB-GPT简介 DB-GPT是一个开源的数据库领域大模型框架。目的是构建大模型领域的基础设施&#xff0c;通过开发多模型管理、Text2SQL效果优化、RAG框架以及优化、Multi-Agents框架协作等多种技术能力&#xff0c;让围绕数据库构建大模型应用更简单&#xff0c;更方便。 GITHU…