计算机怎么实现中断

中断类型

外部中断

在这里插入图片描述

可屏蔽中断

可屏蔽中断是通过INTR引脚进入CPU的,外部设备如硬盘、网卡等发出的中断都是可屏蔽中断

可屏蔽的意思是此外部设备发出的的中断,CPU可以不理会,因为它不会导致系统宕机,所有可以通过eflags寄存器的IF位将所有这些外部设备的中断屏蔽

不可屏蔽中断

不可屏蔽中断是通过NMI引脚进入CPU的,它表示系统中发生了致命的错误,它等同于宣布:计算机的运行到此结束了

内部中断

软中断

软中断,就是由由软件主动发起的中断,因为它来自软件,称之为软中断

由于该中断是软件运行中主动发起的,并不是客观上的某种内部错误

例子: int 3、int 8 、into、bound、ud2

异常

异常是另一种内部中断,是指令执行期间CPU内部产生的错误引起的

由于是运行时错误,所以它不受标志寄存器eflags中IF位影响,无法向用户隐瞒

中断是怎么发生的?

中断是一种硬件或软件事件,它导致CPU停止正在执行的当前任务并转而执行另一个任务

中断可以由多种原因引起,包括以下几种

  • 外部设备: 当外部设备(如键盘、鼠标、网络适配器等)需要CPU执行某些操作时,它们会向CPU发出中断请求
  • 内部事件: 当CPU执行某些操作时,它可能会发生内部故障(如除零错误、页故障等),这些故障会导致CPU停止当前任务并执行中断处理程序
  • 系统调用: 当进程需要访问系统资源(如文件、网络等)时,它必须通过系统调用请求操作系统执行某些操作。在这种情况下,操作系统会将进程的执行暂停,并执行系统调用处理程序

中断描述符 && 中断描述符寄存器

中断描述符(IDT)

在这里插入图片描述

  1. 基地址: 表示该中断处理程序所在的代码段在内存中的基地址
  2. 选择子: 表示该中断处理程序所在的代码段的描述符在全局或局部描述符表中的选择子
  3. 标志位: 表示该中断处理程序的属性,如门类型、特权级,其中包括以下属性
  • DPL: 描述符特权级别,可以是 0、1 或 2,在中断处理程序自身产生一个中断请求时,只会响应大于或等于该值的请求
  • 中断门或陷阱门标志: 描述符类型,中断门或陷阱门,用于触发特定地址空间内的代码执行
  • 存在位: 指示对应的中断处理程序是否存在,如果存在则该值为 1,否则为 0
  1. 偏移量: 指示该中断处理程序在代码段中的偏移量,即处理该中断类型的具体函数在代码段中的地址偏移量

中断描述符寄存器(IDTR)

在这里插入图片描述

  1. 表界限
  • IDT大小减1。16位的表界限,表示最大范围0xfff,即64KB
  • 可容纳的描述符个数是 64KB / 8 = 8KB = 8192个
  • 特别注意的是GDT中的第0是可用的,中断向量号为0的中断是除法错
  • 但处理器只支持256个中断,即0~254,中断描述符中其余的描述符不可用
  • 在门描述符中有个P位,所以,构建IDT时,记得把P位置0,表示门描述符中的中断处理器程序不在内存中
  1. 基地址: IDT的基地址

中断描述符表地址肯定要加载到这个寄存器中,只有寄存器IDTR指向了IDT,当CPU接收到中断向量号时才能找到中断向量处理程序,这样中断系统才能正常运行

CPU通过中断向量号对应的中断处理程序

在这里插入图片描述

根据中断向量号定位中断门

当处理器收到一个外部中断向量后,它用此向量号在中断描述符表中查询对应的中断描述符,然后再去执行该中断中的中断处理程序

由于中断描述符是8个字节(64位),所以处理器用中断向量号乘以8后,再与IDTR中的中断描述符表地址相加,所求的地址之和便是该中断向量号对应的中断描述符

通用流程

当发生中断时,处理器会从IDT中获取中断程序的地址,并用中断描述符中的选择子标识要用哪个段描述符来寻址该中断处理程序

因为在GDT表中,代码段和数据段等描述符和中断处理程序并没有关系,所有要使用中断选择子来寻址对应的代码段描述符,根据代码段描述符中的信息,处理器才能获取代码段的基地址、大小等信息,并根据偏移量计算出中断处理程序在代码段的具体位置

处理器进行特权级检查

