RT_Thread内核源码分析(一)——CM3内核和上下文切换

       

目录

一、程序存储分析

1.1  CM3内核寻址空间映射

1.2  程序静态存储和动态执行

二、CM3内核相关知识

2.1 操作模式和特权极别

2.2 环境相关寄存器

2.2.1  通用寄存器组,

2.2.2  状态寄存器组

2.2.3  模式切换环境自动保存

2.2.4  函数调用形参位置

2.3 中断

2.3.1  PendSV(可悬起软中断)

2.3.2 SysTick(系统定时器),

三、RT_Thread线程结构

四、RT_Thread启动分析

五、RT_Thread上线文切换

5.1 上下文切换本质

5.2 上下文切换场景

5.3 上下文切换源码实现

5.3.1 启动第一个线程

5.3.2 调度中上下文切换

5.3.3 上下文切换源码分析

5.3.4 上下文切换逻辑图

5.4 上下文切换实例分析


        对于实时操作系统来说,上下文切换是线程抢占或轮询的根本,要想吃透操作系统,必需先搞明白上下文切换的过程,若要搞明白上线文切换,必然少不了与CPU内核打交道,本文基于STM32F10X系列单片机( 内核为Cortex-M3核,简称CM3),讲解RT_Thread Nano实时操作系统的上下文切换。

本章基于RT_Thread Nano V3.1.5版本分析

本章基于Cortex-M3内核和Keil5编译器分析,详细内核知识参考《CM3权威指南CnR2 宋岩 译》

一、程序存储分析

1.1  CM3内核寻址空间映射

        如下图所示:

        对于STM32F103ZE型号的单片机,Code区采用容量为512kB总线接口的NorFlash;SRAM区为64kB的SRAM存储器,暂不考虑扩展SRAM。

1.2  程序静态存储和动态执行

        使用keil5编译STM32程序后,程序存储分析如下图所示:

        各存储区说明:

存储区

属性

存储位置

运行位置

存储内容

CODE

只读

NorFlash

NorFlash

代码区:所有程序指令。

(起始为MSP初始化值+Reset向量)

RO-DATA

只读

NorFlash

NorFlash

常量区:const修饰的常量、字符串。

RW-DATA

读/写

NorFlash

SRAM

变量区:初始化为非0的全局、静态变量。

ZI-DATA

变量区

读/写

SRAM

(运行中分配)

变量区:未初始化和初始化为0的全局、静态变量。

heap

读/写

SRAM

(运行中分配)

堆区:使用编译器微库,用于malloc申请动态内存。

stack

读/写

SRAM

(运行中分配)

栈区:特权级线程(Thread)、中断(异常)模式的局部变量。

(1)单片机内部FLASH启动模式下,ICode总线只能从NorFLASH取指令,代码段只能在NorFlash运行。

(2)总线接口的NorFlash可以用作只读内存,RO-DATA(只读数据)可以不搬移至内存。

(2)heap段使用需要启动keil的微库,才能通过malloc等操作进行动态内存申请,操作系统可以使用该方式分配总的动态内存区,但不要使用该方式分配任务栈,任务栈申请使用操作系统自带的方式。

(3)stack段默认给特权级线程、中断(异常)模式使用,由MSP寄存器控制出入栈,如果切换到用户模式,自动切换为进程栈指针寄存器(PSP),用户模式需要重新申请栈区,PSP指向用户栈区;操作系统中的动态内存区可以根据需求设置在RW-DATA段、ZI-DATA堆区、ZI-DATA变量区。

(4)ZI-DATA区:该类变量初始值全为0,故不体现在静态存储中,在运行态由指令进行内存分配,分配代码由Keil编译器自动生成,我们只需要在工程配置和启动文件中将存储器参数和堆栈申请空间配置好就行,如下所示:

        Keil编译器存储器设置NorFLASH、SRAM:

        启动文件(.s)中主程序堆(heap)空间分配:

        启动文件(.s)中主程序栈(stack)空间分配:

        编译结果:

        Map文件查看:

二、CM3内核相关知识

