Chinaunix首页 | 论坛 | 博客
  • 博客访问: 348279
  • 博文数量: 106
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 861
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-10 08:32
文章分类

全部博文(106)

文章存档

2016年(11)

2015年(93)

2013年(2)

分类: LINUX

2015-08-18 16:56:32

一、中断的主要数据结构及其相关函数
1.    中断机制的核心数据结构是 irq_desc, 它完整地描述了一条中断线 (或称为 “中断通道” )。
struct irq_desc
{
    unsigned int irq;                                        //中断号
    struct timer_rand_state *timer_rand_state;    //pointer to timer rand state struct
    unsigned int            *kstat_irqs;            //每个中断的统计
    irq_flow_handler_t handle_irq;            //highlevel irq-events handler [if NULL, __do_IRQ()]
    struct irq_chip *chip;                        //low level interrupt hardware access
    struct msi_desc *msi_desc;                //MSI描述符号
    void *handler_data;                            //chip 方法使用的数据
    void *chip_data;                            //chip 私有数据
    struct irqaction *action;             /* IRQ action list */
    unsigned int status;                  /* IRQ status */
    unsigned int depth;                  /* nested irq disables */disable-depth, 针对irq_disable()调用。/* 关中断次数 */
    unsigned int wake_depth;         /* nested wake enables *//* 唤醒次数 */
    unsigned int irq_count;             /* For detecting broken IRQs *//* 发生的中断次数 */
    unsigned long last_unhandled;     /* Aging timer for unhandled count *//*滞留时间 */
    unsigned int irqs_unhandled;        //统计假的未处理的中断
    spinlock_t lock;                        //对于smp的锁定    /*自选锁*/
    #ifdef CONFIG_SMP
    cpumask_var_t affinity;       //IRQ affinity on SMP
    unsigned int node;            //node index useful for balancing
    #ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t pending_mask;
    #endif
    #endif
    atomic_t threads_active;            //当前运行的irqaction线程的数目
    wait_queue_head_t  wait_for_threads;    //为了等待线程处理同步的等待队列
    #ifdef CONFIG_PROC_FS
    struct proc_dir_entry *dir;        // /proc/irq/ 进程文件系统入口
    #endif
    const char *name;            // flow handler name for /proc/interrupts output

2.    对中断控制器的抽象描述:
struct irq_chip
{
    const char *name;                                    /* /proc/interrupts的名字*/
    unsigned int (*startup)(unsigned int irq);    /*开启这个中断*/
    void (*shutdown)(unsigned int irq);            /*关闭这个中断*/
    void (*enable)(unsigned int irq);                /*使能这个中断*/
    void (*disable)(unsigned int irq);                /*失能这个中断*/
    void (*ack)(unsigned int irq);                    /*开始一个新的中断*/
    void (*mask)(unsigned int irq);                /*屏蔽一个中断源*/
    void (*mask_ack)(unsigned int irq);         /*确认并且屏蔽一个中断源*/
    void (*unmask)(unsigned int irq);            /*解除对一个中断源的屏蔽*/
    void (*eoi)(unsigned int irq);                    /*中断的最后,——chip  level*/
    void (*end)(unsigned int irq);                /*中断的最后,——flow  level*/
    int (*set_affinity)(unsigned int irq,const struct cpumask *dest);                /*在多处理器上设置cpu的affinity*/
    int (*retrigger)(unsigned int irq);                    /*再给cpu发送一个中断*/
    int (*set_type)(unsigned int irq, unsigned int flow_type);    /*设置一个中断的流类型 (IRQ_TYPE_LEVEL/etc.) */
    int (*set_wake)(unsigned int irq, unsigned int on);    /*使能/失能一个中断的电源唤醒*/
    void (*bus_lock)(unsigned int irq);                /*function to lock access to slow bus (i2c) chips*/
    void (*bus_sync_unlock)(unsigned int irq);    /*function to sync and unlock slow bus (i2c) chips*/
    /* Currently used only by UML, might disappear one day.*/
    #ifdef CONFIG_IRQ_RELEASE_METHOD
    void (*release)(unsigned int irq, void *dev_id);    /*被UML独自使用的释放功能*/
    #endif
    /*
    * For compatibility, ->typename is copied into ->name.
    * Will disappear.
    */
    const char *typename;                /*被name淘汰,迁移到helper保持*/
};
3.    Linux中断的申请与释放:在<linux/interrupt.h>, 
    3.1.    实现中断申请接口:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
        函数参数说明:

    3.1.1.    unsigned int irq:所要申请的硬件中断号

    3.1.2.    irq_handler_t handler:中断服务程序的入口地址,中断发生时,系统调用handler这个函数。irq_handler_t为自定义类型,其原型为:

typedef irqreturn_t (*irq_handler_t)(int, void *);

而irqreturn_t的原型为:typedef enum irqreturn irqreturn_t;

enum irqreturn {

    IRQ_NONE,/*此设备没有产生中断*/

    IRQ_HANDLED,/*中断被处理*/

