目录
1、前言
硬件中断(hardware interrupt
):
由系统自身和与之连接的外设自动产生。它们用于支持更高效地实现设备驱动程序,也用于引起处理器自身对异常或错误的关注
软中断(SoftIRQ
):
用于有效实现内核中的延期操作。
同步中断和异常:
这些由CPU自身产生,针对当前执行的程序 触发原因 1)运行时发生的程序设计错误(典型的例子是除0) ;2)出现了异常的情况或条件。
异步中断:
这是经典的中断类型,由外部设备产生,可能发生在任意时间。
2、中断服务例程ISR
在处理程序执行期间,发生了其他中断。尽管可以通过在处理程序执行期间禁用中断来防止,但这会引起其他问题,如遗漏重要的中断,因而只能短时间使用。
1. 注册IRQ
由设备驱动程序动态注册ISR的工作
int request_irq(unsigned int irq,
irqreturn_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
内核首先生成一个新的irqaction实例,然后用函数参数填充其内容,特别重要的是处理程序函数handler
如果安装的处理程序是该IRQ编号对应链表中的第一个,则调用handler->startup初始化函数。如果该IRQ此前已经安装了处理程序,则没有必要再调用该函数。
register_irq_proc在proc文件系统中建立目录/proc/irq/NUM。而register_handler_proc生成proc/irq/NUM/name
在设备请求一个系统中断,而中断控制器通过引发中断将该请求转发到处理器的时候,内核
将调用该处理程序函数
ISR必须满足:尽可能少的代码,以支持快速处理;不能彼此干扰。
那么延时较长的处理在什么地方执行呢? 内核中提供了下半部
软中断、tasklet、工作队列
软中断类型为
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
ISR 软中断 tasklet之间的关系
3、tasklet
- 更易于扩展,因而更适合于设备驱动程序
- 同一个tasklet只能在一个CPU上运行,不同的tasklet可以在不同的CPU上运行
- tasklet使用了软中断枚举的TASKLET_SOFTIRQ和HI_SOFTIRQ
struct tasklet_struct
{
struct tasklet_struct *next;
//TASKLET_STATE_SCHED:在tasklet注册到内核,等待调度执行
//TASKLET_STATE_RUN:当前正在执行
unsigned long state;
atomic_t count;//原子计数
void (*func)(unsigned long);//tasklet的函数执行体
unsigned long data;
};
使用 tasklet_init 函数初始化 tasklet
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
EXPORT_SYMBOL(tasklet_init);
注册tasklet
tasklet_schedule将一个tasklet注册到系统中
tasklet_schedule在什么时候调用呢? 在ISR中调用
irqreturn_t xxx_handler(int irq, void *dev_id) {
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
tasklet_schedule做了什么工作?
tasklet_schedule->__tasklet_schedule->__tasklet_schedule_common
static void __tasklet_schedule_common(struct tasklet_struct *t,
struct tasklet_head __percpu *headp,
unsigned int softirq_nr)
{
struct tasklet_head *head;
unsigned long flags;
local_irq_save(flags);
head = this_cpu_ptr(headp);
t->next = NULL;
*head->tail = t;
head->tail = &(t->next);
raise_softirq_irqoff(softirq_nr);
local_irq_restore(flags);
}
- 如果设置了TASKLET_STATE_SCHED标志位,则结束注册过程,因为该tasklet此前已经注册了。
- 否则,将该tasklet置于一个链表的起始,其表头是特定于CPU的变量tasklet_vec。
- 该链表包含了所有注册的tasklet,使用next成员作为链表元素。
- 在注册了一个tasklet之后,tasklet链表即标记为即将进行处理。
执行tasklet
tasklet执行体的注册,基于软中断
softirq_init->open_softirq->tasklet_action->tasklet_action_common->while循环执行
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
static __latent_entropy void tasklet_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static __latent_entropy void tasklet_hi_action(struct softirq_action *a)
{
tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);
}
tasklet占用了了软中断的两个中断类型(TASKLET_SOFTIRQ和HI_SOFTIRQ),优先级有高低之分,分别对应tasklet_action()和tasklet_hi_action(),需要执行的tasklet保存在tasklet_vec和tasklet_hi_vec链表中
循环执行的过程
static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
struct tasklet_struct *list;
//保持中断状态寄存器并关闭本地CPU中断
local_irq_disable();
list = tl_head->head;
tl_head->head = NULL;
tl_head->tail = &tl_head->head;
//恢复中断寄存器并开本地中断
local_irq_enable();
while (list) {
//循环执行tasklet链表上每个tasklet的处理函数
struct tasklet_struct *t = list;
list = list->next;
//如果tasklet没被执行,执行设备tasklet的state字段为RUNNING
if (tasklet_trylock(t)) {
//如果tasklet的锁计数器为0,执行
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
t->func(t->data);//*****函数的执行*****
//如果不为0 则表示禁用,清楚RUNNING状态
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
//关本地中断,并把没有处理的tasklet 重新挂载到tasklet_vec上
local_irq_disable();
t->next = NULL;
*tl_head->tail = t;
tl_head->tail = &t->next;
//把本地CPU上的TASKLET_SOFTIRQ标记为挂起,并使能中断
__raise_softirq_irqoff(softirq_nr);
local_irq_enable();
}
}
在while循环中执行tasklet,类似于处理软中断使用的机制。
因为一个tasklet只能在一个处理器上执行一次,但其他的tasklet可以并行运行,所以需要特定于 tasklet 的锁。
state状态用作锁变量。在执行一个 tasklet 的处理程序函数之前,内核使用tasklet_trylock检查tasklet的状态是否为TASKLET_STATE_RUN
static inline int tasklet_trylock(struct tasklet_struct *t)
{
return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);
}
除了普通的tasklet之外,内核还使用了另一种tasklet,它具有“较高”的优先级。除以下修改之
外,其实现与普通的tasklet完全相同。
- 使用HI_SOFTIRQ作为软中断,相关的action函数tasklet_hi_action。
- 注册的tasklet在CPU相关的变量tasklet_hi_vec中排队。这是使用tasklet_hi_schedule完成的。
上半部与下半部选择建议
- 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
- 如果要处理的任务对时间敏感,可以放到上半部。
- 如果要处理的任务与硬件有关,可以放到上半部。
- 除了上述三点以外的其他任务,优先考虑放到下半部。
参考:
《深入理解linux内核》
《Linux内核深度解析》