Linux内核深入学习 - 中断与异常(上)

中断与异常

中断通常被定义为一个事件:让事件改变处理器执行的指令顺序这样的事件,与CPU芯片内外部硬件电路产生的电信号相对应!

中断通常分为同步中断与异步中断:

同步中断指的是当指令执行时,由CPU控制单元产生的。之所以称为同步,是因为只有在一条指令终止执行后,CPU才会发出中断!

异步中断是由其他硬件设备依照CPU时钟信号随机产生的

在英特尔微处理器手册中:也会把同步和异步中断分别称为异常和中断

中断则是由间隔定时器或者io设备产生的,举个例子你敲击键盘的时候,你的一次按键就会引发一个中断,希望操作系统介入进行处理!

另一方面异常是由程序的错误产生的,或者是由内核必须处理的异常条件产生的!比如说内核通过发送一个信号来处理异常,或者内核执行恢复异常所需要的步骤,比如说缺页,比如说对内核服务的一个请求

中断信号的作用

顾名思义,中断信号提供了一种特殊的方式来让处理器转而去运行正常控制流之外的代码。当一个中断信号到达的时候CPU必须停止它当前所做的事情,转而切换去处理这些终端。为了做到这一点就需要把内核态堆栈保存PC当前的值,并且把中断相关类型的的一个地址放进程序计数器中。这样才会跳转去执行处理中断的代码。

中断处理器内核执行的最敏感的任务之一,因为它必须要满足:

  1. 让内核正打算去完成别的事情的,由于中断随时都会到来,因此内核的目标就是:让中断尽可能的处理完尽可能把更多的更详细的处理向后推,所以中断响应分为两个部分:

  • 关键而紧急的部分这一部分,内核立即执行。

  • 其推迟的部分,则是内核随后执行。

  1. 因为中断随时会到来,所以内核可能正在处理其中一个中断的时候,另一个中断又发生了。应该尽可能地允许这种情况发生,因为这将会保持更多的io设备处于忙状态。因此中断处理程序必须编写成可以使相应的内核控制路径以嵌套的方式进行,执行到最后一个内核控制路径终止时,内核可以恢复被中断进程的执行,或者如果中断信号已导致了重新调度,内核可以切换到另外的进程。

  2. 尽管内核在处理前一个中断的时候,可以接受新的中断,但在内核代码区中仍然存在着一些临界区,在这些临界区中中断必须被禁止

中断和异常

英特尔文档把中断和异常又分为了以下几类:

  1. 中断:

又分出两类即:

类型说明
可屏蔽中断IO设备发出的所有中断请求(Interruppt Request)都产生可屏蔽的中断,它处于两状态:屏蔽的和非屏蔽的,如果一个中断是被屏蔽的,那么控制单元会被它会忽略它
非可屏蔽中断只有少数的几个危机事件是这样的,非屏蔽的中断总是由CPU辨认

异常:

异常分两类,处理器探测异常和编程异常。

处理器探测异常有三种

类型说明
故障通常可以被纠正,一旦纠正,程序就可以在不失连贯性的情况下,重新开始!保存在EIP中的值就是引起故障的指令地址,因此当异常处理程序终止时,那条指令会被重新执行
陷阱在陷阱执行后,立即报告内核把控制权返回给程序后,就可以继续它的执行而不失去连贯性。保存在EIP中的值是一个随后要执行的指令地址,只有当没有必要重新执行已中止的指令时,才会触发陷阱。陷阱的主要目的是为了调试程序
异常终止异常终止指的是发生了一个严重的错误及控制单元出现了问题,不能在EIP寄存器中保存引发了这个异常指令所在的确切位置。异常终止用的报告严重的错误,如硬件故障或系统表中无效的值或不一样的值。

编程异常:在编程者发出请求时发生是由int或int3指令触发的当into(检查溢出)和bound(检查地址出界)指令检查的条件不为真时,会引发编程异常控制单元!编程异常当作陷阱来处理,编程异常也被称为软中断,这样的异常常有两种的用途执行:系统调用以及给调试程序通报一个特定的事件。

IRQ与中断