2.1 操作模式和特权极别

  1. CM3有2种操作模式: 处理者模式(或中断(异常)模式 handler mode )、线程(Thread mode)模式
  2. CM3有2种权利级别: 特权级、用户级,特权级使用MSP栈指针寄存器,用户级使用PSP栈指针寄存器。两种级别的栈相互独立。
  3. 处理者模式(handler mode)只能运行在特权级别。
  4. 用户级对系统控制空间(SCS)的访问将被阻止——该空间包含了配置寄存器组以及调试组件的寄存器组。还禁止使用 MRS/MSR 访问,除了 APSR 之外的特殊功能寄存器。如果以身试法,则对于访问特殊功能寄存器的,访问操作被忽略;而对于访问 SCS 空间的,将触发fault异常。
  5. 软件触发中断寄存器可以在用户级下访问以产生软件中断(利用这个特性实现用户模式到特权模式转变)。

        各种模式之间的切换:

模式切换

触发条件

栈指针

特权级Thread 模式

上电启动

MSP

特权级Thread 模式->特权级handler模式

中断、异常触发

R14(LD)更新为0XFFFFFFF9

MSP

特权级Thread 模式->用户级Thread 模式

操作寄存器CONTROL[0]置1

MSP切换为PSP

特权级handler模式->特权级Thread 模式

调用指令“BX R14”

其中R14=0XFFFFFFF9

MSP

特权级handler模式->用户级Thread 模式

调用指令“BX R14”

其中R14=0XFFFFFFFD

MSP切换为PSP

用户级Thread 模式->特权级handler模式

中断、异常触发

R14更新为0XFFFFFFFD

PSP切换为MSP

用户级Thread 模式->特权级Thread 模式

主动触发异常(SVCall异常)

调用指令“BX R14”返回异常

其中R14=0XFFFFFFF9

PSP切换为MSP

2.2 环境相关寄存器

        CM3用于上下文切换的寄存器主要包括通用寄存器组、状态寄存器组。

2.2.1  通用寄存器组,

        如下图所示:

(1)通用寄存器

        R0-R12 都是 32 位通用寄存器,用于数据操作、暂存。绝大多数 16 位 Thumb 指令只能访问 R0-R7,而 32 位 Thumb-2 指令可以访问所有寄存器。

