第18章 中断和异常的处理与抢占式多任务

第18章 中断和异常的处理与抢占式多任务

中断和异常

中断和异常概述

中断(Interrupt):

  • 硬件中断是由外围硬件设备发出的中断信号引发的,以请求处理器提供服务。
  • 软中断是由int n指令引发的中断处理,n是中断号或者叫类型码。

异常(Exception):是处理器内部产生的中断,表示在指令执行的过程中遇到了错误的状况。当处理器执行一条非法指令,或者因条件不具备,指令不能正常执行时,将引发这种类型的中断。

分为三种类型:

  • 指令执行异常:程序中的错误;
  • 程序调试异常:为调试程序准备的;
  • 机器检查异常:域处理器型号相关的,比如总线错误、奇偶校验异常等。

根据异常情况的性质和严重性,异常又分为以下三种,

  • 故障(Faults):故障通常是可以纠正的。
  • 陷阱(Traps):陷阱中断通常在执行了截获陷阱条件的指令之后立即产生,如果陷阱条件成立的话。
  • 终止(Aborts):最严重的错误,这种异常发生时,程序或者任务都不可能重新启动。

中断描述符表、中断门和陷阱门

中断向量表(Interrupt Vector Table, IVT):实模式下位于内存最低端的1KB内存,是中断向量表(IVT),定义了256种中断的入口地址,包括16位段地址和16位段内偏移量,占用4字节。

中断描述符表(Interrupt Descriptor Table, IDT):在保护模式下,处理器对中断的管理是相似的,但并非使用传统的中断向量表来保存中断处理过程的地址,而是中断描述符表。在这个表里保存的是和中断处理过程有关的描述符,包括中断门、陷阱门和任务门。

中断门格式:

image

陷阱门格式:和中断门格式相比,就是TYPE位上最后一位为1。

image

中断描述符表寄存器:在处理器内部,有一个48位的中断描述符表寄存器(Interrupt Descriptor Table Register, IDTR),保存着中断描述符表在内存中的线性基地址和界限。

image

中断处理过程

  • 在保护模式下,当中断和异常发生时,处理器用中断向量乘以8的结果去访问IDT,从中取得对应的描述符。
  • 从中断门或者陷阱门描述符中可以获取到目标代码段描述符的选择子,可以从GDT或者LDT中获取到代码段描述符信息。
  • 通过代码段描述符的基地址和中断门陷阱门中的偏移量就可以定位到具体的中断程序了。

image

本章代码清单

本章代码实现的功能,创建内核任务、用户任务1、用户任务2,然后通过中断的方法在几个任务之间切换,不同的任务打印不同的字符串。

image

内核的加载和初始化

创建中断描述符表

put_string例程改进:在打印开始的时候关闭中断,返回前开启。避免打印字符串的过程被中断。

;字符串显示例程
put_string:cli
.....sti

中断描述符标IDT位置:在内核程序的开始部分(第13行)增加了一个常量idt_linear_address,被定义为0x1F000,指定中断描述符表IDT的线性地址。

idt_linear_address    equ  0x1F000 ;中断描述符表的线性地址

内存布局映像图:

image

处理器内部异常:在保护模式下,前20个中断向量基本上都是留给处理器内部异常的。原则上每个异常都需要根据产生的原因单独处理。书中只是为了说明问题,提供了一个 general_exception_handler 来处理所有异常。

general_exception_handler: ;通用的异常处理过程,就是打印异常信息,停机。mov ebx,excep_msgcall sys_routine_seg_sel:put_stringhlt

创建处理器需要的20个异常处理程序的中断门描述符

;前20个向量是处理器异常使用的
mov eax,general_exception_handler  ;门代码在段内偏移地址
mov bx,sys_routine_seg_sel         ;门代码所在段的选择子
mov cx,0x8e00                      ;中断门属性,0特权级
;1 00  0_1110_0000_0000,32位中断、0特权级
;P DPL 0_D110_0000_0000
call sys_routine_seg_sel:make_gate_descriptor ;生成门描述符,EDX:EAX

安装对应20个处理器异常的中断门描述符

     mov ebx,idt_linear_address         ;中断描述符表的线性地址xor esi,esi                        ;从第0个开始安装