    IRQ_WAKE_THREAD,/*唤醒中断*/

};

在枚举类型irqreturn定义在include/linux/irqreturn.h文件中。

    3.1.3.    unsigned long flags:中断处理的属性,与中断管理有关的位掩码选项,有以下几组值:

#define IRQF_DISABLED       0x00000020    /*中断禁止*/

#define IRQF_SAMPLE_RANDOM  0x00000040    /*供系统产生随机数使用*/

#define IRQF_SHARED      0x00000080 /*在设备之间可共享*/

#define IRQF_PROBE_SHARED   0x00000100/*探测共享中断*/

#define IRQF_TIMER       0x00000200/*专用于时钟中断*/

#define IRQF_PERCPU      0x00000400/*每CPU周期执行中断*/

#define IRQF_NOBALANCING 0x00000800/*复位中断*/

#define IRQF_IRQPOLL     0x00001000/*共享中断中根据注册时间判断*/

#define IRQF_ONESHOT     0x00002000/*硬件中断处理完后触发*/

#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/*触发式检测中断*/

     3.1.4.    const char *dev_name:设备描述,表示那一个设备在使用这个中断。

     3.1.5.    void *dev_id:用作共享中断线的指针。一般设置为这个设备的设备结构体或者NULL。它是一个独特的标识, 用在当释放中断线时以及可能还被驱动用来指向它自己的私有数据区,来标识哪个设备在中断 。这个参数在真正的驱动程序中一般是指向设备数据结构的指针.在调用中断处理程序的时候它就会传递给中断处理程序的void *dev_id。如果中断没有被共享, dev_id 可以设置为 NULL。

3.2.    释放IRQ

void free_irq(unsigned int irq, void *dev_id);

    3.2.1.    中断线共享的数据结构

   struct irqaction
        {

    irq_handler_t handler;         /* 具体的中断处理程序 */

    unsigned long flags;            /*中断处理属性*/

    const char *name;             /* 名称,会显示在/proc/interreupts 中 */

    void *dev_id;                     /* 设备ID,用于区分共享一条中断线的多个处理程序 */

    struct irqaction *next;         /* 指向下一个irq_action 结构 */

    int irq;                              /* 中断通道号 */

    struct proc_dir_entry *dir;     /* 指向proc/irq/NN/name 的入口*/

    irq_handler_t thread_fn;        /*线程中断处理函数*/

    struct task_struct *thread;    /*线程中断指针*/

    unsigned long thread_flags;    /*与线程有关的中断标记属性*/

};

thread_flags参见枚举型

enum {

    IRQTF_RUNTHREAD,        /*线程中断处理*/

    IRQTF_DIED,                    /*线程中断死亡*/

    IRQTF_WARNED,                /*警告信息*/

    IRQTF_AFFINITY,            /*调整线程中断的关系*/

};

多个中断处理程序可以共享同一条中断线,irqaction 结构中的 next 成员用来把共享同一条中断线的所有中断处理程序组成一个单向链表,dev_id 成员用于区分各个中断处理程序。
4.    根据以上内容可以得出中断机制各个数据结构之间的联系如下图所示:


 二.中断的处理过程

Linux中断分为两个半部:上半部(tophalf)和下半部(bottom half)。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。
1.    中断号的查看可以使用下面的命令:“cat /proc/interrupts”。
2.    
Linux用taskle实现中断的下半部工作队列。
    2.1.    
小任务tasklet的实现。其数据结构为struct tasklet_struct,每一个结构体代表一个独立的小任务,定义如下:

 struct tasklet_struct

{

    struct tasklet_struct *next;/*指向下一个链表结构*/

    unsigned long state;/*小任务状态*/

    atomic_t count;/*引用计数器*/

    void (*func)(unsigned long);/*小任务的处理函数*/

    unsigned long data;/*传递小任务函数的参数*/

};

state的取值参照下边的枚举型:

enum

{

    TASKLET_STATE_SCHED,    /* 小任务已被调用执行*/

    TASKLET_STATE_RUN   /*仅在多处理器上使用*/

};

count域是小任务的引用计数器。只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。
    2.2.    声明和使用小任务tasklet

静态的创建一个小任务的宏有以下两个:

#define DECLARE_TASKLET(name, func, data)  struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data)  struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

这两个宏的区别在于计数器设置的初始值不同,前者可以看出为0,后者为1。为0的表示激活状态,为1的表示禁止状态。其中ATOMIC_INIT宏为:#define ATOMIC_INIT(i)   { (i) }    即可看出就是设置的数字。此宏在include/asm-generic/atomic.h中定义。这样就创建了一个名为name的小任务,其处理函数为func。当该函数被调用的时候,data参数就被传递给它。
    2.3.    
小任务处理函数程序

    处理函数的的形式为:void my_tasklet_func(unsigned long data)。这样DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)实现了小任务名和处理函数的绑定,而data就是函数参数。
    2.4.    调度编写的tasklet
        2.4.1.    
调度小任务时引用tasklet_schedule(&my_tasklet)函数就能使系统在合适的时候进行调度。函数原型为:

static inline void tasklet_schedule(struct tasklet_struct *t)