(2)堆栈指针(SP)

        R13用于栈指针(SP),指向栈区(stack),通过入栈出栈分配和释放临时变量。比如一个函数中定义了一些临时变量,调整SP指针(入栈)分配临时变量的存储空间,函数返回时,调整SP指针(出栈)释放掉堆栈空间,所以临时变量初始值是个不确定的值,因为出栈仅是调整SP指针,并不对栈空间进行归零等操作,且临时变量不宜过多,防止栈空间溢出。

        CM3内核拥有两个堆栈指针:

        主堆栈指针(MSP:特权级别下使用,复位后缺省使用的堆栈指针,初始值指向内存ZI-DATA栈区(Stack)的栈底(一般是高地址),代码区第一条指令就是MSP的初始化值,装置上电会先更新MSP,然后在执行reset。

        进程堆栈指针(PSP:用户级别下使用,在操作系统中,指向任务的栈区,任务栈区一般从操作系统动态内存区分配,操作系统动态内存区可以根据可以根据需求设置在RW-DATA段、ZI-DATA堆区、ZI-DATA变量区。

(3)连接寄存器(LD)

        R14用作连接寄存器(LD),连接寄存器当调用一个子程序时,R14存储指令返回地址,如果只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。

        程序进入handler模式(中断(异常))时,R14自动入栈保存,保存后R14更新为0xFFFFFFFX,通过指令”BX  R14”进行异常返回。X的bit0为1表示返回thumb状态,bit1和bit2表示返回后sp用msp还是psp及返回到特权级别还是用户级别。合法的返回值如下所示:

0xFFFF_FFF1返回handler模式【应用于中断嵌套的场景】
0xFFFF_FFF9返回线程模式,并使用主堆栈(SP=MSP)【返回特权级线程(Thread)模式】
0xFFFF_FFFD返回线程模式,并使用线程堆栈(SP=PSP)【返回用户级线程(Thread)模式】

(4) 程序计数寄存器(PC)

        R15用作程序计数寄存器(PC),程序计数寄存器指向NorFLASH代码区。正常运行,取指令完成,PC自动加1,既PC指向下一条指令,如果执行跳转指令或者直接修改PC寄存器的值, 就能改变程序的执行流。

2.2.2  状态寄存器组

        状态字寄存器组包括:应用程序 PSR(APSR)、 中断号 PSR(IPSR)、执行 PSR(EPSR),环境保存时是三个寄存器会合并为一个32位xPSR进行保存。

2.2.3  模式切换环境自动保存

        与一些高端内核不同,CM3由线程模式(Thread)进入handler模式会自动进行部分寄存器的入栈保存,下图参考自《CM3权威指南CnR2 宋岩 译》:

2.2.4  函数调用形参位置

        ARM系列平台,函数形参按从左向右顺序存放在寄存器r0,r1,r2,r3里,超过4个参数值传递则放栈里。

        比如函数:

void test(int iv1,int iv2,int iv3,int iv4,int iv5,int iv6);

        形参:iv1、iv2、iv3、iv4分别存入寄存器r0、r1、r2、r3进行传递。

        形参:iv5、iv6则入栈传递。

2.3 中断

        RT_Thread操作系统调度器涉及CM3的2个中断,PendSV用于上下文切换,SysTick提供时间片,如下图:

2.3.1  PendSV(可悬起软中断)

       该中断可以在高优先级中断(异常)中设置为悬起,等所有高优先级中断返回后,再执行PendSV,俗称“缓期执行”,所以PendSV的优先级一般会设置为最低。

        也可以在用户级或特权级线程模式调用该服务进入handler模式。

        在操作系统中,用于上下文切换,悬起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。

2.3.2 SysTick(系统定时器),

        可编程的定时中断(自检),为操作系统提供时间片,进行轮询和抢占式调度。设置方法可以参考《CM3权威指南CnR2 宋岩 译》。

三、RT_Thread线程结构

        RT_Thread操作系统每个线程有独立的线程控制块(TCB)和线程栈,线程栈主要用于分配临时变量,在线程切换和初始化时,存储线程环境(通用寄存器+状态寄存器),寄存器排列顺序固定,如下图所示,可分为自动出入栈部分和手动出入栈部分,自动出入栈部分在CM3内核模式切换时由硬件自动实现(见2.2.3章节),手动出入栈部分则需要上下文切换代码实现。

四、RT_Thread启动分析

        在分析操作系统上下文切换之前,先从宏观上分析操作系统启动和运行的过程。

(1)程序运行先进入特权级Thread模式,系统初始化完成后,取最高优先级线程作为第一个线程运行,执行启动第一个线程接口,触发PendSv中断,进入特权级Handler模式,寄存器LD强制为0XFFFFFFF9;执行上下文切换代码,将第一个线程中的环境(通用寄存器组+状态寄存器组)出栈,并通过执行BX 0XFFFFFFFD,跳转到用户级线程模式执行第一个线程的代码,此时使用的堆栈指针为PSP,指向第一个线程的线程栈。

(2)启动第一个线程接口同时会启动时间片中断(SysTick),为操作系统提供心跳和定时器周期轮询线程的功能,这样操作系统正常运行。

(3)由图中可以看出,操作系统正常运行后,只在用户级Thread模式和特权级Handler模式之间切换,如果不出现异常是不会在返回特权级Thread模式的。

五、RT_Thread上线文切换

5.1 上下文切换本质

        RT_Thread操作系统进行上下文切换方法是挂起PendSv中断,在中断中将线程模式下的寄存器(通用寄存器+状态寄存器)入栈到当前线程栈,从另一线程(高优先级)的线程栈中出栈环境保存的寄存器数值,然后恢复到线程模式;本质是保存和切换寄器(通用寄存器+状态寄存器)数值。

5.2 上下文切换场景

        RT_Thread操作系统支持抢占式调度,在线程运行过程中会涉及到上下文切换,其场景可分为2类:

被动切换:当有更高优先级线程就绪时,调度器强制将当前线程寄存器状态入栈,将高优先级线程寄存器状态出栈,实现任务切换;此过程一般在定时器线程或SysTick中断中触发

主动切换:当前任务主动进入阻塞(vTaskDelay)、接收消息阻塞、接收信号阻塞、释放新线程等;会主动触发上下文切换。

5.3 上下文切换源码实现

        上下文切换接口和相关变量如下所示:

// 相关函数接口/************【1】启动第一个线程********************************************/
void rt_hw_context_switch_to(rt_ubase_t to);
/************【2】线程中上线文切花*******************************************/
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to);
/************【3】中断中上下文切换*******************************************/
void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to);// 相关变量
rt_uint32_t rt_interrupt_from_thread;         // 指向原线程栈顶指针
rt_uint32_t rt_interrupt_to_thread;           // 指向目的线程栈栈顶指针
rt_uint32_t rt_thread_switch_interrupt_flag;  // 上线文正在切换中标志

