【linux kernel】 中断处理-中断下半部
欢迎转载,转载时需保留作者信息,谢谢。
博客园地址:http://www.cnblogs.com/embedded-tzp
Csdn博客地址:http://blog.csdn.net/xiayulewa
1. 概述
Linux内核中断机制:为了在中断执行时间尽可能短和中断处理需要完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部,顶半部和底半部。
顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位就进行“登记工作”,将底半部处理程序挂到该设备的底半部执行队列中去。
那上半部和下半部是分界线是什么? 以request_irq注册的中断函数为分界线。
上半部:
当执行完request_irq注册的中断函数后,上半部结束。在注册的中断函数中,登记下半部要做的工作。
上半部已经讨论了:http://www.cnblogs.com/embedded-tzp/p/4451354.html
底半部实现方式有:
软中断
tasklet
工作队列
2. 软中断
http://www.cnblogs.com/embedded-tzp/p/4452041.html中已讨论。
3. Tasklet
3.1. 数据结构
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
该结构体由tasklet_init初始化。
Softirq.c (src\kernel)中:
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
};
struct tasklet_head tasklet_vec;
3.2. 注册
softirq_init中有:
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
可见其实现方式为软件中断。其处理函数为tasklet_action
tasklet_schedule→__tasklet_schedule
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
将tasklet对象组织成一张链表。
3.3. 执行流程
tasklet_schedule→__tasklet_schedule→raise_softirq_irqoff(TASKLET_SOFTIRQ);
触发软中断执行,软中断执行流程见http://www.cnblogs.com/embedded-tzp/p/4452041.html
最终软中断会执行到tasklet的处理函数tasklet_action
tasklet_action中是个while,循环处理tasklet_vec链表中的struct tasklet_struct。
3.4. 其它:
* 不允许访问用户空间;?????
* 不允许访问current指针;
* 不能执行休眠或调度。
特征:
* 一个tasklet可被禁用或启用;只用启用的次数和禁用的次数相同时,tasklet才会被执行。
* 和定时器类似,tasklet可以注册自己;
* tasklet可被调度在一般优先级或者高优先级上执行,高优先级总会首先执行;
* 如果系统负荷不重,则tasklet会立即得到执行,且始终不会晚于下一个定时器滴答;
* 一个tasklet可以和其他tasklet并发,但对自身来讲必须严格串行处理,即一个tasklet不会在多个处理器上执行。
3.5. 函数集合
DECLARE_TASKLET(name,func,data); /*定义及初始化tasklet*/
DECLARE_TASKLET_DISABLED(name,func,data); /*定义及初始化后禁止该tasklet*/
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); /* 初始化tasklet,func指向要执行的函数,data为传递给函数func的参数 */
void tasklet_disable(struct tasklet_struct *t) /*禁用指定tasklet, */
void tasklet_disable_nosync(struct tasklet_struct *t) /*禁用指定tasklet,但不会等待任何正在运行的tasklet退出*/
void tasklet_enable(struct tasklet_struct *t) /*启用先前被禁用的tasklet*/
void tasklet_schedule(struct tasklet_struct *t) /*注册并调度执行指定的tasklet*/
void tasklet_hi_schedule(struct tasklet_struct *t) /*调度指定的tasklet以高优先级执行*/
void tasklet_kill(struct tasklet_struct *t) /*移除指定tasklet*/
3.6. 实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*硬件相关的数据结构*/
struct btn_resource {
int irq; //中断号
char *name; //中断名称
};
//初始化板卡按键信息
static struct btn_resource btn_info[] = {
[0] = {
.irq = IRQ_EINT(0),
.name = "KEY_UP"
},
[1] = {
.irq = IRQ_EINT(1),
.name = "KEY_DOWN"
}
};
//tasklet的处理函数,在tasklet_action函数中被处理
static void btn_tasklet_func(unsigned long data)
{
struct btn_resource *pdata = (struct btn_resource *)data;
printk("%s: irq = %d, name = %s\n",
__func__, pdata->irq, pdata->name);
}
//定义tasklet对象
static DECLARE_TASKLET(btn_tasklet,
btn_tasklet_func, (unsigned long)&btn_info[0]);
//中断处理函数
//irq:中断号,dev_id:保存注册中断时传递的参数信息
static irqreturn_t button_isr(int irq, void *dev_id)
{
//登记底半部信息
tasklet_schedule(&btn_tasklet); //注册并调度tasklet,CPU会在空闲时执行tasklet的处理函数
printk("%s\n", __func__);
return IRQ_HANDLED; //成功,失败:IRQ_NONE
}
static int btn_init(void)
{
//申请中断和注册中断处理程序
/*
* IRQ_EINT(0):中断号
* button_isr:中断处理程序,中断发生以后,内核执行此函数
* IRQF*:外部中断的触发方式,内部中断此参数为0
* KEY_UP:中断名称,出现在 cat /proc/interrupts
* &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL
*/
int i;
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
request_irq(btn_info[i].irq, button_isr,
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
btn_info[i].name, &btn_info[i]);
printk("%s\n", __func__);
return 0;
}
static void btn_exit(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
free_irq(btn_info[i].irq, &btn_info[i]);
printk("%s\n", __func__);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
4. 工作队列
工作队列涉及到linux内核进程调度策略,但调度策略不是此处应该讨论的。
4.1. 内核默认队列
4.1.1. 数据结构
struct work_struct
struct delayed_work
初始化队列对象
INIT_WORK(&mywork, my_work_func);
INIT_DELAYED_WORK(&mydwork, my_dwork_func);
4.1.2. 函数集合
schedule_work(&mywork)
schedule_delayed_work(&mydwork, 5*HZ)
4.1.3. 为什么是内核默认队列
Workqueue.c (src\kernel) 中:
init_workqueues→
system_wq = alloc_workqueue("events", 0, 0);
而 schedule_work→
queue_work(system_wq, work);
可见struct work_struct对象是默认放在缺省的内核线程线程events中。当缺省工作队列负载太重,执行效率会很低,需要我们自建队列
4.1.4. 实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*硬件相关的数据结构*/
struct btn_resource {
int irq; //中断号
char *name; //中断名称
};
//初始化板卡按键信息
static struct btn_resource btn_info[] = {
[0] = {
.irq = IRQ_EINT(0),
.name = "KEY_UP"
},
[1] = {
.irq = IRQ_EINT(1),
.name = "KEY_DOWN"
}
};
//分配工作和延时工作
static struct work_struct mywork;
//工作队列处理函数
static void my_work_func(struct work_struct *work)
{
printk("%s\n", __func__);
}
//中断处理函数
//irq:中断号,dev_id:保存注册中断时传递的参数信息
static irqreturn_t button_isr(int irq, void *dev_id)
{
//登记工作底半部信息
/*
* schedule_work:登记工作,将工作交给内核默认的工作队列和
* 内核线程去管理和调度执行,cpu会在适当的时候会执行工作的处理函数
* */
schedule_work(&mywork);
printk("%s\n", __func__);
return IRQ_HANDLED; //成功,失败:IRQ_NONE
}
static int btn_init(void)
{
//申请中断和注册中断处理程序
/*
* IRQ_EINT(0):中断号
* button_isr:中断处理程序,中断发生以后,内核执行此函数
* IRQF*:外部中断的触发方式,内部中断此参数为0
* KEY_UP:中断名称,出现在 cat /proc/interrupts
* &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL
*/
int i;
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
request_irq(btn_info[i].irq, button_isr,
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
btn_info[i].name, &btn_info[i]);
//初始化工作和延时工作
INIT_WORK(&mywork, my_work_func);
printk("%s\n", __func__);
return 0;
}
static void btn_exit(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
free_irq(btn_info[i].irq, &btn_info[i]);
printk("%s\n", __func__);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
4.2. 自建队列
如上讨论,当使用work_struct,默认放在缺省的内核线程线程events中。当缺省工作队列负载太重,执行效率会很低,需要我们自建队列
4.2.1. 数据结构
struct workqueue_struct
4.2.2. 函数集合
wq = create_workqueue("tzp");
queue_work(wq, &mywork);
queue_delayed_work(wq, &mydwork, 3*HZ); // 3秒后执行
destroy_workqueue
4.2.3. 实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
/*硬件相关的数据结构*/
struct btn_resource {
int irq; //中断号
char *name; //中断名称
};
//初始化板卡按键信息
static struct btn_resource btn_info[] = {
[0] = {
.irq = IRQ_EINT(0),
.name = "KEY_UP"
},
[1] = {
.irq = IRQ_EINT(1),
.name = "KEY_DOWN"
}
};
//定义工作队列的指针
static struct workqueue_struct *wq;
//分配延时工作
static struct delayed_work mydwork;
//延时工作的处理函数
static void my_dwork_func(struct work_struct *work)
{
printk("%s\n", __func__);
}
//中断处理函数
//irq:中断号,dev_id:保存注册中断时传递的参数信息
static irqreturn_t button_isr(int irq, void *dev_id)
{
//登记关联自己的工作队列和工作
queue_delayed_work(wq, &mydwork, 3*HZ);
printk("%s\n", __func__);
return IRQ_HANDLED; //成功,失败:IRQ_NONE
}
static int btn_init(void)
{
//申请中断和注册中断处理程序
/*
* IRQ_EINT(0):中断号
* button_isr:中断处理程序,中断发生以后,内核执行此函数
* IRQF*:外部中断的触发方式,内部中断此参数为0
* KEY_UP:中断名称,出现在 cat /proc/interrupts
* &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL
*/
int i;
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
request_irq(btn_info[i].irq, button_isr,
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
btn_info[i].name, &btn_info[i]);
//初始化延时工作
INIT_DELAYED_WORK(&mydwork, my_dwork_func);
//创建自己的工作队列和内核线程
wq = create_workqueue("tzp"); //线程名叫tzp
printk("%s\n", __func__);
return 0;
}
static void btn_exit(void)
{
int i;
//销毁自己的工作队列和线程
destroy_workqueue(wq);
for (i = 0; i < ARRAY_SIZE(btn_info); i++)
free_irq(btn_info[i].irq, &btn_info[i]);
printk("%s\n", __func__);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
【linux kernel】 中断处理-中断下半部的更多相关文章
- Linux kernel的中断子系统之(六):ARM中断处理过程
返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...
- Linux kernel的中断子系统之(一):综述
返回目录:<ARM-Linux中断系统>. 总结: 一从作为一名驱动工程师角度看,用好中断需要正确认识request_threaded_irq/request_irq关系.中断临界区保护. ...
- Linux kernel的中断子系统之(八):softirq
返回目录:<ARM-Linux中断系统>. 总结:中断分为上半部和下半部,上半部关中断:下半部开中断,处理可以延迟的事情.下半部有workqueue/softirq/tasklet三种方式 ...
- Linux kernel的中断子系统之(三):IRQ number和中断描述符
返回目录:<ARM-Linux中断系统>. 总结: 二描述了中断处理示意图,以及关中断.开中断,和IRQ number重要概念. 三介绍了三个重要的结构体,irq_desc.irq_dat ...
- Linux kernel的中断子系统之(二):IRQ Domain介绍
返回目录:<ARM-Linux中断系统>. 总结:一.二概述了软硬件不同角度的IRQ Number和HW Interrupt ID,这就需要他们之间架个桥梁. 三介绍了架设这种桥梁的几种方 ...
- Linux kernel的中断子系统之(四):High level irq event handler
返回目录:<ARM-Linux中断系统>. 总结:从架构相关的汇编处理跳转到Machine/控制器相关的handle_arch_irq,generic_handle_irq作为High l ...
- Linux kernel的中断子系统之(九):tasklet
返回目录:<ARM-Linux中断系统>. 总结: 二介绍了tasklet存在的意义. 三介绍了通过tasklet_struct来抽想一个tasklet,每个CPU维护一个tasklet链 ...
- Linux kernel的中断子系统之(七):GIC代码分析
返回目录:<ARM-Linux中断系统>. 总结: 原文地址:<linux kernel的中断子系统之(七):GIC代码分析> 参考代码:http://elixir.free- ...
- linux kernel的中断子系统之(三):IRQ number和中断描述符【转】
转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interr ...
- linux kernel的中断子系统 softirq
linux kernel的中断子系统之(八):softirq http://www.wowotech.net/irq_subsystem/soft-irq.html http://www.ibm.co ...
随机推荐
- typeof操作符的返回值
使用typeof操作符 对一个值使用typeof操作符可能返回下列某个字符串: 1):undefined——如果这个值未定义 2):boolean——如果这个值是布尔值 3):string——如果这个 ...
- MySQL 5.6.x 配置数据库主从复制
[转]http://blog.csdn.net/lwprain/article/details/10966837 备注: 在配置之前如果之前配置过主从没成功的话, 最好把master数据库目录下的my ...
- Nginx阅读笔记(四)之root和alias
nginx指定文件路径有两种方式root和alias,这两者的用法区别,使用方法总结了下,方便大家在应用过程中,快速响应.root与alias主要区别在于nginx如何解释location后面的uri ...
- NET Core,跨平台的轻量级RPC
NET Core,跨平台的轻量级RPC:Rabbit.Rpc 特性一览 Apache License 2.0协议开源 支持客户端负载均衡(提供了轮询.随机算法的实现) 支持ZooKeeper和文件共享 ...
- 让innerHTML方法添加到元素里的js可以被解析执行
<!DOCTYPE html> <html> <head> </head> <body> <div id="test&quo ...
- 说说读卡应用那点事儿,以SCL010为例
前一阵子的项目, 跟读卡应用有关,这篇博客算是我学习智能卡方面知识的而一个总结,也可以看作这个领域的一个很简单的简介,他写得很不书面,更像是沿着我自己认识过程的总结.所以这里面有很多我自己理解的地方, ...
- 数据结构——链表(linkedlist)
基本分类: 1.单向链表 2.带尾指针的单向链表 3.双向循环链表 以下分类进行说明 1.单向链表 基本元素:*front //头节点 *next //下一节点 声明:node<T>*p; ...
- Effective C++:条款37:绝不又一次定义继承而来的缺省參数值
因为又一次定义继承而来的non-virtual函数是不对的(见上一个条款),所以这个条款就将问题局限于:绝不又一次定义继承一个带有缺省參数值的virtual函数. (一) virtual函数是动态绑定 ...
- Java:使用synchronized和Lock对象获取对象锁
在并发环境下,解决共享资源冲突问题时,可以考虑使用锁机制. 1.对象的锁 所有对象都自动含有单一的锁. JVM负责跟踪对象被加锁的次数.如果一个对象被解锁,其计数变为0.在任务(线程)第一次给对象加锁 ...
- UVa401 Palindromes
#include <stdio.h>#include <string.h> char mirror(char c){ static const char m[] = &q ...