【Linux kernel】中断

-------------------------中断上下文注意事项------------------------
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去处理任务队列。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值