Linux内核学习之中断 中断本质【转】

简介: 转自:http://www.linuxidc.com/Linux/2011-11/47657.htm       [中断概述] 中断本质上是一种特殊的电信号,由硬件设备发向处理器。异常和中断的不同是异常在产生时必须考虑与处理器时钟同步。

转自:http://www.linuxidc.com/Linux/2011-11/47657.htm

 

 

 

[中断概述]

中断本质上是一种特殊的电信号,由硬件设备发向处理器。异常和中断的不同是异常在产生时必须考虑与处理器时钟同步。实际上异常也常常称为同步中断。比如在除0或者缺页时,必须靠内核处理的时候,处理器就会产生一个异常。

[中断处理机制的实现]


                                                        中断从硬件到内核的路由

设备产生中断,通过总线把电信号发送给中断控制器。如果中断线是激活的(它们允许被屏蔽的),那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器上禁止该中断,否则处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定的位置开始执行那里的代码。这个预定义的位置是由内核设置的,是中断处理程序的入口点。

[中断处理程序]

在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)也叫做中断服务例程(interrupt service routine)。

中断处理程序就是一个普普通通的C函数,但是它与其他内核函数的真正区别在于,中断处理程序时被内核调用来响应中断的,它运行于中断上下文中。

注册中断处理程序:

static inline int __must_check

request_irq(unsigned int irq, irq_handler_thandler, unsigned long flags,

             const char *name, void *dev)

irq:分配的中断号

handler:中断处理函数程序的指针 typedefirqreturn_t (*irq_handler_t)(int, void *);

flags:中断类型

name:与中断相关的设备的名字;

dev:用于共享中断,因为可能在一条中断线上有几个设备,dev用来区分是哪个设备产生的中断

中断上下文和进程上下文对比

 

中断上下文

进程上下文

定义

当执行一个中断处理程序时,内核处于中断上下文中

当程序调用了系统调用或者触发了某个异常,它就陷入了内核空间,此时,内核代表进程执行并处于进程上下文中。

睡眠情况

不可睡眠,不能被调度,也就是说中断上下文中不能使用有可能睡眠的函数

可睡眠,可调度

同步机制

自旋锁

都可

 

 

 

 

 

 

[中断上半部和下半部]

首先问自己一个问题,为什么要把中断分为上半部和下半部,难道一个就放在中断处理程序中不好吗?答案是否定的

1、中断可以随时的打断其他正在执行的程序,如果被打断的代码对系统很重要,那么此时中断处理程序的执行时间应该是越短越好;

2、中断处理程序正在执行时,会屏蔽同条中断线上的中断请求;而更严重的是,如果设置了IRQF_DISABLED,那么该中断服务程序执行时会屏蔽所有其他的中断请求。那么此时应该让中断处理程序执行的越快越好。

 

上半部:一个快速、异步而简单的处理程序专门来负责对硬件的中断请求做出快速响应,与此同时也要完成那么些对时间要求很严格的操作;

下半部:那么对时间要求相对宽松,其他的剩余工作会在稍后的任意时间执行。

下面是对上半部和下半部的工作划分:

如果一个任务对时间非常敏感,将其放在中断处理程序中执行;

如果一个任务中和硬件相关,将其放在中断处理程序中执行;

如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序执行;

其他所有任务,都应考虑放在下半部执行。

[下半部机制]

软中断

软中断保留给系统中对时间要求最严格以及最重要的下半部使用,目前只有两个子系统(网络和scsi)直接使用软中断。

Tasklet

Tasklet是利用软中断实现的一种下半部机制。在选择使用软中断还是tasklet时,建议使用tasklet,除了网络和SCSI情况。相比软中断,tasklet的接口更简单,锁保护要求较低。

工作队列

工作队列是可以把工作推后交由一个内核线程去执行,下半部总是会在进程上下文中执行,允许重新调度和睡眠。

三种下半部机制对比

 

下半部                                              

上下文                                             

顺序执行保障                                                 

软中断

中断

没有

Tasklet

中断

同类型不能同时执行

工作队列

进程

没有(和进程上下文一样被调度)

 

 

 

[概述]

工作队列是一种将工作推后执行的的形式,工作队列可以把工作推后,交由一个内核线程去执行,占有进程上下文的所有优势,允许重新调度和睡眠。

[工作队列的实现]

工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程称作工作者线程(worker thread)。工作队列子系统提供了一个缺省的工作者线程来处理推后的工作,所以我们大多数情况下,没必要自己去创建工作者线程,使用默认的就OK。

Linux kernel development 3 上对工作队列的实现原理讲的很清楚了,这里就不再详述。具体来看一个实例

[实例]

  1. static struct input_dev *button_dev;  
  2. static struct work_struct button_wq;  

 

在做实际的驱动开发的时候,不推荐使用全局变量,因为全局变量是造成竞争条件的主要原因。

[中断处理程序]

  1. static irqreturn_t button_interrupt(intirq, void *p)  
  2. {  
  3.          schedule_work(&button_wq);  
  4.          returnIRQ_RETVAL(IRQ_HANDLED);  
  5. }  

schedule_work(&work)函数用来调度work,把work提交给缺省的events工作者线程(插入工作任务链表中)。work马上会被调度,一旦其所在的处理器上的工作者线程被唤醒,唤醒的工作者队列会遍历整个工作任务链表,如果有工作,它就会被执行,否则继续睡眠。

Schedule_work_delay(&work, delay) 和schedule_work函数的作用相同,唯一区别就是延迟delay时间后才会执行。

