【linux kernel】 中断处理-中断下半部

欢迎转载,转载时需保留作者信息,谢谢。

邮箱:[email protected]

博客园地址:http://www.cnblogs.com/embedded-tzp

Csdn博客地址:http://blog.csdn.net/xiayulewa

 

 

1.   概述

Linux内核中断机制:为了在中断执行时间尽可能短和中断处理需要完成大量工作之间找到一个平衡点,Linux将中断处理程序分解为两个半部,顶半部和底半部。

      顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位就进行“登记工作”,将底半部处理程序挂到该设备的底半部执行队列中去。

      那上半部和下半部是分界线是什么? 以request_irq注册的中断函数为分界线。

      上半部:

        当执行完request_irq注册的中断函数后,上半部结束。在注册的中断函数中,登记下半部要做的工作。

       上半部已经讨论了:http://www.cnblogs.com/embedded-tzp/p/4451354.html

      底半部实现方式有:

            软中断

            tasklet

            工作队列

     

2.   软中断

http://www.cnblogs.com/embedded-tzp/p/4452041.html中已讨论。

3.   Tasklet

3.1. 数据结构

struct tasklet_struct

{

    struct tasklet_struct *next;

    unsigned long state;

    atomic_t count;

    void (*func)(unsigned long);

    unsigned long data;

};

该结构体由tasklet_init初始化。

 

Softirq.c (src\kernel)中:

struct tasklet_head

{

    struct tasklet_struct *head;

    struct tasklet_struct **tail;

};

struct tasklet_head  tasklet_vec;

3.2. 注册

softirq_init中有:

open_softirq(TASKLET_SOFTIRQ, tasklet_action);

可见其实现方式为软件中断。其处理函数为tasklet_action

 

tasklet_schedule→__tasklet_schedule

    t->next = NULL;

    *__this_cpu_read(tasklet_vec.tail) = t;

    __this_cpu_write(tasklet_vec.tail, &(t->next));

将tasklet对象组织成一张链表。

 

3.3. 执行流程

       tasklet_schedule→__tasklet_schedule→raise_softirq_irqoff(TASKLET_SOFTIRQ);

    触发软中断执行,软中断执行流程见http://www.cnblogs.com/embedded-tzp/p/4452041.html

    最终软中断会执行到tasklet的处理函数tasklet_action

tasklet_action中是个while,循环处理tasklet_vec链表中的struct   tasklet_struct。

 

 

3.4. 其它:

* 不允许访问用户空间;?????
* 不允许访问current指针;
* 不能执行休眠或调度。

特征:

* 一个tasklet可被禁用或启用;只用启用的次数和禁用的次数相同时,tasklet才会被执行。
* 和定时器类似,tasklet可以注册自己;
* tasklet可被调度在一般优先级或者高优先级上执行,高优先级总会首先执行;
* 如果系统负荷不重,则tasklet会立即得到执行,且始终不会晚于下一个定时器滴答;
* 一个tasklet可以和其他tasklet并发,但对自身来讲必须严格串行处理,即一个tasklet不会在多个处理器上执行。

 

3.5. 函数集合

 

DECLARE_TASKLET(name,func,data); /*定义及初始化tasklet*/

DECLARE_TASKLET_DISABLED(name,func,data);      /*定义及初始化后禁止该tasklet*/

 

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); /* 初始化tasklet,func指向要执行的函数,data为传递给函数func的参数 */

 

void tasklet_disable(struct tasklet_struct *t) /*禁用指定tasklet, */

void tasklet_disable_nosync(struct tasklet_struct *t) /*禁用指定tasklet,但不会等待任何正在运行的tasklet退出*/

void tasklet_enable(struct tasklet_struct *t)      /*启用先前被禁用的tasklet*/

 

void tasklet_schedule(struct tasklet_struct *t)    /*注册并调度执行指定的tasklet*/

void tasklet_hi_schedule(struct tasklet_struct *t) /*调度指定的tasklet以高优先级执行*/

 

void tasklet_kill(struct tasklet_struct *t)   /*移除指定tasklet*/

 

