以前学习计算机组成原理的时候就知道了硬件如果要和cpu进行通信有那么几种方法,其实有程序查询法,中断,DMA,通道等,就记得那么多了,我记得好像还有一个。那个时候书上很形象的把这个过程比作是老师分苹果的故事。程序查询法就是老师每一个小孩子都去问,他们的苹果有没有吃完了,想想这也是一件比较累的事情。中断就比较智能了,就是当小孩子自己吃完了苹果,如果还想要的话就自己去想老师要。DMA好像是老师把这个权利给了一个孩子,如果有人要苹果的时候,这个时候老师就叫一个小孩子过来,说接下来的事情就交给你了,你帮我分苹果,但是还是必须要和我说一下的,但是苹果是你去分。通道好像就是老师这个职位对于分苹果已经不管了,有一个专门的一个人来管理这个事情。不过没有一种方法可以解决所有的问题,因为有的硬件与cpu通信数据量比较大,可以选择是DMA和通道,但是如果硬件本身是一个低速的设备,使用通道和DMA可以说是一种资源的浪费,而这个时候中断也就是最好的解决的方式。你有需要才来告诉我,而不是我(cpu)一直去询问你。这是一种从被动到主动的过程。这样cpu就不用花大量的时间周期去查询你有没有要处理的请求。这样解放了cpu,使cpu有更多的时间去处理它要做的事情。
首先,中断的过程其实很简单,比如键盘这个中断过程就是,当键盘被按下了,那么就会产生一个电信号(可以认为中断就是一个电信号),这个信号会传到中断控制器,然后中断控制器在上报到cpu,cpu收到这个请求的时候就会中断自己当前的事情来出来出来键盘的输入。其实这是一个很简单的过程。虽然过程很简单,但是操作系统本身要做可没有想象中的那么简单,因为很多东西都是那么容易让人混淆的。
接下来就对Linux kernel对中断的实现来了解一下中断吧!!!
1.中断的分类:
中断可以分为同步中断和异步中断,当然我现在要讲述的是异步中断。同步中断其实就是异常,当cpu来执行代码的时候,由于碰到了不能定义的指令(除以0)的时候,就中断当前的指令去处理这些异常。为什么称之为同步中断,因为它是与时钟是同步的,如果理解的简单的话同步中断是软件引起的,而异步中断是硬件引起的。
2.异步中断:
一台计算机有很多硬件设备,有在计算机外的,也有在计算机里面的,那么多的外设,cpu到底是怎么样去区别中断是哪个外设产生的,如果同样的外设有很多台,那么又怎么去区别呢?os中给某一种的中断都设定了中断值(IRQ,中断请求线),中断值是唯一的。但是中断值却不是固定不变的,因为计算机永远也不知道他到底要连接多少的外设,所以一些中断值是固定的,但是一些却是动态生成的。
与中断有关的不单单只是设备,还有中断处理程序(ISR)。中断处理程序与特定的中断连接在一起,而且中断处理程序本身是硬件驱动中的一个部分。不同的硬件设备对中断处理程序都有不同的要求。当硬件本身插入计算机的时候,驱动程序就开始运行,这个时候有一部分工作就是去注册中断处理程序,那样当硬件发出中断的时候,os可以调用这个中断处理程序。
3.关于中断处理函数
中断处理函数本身与普通的函数其实也没有什么区别,不过只是它必须按特别的类型去声明。其中最大的区别在于在运行的时候环境上的差别吧,中断处理程序运行在中断上下文中的,而普通的函数是运行在进程上下文中的。关于进程上下文和中断上下文我本身也不是很能理解,书上讲的不是特别好理解。
关于中断上半部和中断下半部的问题:
为什么要分为中断上半部和下半部呢?本来也不知道的我,看了书以后我也渐渐的明白了,这可以在编写中断处理程序一个很重要的一点,这也是与普通函数的一个区别。
1.中断程序程序会打断cpu的执行,而让cpu去执行你的处理程序,那如果处理程序要运行很长时间的话你觉得合理不??如果不是很能明白的话,举个例子就是:别人在讲话,然后你突然打断他的话,然后自己在那里讲了很久很久,你说对方是什么感觉,而且说不定他正在讲很重要的事情,对于人是这样,那么对于cpu本身也是一样的。
2.中断处理程序的运行一定会伴随了中断请求线的屏蔽,好的话是同一个类的中断请求线屏蔽,最坏就是把当前本地所有的中断请求线全部屏蔽掉。这样的话有可能会妨碍到一些中断的中断处理程序。
3.中断面向的是硬件的,而有的硬件本身就是高速的,也就是说本身也是有时间的限制的,如果处理的慢的话就可能导致后面的数据被更加后面的数据给覆盖了。网卡就是这样的。
所以中断处理程序要分为两个过程。上半部必须是快速,简单的,要完成的任务通常也就是给予硬件一个响应,说我已经收到了,你可以继续工作去了。还有就是关于一些数据的复制,以便到下半部进行处理。
注册中断处理函数
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char * devname,
void *dev_id)
request_irq是用来注册中断处理程序的,是驱动程序中的一部分。
irq :中断请求线
handler:真正的中断处理程序
irqflags:默认是0,不过可以通过 ‘|’进行多个重合。
SA_INTERRUPT:快速中断请求,意思就是如果标识了这个的中断,在处理中断的时候会屏蔽一切中断,默认的情况是会屏蔽那个正在运行中断处理程序的中断请求线。
SA_SAMPLE_RANDOM:这个标识主要是把两个中断产生的之间间断放到随机熵中去而已。
SA_SHIRQ:看字面意思就是share irq,也就是共享中断请求线。至于怎么去区别当然是有解决方法的。
devname:设备的名字。想看的话可以去/proc/irq或者/proc/interrupt下面找找看。
dev_id :这个就是用来区分的那个值了。如果不是共享的irq的话,这个值通常为NULL。可是如果是共享的IRQ的话这个值就不能是NULL。因为同一条irq上怎么样达到区分不同的中断的效果呢,同样的IRQ值,这个值通常包含了设备的一些信息,可以用来区分不同的设备。对于共享IRQ,发现中断的时候,操作系统会依次调用这个线上的所有的中断处理程序,根据中断处理程序也知道是不是应该这个中断处理程序来负责这个中断。
request_irq会睡眠所以它不能放在中断上下文或者那种不能被阻断的程序中。
释放中断处理程序
void free_irq(unsigned int irq, void *dev_id)
如果指定的irq不是共享的话,那么free_irq就会删除中断处理程序,并且会禁止这条irq。如果是共享的IRQ,那么free_irq就只会删除中断处理程序。只有当线上的所有的处理程序全部都删除了这irq才会被禁止掉的。
真正的中断处理程序(handler)
static irqreturn_t intr_handler(int irq , void *dev_id, struct pt_regs *regs)
os通过这个函数的返回值来区别是不是有这个处理函数来处理这个中断。使用static这个关键词主要是为了防止别的文件调用中断处理程序。
进程上下文和中断上下文
这两个术语其实我并不是很清楚,但是对于书上的解释我也想提出自己不是很懂的地方。
进程上下文:是一种内核所处的操作模式,此时内核是代表这进程执行的。在进程上下文中可以通过current宏关联到当前的进程。因为进程以进程上下文的形式连接到内核中的,所以可以在进程上下文可以睡眠,或者调用调度程序。
中断上下文:中断上下文和当前进程没任何的关系,与current宏也没有关系(尽管它还会指向中断的进程)。因为进程没有背景,所以中断处理程序是不能睡眠的。这也对中断处理程序中使用的函数做出了限制了。
不明白就是了,唯一知道的就是进程上下文可以睡眠或者调度,但是中断上下文是不能被中断的。这里的中断上下文是指中断上半部的那个时间,下半部的话就不一定了。
4.中断处理机制的实现
步骤:
int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code */
irq_desc_t *desc = irq_desc + irq;
int handle_IRQ_event(unsigned int irq,
struct pt_regs *regs, struct irqaction *action)
{
int status = 1; /* Force the "do bottom halves" bit */
int retval = 0;
if (!(action->flags & SA_INTERRUPT))
local_irq_enable();
do {
status |= action->flags;
retval |= action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
上面的两句话主要是用来取得IRQ值的,这个实现是x86的,不同的体系结构实现是有点区别的。通过得知IRQ值然后通过偏移就可以得知在这个IRQ上有哪些中断处理程序,这样就可以对这些程序进行测试了。
5.总结