-------------------------中断上下文注意事项------------------------
1)"中断上下文(包括软中断上下文)不可以调用schedule()函数及其封装函数,如msleep(msecs), 因为不允许在中断上下文发生进程调”(在支持内核抢占的情况下不成立),
但是可以调用wake_up_process(task)及其封装函数,如schedule_work(work)和tasklet_schedule()
2)为避免睡眠的可能性,不能使用信号量和互斥锁,而应该使用自旋锁 spin lock及其变体。
3)中断上下文不能使用带GFP_KERNEL标志的kmalloc函数,应该使用GFP_ATOMIC标志
4)共享中断
4.1)调用irq_request时,要将dev_id传入,以使得自己的设备action能被free.
4.2)因为同一根中断线上的所有action->handler(也就是ISR)都会被调用(见handle_irq_event_percpu函数),所以要在自己的ISR中读取寄存器的状态判断是否真的是自己的设备发生了中断, 如果不是则要返回IRQ_NONE, 否则返回其他值,如IRQ_HANDLED.
-------------中断和软中断被调用的流程----------------------
/* Interrupt dispatcher */ //entry-armv.S定义了中断向量表
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) //irq_usr
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32) //irq_svc
->__irq_usr:
irq_handler //中断处理入口
b ret_to_user_from_irq
ENDPROC(__irq_usr)
->.macro irq_handler
arch_irq_handler_default //这是默认的irq hanlder, 一般在liunx中会定义MULTI_IRQ_HANDLER配置项,它意味着允许平台的代码可以动态设置irq处理程序:平台代码可以修改全局变量:handle_arch_irq,从而可以修改irq的处理程序。
.endm
-> .macro arch_irq_handler_default
bne asm_do_IRQ //asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
.endm
->handle_IRQ(irq) //处于中断上下文
-> irq_enter()
-> generic_handle_irq(irq) //处理硬中断irq
->desc->handle_irq(irq, desc) //==handle_level_irq()或handle_edge_irq()
<- __irq_set_handler_locked()?
==handle_level_irq()
->handle_irq_event()
->handle_irq_event_percpu()
{
do {
res = action->handler(irq, action->dev_id); //irq中断处理函数
switch (res) {
case IRQ_WAKE_THREAD:
irq_wake_thread(desc, action); //唤醒irq thread(置为TASK_WAKING状态)
}
irqaction* action = action->next; //一个共享中断有多个irqaction(如多个设备共享一个中断号)
} while (action);
}
->irq_exit() //由此可以看出,软中断是中断下半部分的一种处理机制。
->invoke_softirq()
->__do_softirq()
{
softirq_action *h =softirq_vec;
//开始遍历HRTIMER_SOFTIRQ、TIMER_SOFTIRQ、TASKLET_SOFTIRQ等类型的软中断
do {
h->action(h) //==tasklet_action()(如果当前遍历的是TASKLET_SOFTIRQ类型的软中断的话; 也有可能是run_timer_softirq())
->(tasklet_struct*)t->func(t->data) //遍历tasklet_vec tasklet由DECLARE_TASKLET定义的;此时tasklet运行于中断上下文, 因此tasklet中不能睡眠。
h++;
pending >>= 1;
} while (pending);
pending = local_softirq_pending();
if (pending)
wakeup_softirqd();
}
irq_set_irq_type() ?
-> __irq_set_trigger()
-> ret = chip->irq_set_type(&desc->irq_data, flags);
------------------------------tasklet-----------------
tasklet_schedule() //一般是在中断处理函数中调用,也就是说在中断上下文中raise这宗类型的softirq,等待在下半段irq_exit()中来执行
->raise_softirq_irqoff(TASKLET_SOFTIRQ) //在禁止中断的情况下触发tasklet类型的软中断.由open_softirq(TASKLET_SOFTIRQ,tasklet_action) 来注册的
-> wakeup_softirqd()
-> wake_up_process(ksoftirqd) // 唤醒ksoftirqd内核线程处理软中断(即中断的下半部分处理),每个CPU有这样一个内核线程
从tasklet_action()可以看出,tasklet有以下几个特征:
1)同一个tasklet只能同时在一个cpu上执行,但不同的tasklet可以同时在不同的cpu上执行;所以tasklet不必是可重入的
2)一旦tasklet_schedule被调用,内核会保证tasklet一定会在某个cpu上执行一次;
3)如果tasklet_schedule被调用时,tasklet不是出于正在执行状态,则它只会执行一次;
4)如果tasklet_schedule被调用时,tasklet已经正在执行,则它会在稍后被调度再次被执行;
5)两个tasklet之间如果有资源冲突,应该要用自旋锁进行同步保护
run_ksoftirqd() //ksoftirqd 内核线程的入口函数
->__do_softirq() //在禁止中断的情况下调用,此时tasklet运行于内核线程中
----------------------CPU中断的开和关----------------------------------
#define local_irq_save(flags)
->raw_local_irq_save(flags);
->flags = arch_local_irq_save();
unsigned long arch_local_irq_save(void)//CPU interrupt mask handling.
{
unsigned long flags;
asm volatile(
"mrs %0, cpsr @ arch_local_irq_save\n" //将CPSR状态寄存器读取,保存到flags中
"cpsid i" //关中断
: "=r" (flags) : : "memory", "cc");
return flags;
}
另外,
#define local_irq_restore(flags)
->raw_local_irq_restore
->arch_local_irq_restore
->arch_local_irq_restore
arch_local_irq_restore(unsigned long flags) //restore saved IRQ&FIQ state
{
asm volatile(
"msr cpsr_c, %0 @ local_irq_restore" //将flags写会到CPSR
:
: "r" (flags)
: "memory", "cc");
}
--------------------------irq thread(中断线程化)----------------------
1) irq thread是TASK_INTERRUPTIBLE的
2) 入口函数是:irq_thread()
-----------workqueue-------------------
主要文件:workqueue.c, workqueue.h
主要数据结构:
structwork_struct{
atomic_long_t data;
struct list_head entry;
work_func_tfunc;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
/*
* Global per-cpu workqueue. There's one and only one for each cpu
* and all works are queued and processed here regardless of their
* target workqueues.
*/
structglobal_cwq
{
struct list_head worklist; /* L: list of pending works */
}
/*
* The poor guys doing the actual heavy lifting. All on-duty workers
* are either serving the manager role, on idle list or on busy hash.
*/
structworker
{
struct work_struct *current_work; /* L: work being processed */
struct task_struct *task; /* I: worker task */
}
/*
* The per-CPU workqueue. The lower WORK_STRUCT_FLAG_BITS of
* work_struct->data are used for flags and thus cwqs need to be
* aligned at two's power of the number of flag bits.
*/
structcpu_workqueue_struct
{
struct global_cwq *gcwq; /* I: the associated gcwq */
struct workqueue_struct *wq; /* I: the owning workqueue */
}
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
structworkqueue_struct
{
union {
struct cpu_workqueue_struct __percpu *pcpu;
struct cpu_workqueue_struct *single;
unsigned long v;
}cpu_wq;
char name[]; /* I: workqueue name */
}
主要接口:
//用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程
#definecreate_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
//用于创建workqueue,只创建一个内核线程
#define create_singlethread_workqueue(name) \
alloc_workqueue((name), WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
#define alloc_workqueue(fmt, flags, max_active, args...) \
__alloc_workqueue_key((fmt), (flags), (max_active), \
NULL, NULL, ##args)
//调度执行一个具体的任务,执行的任务将会被挂入Linux系统提供的workqueue: system_wq
int schedule_work(struct work_struct *work)
{
returnqueue_work(system_wq, work); //调度执行一个指定workqueue中的任务
}
//延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
{
returnqueue_delayed_work(system_wq, dwork, delay);
}
static LIST_HEAD(workqueues);
extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_nrt_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_nrt_freezable_wq;
当用户调用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue对workqueue队列进行初始化时,内核就开始为用户分配一个workqueue对象,并且将其链到一个全局的workqueue队列中。然后Linux根据当前CPU的情况,为workqueue对象分配与CPU个数相同的cpu_workqueue_struct对象,每个cpu_workqueue_struct对象都会存在一条任务队列。紧接着,Linux为每个cpu_workqueue_struct对象分配一个内核thread,即内核daemon去处理每个队列中的任务。至此,用户调用初始化接口将workqueue初始化完毕,返回workqueue的指针。
在初始化workqueue过程中,内核需要初始化内核线程,注册的内核线程工作比较简单,就是不断的扫描对应cpu_workqueue_struct中的任务队列,从中获取一个有效任务,然后执行该任务。所以如果任务队列为空,那么内核daemon就在cpu_workqueue_struct中的等待队列上睡眠,直到有人唤醒daemon去处理任务队列。