《操作系统真象还原》第七章 中断 第一节 初探中断

news/2024/10/30 23:17:03/

配合视频学习效果更佳:https://www.bilibili.com/video/BV1Nk4y1s7Qg/?vd_source=701807c4f8684b13e922d0a8b116af31

代码仓库地址:https://github.com/xukanshan/the_truth_of_operationg_system

中断就是发生了事情通知CPU,但是处不处理就看情况。中断机制的本质是来了一个中断信号后,cpu去处理,然后调用相应的中断处理程序。

外部中断:

来自CPU外部的中断,因为外部中断源必须是某个硬件,所以也叫硬件中断。CPU为外部中断提供了两条信号线,如下图:
在这里插入图片描述

  • 从INTR引脚接收到的中断信号都是不影响系统运行的,可以随时处理,所以是可屏蔽的,也叫可屏蔽中断,每个中断都有中断向量号。

  • 从NMI引脚,全是对系统运行有致命伤害的,所以是不可屏蔽的,自然叫不可屏蔽中断,共用一个中断向量号,为2。

  • Linux把可屏蔽中断分为上半部分和下半部分,上半部分需要立即执行,下半部分可以推迟执行,例子见书p300。

内部中断:

  • 软中断和异常,软中断是软件主动发起的。
  • 异常是指令执行期间CPU内部产生的错误引起的,不可屏蔽。异常按照轻重程度分为:Fault(故障,可以被修复,比如缺页异常page fault),Trap(陷阱,软件掉进了CPU设下的陷阱,比如调试过程中用的int3),Abort(终止,一旦发生,操作系统为了自保只能将此程序从进程表中去掉)

异常和不可屏蔽中断的中断向量号是由CPU自动提供的,而来自外部设备的可屏蔽中断号是由中断代理提供的,软中断是由软件提供的。

当CPU接收到一个中断时,需要用中断向量在中断段描述符表中检索对应的描述符(中断门描述符),在该描述符中找到中断处理程序的起始地址(一个段描述符选择子与偏移),然后执行中断处理程序。

计算机为了实现对中断的高效管理,而引入了中断控制器,由它负责接收外部设备的中断,负责对所有中断进行仲裁,决定哪个中断优先被CPU受理。

接下来我们来实现一个简陋的时钟中断。大致流程如下图:
在这里插入图片描述
init_all函数用来初始化所有的设备及数据结构,我们打算在主函数中调用它来完成初试化工作。init_all首先调用idt_init,它用来初始化中断相关的内容,其初始化也要分成几部分来做,pic_init用来初始化可编程中断控制器8259A,idt_desc_init用来初始化中断描述符表IDT,最后再加载IDT。

本节代码核心逻辑:

  1. 创建33个中断处理函数
  2. 写函数构建中断描述符表
  3. 写函数初始化中断控制器8295A,并只打开时钟中断
  4. 把2和3封装进入中断始化函数idt_init,调用idt_init函数完成中断描述符表初始化与中断控制器初始化,并加载idtr寄存器的值
  5. 把4封装进入总初始化函数init_all,调用这个函数完成中断初始化
  6. 在main中打开中断测试

首先我们先写好中断发生后的中断处理程序

p320剖析kernel.S代码:

1、代码功能

创建33个中断处理函数

2、实现原理

中断信号进入中断控制器进行处理之后,会被分配中断号,通过特定的中断号码,可以调用特地的中断处理程序去处理。0—19中断号为处理器内部固定的异常类型,20-31是Intel保留的。同时为了演示中断机理,写一个时钟中断处理程序,所以共33个。

3、代码逻辑

定义33个中断处理程序,每个程序包含处理部分与本程序的地址

4、怎么写代码?

A、定义没有压入错误码但为了统一管理需要压入0的宏参数;定义要压入错误码所以我们什么都不做的宏参数

B、定义一个文本段,里面放着要打印的字符串信息,然后定义一个标号(就在文本段下方)。由于编译器的特性,会将同一类型的SECTION组合成一个大的SEGMENT,所以D中调用宏所形成的每个中断处理程序中的入口地址部分(这个入口地址会被定义成文本段)会统一聚集在这个要打印的字符串这里(因为它是被定义成了文本段),也就是字符串信息下面的标号处,于是这个标号便可以管理所有的中断处理程序地址