5.3.1 启动第一个线程

        操作系统启动时会选择最高优先级的线程作为第一个线程启动,调用方式如下,形参为线程的栈顶指针地址,根据2.2.4章节描述,CM3内核会自动使用R0寄存器传递形参(rt_uint32_t)&to_thread->sp。

rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);

        第一个线程汇编启动接口如下所示,设置好目的线程rt_interrupt_to_thread和上下文切换标志rt_thread_switch_interrupt_flag后,触发PendSv中断,在PendSv中断中执行上下文切换。

;函数声明
rt_hw_context_switch_to    PROC
EXPORT rt_hw_context_switch_to;【1】目的线程rt_interrupt_to_thread指向启动线程栈指针,即(rt_uint32_t)&to_thread->spLDR     r1, =rt_interrupt_to_threadSTR     r0, [r1];【2】原线程rt_interrupt_from_thread指向空,设置为0LDR     r1, =rt_interrupt_from_thread               MOV     r0, #0x0STR     r0, [r1];【3】上下文切换标志rt_thread_switch_interrupt_flag 设置为1LDR     r1, =rt_thread_switch_interrupt_flag    MOV     r0, #1STR     r0, [r1];【4】设置中断PendSV和中断SysTick优先级LDR     r0, =NVIC_SYSPRI2 LDR     r1, =NVIC_PENDSV_PRILDR.W   r2, [r0,#0x00]                            ; readORR     r1,r1,r2                                  ; modifySTR     r1, [r0]                                  ; write-back;【5】挂起PendSv (上下文切换)LDR     r0, =NVIC_INT_CTRL                          LDR     r1, =NVIC_PENDSVSETSTR     r1, [r0];【6】恢复 MSPLDR     r0, =SCB_VTOR                                    LDR     r0, [r0]LDR     r0, [r0]MSR     msp, r0;【6】在处理器级别启用中断,中断启用后,由于PendSv中断已经挂起,代码会跳转到PendsV中断执行CPSIE   F                                                        CPSIE   I;【7】跳转PendsV中断后,系统在用户级Thread模式和特权级handler模式间切换,永远运行不到此处!ENDP                                                            