.idt0:mov [es:ebx+esi*8],eax             ;中断门描述符低32位mov [es:ebx+esi*8+4],edx           ;中断门描述符高32位inc esi                            ;安装下1个cmp esi,19                         ;安装前20个异常中断处理过程jle .idt0

安装通用中断处理程序的中断门描述符

     ;其余为保留或硬件使用的中断向量mov eax,general_interrupt_handler  ;门代码在段内偏移地址mov bx,sys_routine_seg_sel         ;门代码所在段的选择子mov cx,0x8e00                      ;32位中断门,0特权级call sys_routine_seg_sel:make_gate_descriptormov ebx,idt_linear_address         ;中断描述符表的线性地址
.idt1:mov [es:ebx+esi*8],eax             ;esi前面已经加到20了,从20开始     mov [es:ebx+esi*8+4],edxinc esicmp esi,255                        ;安装普通的中断处理过程jle .idt1

其中 general_interrupt_handler 的处理过程很简单,只是给8259A中断芯片发送中断结束命令。

general_interrupt_handler:                  ;通用的中断处理过程push eaxmov al,0x20                        ;中断结束命令EOIout 0xa0,al                        ;向从片发送out 0x20,al                        ;向主片发送pop eaxiretd       ;确保从栈中弹出一个双字。

iretd(Interrupt Return Double):确保从栈中弹出一个双字。

[bits 16]
iret        ;CF
iretd       ;66 CF[bits 32]   ; 
iret        ;CF
iretd       ;CF

创建实时时钟中断处理程序的中断门描述符:书中切换进程使用实时时钟中断,默认的中断号是0x70,处理过程为 rtm_0x70_interrupt_handle

创建0x70号中断的中断门,并安装在中断描述符表中,以替换原先那个通用中断处理过程的中断门。

;设置实时时钟中断处理过程
mov eax,rtm_0x70_interrupt_handle  ;门代码在段内偏移地址
mov bx,sys_routine_seg_sel         ;门代码所在段的选择子
mov cx,0x8e00                      ;32位中断门,0特权级
call sys_routine_seg_sel:make_gate_descriptormov ebx,idt_linear_address         ;中断描述符表的线性地址
mov [es:ebx+0x70*8],eax            ;位置在0x70号位置
mov [es:ebx+0x70*8+4],edx

其中 rtm_0x70_interrupt_handle 例程中调用了任务调度的方法。

rtm_0x70_interrupt_handle:                  ;实时时钟中断处理过程pushadmov al,0x20                        ;中断结束命令EOIout 0xa0,al                        ;向8259A从片发送out 0x20,al                        ;向8259A主片发送mov al,0x0c                        ;寄存器C的索引。且开放NMIout 0x70,alin al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况;请求任务调度call sys_routine_seg_sel:initiate_task_switchpopadiretd

中断执行时,可能系统还没有任务,所以 initiate_task_switch 例程中增加了是否有任务的判断:

mov eax,[es:tcb_chain] ;获取tcb链表表头指针
cmp eax,0              ;检测头指针是否为0 
jz .return             ;为0,则直接返回

加载IDTR:把中断描述符表的基地址和界限值加载到中断描述符表寄存器(IDTR)中

lidt m

内核数据段中,标号pidt这里开辟了6字节的空间。其中,前2字节是中断描述符表的界限值;后4字节是中断描述符表的线性地址。界限值和线性地址是在程序中填写的,所以这里初始化为0。

mov word [pidt],256*8-1            ;IDT的界限
mov dword [pidt+2],idt_linear_address
lidt [pidt]                        ;加载中断描述符表寄存器IDTR

8259A芯片的初始化

引子:8259A芯片的主片的中断向量为0x08~0x0F,从片的中断向量是0x70~0x77。在32位处理器上,0x08~0x0F已经被处理器用作异常向量。

中断向量0x20~0xFF(32~255)是用户可以自由分配,所以需要将8259A的主片0x080x0F中断向量调整到0x200x27。

