Linux Kernel编程 --- 中断与时钟

本文档参考宋宝华老师的《linux设备驱动开发详解》

1 中断与定时器

  中断一般有如下类型:

  • 内部中断和外部中断:内部中断来自CPU,例如软件中断指令、溢出、除0错误等;外部中断有外部设备触发
  • 可屏蔽中断和不可屏蔽中断
  • 向量中断(矢量中断)和非向量中断,ARM一般是非向量中断,因为现在的中断源很多,如果做成向量,那中断向量表会很大。不过ARM的总异常还是按照向量的方式组织的。NVIC(CM4) -- 向量中断  CR5/CA55 -- 非向量中断
向量表:发生异常后,CPU直接跳转到响应地址执行。

.section .vectors
_vector_table:
B	_boot
B	Undefined
B	SVCHandler
B	PrefetchAbortHandler
B	DataAbortHandler
NOP	/* Placeholder for address exception vector*/
B	IRQHandler    // 发生中断后,进入此处,后面的中断服务程序再去判断发生了哪个中断,属于非向量中断方式
B	FIQHandler

  ARM处理器常用的中断控制器GIC如下图。

  有若干中断源:

  SGI:software generated interrupt,

  PPI:private peripheral interrupt,各个core私有的中断,包括定时器等

  SPI:shared peripheral interrupt,共享外设中断。对于SPI类型的中断,默认都是在core0上产生的,可以通过接口设置到背的core。

#include <linux/interrupt.h>

extern int irq_set_affinity( unsigned int irq, const struct cpumask *m );

irq_set_affinity( irq, cpumask_of(i));    //把中断irq设置到core i上

2 linux中断处理程序架构

  中断会打断内核的正常进程调度,所以尽量短小精悍。不过实际系统,中断中要的事又比较多。 为了解决这一矛盾,Linux把中断分为两部分:

  •   顶半部,top half,紧急且量小任务,一般为读取寄存器中的中断状态,并清除中断标记。总之,完成必要的硬件操作。处于中断上下文,可会被打断。
  •   底半部,bottom half,完成主要任务,非中断上下文,可以被打断。

  

  注:不一定所有的中断都分两部分,如果要干的事很少,完全可以不要底半部。

  linux查看中断统计信息的方法:cat /proc/interrupts文件

 

3 linux中断编程

3.1 申请和释放中断

本质上是把回调函数设置到内核里,以便发生中断是调用;同时,把一些配置,例如触发方式传递给内核,便于初始化中断。

#include <linux/interrupt.h>
 
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev);
参数:
    irq,硬件中断号      
    handler,顶半部回调函数,发生中断以后,调用此函数      
    dev,handler的参数,一般设置为这个设备的设备结构体      
    flags,中断处理的属性,可以指定中断的触发方式和处理方式,常用宏定义:         
触发方式:          
    #define IRQF_TRIGGER_RISING     0x00000001          
    #define IRQF_TRIGGER_FALLING 0x00000002          
    #define IRQF_TRIGGER_HIGH   0x00000004          
    #define IRQF_TRIGGER_LOW    0x00000008                
处理方式:           
    #define IRQF_SHARED 0x00000080
 返回值: 
    0,成功       
    -EINVAL,irq无效或handler为空        
    -EBUSY,中断已经被占用且不能共享

void free_irq(unsigned int, void *);申请接口的变体,带devm_前缀,内核管理的资源,不用在出错处理和remove接口里显式的释放。

int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
      unsigned long irqflags, const char *devname, void *dev_id);

3.2 使能和屏蔽中断

  注册完中断,还要使能,使能以后,发生中断后才能调用对应的回调函数。

#include <linux/interrupt.h>

1. 单个使能/屏蔽
extern void disable_irq_nosync(unsigned int irq);    // 立即返回
extern void disable_irq(unsigned int irq);        // 等待中断处理完成后返回
extern void enable_irq(unsigned int irq);    
    注意:在顶半部调用disable_irq()会导致死锁,因为
    void disable_irq(unsigned int irq)
    {
        disable irq;            // 关了中断
        while( irq is not finish )    // 等不到中断结束了,死锁
        {
            ;
        }    
    }

2.全局使能屏蔽
#define local_irq_enable()         // 全局使能
#define local_irq_disable()         // 全局关闭
#define local_irq_save(flags)        // 全局关闭,同时保存目前中断状态到flag,flag时unsigned long,不是指针
#define local_irq_restore(flags)    // 全局使能,恢复中断状态

3.3  底半部机制

Linux底半部机制主要包括tasklet、工作队列、软中断、线程化irq

3.3.1 tasklet

  tasklet的执行上下文是软中断,通常是顶半部返回的时候执行。 由于利用软中断,所以不能有阻塞操作