{

    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))

       __tasklet_schedule(t);

}

这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_t handler)后,转去执行下半部的小任务。

如果希望使用DECLARE_TASKLET_DISABLED(name,function,data)创建小任务,那么在激活的时候也得调用相应的函数被使能

tasklet_enable(struct tasklet_struct *); //使能tasklet

tasklet_disble(struct tasklet_struct *); //禁用tasklet

tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long);

当然也可以调用tasklet_kill(struct tasklet_struct *)从挂起队列中删除一个小任务。清除指定tasklet的可调度位,即不允许调度该tasklet 。

2.4.2.    使用tasklet作为下半部的处理中断的设备驱动程序模板如下:

/*定义tasklet和下半部函数并关联*/

void my_do_tasklet(unsigned long);

DECLARE_TASKLET(my_tasklet, my_tasklet_func, 0);

/*中断处理下半部*/

void my_do_tasklet(unsigned long)

{

  ……/*编写自己的处理事件内容*/

}

/*中断处理上半部*/

irpreturn_t my_interrupt(unsigned int irq,void *dev_id)

{

 ……

 tasklet_schedule(&my_tasklet)/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/

 ……

}

/*设备驱动的加载函数*/

int __init xxx_init(void)

{

 ……

 /*申请中断, 转去执行my_interrupt函数并传入参数*/

 result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);

 ……

}

/*设备驱动模块的卸载函数*/

void __exit xxx_exit(void)

{

……

/*释放中断*/

free_irq(my_irq,my_interrupt);

……

}
3.    Linux用工作队列实现中断的下半部

工作队列work_struct结构体,位于/include/linux/workqueue.h

typedef void (*work_func_t)(struct work_struct *work);

struct work_struct
    {

      atomic_long_t data; /*传递给处理函数的参数*/

#define WORK_STRUCT_PENDING 0/*这个工作是否正在等待处理标志*/             

#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

};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。可以通过DECLARE_WORK在编译时静态地创建该结构,以完成推后的工作。

#define DECLARE_WORK(n, f)  struct work_struct n = __WORK_INITIALIZER(n, f)

而后边这个宏为以下内容:

#define __WORK_INITIALIZER(n, f)
     {                      

      .data = WORK_DATA_INIT(),                            \

      .entry      = { &(n).entry, &(n).entry },                    \

      .func = (f),                                        \

      __WORK_INIT_LOCKDEP_MAP(#n, &(n))                   \

}

其为参数data赋值的宏定义为:

#define WORK_DATA_INIT()       ATOMIC_LONG_INIT(0)

这样就会静态地创建一个名为n,待执行函数为f,参数为data的work_struct结构。同样,也可以在运行时通过指针创建一个工作:INIT_WORK(struct work_struct *work, void(*func) (void *));    这会动态地初始化一个由work指向的工作队列,并将其与处理函数绑定。宏原型为:

#define INIT_WORK(_work, _func)                                     

do
     {                                                        

    static struct lock_class_key __key;                

    (_work)->data = (atomic_long_t) WORK_DATA_INIT();  

    lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);

    INIT_LIST_HEAD(&(_work)->entry);                 

    PREPARE_WORK((_work), (_func));                         

 } while (0);

在需要调度的时候引用类似tasklet_schedule()函数的相应调度工作队列执行的函数schedule_work(),如:schedule_work(&work);/*调度工作队列执行*/    如果有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度指定的时间后执行函数:

schedule_delayed_work(&work,delay);  函数原型为:int schedule_delayed_work(struct delayed_work *work, unsigned long delay);其中是以delayed_work为结构体的指针,而这个结构体的定义是在work_struct结构体的基础上增加了一项timer_list结构体。

struct delayed_work
    {

    struct work_struct work;

    struct timer_list timer; /* 延迟的工作队列所用到的定时器,当不需要延迟时初始化为NULL*/

};

这样,便使预设的工作队列直到delay指定的时钟节拍用完以后才会执行。

使用工作队列处理中断下半部的设备驱动程序模板如下:

/*定义工作队列和下半部函数并关联*/

struct work_struct my_wq;

void my_do_work(unsigned long);

/*中断处理下半部*/

void my_do_work(unsigned long)

{

  ……/*编写自己的处理事件内容*/

}

/*中断处理上半部*/

irpreturn_t my_interrupt(unsigned int irq,void *dev_id)

{

 ……

 schedule_work(&my_wq)/*调度my_wq函数,根据工作队列初始化函数将去执行my_do_work函数*/

 ……

}

/*设备驱动的加载函数*/

int __init xxx_init(void)

{

 ……

 /*申请中断,转去执行my_interrupt函数并传入参数*/

 result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);

 ……

 /*初始化工作队列函数,并与自定义处理函数关联*/

 INIT_WORK(&my_irq,(void (*)(void *))my_do_work);

 ……

}

/*设备驱动模块的卸载函数*/

void __exit xxx_exit(void)

{

……

/*释放中断*/

free_irq(my_irq,my_interrupt);

……

}

  
阅读(434) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~