3.6.    实例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相关的数据结构*/

struct btn_resource {

    int irq;  //中断号

    char *name; //中断名称

};

 

//初始化板卡按键信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

 

//tasklet的处理函数,在tasklet_action函数中被处理

static void btn_tasklet_func(unsigned long data)

{

    struct btn_resource *pdata = (struct btn_resource *)data;

    printk("%s: irq = %d, name = %s\n",

            __func__, pdata->irq, pdata->name);

}

 

//定义tasklet对象

static DECLARE_TASKLET(btn_tasklet,

                btn_tasklet_func, (unsigned long)&btn_info[0]);

 

//中断处理函数

//irq:中断号,dev_id:保存注册中断时传递的参数信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登记底半部信息

    tasklet_schedule(&btn_tasklet); //注册并调度tasklet,CPU会在空闲时执行tasklet的处理函数

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失败:IRQ_NONE

}

 

static int btn_init(void)

{

    //申请中断和注册中断处理程序

    /*

     * IRQ_EINT(0):中断号

     * button_isr:中断处理程序,中断发生以后,内核执行此函数

     * IRQF*:外部中断的触发方式,内部中断此参数为0

     * KEY_UP:中断名称,出现在 cat /proc/interrupts

     * &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

 

 

4.   工作队列

       工作队列涉及到linux内核进程调度策略,但调度策略不是此处应该讨论的。

4.1. 内核默认队列

4.1.1.   数据结构

struct work_struct

struct delayed_work

 

初始化队列对象

    INIT_WORK(&mywork, my_work_func);

    INIT_DELAYED_WORK(&mydwork, my_dwork_func);

 

4.1.2.   函数集合

schedule_work(&mywork)

schedule_delayed_work(&mydwork, 5*HZ)

 

4.1.3.   为什么是内核默认队列

Workqueue.c (src\kernel) 中:

    init_workqueues→

       system_wq = alloc_workqueue("events", 0, 0);

 

而 schedule_work→

    queue_work(system_wq, work);

 

可见struct work_struct对象是默认放在缺省的内核线程线程events中。当缺省工作队列负载太重,执行效率会很低,需要我们自建队列

 

4.1.4.   实例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相关的数据结构*/

struct btn_resource {

    int irq;  //中断号

    char *name; //中断名称

};

 

//初始化板卡按键信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

 

//分配工作和延时工作

static struct work_struct mywork;

 

//工作队列处理函数

static void my_work_func(struct work_struct *work)

{

    printk("%s\n", __func__);

}

 

//中断处理函数

//irq:中断号,dev_id:保存注册中断时传递的参数信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登记工作底半部信息

    /*

     * schedule_work:登记工作,将工作交给内核默认的工作队列和

     * 内核线程去管理和调度执行,cpu会在适当的时候会执行工作的处理函数

     * */

    schedule_work(&mywork);

 

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失败:IRQ_NONE

}

 

static int btn_init(void)

{

    //申请中断和注册中断处理程序

    /*

     * IRQ_EINT(0):中断号

     * button_isr:中断处理程序,中断发生以后,内核执行此函数

     * IRQF*:外部中断的触发方式,内部中断此参数为0

     * KEY_UP:中断名称,出现在 cat /proc/interrupts

     * &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

   

    //初始化工作和延时工作

    INIT_WORK(&mywork, my_work_func);

 

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

 

4.2. 自建队列

如上讨论,当使用work_struct,默认放在缺省的内核线程线程events中。当缺省工作队列负载太重,执行效率会很低,需要我们自建队列

4.2.1.   数据结构

struct workqueue_struct

 

4.2.2.   函数集合

wq = create_workqueue("tzp");

 

queue_work(wq, &mywork);

queue_delayed_work(wq, &mydwork, 3*HZ); // 3秒后执行

 

destroy_workqueue

 

 

4.2.3.   实例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相关的数据结构*/

struct btn_resource {

    int irq;  //中断号

    char *name; //中断名称

};

 

//初始化板卡按键信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

//定义工作队列的指针

static struct workqueue_struct *wq;

 

//分配延时工作

static struct delayed_work mydwork;

 

//延时工作的处理函数

static void my_dwork_func(struct work_struct *work)

{

    printk("%s\n", __func__);

}

 

 

//中断处理函数

//irq:中断号,dev_id:保存注册中断时传递的参数信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登记关联自己的工作队列和工作

    queue_delayed_work(wq, &mydwork, 3*HZ);

   

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失败:IRQ_NONE

}

 