实现:使用初始化命令字(Initialize Command Word,ICW),以设置它的工作方式,共有4个初始化命令字,分别是ICW1~ICW4,都是单字节命令。

  • ICW1用于设置中断请求的触发方式,以及级联的芯片数量;
  • ICW2用于设置每个芯片的中断向量;
  • ICW3用于指定用哪个引脚实现芯片的级联;
  • ICW4用于控制芯片的工作方式。

这里就是8259A芯片规则进行设置即可.

;设置8259A中断控制器
mov al,0x11                        ;设置主片
out 0x20,al                        ;ICW1:边沿触发/级联方式
mov al,0x20                        
out 0x21,al                        ;ICW2:起始中断向量为0x20,只需要设置一个起始的就可以了。
mov al,0x04
out 0x21,al                        ;ICW3:从片级联到IR2
mov al,0x01
out 0x21,al                        ;ICW4:非总线缓冲,全嵌套,正常EOImov al,0x11                        ;设置从片
out 0xa0,al                        ;ICW1:边沿触发/级联方式
mov al,0x70
out 0xa1,al                        ;ICW2:起始中断向量
mov al,0x02                        ;!!
out 0xa1,al                        ;ICW3:从片级联到IR2
mov al,0x01
out 0xa1,al                        ;ICW4:非总线缓冲,全嵌套,正常EOI

设置和时钟中断有关的硬件状态:在第10章 中断和动态时钟显示有更详细的说明。

;设置和时钟中断相关的硬件
mov al,0x0b                        ;RTC寄存器B
or al,0x80                         ;阻断NMI
out 0x70,al
mov al,0x12                        ;设置寄存器B,禁止周期性中断,开放更
out 0x71,al                        ;新结束后中断,BCD码,24小时制in al,0xa1                         ;读8259从片的IMR寄存器
and al,0xfe                        ;清除bit 0(此位连接RTC)
out 0xa1,al                        ;写回此寄存器mov al,0x0c
out 0x70,al
in al,0x71                         ;读RTC寄存器C,复位未决的中断状态

开放硬件中断:中断设置完成后就可以开放。

sti

中断和异常处理程序的保护

特权级保护:在进入指定的中断处理过程前,处理器要对中断和异常处理程序进行特权级保护。当目标代码段描述符的特权级低于当前特权级时,即在数值上 CPL<目标代码段的DPL 时,不允许将控制转移到中断或者异常处理程序。

简单的说,中断或者异常处理程序的 特权级要更高。

不同之处:中断和异常处理程序的特权级保护也有一些特别之处。

  • 不检查RPL:因为中断和异常的向量只是一个代表中断号码的数字,没有RPL字段,故当处理器进入中断或异常处理程序,或者通过任务门发起任务切换时,不检查RPL。
  • DPL检测:中断门、陷阱门也有自己的描述符特权级DPL,即门的DPL,但是通常情况下不针对该DPL进行检查,
    • 除了用软中断int n和单步中断int3,以及into引发的中断和异常。在这种情况下,当前特权级CPL必须高于或者和门的特权级DPL相同,即在数值上,CPL<=门描述符的DPL。这主要是为了防止低特权级的软件通过软中断指令访问一些只为内核服务的例程,如页故障处理。相反的,对于硬件中断和处理器检测到异常情况而引发的中断处理,不检查们的DPL。

栈切换:当中断和异常发生时,任务可能正在特权级别为0的全局空间(内核)中执行,也可能正在特权级别为3的局部空间内执行。如果异常或中断处理程序运行在较高的特权级别上(数值较低),那么将切换栈。

  • 根据处理程序的特权级别,从当前任务的TSS取得相应栈段选择子和栈指针,处理器把旧栈的选择子和栈指针压入新栈。
  • (压入旧栈的选择子和栈指针是为了中断返回时,可以回到中断处继续执行。)
  • 处理器把EFLAGS、CS和EIP的当前状态压入新栈。
  • 对于有错误异常的代码,处理器还要把错误代码压入新栈,紧挨着EIP之后。

image