CS 和 SS 中 RPL 就组成了 CPL(当前权限级别), CPL 就表示发起访问者要以什么权限去访问目标段,当 CPL 大于目标段 DPL 时,则 CPU 禁止访问,只有 CPL 小于等于目标段 DPL 时才能访问

如果 CPL 小于等于中断门的 DPL,并且 CPL 大于等于中断门中的段选择子所指向的段描述符的 DPL,就指向段描述符的 DPL

CPL 等于中断门中的段选择子指向段描述符的 DPL,则为同级权限不进行栈切换,否则进行栈切换

如果进行栈切换,还需要从 TSS 中加载具体权限的 SS、ESP,当然也要对 SS 中段选择子指向的段描述符进行检查

外部设备的中断有中断代理芯片接收,处理后将该中断的中断向量号发送到CPU

8259A

在这里插入图片描述

8259A是一种集成电路芯片,也称为可编程中断控制器(Programmable Interrupt Controller,PIC),用于处理PC机中的中断操作, 主要负责管理中断请求(IRQ)并进行相应的调度,使多个设备共享CPU处理时间

8259A可同时管理8个中断路由器,这允许8个设备可同时使用唯一的IRQ线。另一个8259A可以通过一些特殊的串行线路来扩展更多的中断。由于其可编程性,8259A可以通过操作特定的寄存器来达到不同的中断调度方式

x86 平台使用了两片 8259A 芯片,以级联的方式存在。它拥有 15 个中断源(即可以有 15 个中断信号接入)

使用流程

8259A 中的 INTR 输入引脚是一个带有中断缓冲寄存器的输入引脚,它起到了 CPU 硬中断的触发作用。当 8259A 中断控制器检测到一个可处理中断时,会在 INTR 引脚上发送一个高电平信号,以通知 CPU 引发一次硬中断。通过这种方式,CPU 可以在快速响应外设中断的同时,减轻了处理器上轮询中断询问的负担。

下面以实现键盘中断为例,说明如何使用 8259A 中的 INTR 引脚触发 CPU 硬中断:

  1. 在操作系统启动时,使用 outb 方法向控制器的特殊端口(Master: 0x20,Slave: 0xA0)写入 0x11 来初始化 8259A 中断控制器。

  2. 向控制器特殊端口写入两个字节的中断向量表基址,用于存储中断服务程序的地址。例如,将主片的基址设置为 0x20,从片的基址设置为 0x28。

  3. 向主从片的控制端口(Master: 0x21,Slave: 0xA1)写入控制字,控制中断的开启和屏蔽,具体控制字格式参考 8259A 芯片手册。例如,对于键盘中断 IRQ1,可以将相关的 IRQ1 位设置为 1,将其他位设置为 0,表示只允许 IRQ1 中断。

  4. 程序进入循环,等待外设产生可处理中断。当键盘输入事件产生时,会向代表键盘中断的 IRQ1 引脚发送一个高电平信号,控制器会向 CPU 发送一个硬件中断请求信号。这个信号会通过 INTR 引脚输入到 CPU,引发硬中断处理程序的执行。

  5. 在中断服务程序中,程序可以读取键盘输入的数据并进行处理。处理结束后,必须写入 EOI(End Of Interrupt)指令到 控制器的相应端口,向控制器发送一个中断结束信号,以便管理其他挂起的中断。

总之,通过控制 INTR 引脚的信号,可以实现在硬件层面上触发 CPU 的硬中断请求,提高系统对外设中断的响应速度和处理能力。

操作系统如何实现中断

来源

代码来源: 《操作系统实战45讲》

中断入口处理程序

//保存中断后的寄存器
%macro  SAVEALL  0
  push rax
  push rbx
  push rcx
  push rdx
  push rbp
  push rsi
  push rdi
  push r8
  push r9
  push r10
  push r11
  push r12
  push r13
  push r14
  push r15
  xor r14,r14
  mov r14w,ds
  push r14
  mov r14w,es
  push r14
  mov r14w,fs
  push r14
  mov r14w,gs
  push r14
%endmacro
//恢复中断后寄存器
%macro  RESTOREALL  0
  pop r14
  mov gs,r14w
  pop r14 
  mov fs,r14w
  pop r14
  mov es,r14w
  pop r14
  mov ds,r14w
  pop r15
  pop r14
  pop r13
  pop r12
  pop r11
  pop r10
  pop r9
  pop r8
  pop rdi
  pop rsi
  pop rbp
  pop rdx
  pop rcx
  pop rbx
  pop rax
  iretq