C、定义一个中断处理程序宏,宏中包含程序段:程序处理部分(打印字符串信息)、文本段:本程序的入口地址部分

D、调用C定义的宏实现33个中断处理程序的定义(传不同的参数),要理清楚哪些中断要压入错误码,哪些中断不会压入错误码。不压入错误码的我们压入一串0,这样能实现中断统一定义(p303表7-1)

5、代码实现如下: myos/kernel/kernel.S

[bits 32]
%define ERROR_CODE nop		                ; 有些中断进入前CPU会自动压入错误码(32位),为保持栈中格式统一,这里不做操作.
%define ZERO push 0		                    ; 有些中断进入前CPU不会压入错误码,对于这类中断,我们为了与前一类中断统一管理,就自己压入32位的0
extern put_str			                    ;声明外部函数,为的是调用put_strsection .data
intr_str db "interrupt occur!", 0xa, 0       ;第二个是一个换行符,第三个定义一个ascii码为0的字符,用来表示字符串的结尾
global intr_entry_table
intr_entry_table:                           ;编译器会将之后所有同属性的section合成一个大的segment,所以这个标号后面会聚集所有的中断处理程序的地址%macro VECTOR 2                             ;汇编中的宏用法见书p320
section .text                               ;中断处理函数的代码段
intr%1entry:		                        ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少,此标号来表示中断处理程序的入口%2                                      ;这一步是根据宏传入参数的变化而变化的push intr_strcall put_stradd esp,4			                    ; 抛弃调用put_str压入的字符串地址参数; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20                             ; 中断结束命令EOIout 0xa0,al                             ;向主片发送OCW2,其中EOI位为1,告知结束中断,详见书p317out 0x20,al                             ;向从片发送OCW2,其中EOI位为1,告知结束中断add esp,4			                    ;对于会压入错误码的中断会抛弃错误码(这个错误码是执行中断处理函数之前CPU自动压入的),对于不会压入错误码的中断,就会抛弃上面push的0iret				                    ; 从中断返回,32位下等同指令iretdsection .data                               ;这个段就是存的此中断处理函数的地址dd    intr%1entry	                    ; 存储各个中断入口程序的地址,形成intr_entry_table数组,定义的地址是4字节,32%endmacroVECTOR 0x00,ZERO                            ;调用之前写好的宏来批量生成中断处理函数,传入参数是中断号码与上面中断宏的%2步骤,这个步骤是什么都不做,还是压入0看p303
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO 
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO 
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE 
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO 
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO 
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO 
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE 
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO 
VECTOR 0x20,ZERO

6、其他代码详解查看书p321;以上代码的核心就是33个中断处理函数,并且这些中断处理函数的入口地址形成了一个数组。

然后我们进行IDT表建构工作,核心就是为上文写下的中断处理函数建立对应的中断描述符表。下图是中断描述符的结构,字段含义参考段描述符P152
在这里插入图片描述
p324剖析interrupt.c代码:

1、代码功能

构建IDT表,为上文写下的中断处理函数建立对应的中断描述符表

2、实现原理

依据中断描述符表格式,将中断描述符表与中断处理函数建立映射

3、代码逻辑

创建33个中断门描述符结构体,然后通过循环将中断门描述符与特定中断处理函数建立映射

4、怎么写代码?

A、定义中断门描述符结构体,并定义一个中断门描述符结构体数组(33项)

B、写一个函数make_idt_desc,通过传入中断门描述符结构体指针,属性项,特定中断处理函数地址(通过引入intr_entry_table实现引用),将中断门描述符与特定中断处理函数建立联系

C、写一个函数idt_desc_init,循环调用B函数,完成构建中断描述符表

5、代码实现如下:

先定义内核用的段描述符选择子,中断门描述符attr字段 (myos/kernel/global.h)