5.3.2 调度中上下文切换

        操作系统调度正常运行后,时间片中断或线程可能会启动上下文切换,调用接口函数如下所示,根据2.2.4章节描述,CM3内核会自动使用R0寄存器传递形参(rt_uint32_t)&from_thread->sp,R1寄存器传递形参(rt_uint32_t)&to_thread->sp。

 rt_hw_context_switch((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);

         2种函数汇编代码虽然相同,但是内核模式不同,线程中执行接口是在用户级Thread模式,而时间片中断执行接口,是在特权级Handler模式。

        执行接口之前均对高优先级中断进行了屏蔽,所以,以下汇编执行过程不会中断。

       汇编启动接口如下所示, 设置好如下参数,

                rt_uint32_t rt_interrupt_from_thread;            // 原线程栈指针
                rt_uint32_t rt_interrupt_to_thread;                // 目的线程栈指针
                rt_uint32_t rt_thread_switch_interrupt_flag;  // 上线文正在切换中标志

        触发PendSv中断,在PendSv中断中执行上下文切换。

rt_hw_context_switch_interruptEXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch    PROCEXPORT rt_hw_context_switch;【1】上下文切换标志rt_thread_switch_interrupt_flag判断LDR     r2, =rt_thread_switch_interrupt_flagLDR     r3, [r2];【1.1】上下文切换标志rt_thread_switch_interrupt_flag为1,跳至_reswitch CMP     r3, #1BEQ     _reswitch                                     ;【1.2】上下文切换标志rt_thread_switch_interrupt_flag不为1,赋值为1MOV     r3, #1STR     r3, [r2]                                             ;【2】rt_interrupt_from_thread 指向源线程栈指针LDR     r2, =rt_interrupt_from_threadSTR     r0, [r2]
_reswitch;【3】rt_interrupt_to_thread指向目标线程栈指针LDR     r2, =rt_interrupt_to_thread STR     r1, [r2];【4】触发PendSv异常(进行上下文切换)LDR     r0, =NVIC_INT_CTRL LDR     r1, =NVIC_PENDSVSETSTR     r1, [r0];【5】跳出BX      LRENDP

5.3.3 上下文切换源码分析

        从5.3.1章节5.3.2章节可见,不管是启动第一个线程还是系统运行中线程切换,最终执行的位置是PendSv中断,并且在执行中断前,已经通过全局变量传输参数。

        rt_uint32_t rt_interrupt_from_thread;            // 原线程栈指针
        rt_uint32_t rt_interrupt_to_thread;                // 目的线程栈指针
        rt_uint32_t rt_thread_switch_interrupt_flag;  // 上线文正在切换中标志

        如果是启动第一个线程,CM3内核会从特权级Thread模式进入特权级Handler模式,R14数值更新为0XFFFFFFF9,执行完上下文切换后, R14数值更新为0XFFFFFFFD,通过指令BX 0xFFFFFFFD返回到用户级Thread模式。

         如果,操作系统调度启动后,线程中进行上下文切换,会先屏蔽中断,再挂起PendSv,等恢复中断后,PendSv才能响应,CM3内核会从特权级Thread模式进入特权级Handler模式,R14数值更新为0XFFFFFFFD;寄存器R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSR
自动通过指针PSP入栈,堆栈指针切换为MSP。

         如果,时间片执行上下文切换,等时间片中断完全执行完成后,才能响应PendSv中断。

        上下文切换汇编源码分析如下:

PendSV_Handler   PROC
EXPORT PendSV_Handler; 【0.1】如果用户级线程模式进入中断,R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSR;  自动通过指针PSP入栈,堆栈指针切换为MSP,; 【1】屏蔽除NMI和Fault外的中断MRS     r2, PRIMASK      ;中断屏蔽寄存器PRIMASK暂存,用于恢复CPSID   I                ;屏蔽除NMI和Fault中断; 【2】判断上下文切换标志标志rt_thread_switch_interrupt_flagLDR     r0, =rt_thread_switch_interrupt_flag  LDR     r1, [r0]            ; 【2.1】上下文切换未进行,正常切换,否则执行pendsv_exit,退出切换                                              CBZ     r1, pendsv_exit                                            ; 【3】清除上下文切换标志标志rt_thread_switch_interrupt_flagMOV     r1, #0x00 STR     r1, [r0] ; 【4】判断原线程rt_interrupt_from_thread,LDR     r0, =rt_interrupt_from_thread                      			 LDR     r1, [r0]; 【4.1】原线程rt_interrupt_from_thread无效,跳至switch_to_thread,直接恢复目的线程CBZ     r1, switch_to_thread ; 【4.2】原线程rt_interrupt_from_thread有效,对原线程进行环境保存MRS     r1, psp            ;R1用作原线程栈指针STMFD   r1!, {r4 - r11}    ;向原线程栈入栈R4--R11,同时调整R1LDR     r0, [r0]           ;取原线程控制块栈指针*sp,即rt_interrupt_from_threadSTR     r1, [r0]           ;sp更新为最新的栈指针,即完成入栈操作的R1;【5】对目的线程进行环境恢复
switch_to_threadLDR     r1, =rt_interrupt_to_threadLDR     r1, [r1]LDR     r1, [r1]            ; R1用作目的线程栈指针LDMFD   r1!, {r4 - r11}     ; 从目的线程栈出栈 R4-R11,同时调整R1MSR     psp, r1             ; 线程模式堆栈指针psp更换为目的线程栈指针,即完成出栈操作的R1;【6】完成手动部分,恢复屏蔽,进行自动出栈
pendsv_exitMSR     PRIMASK, r2         ; 恢复屏蔽寄存器ORR     lr, lr, #0x04       ; 出栈默认寄存器BX      lr                  ; 执行BX 0xFFFFFFFD,返回用户级线程模式,根据堆栈指针psp,; 自动出栈R0、R1、R2、R3、R12、R14(LR)、R15(PC)、XPSRENDP

5.3.4 上下文切换逻辑图

5.4 上下文切换实例分析

        假设线程A由运行态切换到了阻塞态,而线程B是当前最高优先级的就绪态任务,线程A主动启动PendSv中断,上下文切换过程如下所示:

        由上图可见,对比切换前和切换后,线程A和线程B的栈状态正好相反,线程A进行了环境保存,线程B进行了环境恢复。

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

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

相关文章

信息安全基础(补充)

)的内容主要有数据备份、数据修复、系统恢复等。响应(Respons)的内容主要有应急策略、应急机制、应急手段、入侵过程分析及安全状态评估等。 面向数据挖掘的隐私保护技术主要解决高层应用中的隐私保护问题,致力于研究如何根据不同…

