精华内容
下载资源
问答
  • Linux中断和中断处理程序 由于处理器的速度与外设的速度相差很大,无法采取处理器向外设发出请求然后等待的方法。处理器与外设通信的方法: 轮询:处理器定期对设备的状态进行查询(缺点:在不需要通信的情况下,...

    Linux中断和中断处理程序

    由于处理器的速度与外设的速度相差很大,无法采取处理器向外设发出请求然后等待的方法。处理器与外设通信的方法:

    • 轮询:处理器定期对设备的状态进行查询(缺点:在不需要通信的情况下,处理器仍然需要周期性地重复执行)
    • 中断:一种特殊的电信号,由硬件设备发送给处理器。处理器接收信号后,由操作系统负责对数据进行处理

    中断随时可以产生,内核随时可能因为新到来的中断被打断。

    从物理层面讲,中断是一种电信号。

    • 由硬件设备产生,直接送入中断控制器的输入引脚中。中断控制器是个简单的电子芯片,将多路中断管线采用复用技术只通过一个和处理器相连接的管线与处理器通信。
    • 在接收到一个中断后,中断控制器会给处理器发送一个电信号。
    • 处理器检测到信号后会中断当前工作处理中断。接着,处理器通知操作系统中断产生。
    • 操作系统对中断进行处理。

    不同的设备对应的中断不同,每个中断通过一个唯一的数字标志,使得操作系统能够对中断进行区分。

    中断值被称为中断请求(IRQ)线,每个IRQ线被关联一个数值量。

    异常:在产生时必须考虑与处理器时钟同步,常常被称为同步中断。当处理器在执行时出现错误或者特殊情况(缺页)必须依靠内核来处理的时候,处理器会产生一个异常。

    在响应特定中断的时候,内核会执行一个函数,叫做中断处理程序或者中断服务例程。产生中断的每个设备都有一个相应的中断处理程序。一个设备的中断处理程序是设备驱动程序的一部分(在driver中实现中断处理程序)。设备驱动程序时用于对设备进行管理的内核代码。

    中断处理程序与其他内核函数的真正区别在于中断处理程序是被内核调用来响应终端的,运行在中断上下文(也被称为原子上下文)中。

    中断程序随时可能发生,需要保证中断处理程序快速执行,以便恢复中断代码的执行;同时,中断处理程序的工作量可能会很大。由于这两种目的相排斥,所以把中断处理分为两个部分。

    • 中断处理程序是上半部(top half),接收到一个中断就立刻开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或者复位硬件。
    • 能够允许推迟的工作都会在下半部(bottom half)完成。

    以网卡接收数据为例,将数据拷贝到内存的整个过程是在上半部实现的,对数据包的处理是在下半部实现的。

    上半部

    注册中断处理程序

    如果设备使用中断,那么对用的驱动程序需要注册一个中断处理函数。中断函数的注册可以通过request_irq()实现。

    static inline int __must_check 
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    	    const char *name, void *dev)
    

    __must_check是指调用函数必须处理函数的返回值。

    • 第一个参数irq是要分配的中断号,对于一些设备,该值是预先确定的。对于其他设备,该值可以通过探测获取或者通过编程动态确定。

    • 第二个参数handler是一个指针,指向这个中单的实际中断处理程序。当操作系统接收到中断该函数被调用。

      • typedef irqreturn_t (*irq_handler_t)(int, void *);
        
    • 第三个参数flags是中断处理程序标志,值可能一下以下一个或多个:

      • IRQF_DISABLED:内核在处理中断处理程序期间,禁止所有的其他中断(一般不会进行设置,主要对象是希望快速执行的轻量级中断)。
      • IRQF_SAMPLE_RANDOM:表示该设备产生的中断对内核熵池(entropy pool)有贡献。内核熵池负责提供从各个随机事件中导出的真正的随机数。如果设置该标志,则来自该设备的中断间隔时间会作为墒填充到熵池。如果设备产生中断的速率是不可预知的,则可以进行设置。
      • IRQF_TIMER:为系统定时器的中断处理特别准备。
      • IRQF_SHARED:表示可以在多个中断处理程序之间共享中断线。如果不指定,则每条中断线只能有一个处理程序。
    • 第四个参数是与中断相关的设备的ASCII文本表示。

    • 第五个参数dev用于共享中断线。当一个中断处理程序需要释放时,dev提供唯一的标志信息,以便在共享中断线的多个中断处理程序时删除指定的程序。如果无需共享中断线,则该参数可为NULL。

    中断注册函数request_irq()成功执行会返回0,非0值表示有错误产生,此时,中断处理程序注册失败。

    request_irq()函数可能会睡眠,因此不能在中断上下文或其他不允许阻塞的代码中调用该函数。该函数可能会睡眠的原因是,在注册的过程中,内核需要在/proc/irq文件中创建一个与中断对应的项。函数proc_mkdir()用来创建该procfs项,proc_mkdir()通过proc_create()对procfs项进行设置,而proc_create()会调用kmalloc()请求分配内存,而kmalloc()是可以睡眠的

    释放中断处理程序

    卸载驱动程序时需要注销相应的中断处理程序并释放中断线,需要通过调用函数实现:

    const void *free_irq(unsigned int irq, void *dev_id);
    

    如果指定的中断线是不共享的,那么该函数删除处理程序的同时将禁用这条中断线;如果中断线是共享的,那么仅删除dev对应的中断处理程序。中断线将在所有的处理程序删除完后才被禁用。

    dev是在共享的中断线上确定要删除的中断处理程序的唯一标志。如果dev非空,那么无论中断线是否共享,都必须与需要删除的处理程序相匹配。必须在进程上下文中调用free_irq()。

    中断处理程序

    static irqreturn_t xxx_handler(int irq, void *dev);
    

    类型与request_irq()中的参数类型相匹配。irq是处理程序要响应的终端号,第二个参数dev是通用指针,与request_irq()的dev必须一致,可以用于区分共享同一中断处理程序的多个设备。

    中断处理程序的返回值是一个特殊类型irqreturn_t。中断处理程序可能返回两个特殊的值:

    • IRQ_NONE:中断处理程序检测到一个中断但对应的设备并不是在注册函数时指定的产生源,产生该值
    • IRQ_HANDLED:中断处理程序被正确调用并且是对应的设备产生的中断,产生该值

    通过返回值,内核可以知道设备产生的中断是否已被请求处理。

    重入和中断处理程序

    Linux的中断处理程序是无需重入的,当一个给定的中断处理程序在执行时,相应的中断线在所有处理器上被屏蔽,防止在同一中断线上接收另一个新的中断。不同中断线的中断可以被处理。

    共享的中断处理程序

    共享的处理程序与非共享的处理程序的差异:

    • request_irq()的参数flags必须设置IRQF_SHARED标志。
    • 对每个注册的中断处理程序,dev参数必须唯一。指向任一设备结构的指针可以满足该要求。不能给共享的处理程序传递NULL值。
    • 中断处理程序必须能区分它的设备是否真的产生了中断,这需要硬件的支持与处理程序中相关的处理逻辑。

    内核接收一个中断后,将依次调用在中断线上注册的每一个处理程序。如果处理程序确定相对应的设备没有产生设备,那么处理程序立刻退出。这需要硬件设备提供状态寄存器,以便中断处理程序进行检查。

    中断上下文

    当执行一个中断处理程序时,内核处于中断上下文(interrput context)。

    进程上下文是一种内核所处的操作模式,内核代表进程执行系统调用或者运行内核线程。在进程上下文中,可以通过CURRENT关联当前进程。由于进程是以进程上下文的形式连接到内核中的,因此进程上下文可以睡眠也可以调用调度程序(是指进程可以睡眠也可以调用程序吗??)。

    因为没有后备进程,中断上下文不可以睡眠,不能调用函数。如果一个函数睡眠,就不能在中断处理程序使用,这是对中断处理程序使用函数的限制(为什么中断不能睡眠)。

    中断上下文有严格的时间限制,因为它打断了其他代码。中断上下文中的代码应该迅速简介,尽量不要处理繁重的工作。因为这种异步执行的特性,所以应尽量把工作从中断处理程序中分离,放在下半部在更合适的时间执行。

    中断程序处理栈可以共享所中断进程的内核栈,在32位系统上栈的大小是8KB;也可以每个中断处理程序独占一页,在32位系统上时4KB。尽量节约内核栈空间

    中断处理机制的实现

    过程:

    • 设备产生中断,通过总线把电信号发送给中断控制器
    • 中断控制器在激活的情况下(允许被屏蔽)会把中断发往处理器
    • 处理器停止当前任务,关闭中断系统,跳转到内存预定义位置开始执行代码(预定义位置由内核设置,是中断处理程序的入口点)。对于每条中断线,处理器会跳到对应的位置,获取接收中断的IRQ
    • 内核调用函数do_IRQ()
    • do_IRQ()需要确保在该中断线上有一个有效的处理程序,且程序已启动但尚未被执行。在这种情况下,do_IRQ()会调用handle_IRQ_event()运行中断线的中断处理程序。
    • 函数执行完后,返回到do_IRQ()。该函数做清理工作返回到初始入口点,再从入口点跳转到函数ret_frm_intr(),该函数检查重新调度是否正在挂起。
      • 如果重新调度正在挂起
        • 如果内核返回用户空间(用户进程中断),schedule()将被调用
        • 如果内核返回内核空间(内核本身被中断),只有在preempt_count为0时,schedule()才会被调用
      • 如果没有挂起的工作,那么寄存器恢复,内核恢复到中断的点。

    /proc/interrupts

    存放的是系统中与中断相关的统计信息。

    中断控制

    对整个处理器所有中断的处理

    禁止与激活当前处理器的中断:

    local_irq_disable();//cli
    local_irq_enable(); //sti
    

    这两个函数一般以单个汇编指令实现。这两个函数可能会产生风险,所以需要一种机制把中断恢复到以前的状态而不是简单的禁止或激活。

    unsigned long flags;	//flags通过值传递
    local_irq_save(flags);
    local_irq_restore(flags);
    

    对这两个函数的调用必须在同一个函数中进行。

    而以上几个函数既可以在中断中调用,也可以在进程上下文中调用。

    cli():禁止系统中所有处理器上的中断

    sti():激活所有处理器的中断

    现在已删除这两个函数,如果想保证对共享数据的互斥访问,必须结合自旋锁等方法。

    取消全局cli()的优点:

    • 强制驱动程序编写者实现真正的加锁。因为特定目的的细粒度加锁比全局锁更快
    • 使得代码更具流线型,避免了代码的成簇布局,中断系统更简单也更易于理解。
    对系统中某条中断线的处理
    void disable_irq(unsigned int irq);
    void disable_irq_nosync(unsigned int irq);
    void enable_irq(unsigned int irq);
    void synchronize_irq(unsigned int irq);
    

    前两个函数禁止中断控制器指定的中断线,即禁止给定中断向系统中所有处理器的传递。并且disable_irq()只有在当前正在执行的处理程序完成后才能返回。因此调用者不仅要确保不在指定线上传递新的中断,还要确保所有已经开始执行的处理程序已全部退出。而disable_irq_nosync()不会等待当前中断处理程序执行完毕。

    后两个函数启用中断控制器指定的中断线。synchronize_irq()等待一个特定的中断处理程序的退出。如果该处理程序正在执行,那么该函数必须退出后才能返回。

    这些函数的调用可以嵌套,但是对于disable_irq()、disable_irq_nosync()函数,每调用一次都需要相对应的调用一次enable_irq()。当enable_irq()完成最后一次调用,才能真正激活中断线。这三个函数可以从中断或进程上下文中调用,而且不会睡眠。

    中断系统的状态

    了解中断系统的状态,例如中断是禁止的还是激活的

    irqs_disable()
    

    如果本地处理器中的中断系统被禁止,则返回非0,否则返回0。

    检查内核当前上下文的接口:

    in_interrupt() //如果内核当前处于任何类型的中断处理中,返回非0  说明内核此刻正在执行中断处理程序或者下半部处理程序
    in_irq()//在内核正在执行中断处理程序时返回非0
    

    如果in_interrupt()返回0,说明内核处于进程上下文中。

    中断控制方法的列表

    小结

    中断是一种由设备使用的硬件资源异步向处理器发信号,由硬件打断操作系统。

    大多数硬件通过中断与操作系统通信。对给定硬件进行管理的驱动程序注册中断处理程序是为了响应并处理来自相关硬件的中断。中断过程所做的工作包括应答并重新设置硬件。从设备拷贝数据到内存以及反之,处理硬件请求,并发送新的硬件请求。

    内核提供的接口包括注册和注销中断处理程序、禁止中断、屏蔽中断线以及检查中断系统的状态。

    因为中断打断了其他代码的执行。所以必须赶快执行完,但同时中断有大量的工作要做,所以为了获取平衡,内核把中断的工作分为两半。中断处理程序是上半段,下半段将在后面讨论

    展开全文
  • 中断和中断处理流程

    千次阅读 2020-12-20 04:41:20
    1. 中断概念中断是指由于接收到来自外围硬件(相对于中央处理器和内存)的异步信号或来自软件的同步信号,而进行相应的硬件/软件处理。发出这样的信号称为进行中断请求(interrupt request,IRQ)。硬件中断导致处理器...

    1. 中断概念

    中断是指由于接收到来自外围硬件(相对于中央处理器和内存)的异步信号或来自软件的同步信号,而进行相应的硬件/软件处理。发出这样的信号称为进行中断请求(interrupt request,IRQ)。硬件中断导致处理器通过一个上下文切换(context switch)来保存执行状态(以程序计数器和程序状态字等寄存器信息为主);软件中断则通常作为CPU指令集中的一个指令,以可编程的方式直接指示这种上下文切换,并将处理导向一段中断处理代码。中断在计算机多任务处理,尤其是实时系统中尤为有用。这样的系统,包括运行于其上的操作系统,也被称为“中断驱动的”(interrupt-driven)。

    中断是一种使CPU中止正在执行的程序而转去处理特殊事件的操作,这些引起中断的事件称为中断源,它们可能是来自外设的输入输出请求,也可能是计算机的一些异常事故或其它内部原因。

    中断:在运行一个程序的过程中,断续地以“插入”方式执行一些完成特定处理功能的程序段,这种处理方式称为中断。

    2. 中断的作用

    并行操作

    硬件故障报警与处理

    支持多道程序并发运行,提高计算机系统的运行效率

    支持实时处理功能

    3. 术语

    按中断源进行分类:发出中断请求的设备称为中断源。按中断源的不同,中断可分为

    内中断:即程序运行错误引起的中断

    外中断:即由外部设备、接口卡引起的中断

    软件中断:由写在程序中的语句引起的中断程序的执行,称为软件中断

    允许/禁止(开/关)中断: CPU通过指令限制某些设备发出中断请求,称为屏蔽中断。从CPU要不要接收中断即能不能限制某些中断发生的角度 ,中断可分为

    可屏蔽中断 :可被CPU通过指令限制某些设备发出中断请求的中断, 那是不是意味着进中断时disable整个中断,其实disable的都是可屏蔽中断?

    不可屏蔽中断:不允许屏蔽的中断如电源掉电

    中断允许触发器:在CPU内部设置一个中断允许触发器,只有该触发器置“1”,才允许中断;置“0”,不允许中断。

    指令系统中,开中断指令,使中断触发器置“1”

    关中断指令,使中断触发器置“0”

    中断优先级:为了管理众多的中断请求,需要按每个(类)中断处理的急迫程度,对中断进行分级管理,称其为中断优先级。在有多个中断请求时,总是响应与处理优先级高的设备的中断请求。

    中断嵌套:当CPU正在处理优先级较低的一个中断,又来了优先级更高的一个中断请求,则CPU先停止低优先级的中断处理过程,去响应优先级更高的中断请求,在优先级更高的中断处理完成之后,再继续处理低优先级的中断,这种情况称为中断嵌套。

    Intel的官方文档里将中断和异常理解为两种中断当前程序执行的不同机制。这是中断和异常的共同点。不同点在于:

    中断(interrupt)是异步的事件,典型的比如由I/O设备触发;异常(exception)是同步的事件,典型的比如处理器执行某条指令时发现出错了等等。

    中断又可以分为可屏蔽中断和非可屏蔽中断,异常又分为故障、陷阱和异常中止3种,它们的具体区别很多书籍和官方文档都解释的比较清楚这里不再赘述。

    关于它们的区别有两点是需要注意的:

    平常所说的屏蔽中断是不包括异常的,即异常不会因为CPU的IF位被清(关中断,指令:cli)而受影响,比如缺页异常,即使关了中断也会触发CPU的处理,回答了我上面红色部分疑问。

    通常说的int 80h这种系统调用使用的中断方式实际上硬件上是理解为异常处理的,因此也不会被屏蔽掉,这也很好理解,int 80h这种中断方式是程序里主动触发的,对于CPU来说属于同步事件,因此也就属于异常的范畴。

    4. 中断(异常)处理过程

    需要明确的一点是CPU对于中断和异常的具体处理机制本质上是完全一致的,即:

    当CPU收到中断或者异常的信号时,它会暂停执行当前的程序或任务,通过一定的机制跳转到负责处理这个信号的相关处理程序中,在完成对这个信号的处理后再跳回到刚才被打断的程序或任务中。这里只描述保护模式下的处理过程,搞清楚了保护模式下的处理过程(更复杂),实模式下的处理机制也就容易理解了。

    具体的处理过程如下:

    1)  中断响应的事前准备:

    系统要想能够应对各种不同的中断信号,总的来看就是需要知道每种信号应该由哪个中断服务程序负责以及这些中断服务程序具体是如何工作的。系统只有事前对这两件事都知道得很清楚,才能正确地响应各种中断信号和异常。

    系统将所有的中断信号统一进行了编号(一共256个:0~255),这个号称为中断向量,具体哪个中断向量表示哪种中断有的是规定好的,也有的是在给定范围内自行设定的。  中断向量和中断服务程序的对应关系主要是由IDT(中断向量表)负责。操作系统在IDT中设置好各种中断向量对应的中断描述符(一共有三类中断门描述符:任务门、中断门和陷阱门),留待CPU查询使用。而IDT本身的位置是由idtr保存的,当然这个地址也是由OS填充的。

    中断服务程序具体负责处理中断(异常)的代码是由软件,也就是操作系统实现的,这部分代码属于操作系统内核代码。也就是说从CPU检测中断信号到加载中断服务程序以及从中断服务程序中恢复执行被暂停的程序,这个流程基本上是硬件确定下来的,而具体的中断向量和服务程序的对应关系设置和中断服务程序的内容是由操作系统确定的。

    2) CPU检查是否有中断/异常信号

    CPU在执行完当前程序的每一条指令后,都会去确认在执行刚才的指令过程中中断控制器(如:8259A)是否发送中断请求过来,如果有那么CPU就会在相应的时钟脉冲到来时从总线上读取中断请求对应的中断向量[2]。

    对于异常和系统调用那样的软中断,因为中断向量是直接给出的,所以和通过IRQ(中断请求)线发送的硬件中断请求不同,不会再专门去取其对应的中断向量。

    3) 根据中断向量到IDT表中取得处理这个向量的中断程序的段选择符

    CPU根据得到的中断向量到IDT表里找到该向量对应的中断描述符,中断描述符里保存着中断服务程序的段选择符。

    4) 根据取得的段选择符到GDT中找相应的段描述符

    CPU使用IDT查到的中断服务程序的段选择符从GDT中取得相应的段描述符,段描述符里保存了中断服务程序的段基址和属性信息,此时CPU就得到了中断服务程序的起始地址。这里,CPU会根据当前cs寄存器里的CPL和GDT的段描述符的DPL,以确保中断服务程序是高于当前程序的,如果这次中断是编程异常(如:int 80h系统调用),那么还要检查CPL和IDT表中中断描述符的DPL,以保证当前程序有权限使用中断服务程序,这可以避免用户应用程序访问特殊的陷阱门和中断门[3]。

    5) CPU根据特权级的判断设定即将运行的中断服务程序要使用的栈的地址

    CPU会根据CPL和中断服务程序段描述符的DPL信息确认是否发生了特权级的转换,比如当前程序正运行在用户态,而中断程序是运行在内核态的,则意味着发生了特权级的转换,这时CPU会从当前程序的TSS信息(该信息在内存中的首地址存在TR寄存器中)里取得该程序的内核栈地址,即包括ss和esp的值,并立即将系统当前使用的栈切换成新的栈。这个栈就是即将运行的中断服务程序要使用的栈。紧接着就将当前程序使用的ss,esp压到新栈中保存起来。也就说比如当前在某个函数中,使用的栈,在中断发生时,需要切换新的栈。

    6) 保护当前程序的现场

    CPU开始利用栈保护被暂停执行的程序的现场:依次压入当前程序使用的eflags,cs,eip,errorCode(如果是有错误码的异常)信息。

    官方文档[1]给出的栈变化的示意图如下:

    7) 跳转到中断服务程序的第一条指令开始执行

    CPU利用中断服务程序的段描述符将其第一条指令的地址加载到cs和eip寄存器中,开始执行中断服务程序。这意味着先前的程序被暂停执行,中断服务程序正式开始工作。

    8) 中断服务程序处理完毕,恢复执行先前中断的程序

    在每个中断服务程序的最后,必须有中断完成返回先前程序的指令,这就是iret(或iretd)。程序执行这条返回指令时,会从栈里弹出先前保存的被暂停程序的现场信息,即eflags,cs,eip重新开始执行。

    展开全文
  • 这一章都在介绍中断,包括中断的产生、中断处理程序、中断向量表、中断过程、相关指令。 解决本次实验的前提是将本章的内容理解好,那么在完成这部分(原书第12章-内中断)之后,开始实验吧~ 分析整个中断过程 (1)当...

    实验内容

    编写0号中断处理程序,使得在除法溢出发生时,在屏幕中间显示字符串"divide error!",然后返回到DOS。

    解题

    这一章都在介绍中断,包括中断的产生、中断处理程序、中断向量表、中断过程、相关指令。
    解决本次实验的前提是将本章的内容理解好,那么在完成这部分(原书第12章-内中断)之后,开始实验吧~

    分析整个中断过程

    (1)当发生除法溢出的时候,产生0号中断信息,从而引发中断。
    CPU会完成以下工作:

    1. 取得中断类型码 0
    2. 标志寄存器入栈,TF、IF 设置为 0
    3. CS、IP入栈
    4. (IP) = (0 * 4), (CS) = (0 * 4 + 2)

    对于第二步,为社么要将TF、IF设置为0?
    在十一小节中王爽老师给出了解答:CPU在执行完一条指令之后,如果检测到标志寄存器TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,它引发的中断过程如下:
    在这里插入图片描述
    这也是本问题的关键了,反过来想,如果TF不设置为0,很显然CPU在即将执行中断处理程序的第一条指令时检测到了TF为1,就会在执行完该条指令之后继续进入到单步中断的过程当中… 就这样可能无限循环下去了,所以在中断处理之前必须将TF置为0。
    对于IF书中没有多余解释,对此笔者搜集到了以下资料:
    在这里插入图片描述
    ↑Linux系统中的中断分类

    只看表格中的第二行第一条的处理方式一栏:清除标志寄存器eflags的IF标志可屏蔽中断,笔者推测IF标志位的功能就是显示中断的开(On)关(Off),可以译为:Interrupt Flag,
    (查阅了其他博客也确实是这样)

    IF:中断允许标志位。它用来控制8086是否允许接收外部中断请求。若IF=1,8086能响应外部中断,反之则屏蔽外部中断;

    这么想就能够串通了,只是用来屏蔽掉其他中断(处理中断的过程中关闭中断功能,确保能够顺利执行完本次中断,处理完成后再打开中断)

    好了,现在明白CPU在发生中断之后做的事情了,那么我们需要完成什么呢?
    1.相关处理
    2.向显示缓冲区写入想要显示的字符串 “divide error!”
    3.返回 DOS

    按照王爽老师的讲解,将这段程序命名为 do0。

    但是一个从未碰到过的问题来了:do0程序应该放到哪?
    do0应该放到内存中,因为除法溢出随时可能发生,CPU随时都可能将CS:IP指向do0的入口,然后执行它。

    按理来说我们需要向操作系统申请一块空间去放置do0程序,但是过多的讨论申请内存将偏离问题的主线,所以这里简单做:直接使用一块别的程序不会用到的内存区,将do0拷贝到其中就可以了。

    12.3 中断向量表 章节中作者讲解了中断向量表的存储位置(针对8086CPU):
    0000:0000 到 0000:03FF 共计 400H (1024字节)个单元中存放了256个中断,但是实际上系统中要处理的中断事件没有达到256个,所以在表中有许多单元是空着的。

    根据书中的指示:0000:0200 到 0000:02FF 的256个字节的空间所对应的单元都是空的
    因此,可以将do0拷贝到内存0000:0200处

    do0程序的放置解决了,接下来就是当发生除法溢出的时候,CPU会取得中断码0,然后到(4 * 0) = 0H的地址(0000:0000H)去找中断处理程序的偏移地址,到(4 * 0 + 2) = 2H的地址找中断处理程序的段地址。
    如下图:
    在这里插入图片描述

    总结一下将要做的事情:

    1. 编写可以显示“divide error!”的中断处理程序 do0;
    2. 将do0拷贝到内存0000:0200处;
    3. 将do0的入口地址0000:0200 存储在中断向量表0号单元中。

    程序框架:
    在这里插入图片描述

    安装do0程序与设置中断向量表

    需要明晰的是:我们编写的程序在运行时do0处的代码是不执行的!
    我们要做的是将do0这部分代码放入到之前选择好的那块没有程序会使用的内存空间0000:0200H上,并且设置好中断向量表中0号单元的内容,这样CPU就可以在发生除法溢出的时候乖乖地去0号向量表单元取出对应的偏移地址、段地址,然后顺利完成中断处理程序do0的执行。

    安装do0程序

    将编写好的do0程序代码送入到0000:0200H内存段中,需要使用到 movsb指令,如下(由于csdn没有比较好展示汇编代码的方式,这里笔者先放上Notepad++中的截图方便查看):

    在这里插入图片描述
    上面就是do0程序的安装过程,其中需要注意的是第18行:
    mov cx, offset do0end - offset do0
    为了计算do0段的大小,需要额外设置标号 do0end 放在do0程序段结束的位置。
    注:如果只是单纯的计算出所编写的do0程序块大小,然后赋值给cx,这种方式的编程很明显是不可取的,因为do0稍微改一下可能影响程序段的大小,又得重新计算,所以使用标号法交给编译器去做这些麻烦的事情吧~

    设置中断向量表

    对于0号中断,我们需要在0000:0000处填上偏移地址,在0000:0004处填上中断处理程序段地址。
    在这里插入图片描述
    注:对于N号中断,偏移地址应该填在0000:(N * 4) 字单元中,段地址应该填在0000:(N * 4 + 2) 字单元中。

    编写do0程序

    do0程序需要做的:
    1.相关处理
    2.向显示缓冲区写入想要显示的字符串 “divide error!”
    3.返回 DOS

    这个相关处理指的是什么?
    先别急,看第二条:向显示缓冲区写入想要显示的字符串 “divide error!”
    这个我们熟悉吧?不熟悉请点这里-> 在显示缓冲区内编程完成字符串显示
    这个不难,但是既然是字符串的显示,字符串的存储位置也是需要考虑的问题。

    在这里插入图片描述
    想一想:像上图这样放在代码的起始位置行不行?
    答案是不行,因为那样字符串的位置是不够“安全”的,因为在整个程序(完成拷贝的程序,不是do0程序)结束之后,原来的空间会被释放(别的程序可能会用到它),那么难免这块空间会被修改,我们需要一块在do0程序被执行时装有字符串的空间,说起来比较抽象,看代码:
    在这里插入图片描述

    完整代码

    assume cs:code
    
    ;编写0号中断程序,使得在除法溢出发生时,
    ;在屏幕中显示字符串"divide error!"
    ;然后返回dos
    
    code segment
    start:
    	;首先将中断处理程序送入到中断向量地址处
    	mov ax, cs
    	mov ds, ax
    	mov si, offset do0	;源地址 cs:offset do0
    	
    	mov ax, 0
    	mov es, ax
    	mov di, 200H	;目标地址 0:200H
    	
    	mov cx, offset do0end - offset do0	;用标号计算出do0段的大小
    	cld	;设置si、di递增
    	rep movsb
    	
    	;设置中断向量表
    	mov ax, 0
    	mov es, ax
    	mov word ptr es:[0 * 4], 200h	;04H = 200H
    	mov word ptr es:[0 * 4 + 2], 0	;00 = 0H
    	
    	mov ax, 4c00h
    	int 21h
    	
    	
    do0:
    	jmp short do0start
    	db "divide error!"
    	
    do0start:;中断程序执行开始处:打印字符串
    	mov ax, cs
    	mov ds, ax
    	mov si, 202H
    	
    	mov ax, 0b800H
    	mov es, ax	;找到显卡位置
    	mov di, 160 * 12 + 36 * 2	;显示在显卡中间
    	
    	mov cx, 13	;一共13个字符,挨个拷贝
    	mov dh, 11000010B	;显示 红底闪烁绿字
    	
    s:
    	mov dl, [si]	;将需要显示的字符串放到dl
    	mov es:[di], dl	;输送到显卡处
    	mov es:[di + 1], dh;设置字的属性
    	inc si	;往后偏移一个字节
    	add di, 2;往后偏移一个字
    	loop s
    	
    	mov ax, 4c00h
    	int 21H
    do0end:
    	nop
    code ends
    end start
    

    效果展示

    在这里插入图片描述

    展开全文
  • Linux中断处理

    2020-12-29 10:02:16
    4.中断处理流程。5.各类型中断的具体执行流程。中断的类型及具体的种类Linux0.11内核注释1.可屏蔽硬件中断。优先级较低,可以被忽略或者延后处理,通常有键盘,打印机。2.不可屏蔽硬件中断。CPU必须无条件响应,...

    简介

    1.基于Linux0.11代码进行分析。

    2.中断类型分类以及具体的中断。

    3.中断向量的注册。

    4.中断处理流程。

    5.各类型中断的具体执行流程。

    中断的类型及具体的种类

    Linux0.11内核注释

    1.可屏蔽硬件中断。优先级较低,可以被忽略或者延后处理,通常有键盘,打印机。

    2.不可屏蔽硬件中断。CPU必须无条件响应,优先级非常的高,通常有电源断电,内存校验错误。

    3.软件中断--错误。缺页异常?内存访问时产生缺页异常中断,在中断处理程序中实际分配内存,然后在缺页中断处理完成后,继续访问内存。

    4.软件中断--陷阱。常见的例子有系统调用,int 0x80,首先会调用中断处理程序,处理完成后,会继续执行后面的指令。

    5.软件中断--放弃。常见的例子有除零,该错误发生后,调用中断处理程序,中断处理程序中会产生SIGFPE信号,程序可通过注册对应的信号处理函数处理该信号。

    中断向量的注册

    1.源码在head.s这个文件中。

    2.0x20-0x2f是硬件中断,在head.s中初始化为ignore_int后,后续的硬件初始化过程中会初始化其中断处理函数。

    3.中断向量存储在全局的中断向量数组结构中,该数组长度256,每个元素8个字节,在head.s文件中定义。在system.h文件中,定义了设置该数组的接口。代码如下:

    // head.s

    .align 2

    .word 0

    idt_descr:

    .word 256*8-1 # idt contains 256 entries

    .long idt

    // system.h

    #define _set_gate(gate_addr,type,dpl,addr) \

    __asm__ ("movw %%dx,%%ax\n\t" \

    "movw %0,%%dx\n\t" \

    "movl %%eax,%1\n\t" \

    "movl %%edx,%2" \

    : \

    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \

    "o" (*((char *) (gate_addr))), \

    "o" (*(4+(char *) (gate_addr))), \

    "d" ((char *) (addr)),"a" (0x00080000))

    #define set_intr_gate(n,addr) \

    _set_gate(&idt[n],14,0,addr)

    #define set_trap_gate(n,addr) \

    _set_gate(&idt[n],15,0,addr)

    #define set_system_gate(n,addr) \

    _set_gate(&idt[n],15,3,addr)

    4.在head.s中,调用startup_32函数过程中调用setup_idt函数将全局的中断描述符初始化为ignore_int。代码如下

    setup_idt:

    lea ignore_int,%edx

    movl $0x00080000,%eax

    movw %dx,%ax /* selector = 0x0008 = cs */

    movw $0x8E00,%dx /* interrupt gate - dpl=0, present */

    lea idt,%edi // edi寄存器指向idt数组的起始地址

    mov $256,%ecx // 循环256次

    rp_sidt:

    movl %eax,(%edi)

    movl %edx,4(%edi)

    addl $8,%edi // 下标递增

    dec %ecx

    jne rp_sidt

    lidt idt_descr

    ret

    5.在trap.c文件中,调用trap_init函数注册软件中断。在sched.c中调用sched_init注册2个调度相关的中断,0x20硬件时钟中断,0x80系统调用中断。其余的硬件中断在硬件初始化时注册。以下是部分代码:

    void sched_init(void)

    {

    set_intr_gate(0x20,&timer_interrupt);

    set_system_gate(0x80,&system_call);

    }

    void trap_init(void)

    {

    int i;

    // 设置除操作出错的中断向量值。

    set_trap_gate(0,&divide_error);

    set_trap_gate(1,&debug);

    set_trap_gate(2,&nmi);

    set_system_gate(3,&int3); /* int3-5 can be called from all */

    set_system_gate(4,&overflow);

    set_system_gate(5,&bounds);

    set_trap_gate(6,&invalid_op);

    set_trap_gate(7,&device_not_available);

    set_trap_gate(8,&double_fault);

    set_trap_gate(9,&coprocessor_segment_overrun);

    set_trap_gate(10,&invalid_TSS);

    set_trap_gate(11,&segment_not_present);

    set_trap_gate(12,&stack_segment);

    set_trap_gate(13,&general_protection);

    set_trap_gate(14,&page_fault);

    set_trap_gate(15,&reserved);

    set_trap_gate(16,&coprocessor_error);

    // 下面把int17-47的陷阱门先均设置为reserved,以后各硬件初始化时会重新设置自己的陷阱门。

    for (i=17;i<48;i++)

    set_trap_gate(i,&reserved);

    // 设置协处理器中断0x2d(45)陷阱门描述符,并允许其产生中断请求。设置并行口中断描述符。

    set_trap_gate(45,&irq13);

    outb_p(inb_p(0x21)&0xfb,0x21); // 允许8259A主芯片的IRQ2中断请求。

    outb(inb_p(0xA1)&0xdf,0xA1); // 允许8259A从芯片的IRQ3中断请求。

    set_trap_gate(39,&parallel_interrupt); // 设置并行口1的中断0x27陷阱门的描述符。

    }

    tips:如果想要知道中断处理函数在哪里注册,注册的函数是什么,可搜索system.h文件中的定义的_set_intr_gate, _set_trap_gate, _set_system_gate函数被调用的地方。

    中断的处理流程

    在分析和阅读源码前,先尝试思考通用的中断处理逻辑。

    1.硬件中断。硬件中断通常来自于外部硬件触发。此时进程可能在任意一个状态(用户态执行用户代码,或者在执行中断)。如果是在执行中断,那么就应该判断当前正在执行的中断和触发中断的优先级,然后确定是否要打断正在执行的中断。

    2.软件中断。软件中断来自用户代码主动调用产生,所以此时应该是在用户态执行用户代码。

    3.用户态下的中断应该有一样的通用流程,大致是,保存当前正在执行代码的上下文,切换到内核态调用中断处理函数,完成后回到用户态恢复上下文,然后继续执行。

    中断处理流程

    4.中断处理是在内核态下运行,因此使用的是内核的堆栈,如果中断时正在运行用户态的代码,那么在切到内核态后,将当时的上下文寄存器等信息存在内核中的堆栈中。示意图如下:

    中断时的堆栈

    5.当在中断情况下发生高优先级的中断时,会在中断过程中使用的堆栈的基础上再次保存中断的上下文,然后执行更高优先级的中断。堆栈示意图如下:

    高优先级中断打断当前的中断时的堆栈

    中断的具体执行

    1.源码主要在asm.s和trap.c这2个文件中。

    2.在调用具体的中断处理函数前,都会将段寄存器和ip寄存器入中断栈,这是中断打断的正在运行的进程的上下文。然后将实际要执行的用C实现的中断处理函数push入栈,再将普通的寄存器如eax,ebx等寄存器入栈,下一步将一些段寄存器入栈,最后将返回后执行的指令的栈地址入栈。

    Linux0.11完全注释中断堆栈图

    3.无返回值的中断以int 0x1,除0的中断举例,代码如下:

    divide_error:

    pushl $do_divide_error # 这里push实际要调用的函数,下一条指令又将其和eax寄存器的值交换。

    # 其目的是为了代码复用,其它中断可以直接调用no_error_code代码段

    # do_divide_error在traps.c中实现

    no_error_code: # 这里是五出错号处理的入口处。

    xchgl %eax,(%esp) # _do_divide_error的地址→eax,eax被交换入栈

    pushl %ebx #保存打断进程的寄存器上下文

    pushl %ecx

    pushl %edx

    pushl %edi

    pushl %esi

    pushl %ebp

    push %ds # !!16位的段寄存器入栈后也要占用4个字节。

    push %es

    push %fs

    pushl $0 # "error code" #将数值0作为出错码入栈

    lea 44(%esp),%edx # 取对堆栈中原调用返回地址处堆栈指针位置,并压入堆栈。

    pushl %edx

    movl $0x10,%edx # 初始化段寄存器ds、es和fs,加载内核数据段选择符 0x10是内核栈的段起始地址

    mov %dx,%ds

    mov %dx,%es

    mov %dx,%fs

    # 下行上的 * 号表示调用操作数指定地址处的函数,称为间接调用。这句的含义是调用引起本次

    # 异常的C处理函数,例如do_divide_error等。

    call *%eax

    addl $8,%esp

    pop %fs

    pop %es

    pop %ds

    popl %ebp

    popl %esi

    popl %edi

    popl %edx

    popl %ecx

    popl %ebx

    popl %eax # 弹出原来eax中的内容

    iret # 返回时会取出栈中保存的被打断进程的eip寄存器的值 继续执行后续的指令

    4.其它的类似的无返回值的中断处理函数如下:

    debug:

    pushl $do_int3 # _do_debug C函数指针入栈

    jmp no_error_code # 复用no_error_code代码段

    5.含error_code的中断和有error_code的中断类似,这里就不粘贴重复的代码了,可以参考源码中的asm.s文件。把握住中断处理的核心逻辑,保存上下文,处理中断,恢复上下文,以及参考源码中的system.h, asm.s, traps.c这几个文件。

    6.深究int 0x1除0中断的处理。

    void do_divide_error(long esp, long error_code)

    {

    die("divide error",esp,error_code);

    }

    static void die(char * str,long esp_ptr,long nr)

    {

    long * esp = (long *) esp_ptr;

    int i;

    printk("%s: %04x\n\r",str,nr&0xffff);

    // 下行打印语句显示当前调用进程的CS:EIP、EFLAGS和SS:ESP的值。

    // EIP:\t%04x:%p\n - esp[1]是段选择符(cs),esp[0]是eip.

    // EFLAGS:\t%p\n - esp[2]是eflags

    // ESP:\t%04x:%p\n - esp[4]是源ss,esp[3]是源esp

    printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",

    esp[1],esp[0],esp[2],esp[4],esp[3]);

    printk("fs: %04x\n",_fs());

    printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));

    if (esp[4] == 0x17) {

    printk("Stack: ");

    for (i=0;i<4;i++)

    printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));

    printk("\n");

    }

    str(i); // 取当前运行任务的任务号

    printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);

    for(i=0;i<10;i++)

    printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));

    printk("\n\r");

    // 前面都是打印调试信息 这里才是真正的处理逻辑 在这里是直接退出 错误码为11

    do_exit(11); /* play segment exception */

    }

    总结

    中断处理的分析到这里就告一段落了。首先要对中断进行分类,并且了解每种类型的中断具体有哪些类型。然后了解内核是如何处理中断的,中断处理函数使用的栈都是内核的内存空间,在执行具体的处理函数前,要先保存被中断的进程的上下文,然后再调用具体的处理函数,处理完成后再恢复被中断进程的上下文以继续运行。在这里要说明下,内核中的栈每次起始地址都是一样的,这是因为调用结束后,要么不会返回,要么返回后栈又变空了,所以内核栈是可以重复利用的。

    展开全文
  • 在开始写中断函数之前,我们来一起回顾一下,单片机的中断系统。中断的意思(学习过微机原理与接口技术的同学,没学过单片机,也应该知道),我们在这里就不讲了,首先来回忆下中断系统涉及到哪些问题。(1)中断源:...
  • 汇编语言编写中断处理程序

    千次阅读 2021-02-12 20:41:22
    CPU都具有执行完当前正在执行的指令之后,检测到从CPU外部(外中断)或内部(内...首先,CPU接收到中断类型码(一个字节型数据),然后通过中断向量表找到对应的中断处理程序的入口地址。中断向量表是入口地址的列表
  • 3.3.2函数库intALib函数库intALib是个汇编函数库,里面保存了与CPU结构相关的中断处理函数。这里进行简单的分析。1.intintLevelSet(level)x86系列的CPU并不支持中断级,因此该函数是个空函数。2.intintLock()该...
  • 释放中断号中断处理程序的实现:中断处理程序事实上还是一个普通的函数,只是我们要注意中断处理程序是 在中断期间运行的,具有最高的优先级别。还有中断处理程序必须要在尽量短的时间内完成,这对于那些需在中断...
  • 我正在做一个编程项目——用Python编写一个基本的P2P文件共享应用程序。我使用两个线程:一个主线程调用select并等待来自套接字列表的输入系统标准(接收键入的命令)和一个助手线程,从队列中获取状态更新消息并打印...
  • CPU暂停正在执行的程序,保留CPU环境后,自动地转去执行该I/O设备的中断处理程序。 执行完后,再回到断点,继续执行原来的程序。 I/O设备可以是字符设备、块设备、通信设备等。由于中断是由外部设备引起的,故又称外...
  • 文件:操作系统中断方式小结.rar大小:15KB下载:X86体系中,CPU在INTR引脚上接到一个中断请求信号,如果此时IF=1,CPU就会在当前指令执行完以后开始响应外部的中断请求,这时,CPU在INTA引脚连续发两个负脉冲,外设在...
  • 前言笔者在 《程序是如何在 CPU 中运行的(二)》中从 PC 指针寄存器的角度分析了一级函数调用和二级函数调用执行的过程,那么中断服务子程序又是如何被执行的呢?两者的相同点和不同点是什么呢?该篇文章笔者将详细地...
  • 中断的本质是一种特殊的电信号,由硬件设备发向处理器,处理器接收到中断后,会马上向操作系统反映此信号的到来,然后由操作系统负责处理这些新来的数据。所以中断的生成并不考虑与处理器同步,即中断是可以随时产生...
  • 1、输入/输出软件一般分为四个层次:用户层、与设备无关的软件层、设备驱动程序和中断处理程序。请说明以下各工作是在哪一层完成的: 1)为磁盘读操作计算磁道、扇区和磁头; 2)向设备寄存器写命令; 3)检查用户是否有权...
  • 二、特点不同1、调用中断服务程序:当中央处理器正在处理内部数据时,外界发生了紧急情况,要求CPU暂停当前的工作转去处理这个紧急事件。处理完毕后,再回到原来被中断的地址,继续原来的工作。2、调用子程序:子...
  • c语言中的中断函数注意事项单片机_C语言函数_中断函数(中断服务程序)在开始写中断函数之前,我们来一起回顾一下,单片机的中断系统。中断的意思(学习过微机原理与接口技术的同学,没学过单片机,也应该知道),我们在...
  • 中断服务程序执行顺序

    千次阅读 2021-01-05 19:54:45
    单级中断系统中,中断服务程序执行顺序: ①保护现场 ②中断事件处理 ③恢复现场 ④开中断中断返回
  • 中断处理的详细过程

    2021-07-31 16:36:27
    经过中断判优,中断处理进入中断响应阶段,中断响应时,CPU向中断源发出中断响应信号,同时: 保护硬件现场 关中断 保存断点 获得中断服务程序的入口地址 4. 中断服务阶段 保护现场 开中断:即允许中断服务...
  • 中断系统的运行必须与中断服务子程序配合才能正确使用。设计中断服务子程序需要首先明确以下几个问题。中断服务子程序设计的任务中断服务子程序设计的基本任务有下列4条:(1)设置中断允许控制寄存器IE,允许相应的...
  • UCOSII的中断过程简介系统接收到中断请求后,如果CPU处于开中断状态,系统就会中止正在运行的当前任务,而按中断向量的指向去运行中断服务子程序,当中断服务子程序运行完成后,系统会根据具体情况返回到被中止的...
  • 原标题:ARM的SWI异常中断处理程序设计本节主要介绍编写 处理程序时需要注意的几个问题,包括判断SWI中断号,使用汇编语言编写SWI异常处理函数,使用C语言编写SWI异常处理函数,在特权模式下使用SWI ,从应用程序中...
  • 用户态和内核态 中断处理机制

    千次阅读 2021-01-15 22:57:54
    按照权限管理的原则,多数应用程序应该运行在最小权限下。因此,很多操作系统,将内存分成了两个区域: 内核空间(Kernal Space),这个空间只有内核程序可以访问; 用户空间(User Space),这部分内存专门给...
  • Linux缺页中断处理

    2021-05-17 19:27:23
    Linux缺页中断处理在i386 CPU将一个线性地址映射为物理地址的过程中,如果该地址的映射已经建立,但是发现相应的页面表项或目录项中的P(Present)标志为0,则表明相应的物理页面不在内存.此时,CPU将错误的请求地址放在...
  • 51单片机中断系统程序实例 (STC89C52RC)51单片机有了中断,在程序设计中就可以做到,在做某件事的过程中,停下来先去响应中断,做别的事情,做好别的事情再继续原来的事情。中断优先级是可以给要做的事情排序。...
  • 中断服务子程序的主程序结构;中断服务子程序的流程
  • 在C#中,我可以保留这些异常,并确保如果抛出任何异常,则程序中断。这就是我所希望的-这些异常应停止程序。在Java中,编译时会出现错误。据我所知,我有两种选择:处理所有这些并保持程序运行。但正如我所说-我...
  • 自定义0号中断处理程序,使得除法溢出发生时,调用自定义的0号中断处理程序。 然后在屏幕中间显示红色字:“divide error!” 代码 ; 自定义0号中断处理程序 assume cs:code code segment start: mov ax,cs mov ds,...
  • (2)在中断服务程序中实现流水灯控制,有两个按钮,1个是系统开始工作按钮,一个是系统停止按钮。 (3)掌握单片机定时器中断服务程序的设计方法。 2、原理 (1)8051单片机内部有两个16位可编程定时/计数器,记...
  • 6. ARM中断处理过程

    2020-12-23 01:30:09
    1 前言2 中断处理的准备过程2.1 中断模式的stack准备1 前言本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作。具体整个处理过程分成三个步骤来描述:1、第二章描述了中断处理的...
  • 中断处理的一般过程

    千次阅读 2021-07-03 22:00:18
    中断处理包括一下几个步骤: 1、中断请求 8088/8086CPU的NMI为边沿触发,INTR为电平触发。在中断请求被响应之前会一直发送中断请求。 2、中断源识别 当系统同时有多个中断源发出的中断请求时,系统往往只能相应...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 249,985
精华内容 99,994
关键字:

中断处理程序指令