分类: LINUX
2012-04-10 13:28:39
字符驱动之九中断
2012-04-10
中断
在linux内核2.6之前的并发来源相对比较少,一般只有两种:
1、 中断(中断上下文)
2、 睡眠和在唤醒(进程上下文)
中断作用:要讲中断的作用,那得必须从中断的产生原理来说比较好理解。
举个例子:老师正在兴高采烈的讲课(新知识点),这是碰到一个学生对这知识点不理解,就举手示意(申请中断)。老师看到了,就让这学生发言(申请成功),知道问题后的老师,就解释详细给同学们听,等大家都明白了(中断处理程序)。他就继续往下讲(从被打断后的往下讲)(释放中断)。
1、中断概念
中断是处理器处理外部突发事件的一个重要技术。它能使处理器在运行过程中对外部事件发出的中断请求及时地进行处理,处理完成后又立即返回断点,继续进行处理器原来的工作。引起中断的原因或者说发出中断请求的来源叫做中断源。根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断 两类。
外部中断一般是指由计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断等。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备 的中断请求。
内部中断是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算 溢出、单步中断等)所引起的中断。内部中断是不可屏蔽的中断。
软件中断其实并不是真正的中断,它们只是可被调用执行的一般程序。例如:ROM BIOS中的各种外部设备管理中断服务程序(键盘管理中断、显示器管理中断、打印机管理 中断等,)以及DOS的系统功能调用(INT 21H)等都是软件中断。 CPU为了处理并发的中断请求,规定了中断的优先权,中断优先权由高到低的顺序是: (1)除法错、溢出中断、软件中断 (2)不可屏蔽中断 (3)可屏蔽中断 (4)单步中断。[来源于百度百科]
2、中断号
外部设备那么多,若一下产生好几个中断,我们如何识别哪个设备产生的中断请求呢?(比如说:键盘中断、打印机中断、定时器中断等同时到达,CPU如何去识别呢?)这就是我们所要说的中断请求线(中断号):不同设备所对应的中断不同,每个中断都有唯一的数值来标识。(因此我们申请中断时,这个中断号必须是内核知道的)。
3、中断处理程序
为了响应特定的中断,内核会执行一个特定的函数,被称为中断处理程序或中断例程服务。一般中断处理程序不与特定的设备相连,而是与特定的中断相连。è即一个设备可以产生多个中断,就有多个中断处理程序。若你是与特定设备相连呢?è那就是一个中断处理程序就对应一个特定设备,一个中断处理能对应多个设备吗?
特点:(1)中断随时都可能发生,中断处理程序随时都会被执行,因此中断处理程序必须快速的被运行完毕,快速恢复中断前的状态。从而要求中断处理程序不能阻塞睡眠。
(2)快速、异步、简单的处理程序负责对硬件做出迅速响应并完成那些时间要求很严格的操作。中断处理程序适合做这些操作。
(3)中断处理程序会异步执行,并且在最好的情况下它也会锁住当前的中断线。
从以上三点可以看出:中断处理程序所处理的是时间要求严格、与硬件相关性比较强、或者要求任务不被其他中断(尤其同级别的中断)打断。
引发Question:要求时间短,必然不能完成大工作量的程序?而有时又必须这样做,该如何处理呢?
4、中断上下文
QUESTION:中断上下文如何衔接的?也就是说上文如何通知下文要执行程序呢?è中断处理程序中就要处理了哦。
中断上下文只是个概念而已,关键在于中断下文如何去操作哦。
5、中断上下文的下文处理机制
软中断:软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似。同时,软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断"。[百度百科]
图 软中断实现原理
构成软中断机制的核心元素包括:
1、 软中断状态寄存器soft interrupt state(irq_stat)
2、 软中断向量表(softirq_vec)
3、 软中断守护daemon
软中断的工作工程模拟了实际的中断处理过程,当某一软中断事件发生后,首先需要设置对应的中断标记位,触发中断事务,然后唤醒守护线程去检测中断状态寄存器,如果通过查询发现有软中断事务发生,那么通过查询软中断向量表调用相应的软中断服务程序action()。这就是软中断的过程,与硬件中断唯一不同的地方是从中断标记到中断服务程序的映射过程。在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过向量表映射成具体的服务程序,这个过程是硬件自动完成的,但是软中断不是,其需要守护线程去实现这一过程,这也就是软件模拟的中断,故称之为软中断。
一个软中断不会去抢占另一个软中断,只有硬件中断才可以抢占软中断,所以软中断能够保证对时间的严格要求。
二 使用方法
1)中断使用方法:
【使用方法】
1、注册中断<申请中断>
2、中断处理函数(中断上下文中上文)
3、释放中断
【中断上下文中上文操作方法】
头文件:
#include
/*
* @brief 申请中断
* @param[in] irq 指定要分配的中断号,在“include/mach/irq.h”定义
* @param[in] handler 中断处理函数指针
* @param[in] irqflags 中断处理标记==>是判断下降沿还是上升沿、高电平
* 还是低电平有效
* @param[in] devnmae 该设备名字将显示在/proc/irq 和
* /proc/interrupts
* @param[in] dev_id 若是共享中断设备,是共享设备号
* @return 成功返回 0,失败返回非 0
*/
[NOTE]:该函数会睡眠,必须小心
一下标志有时不止用单一的,混合用
*linux-2.6.29/include/linux/interrupt.h*/
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发中断
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发中断
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发中断
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发中断
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
//共享设备
#define IRQF_DISABLED 0x00000020 //SA_INTERRUPT
#define IRQF_SAMPLE_RANDOM 0x00000040 //SA_SAMPLE_RANDOM
#define IRQF_SHARED 0x00000080 //SA_SHIRQ
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long
irqflags, const char *devname, void *dev_id)
/*
* @brief 中断处理函数(中断上下文中的下文)
* @param[in] irq 在2.6内核中,该作用不大,一般只采用于打印
* @param[in] dev_id 若是共享中断设备,是共享设备号
* @return 有三个,在include/linux/interrupt.h中定义
*/
[NOTE]
#define IRQ_NONE (0)//如果产生的中断并不会执行该中断处理函数时返回该值
#define IRQ_HANDLED (1) //中断处理函数正确调用会返回
#define IRQ_RETVAL(x) ((x) != 0) //指定返回的数值,如果非0,返回IRQ_HADLER,否则
#ifndef IRQ_NONE //返回IRQ_NONE。
static irqreturn_t intr_handler(int irq, void *dev_id)
/*
* @brief 中断处理函数(中断上下文中的下文)
* @param[in] irq 要释放的中断号
* @param[in] dev_id 若是共享中断设备,是共享设备号
* @return no
*/
void free_irq(unsigned int irq, void *dev_id)
2)软中断
1、要使用软中断,必须静态定义声明软中断,即注册一个软中断
#include
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 */
CYYCYH_SOFTIRQ, //added by cyycyh 2012-04-04
NR_SOFTIRQS /**静态维护软中断的个数**/
};
[NOTE]因为这是编译时静态编译的,那内核在哪里维护它呢?
#include
Static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
2、
头文件:
#include <linux/interrupt.h>
struct softirq_action
{
void (*action)(struct softirq_action *); //软中断处理函数
};
/*
* @brief 申请软中断处理函数
* @param[in] nr 注册得到的软中断号 CYYCYH_SOFTIRQ
* @param[in] (*action)(struct softirq_action *) 软中断处理函数
* @return no
*/
void open_softirq(int nr, void (*action)(struct softirq_action *))
/*
* @brief 触发软中断<只是通知内核,并非马上执行>
* @param[in] nr 注册得到的软中断号
* @return no
*/
void raise_softirq(unsigned int nr)
3)Tasklet
【使用方法】
【NOTE】它是原子操作的
1、初始化tasklets结构
2、启动通知内核
3、注销tasklets结构
头文件:
#include
[需要掌握的数据结构]
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
[NOTE]
(1)next指针:指向下一个tasklet的指针。
(2)state:定义了这个tasklet的当前状态。这一个32位的无符号长整数,
当前只使用了bit〔1〕和bit〔0〕两个状态位。其中,bit〔1〕=1表示这
个tasklet当前正在某个CPU上被执行,它仅对SMP系统才有意义,其作用
就是为了防止多个CPU同时执行一个tasklet的情形出现;bit〔0〕=1
表示这个tasklet已经被调度去等待执行了。对这两个状态位的宏定义如下
所示(interrupt.h):
enum
{
0,
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
(3)原子计数count:对这个tasklet的引用计数值。NOTE!只有当count等于
0时,tasklet代码段才能执行,也即此时tasklet是被使能的;如果count
非零,则这个tasklet是被禁止的。任何想要执行一个tasklet代码段的人
都首先必须先检查其count成员是否为0。
(4)函数指针func:指向以函数形式表现的可执行tasklet代码段。
(5)data:函数func的参数。这是一个32位的无符号整数,其具体含义可供func
函数自行解释,比如将其解释成一个指向某个用户自定义数据结构的地址值。
【tasklet机制操作方法】
/*
* @brief 初始化tasklet
* @param[in] 《见数据结构说明》
* @return no
*/
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
/*
* @brief 确定tasklets的优先级
* @param[in] t tasklet_struct结构指针
* @return no
*/
[NOTE]同一个Tasklet若被同时hi-schedule多次,等同于只hi-shedule一次,因为在tasklet未运行时,hi-shedule同一tasklet无意义,会冲掉前一个tasklet.
void tasklet_hi_schedule(struct tasklet_struct *t);
/*
* @brief 启动tasklets调度
* @param[in] t tasklet_struct结构指针
* @return no
*/
[NOTE]不同的tasklet不按先后shedule顺序运行,而是并行运行。
void tasklet_schedule(struct tasklet_struct *t);
/*
* @brief 禁止tasklets
* @param[in] t tasklet_struct结构指针
* @return no
*/
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
/*
* @brief 禁止后重新启动tasklets
* @param[in] t tasklet_struct结构指针
* @return no
*/
void tasklet_enable(struct tasklet_struct *t);
/*
* @brief 退出tasklets
* @param[in] t tasklet_struct结构指针
* @return no
*/
void tasklet_kill(struct tasklet_struct *t);
4)工作队列
【使用方法】
1、创建一个工作队列
2、初始化工作入口
3、加到工作队列中
4、注销工作队列
头文件:
#include
[数据结构]
struct workqueue_struct; //利用内核提供函数进行操作
struct work_struct
{
atomic_long_t data;
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
【workqueue操作方法】
/*
* @brief 创建一个工作队列
* @param[in] name workqueue的名称
* @return 返回workqueue_struct结构指针
*/
struct workqueue_struct *create_workqueue(const char *name);
//用于创建当个线程的
struct workqueue_struct *create_singlethread_workqueue(const char *name);
/*
* @brief 初始化工作入口
* @param[in] work 工作结构指针
* @param[in] function 工作队列要处理的函数指针
* @param[in] data function的参数
* @return no
*/
[NOTE]为何对data赋值时,提示错误
INIT_WORK(struct work_struct *work, void(*function)(void *), (void *)data);
/*
* @brief 工作队列对工作进行排队处理
* @param[in] work 工作结构指针
* @param[in] queue 工作队列指针
* @return
*/
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
//延时一段时间后才执行
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work,
unsigned long delays);
/*
* @brief 从一个工作队列中去除入口
* @param[in] work 工作结构指针
* @return
*/
int cancel_delayed_work(struct work_struct *work);
/*
* @brief flush_workqueue 确保没有工作队列入口在系统中任何地方运行
* @param[in] queue 工作队列指针
* @return no
*/
void flush_workqueue(struct workqueue_struct *queue);
/*
* @brief 销毁一个工作队列
* @param[in] queue 工作队列指针
* @return no
*/
void destroy_workqueue(struct workqueue_struct *queue);
【使用共享队列的函数】
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);