android studio 导入github里的项目后提示:Add Configuration

原文链接:https://blog.csdn.net/weixin_45677723/article/details/125940912 从github上面clone项目,出现下图问题: 解决问题: 我这个的情况是因为多文件嵌套了,我用Android Studio打开的是A文件,而B项…

移除重复节点

题目链接 移除重复节点 题目描述 注意点 链表未排序链表长度在[0, 20000]范围内链表元素在[0, 20000]范围内 解答思路 使用Set存储访问过的链表中出现的节点值,当遍历到链表的某个节点在Set中出现过,则需要将该节点的前一个节点next指针指向该节点的…

InternLM2-Math-Plus全面升级,全尺寸最强的开源数学模型

总览 数学能力是大语言模型推理水平的重要体现。上海人工智能实验室在推出领先的开源数学模型InternLM2-Math的三个月之后对其进行了升级,发布了全新的 InternLM2-Math-Plus。升级后的 InternLM2-Math-Plus 在预训练和微调数据方面进行了全面的优化,显著…

【二叉树】非递归实现前中后序遍历

目录 前言 算法思想 非递归实现前序遍历 过程分析 代码 非递归实现中序遍历 过程分析 代码 非递归实现后序遍历 过程分析 代码 前言 1)前序:根 左子树 右子树 2)中序:左子树 根 右子树 3)后序&#xff1…

邮箱调用接口的服务有哪些?怎么配置接口?

邮箱调用接口安全性如何保障?使用邮箱服务器的方法? 邮箱调用接口为各种应用和系统提供了便捷的电子邮件发送与接收功能。选择合适的邮箱调用接口服务可以大大提升工作效率和用户体验。本AokSend将探讨一些主要的邮箱调用接口服务。 邮箱调用接口&…

MySQL(进阶)--索引