%endmacro
//保存异常下的寄存器
%macro  SAVEALLFAULT 0
  push rax
  push rbx
  push rcx
  push rdx
  push rbp
  push rsi
  push rdi
  push r8
  push r9
  push r10
  push r11
  push r12
  push r13
  push r14
  push r15
  xor r14,r14
  mov r14w,ds
  push r14
  mov r14w,es
  push r14
  mov r14w,fs
  push r14
  mov r14w,gs
  push r14
%endmacro
//恢复异常下寄存器
%macro  RESTOREALLFAULT  0
  pop r14
  mov gs,r14w
  pop r14 
  mov fs,r14w
  pop r14
  mov es,r14w
  pop r14
  mov ds,r14w
  pop r15
  pop r14
  pop r13
  pop r12
  pop r11
  pop r10
  pop r9
  pop r8
  pop rdi
  pop rsi
  pop rbp
  pop rdx
  pop rcx
  pop rbx
  pop rax
  add rsp,8
  iretq
%endmacro
//没有错误码CPU异常
%macro  SRFTFAULT 1
  push    _NOERRO_CODE
  SAVEALLFAULT
  mov r14w,0x10
  mov ds,r14w
  mov es,r14w
  mov fs,r14w
  mov gs,r14w
  mov   rdi,%1 ;rdi, rsi
  mov   rsi,rsp
  call   hal_fault_allocator
  RESTOREALLFAULT
%endmacro
//CPU异常
// CPU异常通常是由于程序错误、硬件错误或者其他异常情况导致的,例如内存越界、除以零、硬件故障等
%macro  SRFTFAULT_ECODE 1
  SAVEALLFAULT
  mov r14w,0x10
  mov ds,r14w
  mov es,r14w
  mov fs,r14w
  mov gs,r14w
  mov   rdi,%1
  mov   rsi,rsp
  call   hal_fault_allocator
  RESTOREALLFAULT
%endmacro
//硬件中断
%macro  HARWINT  1
  SAVEALL
  mov r14w,0x10
  mov ds,r14w
  mov es,r14w
  mov fs,r14w
  mov gs,r14w
  mov  rdi, %1
  mov   rsi,rsp
  call    hal_intpt_allocator
  RESTOREALL
%endmacro

中断异常处理程序入口地址

//除法错误异常 比如除0
exc_divide_error:
  SRFTFAULT 0
//单步执行异常
exc_single_step_exception:
  SRFTFAULT 1
exc_nmi:
  SRFTFAULT 2
//调试断点异常
exc_breakpoint_exception:
  SRFTFAULT 3
//溢出异常
exc_overflow:
  SRFTFAULT 4
//段不存在异常
exc_segment_not_present:
  SRFTFAULT_ECODE 11
//栈异常
exc_stack_exception:
  SRFTFAULT_ECODE 12
//通用异常
exc_general_protection:
  SRFTFAULT_ECODE 13
//缺页异常
exc_page_fault:
  SRFTFAULT_ECODE 14
hxi_exc_general_intpfault:
  SRFTFAULT 256
//硬件1~7号中断
hxi_hwint00:
  HARWINT  (INT_VECTOR_IRQ0+0)
hxi_hwint01:
  HARWINT  (INT_VECTOR_IRQ0+1)
hxi_hwint02:
  HARWINT  (INT_VECTOR_IRQ0+2)
hxi_hwint03:
  HARWINT  (INT_VECTOR_IRQ0+3)
hxi_hwint04:
  HARWINT  (INT_VECTOR_IRQ0+4)
hxi_hwint05:
  HARWINT  (INT_VECTOR_IRQ0+5)
hxi_hwint06:
  HARWINT  (INT_VECTOR_IRQ0+6)
hxi_hwint07:
  HARWINT  (INT_VECTOR_IRQ0+7)

初始化中断程序

// 中断初始化
PUBLIC void init_halintupt()
{
    init_descriptor();
    init_idt_descriptor();  // 中断门描述符
    
    init_intfltdsc();

    init_i8259();
    i8259_enabled_line(0);
    kprint("中断初始化成功\n");
    return;
}

init_idt_descriptor()

  • 中断门描述符,选择子为 SELECTOR_KERNEL_CS(0x08), 偏移量在之前已经设置,如 exc_divide_error