#include <interrupt.h>

#define DECLARE_TASKLET(name, func, data) \    // 定义名字为name的tasklet,回调函数为func,参数为data
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

void tasklet_schedule(struct tasklet_struct *t); // 调度,在顶半部里调用

使用模板:

#include <linux/interrupt.h>

void xxx_do_tasklet( unsigned long );
DECLARE_TASKLET( xxx_tasklet, xxx_do_tasklet, 0 );

/*  中断处理底半部 */
void xxx_do_tasklet( unsigned long )
{
    ...
}

/* 中断处理顶半部 */
irqreturn_t xxx_interrupt( int irq, void * dev_id )
{
    ...
    tasklet_schedule( &xxx_tasklet );    // 顶半部调用
    ...
  return IRQ_HANDLED
}

__init xxx_init( void )
{
    ...
    result = request_irq( xxx_irq, xxx_interrupt,0,"xxx", NULL );    // 注册的是顶半部的执行函数
    ...
}

__exit xxx_exit( void )
{
    ...
    free_irq( xxx_irq, xxx_interrupt );
    ...
}

3.3.2 工作队列

  使用与tasklet相似,区别是执行上线文为内核线程,可以调度和睡眠。内核维护一个工作队列池。

#include <linux/interrupt.h >    // 在次头文件里包含了linux/workqueue.h

1.定义工作队列
struct work_struct my_wq;    // 定义工作队列

2.把工作队列和处理函数初始化及绑定
void my_wq_func( struct work_struct * work );  // 定义一个处理函数,注意形参
INIT_WORK( &my_wq, my_wq_func );          // 初始化工作队列,并绑定处理函数

3.调度
schedule_work( &my_wq );

 使用模板:

#include <linux/interrupt.h>

struct work_struct xxx_wq;
void xxx_do_work( struct work_struct * work );/*  中断处理底半部 */
void xxx_do_work( struct work_struct * work )
{
    ...
}

/* 中断处理顶半部 */
irqreturn_t xxx_interrupt( int irq, void * dev_id )
{
    ...
    schedule_work( &xxx_wq );    // 顶半部调用
    ...
}

__init xxx_init( void )
{
    ...
    result = request_irq( xxx_irq, xxx_interrupt,0,"xxx", NULL );    // 注册的是顶半部的执行函数
    ...
  INIT_WORK( &xxx_wq, xxx_do_work );
    return IRQ_HANDLED;
}

__exit xxx_exit( void )
{
    ...
    free_irq( xxx_irq, xxx_interrupt );
    ...
}

3.3.3 软中断

  •   tasklet就是基于软中断的,一般来说,驱动编写者不宜直接使用软中断,linux封装了例如tasklet这类友好的接口,还有其他场合是用软中断实现的,见后面描述。
  •        优先级: 中断>软中断>线程,当某个时间段内软中断过多时,操作系统会将后面的软中断放入ksoftirq内核线程中执行,软中断适度线程化有利于缓解高负荷情况下的系统响应。
#include  <linux/interrupt.h>


struct softirq_action
{
    void    (*action)(struct softirq_action *);
};


extern void open_softirq(int nr, void (*action)(struct softirq_action *));    // 注册中断函数
extern void raise_softirq(unsigned int nr);                      // 触发软中断


static inline void local_bh_disable(void);                       // 关闭底半部
extern void local_bh_enable(void);                            // 使能底半部


/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    BLOCK_IOPOLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ,
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};

3.3.4 threaded_irq

  • 同时注册顶半部和底半部
#include <linux/interrupt.h>

extern int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn,
             unsigned long flags, const char *name, void *dev);

extern int devm_request_threaded_irq(struct device *dev, unsigned int irq,
              irq_handler_t handler, irq_handler_t thread_fn,
              unsigned long irqflags, const char *devname,
              void *dev_id);

enum irqreturn {
    IRQ_NONE        = (0 << 0),
    IRQ_HANDLED        = (1 << 0),
    IRQ_WAKE_THREAD        = (1 << 1),
};
!!! 如果顶半部返回IRQ_WAKE_THREAD,则顶半部执行结束后,内核会调度对应线程执行thread_fn对应的函数
如果参数handler为NULL,则系统使用如下默认顶半部处理函数

/* 
 * Default primary interrupt handler for threaded interrupts. Is 
 * assigned as primary handler when request_threaded_irq is called 
 * with handler == NULL. Useful for oneshot interrupts. */
 static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
 {
   return IRQ_WAKE_THREAD; 
 }
!!!flag使用IRQ_ONESHOT标记,内核会在中断上下文屏蔽对应中断号,在内核调度thread_fn执行后,重新使能该中断号。避免中断程序一退出,马上又进入中断的情况。
#define IRQF_ONESHOT        0x00002000

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值