目录 一.存储引擎 1.MySQL体系结构​编辑 2.存储引擎简介 3.存储引擎特点 (1.InnoDB (2.MyISAM (3.Memory 4.存储引擎选择 二.索引 1.索引概述 2.索引结构 3.索引分类 4.索引语法 (1.创建索引 (2.查看索引 (3.删除索引 5.SQL性能分析 (1.SQL执行频率 (2.慢查…

【Sql Server】随机查询一条表记录,并重重温回顾下自定义函数的封装和使用

大家好,我是全栈小5,欢迎来到《小5讲堂》。 这是《Sql Server》系列文章,每篇文章将以博主理解的角度展开讲解。 温馨提示:博主能力有限,理解水平有限,若有不对之处望指正! 目录 前言随机查询语…

Android 中资源文件夹RES/RAW和ASSETS的使用区别

文章目录 1、res/raw 文件夹1.1、特点1.2、使用方法1.3、示例: 2. assets 文件夹2.1、特点2.2、使用方法2.3、示例: 3、使用场景3.1、res/raw 使用场景3.2、assets 使用场景 4、比较与选择5、文件夹选择的建议6、 示例代码总结6.1、res/raw 示例6.2、ass…

电瓶车进电梯识别报警摄像机

随着电动车的普及,越来越多的人选择电动车作为出行工具。在诸多场景中,电梯作为一种常见的交通工具,也受到了电动车用户的青睐。然而,电动车进入电梯时存在一些安全隐患,为了提高电动车进电梯的安全性,可以…

小程序自动化辅助渗透脚本(2024)

简介 1.还在一个个反编译小程序吗? 2.还在自己一个个注入hook吗? 3.还在一个个查看找接口、查找泄露吗? 现在有自动化辅助渗透脚本了,自动化辅助反编译、自动化注入hook、自动化查看泄露 注:本工具仅用于学习交流&…

Java中的JSON神器,如何轻松玩转复杂数据结构

哈喽,大家好,我是木头左! 一、揭秘JSON世界的基石 在Java的世界中,JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于文本,易于阅读和编写,同时也易于…

站内信设计

参考文章:https://cloud.tencent.com/developer/article/1684449 b站站内信业务设计: 消息的类型分为: 1、系统消息 2、、点赞、回复等用户行为之间的消息(事件提醒) 3、用户之间的消息 系统消息 用一个用户消息表可以吗? 可…

XS2185一款八通道以太网供电控制器

XS2185是一款八通道以太网供电控制器。 XS2185通过侦测各通道的DET管脚输入电压 来判断是否有合格的负载/PD接入系统,以决定 是否开启MOS供电开关。 当通道已经处于供电状态时,XS2185通过侦 测SENSE管脚的输入电压,以判断供电是否发生 …

免费,Python蓝桥杯等级考试真题--第15级(含答案解析和代码)

Python蓝桥杯等级考试真题–第15级 一、 选择题 答案:B 答案:D 解析:集合的并集运算有两种方式,一种是使用“|”运算符进行操作,另一种是使用union()方法来实现,故答案为D。 答案:A 解析&…

Caused by: java.lang.IllegalArgumentException: Unknown flag 0x1000

Dubbo使用Tomcat安装admin2.5.x管理平台时发生的错误: Caused by: java.lang.IllegalArgumentException: Unknown flag 0x1000 解决方法: 将本地的jdk环境变量切换成jdk8即可。

[ C++ ] 类和对象( 下 )

初始化列表 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。 class Date { public: Date(int year, int month, int day): _year(year), _month(month), _d…

视频汇聚/云存储/安防监控EasyCVR接入GB28181设备未回复ack信息的原因排查

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。 用户反馈,设备通过国标GB28181注…

kubeadm引导欧拉系统高可用的K8S1.28.X

文章目录 一. 核心组件架构二. 有状态与无状态应用三. 资源对象3.1 规约与状态3.2 资源的分类-元数据,集群,命名空间3.2.1 元数据3.2.2 集群资源 3.3 命名空间级3.3.1 pod3.3.2 pod-副本集3.3.3 pod-控制器 四. Kubeadm安装k8s集群4.1 初始操作4.2 ~~所有节点安装Docker&#x…

关于高性能滤波器和普通型滤波器的区别说明

高性能滤波器和普通型滤波器在性能和滤波效果上存在显著差异。以三安培为代表分析高性能滤波器和普通型滤波器的区别: 从上图曲线可看出: 1.高性能滤波器和普通型滤波器的滤波范围不同。普通型滤波器有效滤波范围为 150KHz~30MHz,而高性能滤…