EFLAGS中IF位

  • 中断门:通过中断门进入中断处理程序时,寄存器EFLAGS的IF位被处理器自动清零,以禁止嵌套的中断,当中断返回时,将从栈中恢复寄存器EFLAGS的原始状态。(中断门的优先级很高,处理过程不允许中断。)
  • 陷阱门:陷阱中断的优先级比较低,当通过陷阱门进入中断处理程序时,寄存器EFLAGS的IF位保持不变,以允许其他中断优先处理。(陷阱的优先级低,允许在陷阱门处理过程中切换到其它中断)

寄存器EFLAGS的IF位仅影响硬件中断,对NMI、异常和int n形式的软件中断不起作用。

我理解就是NMI、异常和int n形式的软件中断不判断EFLAGS的IF位。

中断任务

中断是任务门:当中断发生时,如果根据中断向量表从IDT中找到的描述符是任务门,则不是进行一般的中断处理过程,而是发起任务切换。

image

使用任务门的好处

  • 被中断的那个程序或任务的整个执行环境可以被完整的保存起来(保存到它的TSS中)
  • 由于接管控制的是一个新的任务,因此,可以使用一个全新的0特权级栈。这可以有效地防止因当前任务的0特权级栈遭到破坏而使系统崩溃。
  • 由于是切换到一个新任务,因此,它有一个独立的地址空间。

使用任务门的坏处

  • 因为要保持大量的机器状态,并进行一系列的特权级和内存访问的检查工作,速度很慢。(貌似正常的任务切换也要做这些?)
  • 中断和异常发起的任务切换,不再保存CS、EIP的状态,但是在任务完成后,处理器需要把错误代码压入到新任务的栈中。(怎么获取新任务的?通过当前任务的TSS中的指向上一个TSS的指针可以获取到。)
  • 任务是不可重入的,因此在进入中断任务之后和执行iretd指令之前,必须关中断。
  • 只对通过int3、int n和into指令发起的任务切换实施特权级检查,数值上:CPL<=任务门的DPL(当前特权级高于等于任务门的特权级),其他情况不检查任务门的特权级。

错误代码

错误代码的格式如下:

image

  • EXT位:异常是由外部事件引发的(External Event)。等于1,表示异常是由NMI、硬件中断等引发的。
  • IDT位:指示描述符的位置(Descriptor Location)。(引用了一个异常的段)
    • 为“1”时,表示段选择子的索引部分(错误代码的位15~3)是指向中断描述符表。
    • 为“0”时,表示段选择子的索引部分指向GDT或者LDT。
  • IT位:TI位仅在IDT位是“0”的情况下才有意义。
    • 此位是“0”时,表示段选择子的索引部分指向GDT,
    • 否则,指向LDT。
  • 段选择子的索引部分:指示GDT/LDT内的段描述符,或者IDT内的门描述符,它就是我们平时所用的段选择子的高13位。

对于外部异常(通过处理器引脚触发),以及用软中断指令int n引发的异常,处理器不会压入错误代码,即使它原本是一个有错误代码的异常。

错误代码全0

  • 异常产生并非由于引用了一个特定的段。
  • 引用了一个段,只是段描述符是空描述符。

弹出错误代码:通过iret/iretd指令从中断处理程序返回时,处理器并不会自动弹出错误代码。因此,对于那些有异常代码的异常处理过程来说,必须在执行iret/iretd指令前,先从栈中移去(或弹出)错误代码。

异常处理过程:......pop xxx ;弹出错误代码iret

不会压入错误代码:对于外部异常(通过处理器引脚触发),以及用软中断指令int n引发的异常,处理器不会压入错误代码。

  • 分配给外部中断的向量号31~255;
  • 8259A或者IO APIC芯片可能会给出一个0~19的向量号;
int 0x0d ;向量号13,常规保护异常#GP

用定时中断实施任务切换

中断随时可能发生:当通过sti指令开放中断后,中断随时可能发生。假定在执行sti后的两行代码发生了0x70中断。

;指向下面两行时发生中断
mov ebx,message_0                    
call sys_routine_seg_sel:put_string

rtm_0x70_interrupt_handle中断处理过程