#ifndef __KERNEL_GLOBAL_H
#define __KERNEL_GLOBAL_H
#include "stdint.h"//选择子的RPL字段
#define	 RPL0  0
#define	 RPL1  1
#define	 RPL2  2
#define	 RPL3  3//选择子的TI字段
#define TI_GDT 0
#define TI_LDT 1//定义不同的内核用的段描述符选择子
#define SELECTOR_K_CODE	   ((1 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_DATA	   ((2 << 3) + (TI_GDT << 2) + RPL0)
#define SELECTOR_K_STACK   SELECTOR_K_DATA 
#define SELECTOR_K_GS	   ((3 << 3) + (TI_GDT << 2) + RPL0)定义模块化的中断门描述符attr字段,attr字段指的是中断门描述符高字第8到16bit
#define	 IDT_DESC_P 1 
#define	 IDT_DESC_DPL0 0
#define	 IDT_DESC_DPL3 3
#define	 IDT_DESC_32_TYPE 0xE   // 32位的门
#define	 IDT_DESC_16_TYPE 0x6   // 16位的门,不用,定义它只为和32位门区分#define	 IDT_DESC_ATTR_DPL0  ((IDT_DESC_P << 7) + (IDT_DESC_DPL0 << 5) + IDT_DESC_32_TYPE)  //DPL为0的中断门描述符attr字段
#define	 IDT_DESC_ATTR_DPL3  ((IDT_DESC_P << 7) + (IDT_DESC_DPL3 << 5) + IDT_DESC_32_TYPE)  //DPL为3的中断门描述符attr字段#endif

接下来定义了一些数据类型(intr_handler) (myos/kernel/interrupt.h)

#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H
typedef void* intr_handler;		//将intr_handler定义为void*同类型
#endif

核心代码: (myos/kernel/interrupt.c)

#include "interrupt.h"      //里面定义了intr_handler类型
#include "stdint.h"         //各种uint_t类型
#include "global.h"         //里面定义了选择子         
#include "print.h"#define INTR_DESC_CONT 0x21     //支持的中断描述符个数33//按照中断门描述符格式定义结构体
struct gate_desc {uint16_t    func_offset_low_word;        //函数地址低字uint16_t    selector;                    //选择子字段uint8_t     dcount;                      //此项为双字计数字段,是门描述符中的第4字节。这个字段无用uint8_t     attribute;                   //属性字段uint16_t    func_offset_high_word;       //函数地址高字
};// 静态函数声明,非必须
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];   //中断门描述符(结构体)数组,名字叫idtextern intr_handler intr_entry_table[IDT_DESC_CNT];	    //引入kernel.s中定义好的中断处理函数地址数组,intr_handler就是void* 表明是一般地址类型//此函数用于将传入的中断门描述符与中断处理函数建立映射,三个参数:中断门描述符地址,属性,中断处理函数地址
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}//此函数用来循环调用make_idt_desc函数来完成中断门描述符与中断处理函数映射关系的建立,传入三个参数:中断描述符表某个中段描述符(一个结构体)的地址
//属性字段,中断处理函数的地址
static void idt_desc_init(void) {int i;for (i = 0; i < IDT_DESC_CNT; i++) {make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); }put_str("   idt_desc_init done\n");
}

6、其他代码详解查看书p325

接下来就是设定中断控制器。对任何硬件的控制都要通过端口,我们现在先把常用的端口读写功能利用内联汇编封装成C函数。这四个函数定义在io.h中,这样包含此.h文件就能够直接使用inline函数(原封不动展开,直接操作寄存器)。比一般的包含一个.h引入一个函数声明再链接一起要快得多,因为一般方式会涉及到call与ret指令。详细理由见p327 。内联汇编基础见p283

代码剖析略,具体代码如下: (myos/lib/kernel/io.h)