[工作队列处理函数]

  1. void work_handler(void *data)  
  2. {  
  3.          /*get pin value <down 0, up 1> */  
  4.          intval = s3c2410_gpio_getpin(S3C2410_GPG(0));  
  5.    
  6.          input_report_key(button_dev,KEY_1, val);  
  7.          input_sync(button_dev);  
  8. }  

 

完成按键信息的获取和上报工作。

[模块初始化函数]

  1. static int __init button_init(void)  
  2. {  
  3.          interr;  
  4.    
  5.          if(request_irq(BUTTON_IRQ, button_interrupt,   
  6.                                                IRQ_TYPE_EDGE_BOTH,DEV_NAME, NULL)) {  
  7.                    printk(KERN_ERR"cannotallocate irq");  
  8.                    return- EBUSY;  
  9.          }  
  10.           
  11.          ……  
  12.    
  13.          INIT_WORK(&button_wq,work_handler);  
  14.    
  15.          printk("initialized\n");  
  16.          return0;  
  17. }  

 

初始化函数主要完成了两个工作:

1、  申请中断

2、  创建一个推后的工作button_wq,有两种方法:

动态创建:

INIT_WORK(struct work_struct work, void (*func)(void *)), 在新内核里,INIT_WORK已经发生了变化,少了第三个参数。可以参考http://www.linuxidc.com/Linux/2011-11/47658.htm

静态创建:

DECLARE_WORK(name,func) 创建一个名为name,处理函数为func的work_struct结构体。

不管是动态还是静态,都是填充work struct 结构体。

有兴趣可以去看内核里gpio_keys.c的实现代码,一个典型的work queue的实现例子

 

[概述]

Tasklet是软中断的特殊实现。Tasklet通常是下半部处理的优选机制,它在性能和易用性之间有着很好的平衡。较softirq,tasklet不需要考虑SMP下的并发问题,而又比workqueue有着更好的性能。

不同类型的tasklet可以在不同的处理器上同时执行,但同类型的tasklet不能同时执行。这就避免了并发问题。

ksoftirqd内核线程有着类似工作者线程一样的职责,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量挂起了(pending),就执行相应的处理函数,

关于tasklet的实现同样可以拜读LKD3,这书真的很经典。

[实例]

  1. static struct input_dev *button_dev;  
  2. static struct tasklet_structbutton_tasklet;  

 

在做实际的驱动开发的时候,不推荐使用全局变量,因为全局变量是造成竞争条件的主要原因。

[中断处理程序]

  1. static irqreturn_t button_interrupt(intirq, void *p)  
  2. {  
  3.          tasklet_schedule(&button_tasklet);  
  4.          returnIRQ_RETVAL(IRQ_HANDLED);  
  5. }  

 

调用tasklet_schedule函数,把button_tasklet加入到tasklet_vec(软中断向量)链表中,并触发软中断,这样在下一次调用do_softirq函数时就会执行button_tasklet。Button_tasklet会在尽短的时间内执行。

[tasklet处理函数]

  1. void tasklet_handler(unsigned long data)  
  2. {  
  3.          /*get pin value <down 0, up 1> */  
  4.          intval = s3c2410_gpio_getpin(S3C2410_GPG(0));  
  5.    
  6.          input_report_key(button_dev,KEY_1, val);  
  7.          input_sync(button_dev);  
  8. }  

 

因为是靠软中断实现的,所以tasklet不能睡眠,这意味着不能在tasklet中使用信号量和其他可能发生阻塞的函数。但是tasklet运行时允许响应中断。

  1. static int __init button_init(void)  
  2. {  
  3.          interr;  
  4.    
  5.          if(request_irq(BUTTON_IRQ, button_interrupt,   
  6.                      IRQ_TYPE_EDGE_BOTH,DEV_NAME, (void *)button_dev)) {  
  7.                    printk(KERN_ERR"cannotallocate irq");  
  8.                    return- EBUSY;  
  9.          }  
  10.    
  11.          ……  
  12.    
  13.          tasklet_init(&button_tasklet,tasklet_handler, 0);  
  14.    
  15.          debug("initialized\n");  
  16.          return0;  
  17. }  

 

动态初始化tasklet:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

t:tasklet_struct结构体指针

func:tasklet处理函数指针

data:func的参数

静态初始化tasklet:

#define DECLARE_TASKLET(name, func, data) \

struct tasklet_struct name = { NULL, 0,ATOMIC_INIT(0), func, data }

 

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
目录
相关文章
|
14天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
19天前
|
Linux 虚拟化 芯片
Linux 中断子系统中GIC 中断控制器基本分析
Linux 中断子系统中GIC 中断控制器基本分析
66 0
|
11月前
|
存储 Linux
Linux内核18-中断和异常的嵌套处理
Linux内核18-中断和异常的嵌套处理
Linux内核18-中断和异常的嵌套处理
|
11月前
|
存储 负载均衡 算法
Linux内核17-硬件如何处理中断和异常
Linux内核17-硬件如何处理中断和异常
|
11月前
|
存储 Linux
Linux内核19-中断描述符表IDT的初始化
Linux内核19-中断描述符表IDT的初始化
|
11月前
|
Linux API 调度
Linux内核中断系统
中断在驱动中是非常常用的,无论是外部的GPIO中断,还是SPI,I2C等发送或接收中断,都是必不可少的。所以今天来看看Linux中的中断处理。
|
安全 编译器 API
【操作系统】第二章启动、中断、异常和系统调用
【操作系统】第二章启动、中断、异常和系统调用
140 0
【操作系统】第二章启动、中断、异常和系统调用
|
弹性计算 监控 负载均衡
关于Linux cpu中断问题及案例
Linux网络中断的设置与案例分享
2595 0
关于Linux cpu中断问题及案例