rtm_0x70_interrupt_handle:    ;实时时钟中断处理过程pushadmov al,0x20                        ;中断结束命令EOIout 0xa0,al                        ;向8259A从片发送out 0x20,al                        ;向8259A主片发送mov al,0x0c                        ;寄存器C的索引。且开放NMIout 0x70,alin al,0x71                         ;读一下RTC的寄存器C,否则只发生一次中断;此处不考虑闹钟和周期性中断的情况;请求任务调度call sys_routine_seg_sel:initiate_task_switchpopadiretd

中断结束命令EOI和寄存器C的索引这个在第10章介绍很详细了,我忘记了回去查一下就明白了。

判断TCB为空:对这个例程的调用是在中断处理过程内部进行,当中断发生时,系统中可能还不存在任何任务。因此需要判断任务控制块链表是否为空。

mov eax,[es:tcb_chain] ;获取TCB链表头指针
cmp eax,0              ;为0表示为空,如果有值则是下一个tcb的地址
jz .return             ;为0则返回

这里是不是可以用 int 0x70 触发一个中断?
目测是可以,尽管 rtm_0x70_interrupt_handle 有硬件触发的处理部分,但是通过软件触发,硬件的处理程序也没有冲突。

中断内实施任务切换:在中断内实施任务切换,可以使用jmp指令,从当前正在运行的任务切换到另一个空闲任务。中断的发生是随机的,但是,要在中断处理过程内执行任务切换,处理器必须正在执行一个任务。

假定任务在局部空间执行时触发了中断:

image

内核任务的创建

内核任务的创建和上一章基本一样,一个不同的地方。

临时关闭中断:创建任务之前,必须先禁止中断,创建内核任务完成后才能开启。预防任务创建到一半触发中断,程序崩溃。

;为内核任务创建任务控制块TCB
cli                        ;这里要先禁止中断
mov ecx,0x46
call sys_routine_seg_sel:allocate_memory
call append_to_tcb_link            ;将此TCB添加到TCB链中
mov esi,ecx......;任务寄存器TR中的内容是任务存在的标志,该内容也决定了当前任务是谁。
;下面的指令为当前正在执行的0特权级任务“程序管理器”后补手续(TSS)。
ltr cx
sti            ;这里开启中断

用户任务的创建和执行

临时关闭中断:加载用户程序时也需要先关闭中断,任务创建之后再开放中断。

切换到第1个用户任务:假定用户程序创建完成,并开放中断后,发生了0x70中断。这时

  • 系统中就内核任务和用户任务两个任务;
  • 当前运行的任务时内核任务;
  • 发生切换就一定切换到用户任务;

用户任务运行:就是循环打印一个字符串。

.do_prn:                            ;一直循环打印一个字符串mov ebx,message_1call far [fs:PrintString]jmp .do_prn;message_1定义:
message_1 db '[USER TASK]: ,,,,,,,,,,,,,,,,,,,,,,,',0x0d,0x0a,0

发生0x70中断切回内核任务:在第一个用户任务的执行期间,如果发生了0x70号中断,则又转到中断处理过程,并发起任务切换,这一次就会切回内核任务。

内核任务继续运行

1)切回内核:切换会内核后,从 initiate_task_switch 例程的 jmp far [edi+0x14] 后一行开始执行。

initiate_task_switch:......jmp far [edi+0x14]                ;任务切换;切换回来后从这里开始执行
.return:pop espop dspopadretf

这里执行完后就是返回上次中断发生的地方:

call load_relocate_program
sti
;上次假定是在这里发生了中断
;为说明任务切换而特意添加的无操作指令
nop     ;No Operation
nop
nop

2)创建第2个用户任务:接着内核任务继续创建第2个用户任务,第2个用户程序和第1个一样,就是打印的字符串变成一串C。

message_1 db '[USER TASK]: CCCCCCCCCCCCCCCCCCCCCCC',0x0d,0x0a,0

此时系统中就有3个任务了。

3)无限循环:内核任务执行到最后就是无限循环,无论切换到哪个任务再切换回来时都会反复执行这段代码。

.do_switch:mov ebx,core_msg2call sys_routine_seg_sel:put_string;清理已经终止的任务,并回收它们占用的资源call sys_routine_seg_sel:do_task_cleanhltjmp .do_switch;core_msg2 定义
core_msg2        db  '[CORE TASK]: I am working!',0x0d,0x0a,0