#ifndef __LIB_IO_H
#define __LIB_IO_H
#include "stdint.h"//一次送一字节的数据到指定端口,static指定只在本.h内有效,inline是让处理器将函数编译成内嵌的方式,就是在该函数调用处原封不动地展开//此函数有两个参数,一个端口号,一个要送往端口的数据
static inline void outb(uint16_t port, uint8_t data) {
/*********************************************************a表示用寄存器al或ax或eax,对端口指定N表示0~255, d表示用dx存储端口号, %b0表示对应al,%w1表示对应dx */ asm volatile ( "outb %b0, %w1" : : "a" (data), "Nd" (port));    
}//利用outsw(端口输出串,一次一字)指令,将ds:esi指向的addr处起始的word_cnt(存在ecx中)个字写入端口port,ecx与esi会自动变化
static inline void outsw(uint16_t port, const void* addr, uint32_t word_cnt) {
/*********************************************************+表示此限制即做输入又做输出.outsw是把ds:esi处的16位的内容写入port端口, 我们在设置段描述符时, 已经将ds,es,ss段的选择子都设置为相同的值了,此时不用担心数据错乱。*/asm volatile ("cld; rep outsw" : "+S" (addr), "+c" (word_cnt) : "d" (port));
}                                       //S表示寄存器esi/si/* 将从端口port读入的一个字节返回 */
static inline uint8_t inb(uint16_t port) {uint8_t data;asm volatile ("inb %w1, %b0" : "=a" (data) : "Nd" (port));return data;
}/* 将从端口port读入的word_cnt个字写入addr */
static inline void insw(uint16_t port, void* addr, uint32_t word_cnt) {
/******************************************************insw是将从端口port处读入的16位内容写入es:edi指向的内存,我们在设置段描述符时, 已经将ds,es,ss段的选择子都设置为相同的值了,此时不用担心数据错乱。*/asm volatile ("cld; rep insw" : "+D" (addr), "+c" (word_cnt) : "d" (port) : "memory");
}                                   //D表示寄存器edi/di                       //通知编译器,内存已经被改变了#endif

接下来就是对中断控制器进行编程了,设定好中断控制器,并只接受来自时钟中断的信号

p330剖析interrupt.c代码:

1、代码功能

设置中断控制器,对中断控制器操作,只接受来自时钟中断的信号

2、实现原理

CPU直接与中断打交道,不仅浪费CPU强大的性能,且CPU为了接收各种各样的中断信号,将会无比冗余(cpu将会有过多引脚),引入专门的中断控制器来先处理一下中断。我们需要先设置中断处理器(初始化),然后操作它来处理对应的中断信号。设置与操作都是通过它暴露在外的寄存器来进行。操作详情见p330

3、代码逻辑

初始化中断处理器

设定只接受时钟中断信号

4、怎么写代码?

A、看书p315设定相应的ICW与OCW

第一轮设定:(因为要按照ICW1-4的顺序推送,先主后从)

主片ICW1:00010001,0x11(送入主片控制端口):

  • 0号位为1,表示要写入ICW4,x86系统必须为1;
  • 1号位为0,表示级联;
  • 2号位为0,用于是设定8085的调用时间间隔,x86不需要设置;
  • 3号位为0,表示边沿触发;
  • 4号位为1,ICW1的标记;
  • 高3位x86不需要设置,直接为0。

主片ICW2:00100000,0x20(送入主片的数据端口):ICW2用来设置起始中断向量号,由于中断控制器上的IRQ接口是按顺序排列的,所以我们这里设定的实际就是IRQ0的中断向量号。这里我们设定32(也就是第33个中断向量号),因为前32个(0-31)已经被占用了。而且只需要填入高5位,也就是填一个8的倍数,然后8295A的8个IRQ接口就在此基础上顺序排号。如第一个主片,八个接口就是,IRQ0 = 32 + 0; IRQ1 = 32 + 1… 第一个从片就是IRQ0 = 32 + 8 + 0, IRQ1 = 32 + 8 + 1;

主片ICW3:00000100,0x04(送入主片数据端口):8位中哪位置1,表示哪个IRQ与从片连接,前面的值表示主片的IRQ2用于与从片级联。

主片ICW4:00000001,0x01(送入主片数据端口):

  • 0号位为1,表示x86处理器;
  • 1号位为0,表示手动结束中断(我们的中断处理程序中有通知主从片结束中断的步骤);
  • 2号位为0,因为3号位设定为0(非缓冲模式工作),所以此位无用;在非缓冲模式下,8259A的数据总线直接连接到系统总线上,而不是通过缓冲器。当中断发生时,8259A会直接向CPU发送中断信号,而不经过任何缓冲或处理。这种模式可能会使系统在处理大量的中断请求时表现得不那么稳定,因为它对系统总线的要求更高。然而,非缓冲模式的系统设计会更简单一些,因为不需要缓冲器的附加硬件。
  • 4号位为0,表示全嵌套模式,也就是优先处理较低中断请求线编号的中断请求(IRQ0最优先),特殊全嵌套模式是可以允许在中断处理过程中,如果来了一个优先级更高的中断请求,就暂停当前正在执行的中断,转而去执行那个优先级更高的中断请求;
  • 高3位无用。