每个能够发出中断请求的硬件设备系都有一条名为IRQ的输出线,所有现有的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连,可编程中断控制器执行下列动作:

  • 监视IRQ线检查产生的信号,如果有两条或两条以上的IRQ线上产生信号,选择引脚编号较小的IRQ线

  • 如果一个引发信号出现在线上,那么它会把接收到的信发信号转换成对应的向量,把这个向量存放在中断控制器的一个io端口,从而允许CPU通过数据总线读取此向量。把引发信号发送到处理器的INTR引脚,也就是产生了一个中断

  • 等待直到CPU通过把这个中断信号写进可编程中断控制器的io端口号,来确认它,当这种情况发生时,清理INTR线。然后继续监视。

异常

这里给出一些常见的异常:

异 常 一 览 表向量号异常名称异常类型出错代码相关指令
0除法出错故障DIV,IDIV
1调试异常故障/陷阱任何指令
3单字节INT3陷阱INT 3
4溢出陷阱INTO
5边界检查故障BOUNT
6非法操作码故障非法指令编码或操作数
7设备不可用故障浮点指令或WAIT
8双重故障中止任何指令
9协处理器段越界中止访问存储器的浮点指令
0AH无效TSS异常故障JMP、CALL、IRET或中断
0BH段不存在故障装载段寄存器的指令
0CH堆栈段异常故障装载SS寄存器的任何指令、对SS寻址的段访问的任何指令
0DH通用保护异常故障任何特权指令、任何访问存储器的指令
0EH页异常故障任何访问存储器的指令
10H协处理器出错故障浮点指令或WAIT
11H—0FFH软中断陷阱INT n

中断向量表

中断描述符表IDT是一个系统表,它与每一个中断或者异常向量相联系

每一个向量在表中都有相应的中断或异常处理程序的入口地址,内核在允许中断发生前,必须适当初始化IDT

下图为IDT的结构表示图:

这些描述符是:

  • 任务门Task Gate:当中断信号发生时,必须取代当前进程的那个进程的TSS选择符,存放在任务门中

  • 中断门Interrupt Gate:包含段选择符和中断或异常处理程序的段内偏移量,当控制权转移到应适当的段时,处理器清IF标志,从而关闭即将会发生的可屏蔽中断。

  • 陷阱门Trap Gate:与中断门相似只是控制权传递到一个适当的段时处理器不修改IF标志Linux使用中断门处理中断应用陷阱门处理异常

硬件处理

现在描述CPU控制单元是如何处理中断和异常的!

我们现在假定内核已经被初始化,因此CPU将会在保护模式下运行。

当执行了一条指令后,CS和EIP这对寄存器将会包含下一条将要执行的指令的逻辑地址。在处理那条指令之前控制单元会检查在运行前一条指令是否已经发生一个中断或者异常,如果发生了,那么它将会:

  1. 确定与中断或异常关联的向量(看一眼是哪个异常)

  2. 读由寄存器指向的IDT表中的i项(肘!IDT表爆破)

  3. gdpr寄存器中获取GDP的基地址,并在GDP中查找,以读取IDT表项的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址(看看这个程序在哪里)

  4. 确信中断事由授权的中断发生源发出的,首先将当前特权级CPL与段描述符的描述符特权级DPL的相比较:如果CPL小于DPL了就会产生一个general protection异常,因为中断处理程序的特权不能低于引起中断的程序的特权!对于编程异常则需要做进一步的安全检查:比较CPL了与处于IDT中的门描述符的DPL了,如果DPL小于CPL了那么就产生一个general protection,这最后一个检查可以避免用户应用程序访问特殊的陷阱门和中断门(配不配处理这个异常)

  5. 检查是否发生了特权级变化:也就是说CPL是否不同于所选择的段描述符的DPL,如果是,控制单元必须开始使用与新的特权级相关的栈,通过执行以下步骤来做到这一点:

    1. 读TR寄存器以访问运行进程的TSS段

    2. 用于新特权级相关的栈段和栈指针的正确值装载SS和ESP寄存器这些值,可以在TSS中找到在新的段中

    3. 保存SS和ESP以前的值,这些值定义了与旧特训级相关的栈的逻辑地址。

  6. 如果故障已经发生,用引起异常的指令地址装载CS和EIP寄存器,从而使得这条指令能够再次被执行!

  7. 在栈中保存eflags,cs,EIP等内容,如果引用异常产生了一个硬件出错码,把它保存到栈中,装在CS和EIP寄存器其值分别为IDT表中第I项门描述服务的段选择符和偏移量字段,这些值给出了中断或者异常处理的第一条指令的逻辑地址!

