Linux内核中断设计与实现

嵌入式技术

1368人已加入

描述

 

裸机编程中使用中断比较麻烦,需要配置寄存器、使能IRQ等等。而在Linux驱动编程中,内核提供了完善的终端框架,只需要申请中断,然后注册中断处理函数即可,使用非常方便

1. 内核中断

Linux内核中断分为上半部和下半部,其主要目的就是实现中断处理函数的快进快出,两者的区别如下:

  • 上半部:就是中断处理函数,处理过程比较快,不会占用很长时间

  • 下半部:若中断处理过程很耗时,就将这些代码提出来,交给下半部去执行,这样中断处理函数就会快进快出

一般情况下,若要处理的内容不希望被其他中断打断、要处理的任务对时间敏感或者要处理的任务与硬件有关,通常放到上半部;其他情况优先考虑放到下半部

 

2. 中断下半部实现方式

上半部直接编写中断处理函数处理即可,下半部的实现方式主要有如下三种:

Linux

 软中断:内核使用结构体softirq_action表示软中断,定义在文件include/linux/interrupt.h
struct softirq_action{
 void (*action)(struct softirq_action *);
}
//文件 kernel/softirq.c 中共定义了10个软中断
static struct softirq_action softirq_vec[NR_SOFTIRQS];
//NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h中
enum
{
 HI_SOFTIRQ=0,      /* 高优先级软中断 */
 TIMER_SOFTIRQ,     /* 定时器软中断 */
 NET_TX_SOFTIRQ,    /* 网络数据发送软中断 */
 NET_RX_SOFTIRQ,    /* 网络数据接收软中断 */
 BLOCK_SOFTIRQ,
 BLOCK_IOPOLL_SOFTIRQ,
 TASKLET_SOFTIRQ,   /* tasklet 软中断 */
 SCHED_SOFTIRQ,     /* 调度软中断 */
 HRTIMER_SOFTIRQ,   /* 高精度定时器软中断 */
 RCU_SOFTIRQ,       /* RCU 软中断 */
 NR_SOFTIRQS
};

软中断的使用方法如下示:

  • 先使用open_softirq函数注册对应的软中断处理函数

  • 注册好软中断后,需要通过raise_softirq函数触发

  • 软中断一定要在编译的时候静态注册,使用softirq_init函数来静态注册

tasklet:是利用软中断来实现的另一种下半部机制,两者之间,建议使用 tasklet,内核使用tasklet_struct结构体来表示tasklet

struct tasklet_struct
{
 struct tasklet_struct *next;   /* 下一个 tasklet */
 unsigned long state;           /* tasklet 状态 */
 atomic_t count;                /* 计数器,记录对 tasklet 的引用数 */
 void (*func)(unsigned long);   /* tasklet 执行的函数 */
 unsigned long data;            /* 函数 func 的参数 */
};

tasklet 的使用方法如下示:

  • 先定义一个tasklet,然后使用tasklet_init函数初始化tasklet

  • 也可用宏DECLARE_TASKLET()来一次性完成tasklet的定义和初始化 

  • 在上半部(即中断处理函数)中调用tasklet_schedule函数使tasklet处理函数在合适的时间运行

tasklet的参考使用示例如下所示:

/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
 /* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
 ......
 /* 调度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
 ......
 /* 初始化 tasklet */
 tasklet_init(&testtasklet, testtasklet_func, data);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0"xxx", &xxx_dev);
 ......
}

 工作队列:在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,工作队列允许睡眠或重新调度。因此若你要推后的工作需要睡眠功能,就可选择工作队列,否则就只能选择软中断或tasklet

//内核使用 work_struct 结构体表示一个工作
struct work_struct {
 atomic_long_t data;
 struct list_head entry;
 work_func_t func;   /* 工作队列处理函数 */
};
tasklet 的使用方法如下示:
  • 先定义一个work,然后使用INIT_WORK()宏函数来初始化工作

  • 也可以使用DECLARE_WORK()宏函数一次性完成work的创建和初始化

  • 在上半部(即中断处理函数)中调用schedule_work函数使work处理函数在合适的时间运行

工作队列的参考使用示例如下所示:

/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
 /* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
 ......
 /* 调度 work */
 schedule_work(&testwork);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
 ......
 /* 初始化 work */
 INIT_WORK(&testwork, testwork_func_t);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0"xxx", &xxx_dev);
 ......
}

 

3. 中断API函数

每个中断都有一个中断号,通过中断号可区分不同的中断。在Linux内核中使用一个 int 变量表示中断号

request_irq函数:申请中断,会激活中断,所以无需手动使能中断

int request_irq(unsigned int  irq,
    irq_handler_t handler,
    unsigned long flags,
    const char  *name,
    void   *dev)
//irq:要申请中断的中断号
//handler:中断处理函数,中断发生后会执行此函数
//flags:中断标志,在include/linux/interrupt.h中定义
//name:中断名字,设置后可以在/proc/interrupts中看到对应的中断名字
//dev:若flags设置为IRQF_SHARED的话,dev用来区分不同的中断
//返回值:0表示中断申请成功,其他负值表示中断申请失败,-EBUSY表示中断已被申请

free_irq函数:释放中断,会删除中断处理函数并且禁止中断

void free_irq(unsigned int irq,
     void *dev)
//irq:要释放的中断
//dev:若flags设置为IRQF_SHARED的话,dev用来区分不同的中断
//     共享中断只有在释放最后中断处理函数的时候才会被禁止掉
//返回值:无

中断处理函数:申请中断时需要设置中断处理函数,格式如下

irqreturn_t (*irq_handler_t) (intvoid *)
//返回值irqreturn_t是一个枚举类型,共有三种返回值:
enum irqreturn {
 IRQ_NONE = (0 << 0),
 IRQ_HANDLED = (1 << 0),
 IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
//一般中断服务函数返回值使用形式为:
return IRQ_RETVAL(IRQ_HANDLED)

中断使能与禁止函数

void enable_irq(unsigned int irq)//使能指定的中断
void disable_irq(unsigned int irq)//禁止中断,会等当前处理函数执行完才返回
void disable_irq_nosync(unsigned int irq)//禁止中断,不等执行完,立即返回
local_irq_enable();  //使能当前处理器中断系统
local_irq_disable(); //禁止当前处理器中断系统
local_irq_save(flags); //禁止中断,并将中断状态保存在flags中
local_irq_restore(flags);  //恢复中断,并将中断状态保存在flags中

 

4. 内核中断使用模板

以上半部中断为例,介绍内核中断的使用流程:

Linux

设备树中与中断有关的设备树属性信息有:
#interrupt-cells //指定中断源的信息 cells 个数
interrupt-controller //表示当前节点为中断控制器
interrupts //指定中断号,触发方式等
interrupt-parent //指定父中断,也就是中断控制器

获取中断号函数有两个,如下示:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
//dev:设备节点
//index:索引号,通过索引号来指定要获取的信息
//返回值:中断号
/*****若使用GPIO中断,可以下函数来获取对应中断号*****/
int gpio_to_irq(unsigned int gpio)
//gpio:要获取的GPIO编号
//返回值:GPIO对应的中断号

 

  审核编辑:汤梓红


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分