程序的编译和执行

开始执行本章程序:

  • 第15章的mbr.bin写入虚拟硬盘的逻辑0扇区;
  • 本章核心core.bin写入虚拟硬盘的逻辑1扇区;
  • 第1个用户程序bin写入虚拟硬盘的逻辑50扇区;
  • 第2个用户程序bin写入虚拟硬盘的逻辑100扇区;

执行效果:在内核任务、用户1任务、用户2任务之间来回切换,打印字符串。

image

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

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

相关文章

【Python】数据可视化之分布图

分布图主要用来展示某些现象或数据在地理空间、时间或其他维度上的分布情况。它可以清晰地反映出数据的空间位置、数量、密度等特征&#xff0c;帮助人们更好地理解数据的内在规律和相互关系。 目录 单变量分布 变量关系组图 双变量关系 核密度估计 山脊分布图 单变量分布…

5.数据结构与算法-类C语言的有关操作

元素类型说明 数组定义 C语言的动态内存分配 C动态存储分配 C的参数传递 传值方式 传地址方式 形参变化影响实参 形参变化不影响实参 数组名做参数 引用类型做参数

高通AI应用程序开发3:网络模型(一)

1. 支持的网络模型 Qualcomm神经处理SDK支持下表所列的网络模型。 有关支持的运行时和单个图层类型的限制和约束的详细信息&#xff0c;请参阅 限制 。 GPU运行时中支持的所有层对两种GPU模式都有效&#xff1a;GPU_FLOAT32_16_HYBRID和GPU_FLAAT16。GPU_FLOAT32_16_HYBRID-…