控制单元所执行的最后一步就是跳转到这些异常处理程序,换句话说处理完中断信号后控制单元所执行的指令,就是被选中处理程序的第一条指令!

当中断或处理结束后和异常被处理结束后相应的处理程序必须参与生一条iret指令,他把控制权转交给被中断的进程。这将会迫使控制单元:

  1. 用保存在栈中的值装在CS或eflags寄存器,如果一个硬件出错码曾经被压入栈中,并且在EIP内容的上面,那么执行Iret指令前必须弹出这个硬件错误码(准备回家)

  2. 检查处理程序的CPL是否等于CS中的最低两位的值如果是iret终止执行,否则执行下一步。(看看特权级够不够)

  3. 从栈中装载SS和ESP寄存器因此返回到与特权级相关的栈(也就是恢复栈)

  4. 检查DS ES FS 以及 GS段寄存器的内容,如果其中一个寄存器包含选择符是一个段描述符,并且其的DPL值小于CPL了,那么我们会清理相应的段寄存器控制单元,这么做是为了防止用户态的程序利用内核以前所用的段寄存器,如果不清理这些寄存器,那么一些怀有恶意的用户态程序就有可能利用他们来访问内核地址。(安全处理,安全恢复环境)

中断和异常处理程序的嵌套执行

每个中断或异常都会引起一个内核控制路径,或者说当前的进程在内核态执行单独的指令序列。

内核控制路径可以任意嵌套!一个中断处理程序可以被另一个中断处理程序所中断,因此这样就引起了内核控制路径的嵌套执行。允许内核控制路径嵌套执行必须要付出相应的代价,也就是中断处理程序必须永不阻塞换,中断处理程序在运行期间是不能够发生进程切换。

基于以下两个主要原因,Linux交错执行内核控制路径:

  1. 为了提高可编程中断控制器和设备控制中器的吞吐量。假定设备控制器在一条线上产生了一条信号,pic把这个信号转换成一个外部中断,然后pic和控制设备器保持阻塞一直到pic从内核CPU处接收一条应答信息!由于内核控制路径的交错执行内核即使正在处理前一个中断也能够发送应答。

  2. 为了实现一种没有优先级的中断模型,每个C中断处理程序都可以被另一个中断处理程序所延缓,因此在硬件设备之间没必要预定义优先级,这简化了内核代码,也提高了内核的可移植性!

初始化IDT

Linux在基于Intel给出的三种门之外,还更加细分了他们:

中断门(interrupt gate):用户态的进程不能访问的一个lntel中断门(门的DPL字段为0)。所有的Linux中 断处理程序都通过中断门激活,并全部限制在内核态。

系统门(syslem gate):用户态的进程可以访问的一个Intel陷阱门(门的DPL字段为到.通过系统门来激 活三个Linux异常处理程序,它们的向量是4,5及128,因此,在用户态下.可以 发布into、 bound及int $Ox80三条汇编语言指令。

系统中断门(system interrupt gate):能够被用户态进程访问的Intel中断门(门的DPL字段为3). 与向量3相关的异常 处理程序是由系统中断门激活的,因此,在用户态可以使用汇编语言指令int3.

陷阱门(Irapgate):用户态的进程不能访问的一个Inte)陷阱门(f]的DPL字段为0). 大部分Linux异 常处理程序都通过陷阱门来激活.

任务门(task gate):不能被用户态进程访问的Intel任务门(门的DPL字段为0).Linux对"Doublefault" 异常的处理程序是由任务门激活的.

IDT会被初始化两次。第一次是在BIOS程序中,此时CPU还工作在实模式下。一旦Linux启动,IDT会被搬运到RAM的受保护区域并被第二次初始化,因为Linux不会使用任何BIOS程序。

IDT结构被存储在idt_table表中,包含256项。idt_descr变量存储IDT的大小和它的地址,在系统的初始化阶段,内核用来设置idtr寄存器,专用汇编指令是lidt。

内核初始化的时候,汇编函数setup_idt()用相同的中断门填充idt_table表的所有项,都指向ignore_int()中断处理函数:

setup_idt:
    lea ignore_int, %edx
    movl $(__KERNEL_CS << 16), %eax
    movw %dx, %ax           /*  = 0x0010 = cs */
    movw $0x8e00, %dx       /* 中断门,DPL=0 */
    lea idt_table, %edi     /* 加载idt表的地址到寄存器edi中 */
    mov $256, %ecx
rp_sidt:
    movl %eax, (%edi)       /* 设置中断处理函数 */
    movl %edx, 4(%edi)      /* 设置段描述符 */
    addl $8, %edi           /* 跳转到IDT表的下一项 */
    dec %ecx                /* 自减 */
    jne rp_sidt
    ret

中断处理函数ignore_int(),也是一个汇编语言编写的函数,相当于一个null函数,它执行:

  1. 保存一些寄存器到堆栈中。

  2. 调用printk()函数打印Unknown interrupt系统消息`。

  3. 从堆栈中恢复寄存器的内容。

  4. 执行iret指令回到调用处。

正常情况下,此时的中断处理函数ignore_int()是不应该被执行的。如果在console或者log日志中出现Unknown interrupt的消息,说明发生硬件错误或者内核错误。

完成这次IDT表的初始化之后,内核还会进行第二次初始化,用真正的trap或中断处理函数代替刚才的null函数。一旦这两步初始化都完成,IDT表就包含具体的中断、陷阱和系统门,用以控制每个中断请求。

中断处理

这里讨论三种中断类型:

IO中断,时钟中断。和处理器间中断io

中断处理程序必须足够灵活地给多个设备同时提供服务,比如说在PCI总线的体系架构中几个设备可以共享一个IRQ线,这也就意味着仅仅中断向量是并不能说明所有问题的!中断处理程序的灵活性是以两种不同的方式实现的:

IRQ共享

中断处理程序执行多个中断服务例程,每个中断服务例程是一个与单独设备相关的函数,因此不可能预先知道哪个特定的设备产生,因此每个IRQ,也就是中断服务例程,都会被执行验证它的设备是否需要关注,如果是,当设备产生中断时则需要执行相关的所有操作

IRQ动态分配

一条线可能在最后的时刻才会与一个设备驱动程序相关联,这样,即使几个硬件设备并不共享线,同一个向量也可以在这几个设备在不同时刻中使用。当一个中断发生时,并不是所有的操作都具有急迫性,,因此Linux会把紧随中断要执行的操作分为三类:

紧急的:这样的操作比如说对pic应答中断,对pic或设备控制器中编程重修改,由设备和处理器同时访问的数据结构这样的操作都可以很快的被执行,他们是紧急的,因为他们必须要尽快的执行紧急操作!要在一个中断处理程序内立即执行,而且是在禁止可屏蔽中断的情况下

非紧急的:这样的操作比如说修改那些只有处理器才会访问的数据结构,这样的操作必须也很快完成,因此它们由中断处理程序立即执行,但它们是在开中断的情况下执行的

非紧急可延迟的:比如说把缓冲区的内容拷贝到进程的地址空间中,这样的操作可能被延迟较长的时间间隔,而不会影响内核的操作!

不管引起中断的电路类型如何所有的io中断处理程序都执行四个基本的操作:

  1. 在内核态堆栈中保存的值与寄存器的内容

  2. 为正在给线服务的pic发送个应答,这将允许pic进一步发出中断

  3. 执行共享这个IRQ的所有设备都中断服务例程

  4. 跳到ret_from_intr的地址后终止

为一个IRQ可配置设备选择一条线,有三种方式:

  • 设置一些硬件跳线跳接器

  • 安装设备时执行一个实用程序,这样的程序可以让用户选择一个可用的RQ号或者探测系统自身以确定一个可用的IRQ号

  • 在系统启动时执行一个硬件协议,外设宣布他们准备使用哪些中断线,然后协商一个最终的值以尽可能减少冲突,该过程一旦完成,每个中断处理程序都会通过访问设备的某个IO端口函数来读取所分配的IRQ

数据结构

对于每一个外设的IRQ都用 struct irq_desc 来描述,我们称之中断描述符(struct irq_desc)。linux kernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,我们称之中断描述符DB(上图中红色框图内)。当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ number,然后通过IRQ number就可以获取对应的中断描述符

中断描述符

通用中断处理模块可以用一个线性的table来管理一个个的外部中断,这个表的每个元素就是一个irq描述符,在kernel中定义如下:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { 
    [0 ... NR_IRQS-1] = { 
        .handle_irq    = handle_bad_irq, 
        .depth        = 1, 
        .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), 
    } 
};

系统中每一个连接外设的中断线(irq request line)用一个中断描述符来描述,每一个外设的 interrupt request line 分配一个中断号(irq number),系统中有多少个中断线(或者叫做中断源)就有多少个中断描述符(struct irq_desc)。NR_IRQS定义了该硬件平台IRQ的最大数目。

struct irq_desc { 
    struct irq_data        irq_data; 
    unsigned int __percpu    *kstat_irqs;------IRQ的统计信息 
    irq_flow_handler_t    handle_irq;--------流控函数 
    struct irqaction    *action; -----------处理函数
    unsigned int        status_use_accessors;-----中断描述符的状态,参考IRQ_xxxx 
    unsigned int        core_internal_state__do_not_mess_with_it;
    unsigned int        depth;----------描述嵌套深度的信息
    unsigned int        wake_depth;--------电源管理中的wake up source相关
    unsigned int        irq_count; 
    unsigned long        last_unhandled;   
    unsigned int        irqs_unhandled; 
    raw_spinlock_t        lock; 
    struct cpumask        *percpu_enabled;
#ifdef CONFIG_SMP 
    const struct cpumask    *affinity_hint;----和irq affinity相关,后续单独文档描述 
    struct irq_affinity_notify *affinity_notify; 
#ifdef CONFIG_GENERIC_PENDING_IRQ 
    cpumask_var_t        pending_mask; 
#endif 
#endif 
    unsigned long        threads_oneshot; 
    atomic_t        threads_active; 
    wait_queue_head_t       wait_for_threads; 
#ifdef CONFIG_PROC_FS 
    struct proc_dir_entry    *dir;--------该IRQ对应的proc接口 
#endif 
    int            parent_irq; 
    struct module        *owner; 
    const char        *name; 
} ____cacheline_internodealigned_in_smp
响应函数 irqaction

在 irq_desc 中,struct irqaction *action,主要是用来存用户注册的中断处理函数,一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断。所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断

struct irqaction {
         irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
         unsigned long flags;         //中断标志,注册时设置,比如上升沿中断,下降沿中断等
         cpumask_t mask;           //中断掩码
         const char *name;          //中断名称,产生中断的硬件的名字
         void *dev_id;              //设备id
         struct irqaction *next;        //指向下一个成员
         int irq;                    //中断号,
         struct proc_dir_entry *dir;    //指向IRQn相关的/proc/irq/
 
};
中断数据 irq_data

中断描述符中应该会包括底层irq chip相关的数据结构,linux kernel中把这些数据组织在一起,形成struct irq_data,具体代码如下:

struct irq_data { 
    u32            mask;----------TODO 
    unsigned int        irq;--------IRQ number 
    unsigned long        hwirq;-------HW interrupt ID 
    unsigned int        node;-------NUMA node index 
    unsigned int        state_use_accessors;--------底层状态,参考IRQD_xxxx 
    struct irq_chip        *chip;----------该中断描述符对应的irq chip数据结构 
    struct irq_domain    *domain;--------该中断描述符对应的irq domain数据结构 
    void            *handler_data;--------和外设specific handler相关的私有数据 
    void            *chip_data;---------和中断控制器相关的私有数据 
    struct msi_desc        *msi_desc; 
    cpumask_var_t        affinity;-------和irq affinity相关 
};
操作合集 irq_chip
struct irq_chip {
    const char    *name;
    unsigned int    (*irq_startup)(struct irq_data *data);-------------初始化中断
    void        (*irq_shutdown)(struct irq_data *data);----------------结束中断
    void        (*irq_enable)(struct irq_data *data);------------------使能中断
    void        (*irq_disable)(struct irq_data *data);-----------------关闭中断
 
    void        (*irq_ack)(struct irq_data *data);---------------------应答中断
    void        (*irq_mask)(struct irq_data *data);--------------------屏蔽中断
    void        (*irq_mask_ack)(struct irq_data *data);----------------应答并屏蔽中断
    void        (*irq_unmask)(struct irq_data *data);------------------解除中断屏蔽
    void        (*irq_eoi)(struct irq_data *data);---------------------发送EOI信号,表示硬件中断处理已经完成。
 
    int        (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);--------绑定中断到某个CPU
    int        (*irq_retrigger)(struct irq_data *data);----------------重新发送中断到CPU
    int        (*irq_set_type)(struct irq_data *data, unsigned int flow_type);----------------------------设置触发类型
    int        (*irq_set_wake)(struct irq_data *data, unsigned int on);-----------------------------------使能/关闭中断在电源管理中的唤醒功能。
 
    void        (*irq_bus_lock)(struct irq_data *data);
    void        (*irq_bus_sync_unlock)(struct irq_data *data);
 
    void        (*irq_cpu_online)(struct irq_data *data);
    void        (*irq_cpu_offline)(struct irq_data *data);
 
    void        (*irq_suspend)(struct irq_data *data);
    void        (*irq_resume)(struct irq_data *data);
    void        (*irq_pm_shutdown)(struct irq_data *data);
...
    unsigned long    flags;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Java-jsp的现代数字化城市公交查询系统论文(四)

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ ➡️点击免费下载全套资料:源码、数据库、部署教程、论文、答辩ppt一条龙服务 ➡️有部署问题可私信联系 ⬆️⬆️⬆️​​​​​​​⬆️…

音视频入门基础:像素格式专题(1)——RGB简介

一、像素格式简介 像素格式&#xff08;pixel format&#xff09;指像素色彩按分量的大小和排列。这种格式以每个像素所使用的总位数以及用于存储像素色彩的红、绿、蓝和 alpha 分量的位数指定。在音视频领域&#xff0c;常用的像素格式包括RGB格式和YUV格式&#xff0c;本文…

a-table 控制列的展示和隐藏

一、业务场景&#xff1a; 最近在使用 Antd-vue 组件库的时候&#xff0c;a-table需要根据不同角色的权限显示和隐藏 columns的列 为了避免大家走弯路&#xff0c;为大家整理了一下&#xff0c;粘走可以直接用的那种 二、具体实现步骤&#xff1a; 1.在需要显示与隐藏的列增加一…

制定语音芯片的语音识别指令时需要关注的内容

背景 最近定义设备识别的语音指令以及对应的语音反馈。虽然语音控制在软件里只是很小的一块功能&#xff0c;但也不能太马虎。新人入坑就要学习&#xff0c;学习前人的经验规避问题&#xff0c;最后总结经验给后人&#xff0c;给未来的自己。好记性不如烂笔头~ 下面一些问题是…

1700java进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web进销存管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为sqlser…

『FPGA通信接口』DDR(3)DDR3颗粒读写测试

文章目录 前言1.配套工程简介2.测试内容与策略3. 测试程序分析4.程序结果分析5.一个IP控制两颗DDR36.传送门 前言 以四颗MT41K512M16HA-125AIT颗粒为例&#xff0c;介绍如何在一块新制板卡上做关于DDR3的器件测试。前面两篇介绍了什么是DDR&#xff0c;并介绍了xilinx给出的FPG…

基于Springboot的音乐翻唱与分享平台

基于SpringbootVue的音乐翻唱与分享平台设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 音乐资讯 音乐翻唱 在线听歌 后台登录 后台首页 用户管理 音乐资讯管理…

Elasticsearch:探索 11 种流行的机器学习算法

作者&#xff1a;来自 Elastic Elastic Platform Team 过去几年中&#xff0c;机器学习&#xff08;ML&#xff09;已经悄然成为我们日常生活中不可或缺的一部分。它影响着从购物网站和流媒体网站上的个性化推荐&#xff0c;到保护我们的收件箱免受我们每天收到的大量垃圾邮件的…

3.9设计模式——Strategy 策略模式(行为型)

意图 定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使他们可以相互替换此模式使得算法可以独立于使用它们的客户而变化 结构 Strategy&#xff08;策略&#xff09;定义所有支持的算法的公共入口。Context使用这个接口来调用某ConcreteStrategy定义的方…

手撕spring框架(2)

相关系列 java中spring底层核心原理解析&#xff08;1&#xff09;-CSDN博客 java中spring底层核心原理解析(2)-CSDN博客 手撕spring框架&#xff08;1&#xff09;-CSDN博客 依赖注入原理 依赖注入(Dependency Injection&#xff0c;简称DI)是一种设计模式&#xff0c;它允许我…

DS高阶:图论基础知识

一、图的基本概念及相关名词解释 1.1 图的基本概念 图是比线性表和树更为复杂且抽象的结&#xff0c;和以往所学结构不同的是图是一种表示型的结构&#xff0c;也就是说他更关注的是元素与元素之间的关系。下面进入正题。 图是由顶点集合及顶点间的关系组成的一种数据结构&…

深入浅出DBus-C++:Linux下的高效IPC通信

目录标题 1. DBus简介2. DBus-C的优势3. 安装DBus-C4. 使用DBus-C初始化和连接到DBus定义接口和方法发送和接收信号 5. dbus-cpp 0.9.0 的安装6. 创建一个 DBus 服务7. 客户端的实现8. 编译和运行你的应用9. 瑞芯微&#xff08;Rockchip&#xff09;的 Linux 系统通常会自带 db…

上位机开发PyQt(五)【Qt Designer】

PyQt5提供了一个可视化图形工具Qt Designer&#xff0c;文件名为designer.exe。如果在电脑上找不到&#xff0c;可以用如下命令进行安装&#xff1a; pip install PyQt5-tools 安装完毕后&#xff0c;可在如下目录找到此工具软件&#xff1a; %LOCALAPPDATA%\Programs\Python\…

智能体可靠性的革命性提升,揭秘知识工程领域的参考架构新篇章

引言&#xff1a;知识工程的演变与重要性 知识工程&#xff08;Knowledge Engineering&#xff0c;KE&#xff09;是一个涉及激发、捕获、概念化和形式化知识以用于信息系统的过程。自计算机科学和人工智能&#xff08;AI&#xff09;历史以来&#xff0c;知识工程的工作流程因…

【Web】2024XYCTF题解(全)

目录 ezhttp ezmd5 warm up ezMake ez?Make εZ?мKε? 我是一个复读机 牢牢记住&#xff0c;逝者为大 ezRCE ezPOP ezSerialize ezClass pharme 连连看到底是连连什么看 ezLFI login give me flag baby_unserialize ezhttp 访问./robots.txt 继…

ChatGPT向付费用户推“记忆”功能,可记住用户喜好 | 最新快讯

4月30日消息&#xff0c;人工智能巨头OpenAI宣布&#xff0c;其开发的聊天机器人ChatGPT将在除欧洲和韩国以外的市场全面上线“记忆”功能。这使得聊天机器人能够“记住”ChatGPT Plus付费订阅用户的详细信息&#xff0c;从而提供更个性化的服务。 OpenAI早在今年2月就已经宣布…

无缝对接配电自动化:IEC104转OPC UA网关解决方案

随着水电厂自动化发展的要求&#xff0c;具有一定规模的梯级水电站越来越多&#xff0c;为了实现水电站的无人值班(少人值守)&#xff0c;并考虑到节能控制&#xff0c;电厂采用了集中监控。集中监控关注的是整个电网的安全稳定运行及电压、频率和整个电网的电力需求&#xff0…

C++必修:类与对象(二)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. 构造函数 1.1. 定义 构造函数是一个特殊的成员函数&#xff0c;名字与类名相…

软件工程物联网方向嵌入式系统复习笔记--如何控制硬件

5-如何控制硬件 #mermaid-svg-of9KvkxJqwLjSYzH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-of9KvkxJqwLjSYzH .error-icon{fill:#552222;}#mermaid-svg-of9KvkxJqwLjSYzH .error-text{fill:#552222;stroke:#552…

vue3步骤条带边框点击切换高亮

如果是div使用clip-path: polygon(0% 0%, 92% 0%, 100% 50%, 92% 100%, 0% 100%, 8% 50%);进行裁剪加边框没实现成功。目前这个使用svg完成带边框的。 形状可自行更改path 标签里的 :d“[num ! 1 ? ‘M 0 0 L 160 0 L 176 18 L 160 38 L 0 38 L 15.5 18 Z’ : ‘M 0,0 L 160,0…