第二轮设定:

从片ICW1:00010001,0x11(送入从片控制端口):含义参照主片ICW1。

从片ICW2:00101000,0x28(送入从片的数据端口):主片起始中断向量号是32,主片自己8个IRQ,所以从片自然从40开始。

从片ICW3:00000010,0x02(送入从片数据端口):用来表明主片哪个IRQ与自己级联,前面的值表明是主片的IRQ2用于与自己级联。

从片ICW4:00000001,0x01(送入从片数据端口),含义参照主片ICW4。

设定只接受时钟中断的OCW1:

主片OCW1:11111110,0xfe(送入主片数据端口),我们先只打开时钟中断看看效果,而时钟中断在主片IRQ0上,所以OCW1的0号位置为0,表示放行IRQ0送入的中断信号。

从片OCW1:11111111,0xff(送入从片数据端口),从片中断信号全部屏蔽

B、将A设定好的按照P330步骤推送至中断控制器的特定寄存器(通过io.h中封装的函数),并封装成一个pic_init函数

5、新加入的代码如下: (myos/kernel/interrupt.c)

#include "io.h" //里面封装了一系列与端口操作的函数#define PIC_M_CTRL 0x20	       // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21	       // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0	       // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1	       // 从片的数据端口是0xa1/* 初始化可编程中断控制器8259A */
static void pic_init(void) {/* 初始化主片 */outb (PIC_M_CTRL, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_M_DATA, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb (PIC_M_DATA, 0x04);   // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb (PIC_S_CTRL, 0x11);	// ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_S_DATA, 0x28);	// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb (PIC_S_DATA, 0x02);	// ICW3: 设置从片连接到主片的IR2引脚outb (PIC_S_DATA, 0x01);	// ICW4: 8086模式, 正常EOI/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */outb (PIC_M_DATA, 0xfe);outb (PIC_S_DATA, 0xff);put_str("   pic_init done\n");
}

6、其他代码详解查看书p331

接下来我们就调用之前我们写好的函数,并定义加载到IDTR寄存器中的值(参照书P306图IDTR结构),并最终加载IDTR来完成整个idt的构建工作,并封装成一个函数idt_init。

myos/kernel/interrupt.c 新加入如下代码,代码剖析略

/*完成有关中断的所有初始化工作*/
void idt_init() {put_str("idt_init start\n");idt_desc_init();	   //调用上面写好的函数完成中段描述符表的构建pic_init();		  //设定化中断控制器,只接受来自时钟中断的信号/* 加载idt */uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));    //定义要加载到IDTR寄存器中的值asm volatile("lidt %0" : : "m" (idt_operand));put_str("idt_init done\n");
}

至此,只需要调用idt_init,整个idt的建构工作就完成了。

myos/kernel/interrup.h 中声明idt_init函数,其他文件只需要包含interrupt.h就可以调用idt_init函数

myos/kernel/interrupt.h中加入如下代码:

void idt_init(void);

我们写一个init_all调用上面写好的idt_init

myos/kernel/init.c 代码剖析略

#include "init.h"
#include "print.h"
#include "interrupt.h"/*负责初始化所有模块 */
void init_all() {put_str("init_all\n");idt_init();   //初始化中断
}

为了其他的函数调用我们的init_all,我们需要建立头文件init.h,声明函数init_all,其他的函数包含我们的头文件,就可以调用我们的函数

myos/kernel/init.h 代码剖析略

#ifndef __KERNEL_INIT_H
#define __KERNEL_INIT_H
void init_all(void);
#endif

最后,我们来写一个main.c来验证我们的之前关于中断的所有工作的正确性

myos/kernel/main.c 代码剖析略

#include "print.h"
#include "init.h"
void main(void)
{put_str("\nThis is Kernel!\n");init_all();asm volatile("sti"   //为了演示中断,这里先临时开启中断);while(1);}

接下来进行编译,为了目录不至于太乱,建立build目录(myos/build)用于将所有目标文件和编译后的内核文件都放在此目录中

nasm -f elf -o build/print.o lib/kernel/print.S

gcc-4.4 -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o build/main.o -m32 kernel/main.c (-fno-builtin是不使用GCC的内建函数https://blog.csdn.net/baiyu9821179/article/details/73007124)

nasm -f elf -o build/kernel.o kernel/kernel.S

gcc-4.4 -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o build/interrput.o -m32 kernel/interrupt.c

gcc-4.4 -m32 -I lib/kernel/ -I lib/ -I kernel/ -c -fno-builtin -o build/init.o kernel/init.c

ld -m elf_i386 -Ttext 0x00001500 -e main -o build/kernel.bin build/kernel.o build/main.o build/init.o build/interrput.o build/print.o

dd if=build/kernel.bin of=/home/rlk/Desktop/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

我们程序的含义,就是每发生一次时钟中断,就打印一次信息!


http://www.ppmy.cn/news/148743.html

相关文章

day03_《谷粒商城》的完整流程(详细版二)

文章目录 笔记链接:P247—P260 RabbitMQ的知识P261—P263 订单服务—环境搭建P264 订单确认页—登录拦截P265 订单确认页—数据获取1P266 订单确认页—数据获取2P267 订单确认页—Feign远程调用丢失请求头问题1.总说:2.代码:P268 订单确认页—Feign异步调用丢失请求头问题P2…

数据挖掘期末考题针对复习

选择题 1、下面不属于数据挖掘迭代序列的是( ) A、数据清理 B、数据集成 C、数据删除 D、数据变换 C 解析&#xff1a; 数据清理、数据集成、数据变换、数据归约 2、属性(attribute)是一个数据字段&#xff0c;表示数据对象的一个特征。下面不属于典型的属性分类的是( ) A、…

寄存器操作方法

一, 寄存器的设置和操作特性 1&#xff0c;一个寄存器的每个位有其不同的意义&#xff0c;进行不同的设置会使硬件产生不同的效果和功能&#xff1b; 2&#xff0c;有些情况下需要对一个寄存器进行连续的不同的甚至完全相反的设置&#xff1b; 3&#xff0c;有些情况下需要对…

SSD性能怎么测?看这一篇就够了!

转载自&#xff1a;https://www.sohu.com/a/390625596_505795 一、概述 自从很多年前开始做SSD方面的测试以来&#xff0c;我就和某些行业工作者产生了同感&#xff1a;存储性能测试基本是所有计算机硬件测试里最复杂困难的一项。存储系统的性能受到整个机器其他硬件甚至软件…

洛谷 P3203 [HNOI2010]BOUNCE 弹飞绵羊(分块)

题目描述 某天&#xff0c;Lostmonkey发明了一种超级弹力装置&#xff0c;为了在他的绵羊朋友面前显摆&#xff0c;他邀请小绵羊一起玩个游戏。游戏一开始&#xff0c;Lostmonkey在地上沿着一条直线摆上n个装置&#xff0c;每个装置设定初始弹力系数ki&#xff0c;当绵羊达到第…

使用U盘安装操作系统

前些天对门寝室朋友的电脑系统崩溃啦&#xff0c;让我过去帮忙弄一下。要是在平时我三两下就给他弄好啦&#xff0c;但是那天却把我给难住啦&#xff0c;为什么呢&#xff1f;那是因为他的光驱是坏的&#xff0c;⊙﹏⊙b汗&#xff0c;无法正常读取系统安装盘&#xff0c;是怎么…

Stack与Queue

Stack(栈) 堆栈&#xff08;Stack&#xff09;代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时&#xff0c;则使用堆栈。当您在列表中添加一项&#xff0c;称为推入元素&#xff0c;当您从列表中移除一项时&#xff0c;称为弹出元素。 方法 Clear(); 从 S…

c语言程序设计P320,《C程序设计》作业内容

《《C程序设计》作业内容》由会员分享,可在线阅读,更多相关《《C程序设计》作业内容(11页珍藏版)》请在人人文库网上搜索。 1、实验一 C语言的运行环境的使用一、目的与要求1. 了解Windows系统下C语言的运行环境,熟悉C程序调试、运行的基本操作方法。2. 熟练掌握编辑、编译、…