【刷点笔试面试题试试水】找错—使用strlen()函数代替sizeof计算字符串长度

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> using namespace std;void UpperCase(ch…

Qt Linguist手册-翻译员

翻译人员 Qt Linguist 是为 Qt 应用程序添加翻译的工具。一旦安装了 Qt&#xff0c;就可以像开发主机上的其他应用程序一样启动 Qt Linguist。 Qt Linguist 主窗口包含一个菜单栏和以下视图&#xff1a; 上下文 (F6) 用于从上下文列表中选择要翻译的字符串。字符串 (F7) 用于…

信号处理快速傅里叶变换(FFT)的学习

FFT是离散傅立叶变换的快速算法&#xff0c;可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的&#xff0c;但是如果变换到频域之后&#xff0c;就很容易看出特征了。这就是很多信号分析采用FFT变换的原因。另外&#xff0c;FFT可以将一个信号的频谱提取出来&am…

leetcode每日一题day19(24.9.29)——买票需要的时间

思路&#xff1a;在最开始的情况下每人需要买的票数减一是能保持相对位置不变的&#xff0c; 如果再想减一就有可能 有某些人只买一张票&#xff0c;而离开了队伍&#xff0c; 所有容易想到对于某个人如果比当前的人买的多就按当前的人数量算 因为在一次次减一的情况下&#xf…

从零开始手写STL库:Stack

从零开始手写STL库–Stack的实现 Gihub链接&#xff1a;miniSTL 文章目录 从零开始手写STL库–Stack的实现一、stack是什么&#xff1f;二、stack要包含什么函数总结 一、stack是什么&#xff1f; 栈是一种后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09…

计算机网络--TCP、UDP抓包分析实验

计算机网络实验 目录 实验目的 实验环境 实验原理 1、UDP协议 2、TCP协议 实验具体步骤 实验目的 1、掌握使用wireshark工具对UDP协议进行抓包分析的方法&#xff0c;掌握UDP协议的报文格式&#xff0c;掌握UDP协议校验和的计算方法&#xff0c;理解UDP协议的优缺点&am…

【数据库文档】数据库设计说明书(Word原件参考)

一、 总述 &#xff08;一&#xff09; 编写目的 二、 外部设计 &#xff08;一&#xff09; 环境说明 &#xff08;二&#xff09; 指导 三、 物理实现 &#xff08;一&#xff09; 物理结构 &#xff08;二&#xff09; 安全设计 四、 表设计结构 &#xff08;一&#xff09;…

SpringAOP学习

面向切面编程&#xff0c;指导开发者如何组织程序结构 增强原始设计的功能 oop:面向对象编程 1.导入aop相关坐标&#xff0c;创建 <!--spring依赖--><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spri…

Python 读取与处理出入库 Excel 数据实战案例(HTML 网页展示)

有如下数据&#xff0c;需要对数据合并处理&#xff0c;输出到数据库。 数据样例&#xff1a;&#x1f447; excel内容&#xff1a; 出入库统计表河北库.xlsx: 出入库统计表天津库.xlsx: 01实现过程 1、创建test.py文件&#xff0c;然后将下面代码复制到里面&#xff0c;最后…

广西容县霞烟鸡,品牌兴农,助力乡村振兴!

在两广与港澳地区,流传着一句深入人心的饮食谚语——“无鸡不成宴”,它不仅是一种习俗的体现,更是对餐桌礼仪与待客之道的深刻诠释。每逢家宴欢聚、祭祖庆典或盛宴宾客,一只精心烹制的鸡总是不可或缺的主角,其缺席往往被视为对宾客的不敬。在这片美食文化的沃土上,广西容县的霞…

个性化大语言模型:PPlug——让AI更懂你

在当今数字化转型的时代&#xff0c;大型语言模型&#xff08;LLMs&#xff09;已经成为了不可或缺的工具&#xff0c;它们在自然语言理解、生成和推理方面展现了非凡的能力。然而&#xff0c;这些模型普遍采用的是“一刀切”的方式&#xff0c;即对于相同的输入给予所有用户相…

uniapp监听滚动实现顶部透明度变化

效果如图&#xff1a; 实现思路&#xff1a; 1、使用onPageScroll监听页面滚动&#xff0c;改变导航条的透明度&#xff1b; 2、关于顶部图片的高度&#xff1a; 如果是小程序&#xff1a;使用getMenuButtonBoundingClientRect获取胶囊顶部距离和胶囊高度&#xff1b; 如果…

YOLOv8改进 - 注意力篇 - 引入SCAM注意力机制

一、本文介绍 作为入门性篇章&#xff0c;这里介绍了SCAM注意力在YOLOv8中的使用。包含SCAM原理分析&#xff0c;SCAM的代码、SCAM的使用方法、以及添加以后的yaml文件及运行记录。 二、SCAM原理分析 SCAM官方论文地址&#xff1a;SCAM文章 SCAM官方代码地址&#xff1a;SC…

Google Protocol Buffers快速入门指南

声明&#xff1a;未经作者允许&#xff0c;禁止转载。 概念 Portocol Buffer是谷歌提出来的一种序列化结构数据的机制&#xff0c;它的可扩展性特别强&#xff0c;支持C、C#、Java、Go和Python等主流编程语言。使用Portocol Buffer时&#xff0c;仅需要定义好数据的结构化方式…

Mysql梳理10——使用SQL99实现7中JOIN操作

10 使用SQL99实现7中JOIN操作 10.1 使用SQL99实现7中JOIN操作 本案例的数据库文件分享&#xff1a; 通过百度网盘分享的文件&#xff1a;atguigudb.sql 链接&#xff1a;https://pan.baidu.com/s/1iEAJIl0ne3Y07kHd8diMag?pwd2233 提取码&#xff1a;2233 # 正中图 SEL…

每日OJ题_牛客_添加逗号_模拟_C++_Java

目录 牛客_添加逗号_模拟 题目解析 C代码1 C代码2 Java代码 牛客_添加逗号_模拟 添加逗号_牛客题霸_牛客网 题目解析 读取输入&#xff1a;读取一行字符串。分割字符串&#xff1a;使用空格将字符串分割成单词数组。拼接字符串&#xff1a;将单词数组中的每个单词用逗号…

Oracle控制文件全部丢失如何使用RMAN智能恢复?

1.手动删除所有控制文件模拟故障产生 2.此时启动数据库发现控制文件丢失 3.登录rman 4.列出故障 list failure; 5.让RMAN列举恢复建议 advise failure; 6.使用RMAN智能修复 repair failure;