init_intfltdsc(): 构建中断处理框架

  • 按中断向量号,构建对应中断向量号的对应的执行链表
  • 中断可以由线程的方式执行,也可以是一个回调函数,该函数的地址放另一个结构体中。然后把对应中断响应加入到中断处理框架对应的向量号的链表中

缺页中断

数据访问

// 把intstkregs_t结构的空间初始化为0
hal_memset((void*)arp, 0, sizeof(intstkregs_t));

当对arp的内存进行访问时,发现所需要的页面(Page)还没有加载到内存中,需要将其从内存分配器或外部存储(如硬盘)中取出,导致程序执行暂停

中断异常处理程序

; CPU异常
%macro	SRFTFAULT_ECODE 1
	SAVEALLFAULT
	mov r14w,0x10
	mov ds, r14w
	mov es, r14w
	mov fs, r14w
	mov gs, r14w

	mov 	rdi, %1
	mov 	rsi, rsp
	call 	hal_fault_allocator
	RESTOREALLFAULT
%endmacro

中断异常处理程序入口地址

; 缺页异常
exc_page_fault:
	SRFTFAULT_ECODE 14

异常分发器

/**
 * @brief 异常分发器
 *  1. 缺页异常(异常号14)
 *      1. 缺页异常是从 kernel.asm 文件中的 exc_page_fault 标号处开始,但它只是保存了 CPU 的上下文
 *      2. 然后调用了内核的通用异常分发器函数,最后由异常分发器函数调用不同的异常处理函数
 * 
 * @param faultnumb 异常码
 * @param krnlsframp 
 */
void hal_fault_allocator(uint_t faultnumb, void *krnlsframp)
{
    adr_t fairvadrs;
    cpuflg_t cpuflg;
    hal_cpuflag_sti(&cpuflg);

    // 如果异常号等于14则是内存缺页异常
    if (faultnumb == 14) {
        // 获取缺页的地址。打印缺页地址,这地址保存在CPU的CR2寄存器中
        fairvadrs = (adr_t)read_cr2();
        sint_t ret = krluserspace_accessfailed(fairvadrs);
        if (ret != 0) {
            dump_stack(krnlsframp);
            // 处理缺页失败就死机
            system_error("缺页处理失败\n");
        }

        hal_cpuflag_cli(&cpuflg);
        // 成功就返回
        return;
    }
    
    krlsve_exit_thread();
    hal_cpuflag_cli(&cpuflg);
    return;
}

缺页异常处理核心函数

/**
 * @brief 缺页异常处理核心函数
 * 	1. 查找缺页地址对应的 kmvarsdsc_t 结构
 * 	2. 查找 kmvarsdsc_t 结构下面的对应 kvmemcbox_t 结构,它是用来挂载物理内存页面的
 * 	3. 分配物理内存页面并建立 MMU 页表映射关系
 * 
 * @param mm mmadrsdsc内存指针
 * @param vadrs 缺页异常地址
 * @return sint_t 返回状态码
 */
sint_t vma_map_fairvadrs_core(mmadrsdsc_t *mm, adr_t vadrs)
{
	sint_t rets = FALSE;
	adr_t phyadrs = NULL;
	virmemadrs_t *vma = &mm->msd_virmemadrs;
	kmvarsdsc_t *kmvd = NULL;
	kvmemcbox_t *kmbox = NULL;
	cpuflg_t cpuflg;
	krlspinlock_cli(&vma->vs_lock, &cpuflg);

	// 查找对应的kmvarsdsc_t结构。 没找到说明没有分配该虚拟地址空间,那属于非法访问不予处理
	kmvd = vma_map_find_kmvarsdsc(vma, vadrs);
	if (NULL == kmvd) {
		rets = -EFAULT;
		goto out;
	}

	// 返回kmvarsdsc_t结构下对应kvmemcbox_t结构,它是用来挂载物理内存页面的
	kmbox = vma_map_retn_kvmemcbox(kmvd);
	if (NULL == kmbox) {
		rets = -ENOOBJ;
		goto out;
	}

	// 分配物理内存页面并建立MMU页表
	phyadrs = vma_map_phyadrs(mm, kmvd, vadrs, (0 | PML4E_US | PML4E_RW | PML4E_P));
	if (NULL == phyadrs) {
		kprint("vma_map_phyadrs null %x\n", vadrs);
		rets = -ENOMEM;
		goto out;
	}

	vma_full_textbin(mm, kmvd, vadrs); // 骚操作
	rets = EOK;

out:
	krlspinunlock_sti(&vma->vs_lock, &cpuflg);
	return rets;
}

