转载自:http://blog.csdn.net/sailor_8318/archive/2008/08/28/2841004.aspx
内核提供的中断接口包括注册和注销中断处理程序,禁止中断,屏蔽中断线,以及检查中断系统的状态。
在设备驱动程序的初始化阶段,必须通过request_irq()函数将对相应的中断服务例程挂入中断请求队列,注册并激活一个中断处理程序,以便处理中断:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
² unsigned int irq
要分配的中断号,通常是预先设计固定的。某些情况可以通过探测获得,如PCI。
² irqreturn_t (*handler)
一个指针,指向处理这个中断的实际中断处理程序。需要符合中断处理函数的原型,其2.4及2.6的早期版本有很大差别,目前只支持两个参数且有返回值。
² unsigned long flags
一个与中断管理相关的选项的位掩码,共享、屏蔽之类的
² const char *dev_name
这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者
² void *dev_id
用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区(来标识哪个设备在中断)。如果中断没有被共享, dev_id 可以设置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意。
request_irq()成功执行会返回0。如果返回非0值,就表示有错误发生,在这种情况下,指定的中断处理程序不会被注册。最常见的错误是一EBUSY,它表示给定的中断线已经在使用(或者当前用户没有指定SA_ SHIRQ)。
注意,request_ irq函数可能会睡眠,因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。为什么request_irq会引起睡眠—这确实让人费解。在注册的过程中,内核需要在/proc/irq文件创建一个与中断对应的项。函数proc_mkdirQ就是用来创建这个新的procfs项的。proc_makedir()通过调用函数proc_create()对这个新的profs项进行设置,而proc_create()会调用函数kmalloc()来请求分配内存。
有一点很重要,初始化硬件和注册中断处理程序的顺序必须正确,以防止中断处理程序在设备初始化完成之前就开始执行。
在驱动程序初始化或者在设备第一次打开时,首先要调用该函数,以申请使用该irq。其中参数handler指的是要挂入到中断请求队列中的中断服务例程。假定一个程序要对/dev/fd0/设备进行访问,通常将IRQ6分配给软盘控制器,给定这个中断号6,软盘驱动程序就可以发出下列请求,以将其中断服务例程挂入中断请求队列:
request_irq(6, floppy_interrupt, SA_INTERRUPT|SA_SAMPLE_RANDOM, "floppy", NULL);
我们可以看到,floppy_interrupt()中断服务例程运行时必须禁用中断(设置了SA_INTERRUPT标志),并且不允许共享这个IRQ(清SA_SHIRQ标志)。
request_irq()函数的代码在linux+v2.6.19/kernel/irq/manage.c中:
435int request_irq(unsigned int irq, irq_handler_t handler,
436 unsigned long irqflags, const char *devname, void *dev_id)
437{
438 struct irqaction *action;
439 int retval;
440
453 if ((irqflags & IRQF_SHARED) && !dev_id) //共享中断id不能为NULL
454 return -EINVAL;
455 if (irq >= NR_IRQS)
456 return -EINVAL;
457 if (irq_desc[irq].status & IRQ_NOREQUEST)
458 return -EINVAL;
459 if (!handler)
460 return -EINVAL;
461
462 action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); //可能休眠
463 if (!action)
464 return -ENOMEM;
465//对action进行初始化
466 action->handler = handler;
467 action->flags = irqflags;
468 cpus_clear(action->mask);
469 action->name = devname;
470 action->next = NULL;
471 action->dev_id = dev_id;
472
473 select_smp_affinity(irq); // SMP机器可设置中断的亲和力
474
475 retval = setup_irq(irq, action); //挂接到全局的中断irq_action中
476 if (retval)
477 kfree(action);
478
479 return retval;
480}
481EXPORT_SYMBOL(request_irq);
setup_irq
该函数为内部函数,用户驱动程序中一般不用,其才是真正对中断请求队列进行初始化的函数
在系统初始化阶段,内核为了初始化时钟中断设备irq0描述符,在time_init( )函数中使用了下面的语句:
struct irqaction irq0 = {timer_interrupt, SA_INTERRUPT, 0, "timer", NULL,};
setup_irq(0, &irq0);
首先,初始化类型为irqaction的irq0变量,把handler域设置成timer_interrupt( )函数的地址,flags域设置成SA_INTERRUPT,name域设置成"timer",最后一个域设置成NULL以表示没有用dev_id值。接下来,内核调用setup_irq( ),把irq0插入到IRQ0的中断请求队列
215int setup_irq(unsigned int irq, struct irqaction *new)
216{
217 struct irq_desc *desc = irq_desc + irq;
218 struct irqaction *old, **p;
219 const char *old_name = NULL;
220 unsigned long flags;
221 int shared = 0;
222
223 if (irq >= NR_IRQS)
224 return -EINVAL;
225
226 if (desc->chip == &no_irq_chip)
227 return -ENOSYS;
244
248 spin_lock_irqsave(&desc->lock, flags);
249 p = &desc->action;
250 old = *p;
251 if (old) {
252 /*双方声明为共享中断且触发类型一致*/
258 if (!((old->flags & new->flags) & IRQF_SHARED) ||
259 ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
260 old_name = old->name;
261 goto mismatch;
262 }
263
271 /* 找到IRQ队列的尾部*/
272 do {
273 p = &old->next;
274 old = *p;
275 } while (old);
276 shared = 1;
277 }
278
279 *p = new;
284 if (!shared) {// 第一次插入action
285 irq_chip_set_defaults(desc->chip);
286
287 /* 若指定了触发类型则进行配置*/
288 if (new->flags & IRQF_TRIGGER_MASK) {
289 if (desc->chip && desc->chip->set_type)
290 desc->chip->set_type(irq, new->flags & IRQF_TRIGGER_MASK);
292 else
297 printk(KERN_WARNING "No IRQF_TRIGGER set_type ");
301 } else
302 compat_irq_chip_set_default_handler(desc);
303
304 desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
305 IRQ_INPROGRESS);
306
307 if (!(desc->status & IRQ_NOAUTOEN)) {
308 desc->depth = 0;
309 desc->status &= ~IRQ_DISABLED;
310 if (desc->chip->startup)
311 desc->chip->startup(irq);
312 else
313 desc->chip->enable(irq);
314 } else
315 /* Undo nested disables: */
316 desc->depth = 1;
317 }
318 spin_unlock_irqrestore(&desc->lock, flags);
319
320 new->irq = irq;
321 register_irq_proc(irq);
322 new->dir = NULL;
323 register_handler_proc(irq, new); //更新该中断的proc记录
324
325 return 0;
326
327mismatch:
328 if (!(new->flags & IRQF_PROBE_SHARED)) {
329 printk(KERN_ERR "IRQ handler type mismatch for IRQ %d/n", irq);
330 if (old_name)
331 printk(KERN_ERR "current handler: %s/n", old_name);
332 dump_stack();
333 }
334 spin_unlock_irqrestore(&desc->lock, flags);
335 return -EBUSY;
336}
卸载驱动程序或者关闭设备时,需要注销相应的中断处理程序,并释放中断线。可以调用void free_irq(unsigned int irq,void *dev_ id)来释放中断线。
如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。由此可以看出为什么惟一的dev_ id如此重要。对于共享的中断线,需要一个惟一的信息来区分其上面的多个处理程序,并让free_irq()仅仅删除指定的处理程序。如果dev_id非空,它都必须与需要删除的处理程序相匹配。非共享中断,该域可以为空,但需要和注册时使用的指针一致。
直到该中断线的任何中断函数执行完毕其才返回,不能在中断上下文调用函数free_ irq。例如,当软盘操作终止时(或者终止对/dev/fd0/的I/O操作,或者卸载这个文件系统),驱动程序就放弃这个中断号:
free_irq(6, NULL);
/linux+v2.6.19/kernel/irq/manage.c
352void free_irq(unsigned int irq, void *dev_id)
353{
354 struct irq_desc *desc;
355 struct irqaction **p;
356 unsigned long flags;
357
358 WARN_ON(in_interrupt());
359 if (irq >= NR_IRQS)
360 return;
361
362 desc = irq_desc + irq;
363 spin_lock_irqsave(&desc->lock, flags); //若当前中断正在执行时,将无法获得锁,会死锁,故不能在中断上下文释放中断
364 p = &desc->action;
365 for (;;) {
366 struct irqaction *action = *p;
367
368 if (action) {
369 struct irqaction **pp = p;
370
371 p = &action->next;
372 if (action->dev_id != dev_id)
373 continue;
374
375 /* Found it - now remove it from the list of entries */
376 *pp = action->next;
377
384 if (!desc->action) {//无其他中断使用该中断线则禁止
385 desc->status |= IRQ_DISABLED;
386 if (desc->chip->shutdown)
387 desc->chip->shutdown(irq);
388 else
389 desc->chip->disable(irq);
390 }
391 spin_unlock_irqrestore(&desc->lock, flags);
392 unregister_handler_proc(irq, action);
393
394 /* Make sure it's not being used on another CPU */
395 synchronize_irq(irq);
396 kfree(action);
397 return;
398 }
399 printk(KERN_ERR "Trying to free already-free IRQ %d/n", irq);
400 spin_unlock_irqrestore(&desc->lock, flags);
401 return;
402 }
403}
404EXPORT_SYMBOL(free_irq);
Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在<asm/system.h>和<asm/irq.h>中找到。
一般来说,控制中断系统的原因归根结底是需要提供同步。通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码。此外,禁止中断还可以禁止内核抢占。然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问。Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自其他处理器对共享数据的并发访问。获取这些锁的同时也可以禁止本地中断。这样锁提供保护机制,防止来自其他处理器的并发访问,而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问。
用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的语句为:
void local_irq_disable(void);
void local_irq_enable(void);
这两个函数通常以单个汇编指令来实现(当然,这取决于体系结构)。在发出中断的处理器上,它们将禁止和激活中断的传递。
如果在调用local_irq_disable()例程之前已经禁止了中断,那么该例程往往会带来潜在的危险;同样相应的local_irq_enable()例程也存在潜在危险,因为它将无条件地激活中断,尽管这些中断可能在开始时就是关闭的。所以我们需要一种机制把中断恢复到以前的状态而不是简单地禁止或激活。内核普遍关心这点,是因为内核中一个给定的代码路径既可以在中断激活的情况下达到,也可以在中断禁止的情况下达到,这取决于具体的调用链。因为随着内核的不断增长,要想知道到达这个函数的所有代码路径将变得越来越困难,因此,在禁止中断之前保存中断系统的状态会更加安全一些。相反,在准备激活中断时,只需把中断恢复到它们原来的状态。
unsigned long flags;
void local_irq_save(unsigned long flags);
void local_irq_restore(unsigned long flags);
一个对 local_irq_save 的调用在当前处理器上禁止中断递交, 在保存当前中断状态到 flags 之后. 注意, flags 是直接传递, 不是通过指针。local_irq_restore恢复由 local_irq_save 存储于 flags 的状态, 而 local_irq_enable 无条件打开中断。
这些方法至少部分要以宏的形式实现,因此表面上flags参数(这些参数必须定义为unsigned long类型)是以值传递的。该参数包含具体体系结构的数据,也就是包含中断系统的状态。至少有一种体系结构把栈信息与值相结合(SPARC ),因此flags不能传递给另一个函数。基于这个原因,对local_irq_save()的调用和对local_irq_restore()的调用必须在同一个函数中进行。
前面的所有函数既可以在中断中调用,也可以在进程上下文中调用。
不再使用全局的cli(),所有的中断同步现在必须结合使用本地中断控制和自旋锁。这就意味着,为了确保对共享数据的互斥访问,以前代码仅仅需要通过全局禁止中断达到互斥,而现在则需要多做些工作了。
在某些情况下,只禁止整个系统中一条特定的中断线就够了。这就是所谓的屏蔽掉(masking out)一条中断线。作为例子,你可能想在对中断的状态操作之前禁止设备中断的传递。为此,Linux提供了四个接口:
/linux+v2.6.19/kernel/irq/manage
void disable_irq(unsigned int irq);
void enable_irq(unsigned int irq);
这两个函数禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。disable_irq函数只有在当前所有CPU上的该中断的处理程序完成后,disable_irq才能返回。因此,调用者不仅要确保不在指定线上传递新的中断,同时还要确保所有已经开始执行的处理程序已全部退出。
void disable_irq_nosync(unsigned int irq);
函数disable_irq_nosync()不会等待当前中断处理程序执行完毕就禁止了该中断,可用于中断上下文
void synchronize_irq(unsigned int irq);
17#ifdef CONFIG_SMP
29void synchronize_irq(unsigned int irq)
30{
31 struct irq_desc *desc = irq_desc + irq;
32
33 if (irq >= NR_IRQS)
34 return;
35
36 while (desc->status & IRQ_INPROGRESS)
37 cpu_relax();
38}
39EXPORT_SYMBOL(synchronize_irq);
41#endif
synchronize_irq - SMP平台有效,等到其他CPU上的中断处理程序完毕
只有在所有的该中断处理完毕后才返回,拥有该中断所需要的资源时调用该函数可能导致死锁,可在中断上下文用
如果从中断上下文中调用,就要特别小心!例如,当你正在处理一条中断线时,并不想激活它。
禁止多个中断处理程序共享的中断线是不合适的。禁止中断线也就禁止了这条线上所有设备的中断传递。因此,用于新设备的驱动程序应该倾向于不使用这些接口。
每个中断服务例程都有相同的原型,它的类型与request_irq参数中handler所要求的参数类型相匹配:
typedef irqreturn_t (*irq_handler_t)(int, void *);
即irqreturn_t irq_handler(int irq, void * dev_id)
参数:
² IRQ:是这个处理程序要响应的中断的中断线号。如今,这个参数已经没有太大用处了,可能只是在打印日志信息时会用到。
² dev_id: 设备标识符,其类型为void*;dev_id是一个通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev_ id必须一致。如果该值有惟一确定性(建议采用这样的值,以便支持共享),那么它就相当于一个cookie,可以用来区分共享同一中断处理程序的多个设备。另外dev_ id也可能指向中断处理程序使用的一个数据结构。因为对每个设备而言,设备结构都是惟一的,而且可能在中断处理程序中也用得到,因此,也通常会把设备结构传递给dev_id。
在2.4内核中还有一个regs参数
static irqreturn_t intr_handler(int irq, void *dev-id,struct pt_regs *regs)
regs: 指向内核堆栈区的指针,该结构包含处理中断之前处理器的寄存器和状态。除了调试的时候,它们很少使用到。
中断处理程序的返回值是一个特殊类型: irqreturn_t。中断处理程序可能返回两个特殊的值:
IRQ_ NONE和IRQ_ HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_ NONE;当中断处理程序被正确调用,且确实是它所对应的设备产生了中断时,返回IRQ_ HANDLED。另外,也可以使用宏IRQ_ RETVAL(x)。如果x为非0值,那么该宏返回IRQ_ HANDLED;否则,返回IRQ NONE。注意,irqreturn一这个返回类型实际上就是一个int型。之所以使用这些特殊值是为了与早期的内核保持兼容—2.6版之前的内核并不支持这种特性,中断处理程序只需返回void就行了。
中断处理程序通常会标记为static,因为它从来不会被别的文件中的代码直接调用。
Linux中的中断处理程序是无需重人的。当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一中断线上接收另一个新的中断。通常情况下,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断线总是被禁止的。由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。这极大地简化了中断处理程序的编写。
共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下三处:
² request_ irq()的参数flags必须设置SA_ SHIRQ标志。
² 对每个注册的中断处理程序来说,dev_ id参数必须惟一。指向任一设备结构的指针就可以满足这一要求;通常会选择设备结构,因为它是惟一的,而且中断处理程序可能会用到它。不能给共享的处理程序传递NULL值。
² 中断处理程序必须能够区分它的设备是否真的产生了中断。这既需要硬件的支持,也需要处理程序中有相关的处理逻辑。如果硬件不支持这一功能,那中断处理程序肯定会束手无策,它根本没法知道到底是与它对应的设备发出了这个中断,还是共享这条中断线的其他设备发出了这个中断。
所有共享中断线的驱动程序都必须满足以上要求。只要有任何一个设备没有按规则进行共享,那么中断线就无法共享了。指定SA_SHIRQ标志以调用request_irq()时,只有在以下两种情况下才可能成功:中断线当前未被注册,或者在该线上的所有已注册处理程序都指定了SA_SHIRQ。注意,在这一点上2.6与以前的内核是不同的,共享的处理程序可以混用SA INTERRUPT
内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序。程序必须知道它是否应该为这个中断负责。因此,一个处理如果与它相关的设备并没有产生中断,那么处理程序应该立即退出。这需要硬件设备提供状态寄存器(或类似机制),以便中断处理程序进行检查。
当执行一个中断处理程序或下半部时,内核处于中断上下文(interrupt context)中。让我们先回忆一下进程上下文。进程上下文是一种内核所处的操作模式,此时内核代表进程执行----例如,执行系统调用或运行内核线程。在进程上下文中,进程是以进程上下文的形式连接到内核中的,因此,可以通过current宏关联当前进程。此外在进程上下文可以睡眠,也可以调用调度程序。
与之相反,中断上下文和进程并没有什么瓜葛。与current宏也是不相干的(尽管它会指向被中断的进程)。因为没有进程的背景,所以中断上下文不可以睡眠—否则又怎能再对它重新调度呢?因此,不能从中断上下文中调用某些函数。如果一个函数睡眠,就不能在你的中断处理程序中使用它。这是对什么样的函数可以在中断处理程序中使用的限制。
中断上下文具有较为严格的时间限制,因为它打断了其他代码。中断上下文中的代码应当迅速简洁,尽量不要使用循环去处理繁重的工作。有一点非常重要,请永远牢记;中断处理程序打断了其他的代码(甚至可能是打断了在其他中断线上的另一中断处理程序)。正是因为这种异步执行的特性,所以所有的中断处理程序必须尽可能的迅速、简洁。尽量把工作从中断处理程序中分离出来,放在下半部来执行,因为下半部可以在更合适的时间运行。
中断处理程序栈的设置是一个配置选项。曾经,中断处理程序并不具有自己的栈。相反,它们共享所中断进程的内核栈。内核栈的大小是两页,具体地说,在32位体系结构上是8KB,在64位体系结构上是16KB。因为在这种设置中,中断处理程序共享别人的堆栈,所以它们在栈中获取空间时必须非常节省。
在2.6早期的内核中,增加了一个选项,把栈的大小从两页减到一页,也就是在32位的系统上只提供4KB的栈。这就减轻了内存的压力,因为系统中每个进程原先都需要两页不可换出的内核内存。为了应对栈大小的减少,中断处理程序拥有了自己的栈,每个处理器一个,大小为一页,这个栈就称为中断栈。
通常有必要了解中断系统的状态(例如中断是禁止的还是激活的),或者你当前是否正处于中断上下文的执行状态中。
在</linux/hardirq.h >中定义的两个宏提供一个用来检查内核的当前上下文的接口,它们是:
in_interrupt():是否处于硬件中断或软中断上下文。如果内核处于中断上下文中,它返回非0。说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序。
in_irq():是否处于硬件中断上下文。内核确实正在执行中断处理程序时才返回非0
通常情况下,你要检查自己是否处于进程上下文中。这种情况很常见,因为代码要做一些像睡眠这样只能从进程上下文中做的事。此时应确保自己不在中断上下文中。
linux+v2.6.19/include/linux/hardirq.h
10/*
11 * We put the hardirq and softirq counter into the preemption
12 * counter. The bitmask has the following meaning:
14 * - bits 0-7 are the preemption count (max preemption depth: 256)
15 * - bits 8-15 are the softirq count (max # of softirqs: 256)
19 * - bits 16-27 are the hardirq count (max # of hardirqs: 4096)
20 * - ( bit 28 is the PREEMPT_ACTIVE flag. )
21 *
22 * PREEMPT_MASK: 0x000000ff
23 * SOFTIRQ_MASK: 0x0000ff00
24 * HARDIRQ_MASK: 0x0fff0000
25 */
63#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
64#define softirq_count() (preempt_count() & SOFTIRQ_MASK)
65#define irq_count() (preempt_count() & (HARDIRQ_MASK |SOFTIRQ_MASK))
////////////////////////////////
linux+v2.6.19/include/linux/preempt.h
16# define add_preempt_count(val) do { preempt_count() += (val); } while (0)
17# define sub_preempt_count(val) do { preempt_count() -= (val); } while (0)
18#endif
19
20#define inc_preempt_count() add_preempt_count(1)
21#define dec_preempt_count() sub_preempt_count(1)
22
23#define preempt_count() (current_thread_info()->preempt_count)
//////////////////////////////////
67/*
68 * Are we doing bottom half or hardware interrupt processing?
69 * Are we in a softirq context? Interrupt context?
70 */
71#define in_irq() (hardirq_count())
72#define in_softirq() (softirq_count())
73#define in_interrupt() (irq_count())
跟踪中断进入退出状态,便于确定是否是中断上下文
109#define irq_enter() /
110 do { /
111 account_system_vtime(current); /
112 add_preempt_count(HARDIRQ_OFFSET); /
113 trace_hardirq_enter(); /
114 } while (0)
115
116/*
117 * Exit irq context without processing softirqs:
118 */
119#define __irq_exit() /
120 do { /
121 trace_hardirq_exit(); /
122 account_system_vtime(current); /
123 sub_preempt_count(HARDIRQ_OFFSET); /
124 } while (0)
125
126/*
127 * Exit irq context and process softirqs if needed:
128 */
129extern void irq_exit(void);
procfs是一个虚拟文件系统,它只存在于内核内存,一般安装于/proc目录下。在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写。与此相关的例子是/proc/interrupts文件,该文件存放的是系统中与中断相关的统计信息。下面是从单处理器PC上输出的信息:
第1列是中断线。在这个系统中,现有的中断号为0-2, 4, 5, 12及15。这里没有显示没有安装处理程序的中断线。
第2列是一个接收中断数目的计数器。事实上,系统中的每个处理器都存在这样的列,但是,这个机器只有一个处理器。我们看到,时钟中断已接收3,602,371次中断,这里,声卡(EMU 10K1)没有接收一次中断(这表示机器启动以来还没有使用它)。
第3列是处理这个中断的中断控制器。
最后一列是与这个中断相关的设备名字。这个名字是通过参数devname提供给函数request irq()的。如果中断是共享的(例子中的4号中断就是这种情况),则这条中断线上注册的所有设备都会列出来。
对于想深人探究procfs内部的人来说,procfs代码位于fs/proc中。不必惊讶,提供/proc/interrupts的函数是与体系结构相关的,叫做show_interrupts()。