linux禁用、打开中断

linux禁用、打开中断,第1张

local_irq_save是内核中用的函数吧,不能用到应用层。

对 local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。

unsigned long flags

local_irq_save(flags) //禁止

local_irq_restore(flags) // 激活

设备与处理器之间的工作通常来说是异步,设备数据要传递给处理器通常来说有以下几种方法:轮询、等待和中断。

让CPU进行轮询等待总是不能让人满意,所以通常都采用中断的形式,让设备来通知CPU读取数据。

2.6内核的函数参数与现在的参数有所区别,这里都主要介绍概念,具体实现方法需要结合具体的内核版本。

request_irq函数申请中断,返回0表示申请成功,其他返回值表示申请失败,其具体参数解释如下:

flags 掩码可以使用以下几个:

快速和慢速处理例程 :现代内核中基本没有这两个概念了,使用SA_INTERRUPT位后,当中断被执行时,当前处理器的其他中断都将被禁止。通常不要使用SA_INTERRUPT标志位,除非自己明确知道会发生什么。

共享中断 :使用共享中断时,一方面要使用SA_SHIRQ位,另一个是request_irq中的dev_id必须是唯一的,不能为NULL。这个限制的原因是:内核为每个中断维护了一个共享处理例程的列表,例程中的dev_id各不相同,就像设备签名。如果dev_id相同,在卸载的时候引起混淆(卸载了另一个中断),当中断到达时会产生内核OOP消息。

共享中断需要满足以下一个条件才能申请成功:

当不需要使用该中断时,需要使用free_irq释放中断。

通常我们会在模块加载的时候申请安装中断处理例程,但书中建议:在设备第一次打开的时候安装,在设备最后一次关闭的时候卸载。

如果要查看中断触发的次数,可以查看 /proc/interrupts 和 /proc/stat。

书中讲述了如何自动检测中断号,在嵌入式开发中通常都是查看原理图和datasheet来直接确定。

自动检测的原理如下:驱动程序通知设备产生中断,然后查看哪些中断信号线被触发了。Linux提供了以下方法来进行探测:

探测工作耗时较长,建议在模块加载的时候做。

中断处理函数和普通函数其实差不多,唯一的区别是其运行的中断上下文中,在这个上下文中有以下注意事项:

中断处理函数典型用法如下:

中断处理函数的参数和返回值含义如下:

返回值主要有两个:IRQ_NONE和IRQ_HANDLED。

对于中断我们是可以进行开启和关闭的,Linux中提供了以下函数 *** 作单个中断的开关:

该方法可以在所有处理器上禁止或启用中断。

需要注意的是:

如果要关闭当前处理器上所有的中断,则可以调用以下方法:

local_irq_save 会将中断状态保持到flags中,然后禁用处理器上的中断;如果明确知道中断没有在其他地方被禁用,则可以使用local_irq_disable,否则请使用local_irq_save。

locat_irq_restore 会根据上面获取到flags来恢复中断;local_irq_enable 会无条件打开所有中断。

在中断中需要做一些工作,如果工作内容太多,必然导致中断处理所需的时间过长;而中断处理又要求能够尽快完成,这样才不会影响正常的系统调度,这两个之间就产生了矛盾。

现在很多 *** 作系统将中断分为两个部分来处理上面的矛盾:顶半部和底半部。

顶半部就是我们用request_irq来注册的中断处理函数,这个函数要求能够尽快结束,同时在其中调度底半部,让底半部在之后来进行后续的耗时工作。

顶半部就不再说明了,就是上面的中断处理函数,只是要求能够尽快处理完成并返回,不要处理耗时工作。

底半部通常使用tasklet或者工作队列来实现。

tasklet的特点和注意事项:

工作队列的特点和注意事项:

你可以参考一下

一个硬中断的完整处理过程(2.4.24版本) ★ CPU做的工作:

CPU收到中断/异常信号;

CPU判断当前CPL级别如果等于3,则导致堆栈切换3->0,堆栈切换过程:

a. CPU从当前TR指向的TSS中读取SS0和ESP0;

b. CPU将当前的【SS:ESP】寄存器内容临时保存起来,假设为SSt和ESPt;

c. CPU将SS0和ESP0恢复到【SS:ESP】寄存器中;

d. CPU将在b中临时保存的SSt和ESPt压入当前的堆栈【SS:ESP】中(其实就是SS0和ESP0);

CPU判断当前CPL级别如果等于0,则不会有2中的步骤;

CPU将EFLAGS、CS、EIP依次压入当前的堆栈【SS:ESP】中;

如果当前是异常,则CPU将异常码error code压入当前的堆栈【SS:ESP】中,否则会省略该步骤;

对于中断,CPU清零当前EFLAGS->IF位,即关闭CPU中断使能,对于异常,CPU则不会清零该位;

执行对中断/异常处理程序的调用;

注:对中断/异常处理程序的要求,为了正常的从中断/异常处理程序中返回,中断/异常处理程序必须使用IRET指令返回,该指令会依次出栈EIP、CS和EFLAGS,比RET多一个EFLAGS,当EFLAGS恢复后,由于原来保存时IF位为1,因此这里相当于CPU中断使能又打开了;

★ Linux内核做的工作:

1. 中断向量表-->common_interrupt:

common_interrupt:

SAVE_ALL

pushl $ret_from_intr

SYMBOL_NAME_STR(call_do_IRQ):

jmp SYMBOL_NAME_STR(do_IRQ)