static int btn_init(void)

{

    //申请中断和注册中断处理程序

    /*

     * IRQ_EINT(0):中断号

     * button_isr:中断处理程序,中断发生以后,内核执行此函数

     * IRQF*:外部中断的触发方式,内部中断此参数为0

     * KEY_UP:中断名称,出现在 cat /proc/interrupts

     * &mydata:给中断处理程序传递的参数信息,不传递参数指定为NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

   

    //初始化延时工作

    INIT_DELAYED_WORK(&mydwork, my_dwork_func);

 

    //创建自己的工作队列和内核线程

    wq = create_workqueue("tzp"); //线程名叫tzp

 

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    //销毁自己的工作队列和线程

    destroy_workqueue(wq);

   

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

 

 

 

时间: 2024-11-06 03:43:51

【linux kernel】 中断处理-中断下半部的相关文章

linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理

linux kernel的中断子系统之(四):High level irq event handler

一.前言 当外设触发一次中断后,一个大概的处理过程是: 1.具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler 2.machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number 3.调用该IRQ number对应的high level irq event handler,在这个high level的handler中

linux kernel的中断子系统之(七):GIC代码分析

一.前言 GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1-V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64服务器系统结构).目前在ARM官方网站只能下载到Version 2的GIC architecture specification,因此,本文主要描述符合V2规范的GIC硬件及其驱动. 具体GIC硬件的实现形态有两

Linux kernel的中断子系统之(一):综述

一.前言 一个合格的linux驱动工程师需要对kernel中的中断子系统有深刻的理解,只有这样,在写具体driver的时候才能: 1.正确的使用linux kernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口 2.正确使用同步机制保护驱动代码中的临界区 3.正确的使用kernel提供的softirq.tasklet.workqueue等机制来完成具体的中断处理 基于上面的原因,我希望能够通过一系列的文档来描述清楚linux kernel中

Linux kernel的中断子系统之(六):ARM中断处理过程

一.前言 本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作.具体整个处理过程分成三个步骤来描述: 1.第二章描述了中断处理的准备过程 2.第三章描述了当发生中的时候,ARM硬件的行为 3.第四章描述了ARM的中断进入过程 4.第五章描述了ARM的中断退出过程 本文涉及的代码来自3.14内核.另外,本文注意描述ARM指令集的内容,有些source code为了简短一些,删除了THUMB相关的代码,除此之外,有些debug相关的内容也会删除. 二.中断处理的准备过

linux kernel的中断子系统之(九):tasklet

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),属于不那么紧急需要处理的事情被推迟执行,我们称之deferable task,或者叫做bottom half,.具体如何推迟执行分成下面几种情况: 1.推迟到top half执行完毕 2.推迟到某个指定的时间片(例如40ms)之后执行 3.推迟到某个内核线程被调度的时候执行 对于第一种情况,内核中的机制包括softirq机制和tasklet机制.第二种情况是属于softirq机制的一种应用场

linux kernel的中断子系统之(八):softirq

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情.在执行bottom half的时候,是开中断的.有多种bottom half的机制,例如:softirq.tasklet.workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是

Linux kernel的中断子系统之(二):IRQ Domain介绍

一.概述 在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断: 1.IRQ number.CPU需要为每一个外设中断编号,我们称之IRQ Number.这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断. 2.HW interrupt ID.对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对

linux kernel的中断子系统之(三):IRQ number和中断描述符

一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理示意图 一个关于通用中断处理的示意图如下: 在linux kernel中,对于每一个外设的IRQ都用struct irq_desc来