定时器中断

定时器中断定义及启动

/**
 * @brief 设备中断处理函数
 *  1. systick 设备每秒钟产生 1000 次中断
 * 
 * @param ift_nr 
 * @param devp 
 * @param sframe 
 * @return drvstus_t 
 */
drvstus_t systick_handle(uint_t ift_nr, void *devp, void *sframe)
{
    // 更新当前进程的tick
    krlthd_inc_tick(krlsched_retn_currthread());
    krlupdate_times_from_cmos();
    return DFCOKSTUS;
}

drvstus_t systick_entry(driver_t *drvp, uint_t val, void *p)
{
    // 安装中断回调函数接口. 中断号32~255为 Maskable Interrupts
    // krlnew_devhandle 用于安装中断回调函数接口
    // devp: 设备实例、systick_handle: 设备中断回调函数、0x20: 中断向量号
    if (krlnew_devhandle(devp, systick_handle, 0x20) == DFCERRSTUS) {
        //注意释放资源
        return DFCERRSTUS; 
    }

    // 初始化8254定时器
    init_8254();

    // 开启中断请求
    if (krlenable_intline(20) == DFCERRSTUS) {
        return DFCERRSTUS;
    }
}

通过krlnew_devhandle函数把中断向量号和中断回调函数生成一个intserdsc_t的中断运行结构体

然后根据中断向量号找出对应的中断异常描述符结构体(intfltdsc_t),然后把中断运行结构体填充到链表中,用于中断回调

8254定时器

8254定时器的计数器0(0x40端口)的作用是产生时钟信号,这个时钟是指连接到主片IRQ0 引脚的那个时钟

同时会每隔 1ms 产生一个中断,相当于一个定时器。

中断异常处理程序

; 硬件中断,硬件分发器函数 hal_hwint_allocator
%macro	HARWINT	1
	SAVEALL
	
	mov r14w, 0x10
	mov ds, r14w
	mov es, r14w
	mov fs, r14w
	mov gs, r14w

	mov	rdi, %1
	mov rsi, rsp
	call hal_hwint_allocator

	RESTOREALL
%endmacro

中断异常处理程序入口地址

; 硬件1~7号中断. 定义了各种硬件中断编号,比如hxi_hwint00,作为中断处理入口
ALIGN	16
hxi_hwint00:
	HARWINT	(INT_VECTOR_IRQ0+0)

硬中断分发

/**
 * 有硬件中断时,会先到达中断处理入口,然后调用到硬件中断分发器函数hal_hwint_allocator
 *  第一个参数为中断编号,在rdi
 *  第二个参数为中断发生时的栈指针,在rsi
 *  然后调用异常处理函数hal_do_hwint
 */
void hal_hwint_allocator(uint_t intnumb, void *krnlsframp)
{
    cpuflg_t cpuflg;
    hal_cpuflag_sti(&cpuflg);

    hal_hwint_eoi();
    hal_do_hwint(intnumb, krnlsframp);
    krlsched_chkneed_pmptsched();

    hal_cpuflag_cli(&cpuflg);
    //kprint("暂时无法向任何服务进程发送中断消息,直接丢弃......\n");
    return;
}

中断运行

/**
 * @brief 负责调用中断处理的回调函数
 *  1. 先获取中断异常表machintflt
 *  2. 然后调用i_serlist 链表上所有挂载intserdsc_t 结构中的中断处理的回调函数,是否处理由函数自己判断
 * 
 * @param ifdnr 中断码
 * @param sframe 
 */
void hal_run_intflthandle(uint_t ifdnr, void *sframe)
{
    intserdsc_t *isdscp;
    list_h_t *lst;
    // 根据中断号获取中断异常描述符地址
    intfltdsc_t *ifdscp = hal_retn_intfltdsc(ifdnr);

    if (ifdscp == NULL) {
        hal_sysdie("hal_run_intfdsc err");
        return;
    }
    
    // 遍历i_serlist链表
    list_for_each(lst, &ifdscp->i_serlist) {
        // 获取i_serlist链表上对象即intserdsc_t结构
        isdscp = list_entry(lst, intserdsc_t, s_list);
        // 调用中断处理回调函数
        isdscp->s_handle(ifdnr, isdscp->s_device, sframe);
    }

    return;
}
  • 45
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值