SAVE_ALL保存所有CPU没有保存的寄存器,由于do_IRQ是函数,这里直接调用jmp,(一般用call来调用函数,call会导致pushEIP,但jmp不会)这样当do_IRQ返回调用ret(ret相当于popEIP)时,会d出栈中最后一个元素到EIP,很显然这里就是ret_from_intr,也就是说,从do_IRQ中返回后,会跳转到ret_from_intr处继续执行;

2. 来到do_IRQ:

a. 首先给硬中断计数加1,irq_enter(cpu, irq)也就是:++local_irq_count(cpu);每进入一个硬中断处理函数前,local_irq_count(cpu)计数便被加1,处理完毕后减1;

b. 如果当前设备中断处理函数可以在中断打开的情况下运行,则调用sti将EFLAGS.IF置位,打开硬中断使能;

c. 调用request_irq注册的设备硬中断处理函数;

d. 无论EFLAGS.IF是否为0,都调用cli将EFLAGS.IF清零,将硬中断使能关闭;

e. 给硬中断计数减1,irq_enter(cpu, irq)该函数其实就是:--local_irq_count(cpu)

f. 如果此时有软中断需要运行(如在前面的硬中断处理函数中调用__cpu_raise_softirq),则进入do_softirq中处理软中断,关于do_softirq中的处理步骤见3;

e. do_IRQ执行ret,返回到ret_from_intr。

3. do_softirq:

a. 首先判断当前是否还有没有处理完毕的硬中断处理程序或软中断处理程序,如果有,则直接退出该函数。判断方法见附注【文(6)】。

b.将软中断处理计数加1,local_bh_disable()也就是local_bh_count(cpu)++;每进入do_softirq准备进行处理软中断前,local_bh_count(cpu)计数便被加1,软中断处理函数处理完毕后,在do_softirq返回前,将其值减1;

c. 无论EFLAGS.IF是否为0,都调用cli将EFLAGS.IF清零,将硬中断使能关闭,处理些软中断标志位的问题,随后调用sti将EFLAGS.IF置位,打开硬中断使能;

d. 执行软中断处理函数;

e. 调用cli将EFLAGS.IF清零,将硬中断使能关闭,处理些软中断标志位的问题;

f. 将软中断处理计数减1,local_bh_enable()也就是local_bh_count(cpu)--;

g. 返回到2.e中;

4. ret_from_intr:

ENTRY(ret_from_intr)

GET_CURRENT(%ebx)

movl EFLAGS(%esp),%eax

movb CS(%esp),%al

testl $(VM_MASK | 3),%eax

jne ret_with_reschedule

jmp restore_all

在这段代码中,通过"testl $(VM_MASK |3),%eax"检测中断前夕寄存器EFLAGS的高6位和代码段寄存器CS的内容,来判断中断前夕CPU是否运行于VM86模式、用户空间还是系统空间,对VM86这里不讲了,但是我们知道CS最低两位代表着中断发生时CPU的运行级别CPL,我们知道Linux只采用两种运行级别,系统为0,用户为3,所以,如果CS的最低两位为非0,那就说明中断发生于用户空间。如果中断发生于系统空间,控制就直接转移到restore_all,而如果发生于用户空间,则转移到ret_with_reschedule。在restore_all中恢复1中保存的寄存器,随后调用iret恢复EIP、CS、EFLAGS返回到中断发生时的状态。

5. ret_with_reschedule:

a. 如果发现当前进程的need_resched==1,则会调用schedule;

b. 如果发现还有待需要处理的软中断,则会调用do_softirq;

说明:能够走到ret_with_reschedule处,从4中可知,该中断前一定位于用户层,而且不可能有可中断的硬中断或软中断没有执行,也就是说到达这里in_interrupt必然为0。为什么这里要说不可能有可中断的硬中断或软中断没有执行呢?可中断的硬中断或软中断必然是被中断或者异常所打断的,当后者处理完毕后,从4中可知,由于后者发生前位于内核态(也就是可中断的硬中断或软中断处理过程中的那个点),故控制就直接转移到restore_all,即返回到了可中断的硬中断或软中断被打断时的那个点,继续处理完毕,可见,到达这里,必然不存在可中断的硬中断或软中断未被执行。

附注:关于in_interrupt宏,也就是(local_irq_count(__cpu) +local_bh_count(__cpu) !=0)。什么情况下会导致进入do_softriq时,in_interrupt不为0呢?第一种,如果当前正在处理可中断的硬中断处理函数,假设此时来了另一个通道的硬中断,将导致当前硬中断处理函数被中断,进入do_IRQ,随后处理新来的硬中断处理函数,当处理完毕后,到达do_softirq,由2中可知,此时local_irq_count(__cpu)被原先的硬中断加1,由于其还没有处理完毕,故--local_irq_count(cpu)还没来得及执行,因此local_irq_count(__cpu)>0,也就是in_interrupt!=0;第二种,如果当前正在do_softirq中处理软中断处理函数,现在来了个硬中断,将导致当前硬中断处理函数被中断,进入do_IRQ,随后处理新来的硬中断处理函数,当处理完毕后,又来带到了do_softirq,由3中可知,此时local_bh_count(cpu)被前一个do_softirq加1了,但是由于其是中途被中断的,故local_bh_count(cpu)--还没来得及执行,因此local_bh_count(__cpu)>0,也就是in_interrupt!=0;第三种就是综合第一种和第二种两种情况。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7435681.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-05
下一篇 2023-04-05

发表评论

登录后才能评论

评论列表(0条)

保存