37 linux内核的中断底半部实现方法

linux内核里的中断处理:
在内核当发生中断时,会记录哪个中断产生并把中断相应的处理函数加入一个处理队列里, 直到把所有同一时间发生的中断全部记录好后,处理队列里的函数才会逐一得到调用。所以当一个中断处理函数处理过久时,就有可能影响它后面的中断处函数的执行时机。我们用的中断处理函数是可以被中断信号打断的。

中断处理函数需要注意:
1.不能执行时间长的代码. 注意printk也会耗时, 中断处理函数里不应再用printk
2.不能在中断处理函数里执行可能会引起堵塞的代码. 如kmalloc, vmalloc. 可用内存里的缓冲池:kmalloc(size, GFP_ATOMIC);
3.中断处理函数不是用户进程来触发调用的, 是由内核里的中断异常处理来调用。多个中断号可共用一个中断处理函数,一个中断号的中断可能会发生连续多次. 所以需要考虑中断处理函数的可重入性.
4. 因中断处理函数不是用户进程来调用的,所以中断处理函数的返回值在Linux内核里只是标明是否处理而已.
在祼板上,中断处理函数是没有返回值的。

为了提高中断的响应效率,在linux内核里中断处理可分成顶半部和底半部来处理
顶半部就是中断处理函数
顶半部作实时性要求高的工作, 并安排实时性要求不高的工作任务在底半部完成
底半部的被调用时机一般都是在所有的顶半部处理完成后才会得到调用。

也就是说把中断处理中实时性要求高的工作放在顶半部处理, 实时性要求不高的工作放在底半部来处理。因底半部是在所有的顶半部全部处理完后才会得到调用处理,所以这种处理方式会提供中断的响应速度.
如果中断处理是非常简单的工作,也可以不用分成顶半部和底半部的方式来处理.

linux内核里提供的宏,用于判断当前的环境是否在中断处理里:

#define in_irq()        (hardirq_count())   //如返回真表示在硬件中断处理函数里(顶半部)
#define in_softirq()        (softirq_count())   //判断是否在软件中断底半部函数里
#define in_interrupt()      (irq_count())       //判断是否在中断上下文内(interrupt context)(是否由中断异常处理来调用触发)

以上的宏是用于在代码里判断被调用的场合, 以便针对不同的场合作相应的处理.

/
中断底半部的实现方法
1. 软件中断, 不是指swi指令
是指用代码来实现中断向量表, 中断控制器等, 像硬件中断的功能.
软件中断使用一个内核线程(ksoftirqd)来轮询检查, 当软件中断发生的时则调用相应的
处理函数. ksoftirqd线程在硬件中断处理完成后得到调度.
使用软件中断的方式可以固定使用一个软件的中断号, 像内核里的网络协议栈有使用到一个固定的软件中断号.

  1. tasklet 是基于软件中断来实现的中断底半部处理方式.
    注意因它还是由内核里的中断异常处理在处理完硬件中断后触发处理的(处于中断上下文), 所以不能长时间运行, 不能睡眠
#include <linux/interrupt.h>
struct tasklet_struct
{
    ...
    void (*func)(unsigned long data); //底半部要运行的函数
    unsigned long data;
}; //一个对象表示一个底半部


用法:
   1) 创建对象并初始化
   struct tasklet_struct mytask
   mytask.func = 要底半部执行的函数
   mytask.data = 给底半部执行函数的参数
   或:   DECLARE_TASKLET(name, func, data); //声明一个对象并初始化


   2) 在中断的顶半部(中断处理函数)安排运行
    mytask.data = 999;
    tasklet_schedule(&mytask); //安排工作, 注意不是马上执行。是在所有的顶半部处理完成后才会得到调用
    或者tasklet_hi_schedule(struct tasklet_struct *t) //高优先级别的软件中断


测试代码:
test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/gpio.h>


struct tasklet_struct mytask;

// 红外接收头的数据脚接的是PL11
#define IR_IO  GPIOL(11)


//底半部的处理函数
void task_func(unsigned long data)
{
    printk("in task_func: %p, %p\n", in_irq(), in_interrupt());
}

irqreturn_t irq_func(int irqno, void *arg)
{
    tasklet_schedule(&mytask);//在顶半部里安排底半部工作
    printk("in irq_func: %p, %p\n", in_irq(), in_interrupt());
    return IRQ_HANDLED;
}

static int __init test_init(void)
{
    int ret;

    ret = request_irq(gpio_to_irq(IR_IO), irq_func, IRQF_TRIGGER_FALLING, "myir", NULL);
    if (ret < 0)
        goto err0;

    //初始化底半部对象
    mytask.func = task_func;    

    return 0;
err0:
    return ret;

}


static void __exit test_exit(void)
{
    free_irq(gpio_to_irq(IR_IO), NULL);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

///
3. workqueue 是使用创建一个内核线程来专门处理中断的底半部工作(不属于中断上下文), 在底半部里可以像用户进程一样睡眠.

#include <linux/workqueue.h>

typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
        work_func_t func;
    ...
}; //表示一个中断底半部的工作任务(也是工作队列中的一项工作), 此作务在用户态执行 

struct workqueue_struct *myqueue; //表示一个队列, 队列里可装多项工作.(会创建出一个专用的线程处理work_struct对象, work_struct对象就是在此线程里调度的工作任务)




用法:
   1). 创建work_struct对象,并初始化
    void mywork_func(struct work_struct *work); //单项工作的函数原型

    struct work_struct mywork; //创建单项工作对象
    INIT_WORK(&mywork, mywork_func); //初始化工作对象并指定处理函数

    myqueue = create_singlethread_workqueue("myqueue"); //创建一个工作队列,此工作队列由一个专属的内核线程调度加入此队列的底半部工作

   2).在中断顶半部(中断处理函数)安排运行
    queue_work(myqueue, &mywork);

   3). 退出时,清除底半部工作,内核线程退出
        cancel_work_sync(&mywork); 
        destroy_workqueue(myqueue);



----------
以上的代码功能在现版的内核已可以用下面的中断请求函数实现:
static inline int __must_check
request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn,
             unsigned long flags, const char *name, void *dev)
handler用于指定中断的顶半部处理函数地址, 如果为NULL而thread_fn不为NULL, 则系统会安排一个默认的处理函数. thread_fn用于指定在线程里执行的中断底半部处理函数地址.

如果handler和thread_fn都分别指定了函数地址,则handler指向的处理函数的返回值应为IRQ_WAKE_THREAD, 它可以在顶半部处理函数完成后唤醒底半部线程处理thread_fn指向的函数.
当handler为NULL而thread_fn不为NULL时, irqflags标志需要包含有IRQF_ONESHOT(不可嵌套执行底半部的中断处理函数).

/
测试代码:
test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/workqueue.h>



// 红外接收头的数据脚接的是PL11
#define IR_IO  GPIOL(11)


struct work_struct mywork;
struct workqueue_struct *myqueue;

void work_func(struct work_struct *wk)
{
    printk("in work_func: %p, %p\n", in_irq(), in_interrupt());
}


irqreturn_t irq_func(int irqno, void *arg)
{
    queue_work(myqueue, &mywork);
    printk("in irq_func: %p, %p\n", in_irq(), in_interrupt());
    return IRQ_HANDLED;
}

static int __init test_init(void)
{
    int ret;

    ret = request_irq(gpio_to_irq(IR_IO), irq_func, IRQF_TRIGGER_FALLING, "myir", NULL);
    if (ret < 0)
        goto err0;

    INIT_WORK(&mywork, work_func);
    myqueue = create_singlethread_workqueue("myqueue");

    return 0;
err0:
    return ret;

}


static void __exit test_exit(void)
{
    free_irq(gpio_to_irq(IR_IO), NULL);

    cancel_work_sync(&mywork);
    destroy_workqueue(myqueue);
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");

4 内核线程: 创建线程时可能需要休眠等待, 所以不能在中断处理函数里作此操作

#include <linux/kthread.h>
    struct task_struct *kthread_create(int (*threadfn)(void *data),
                   void *data,
                   const char namefmt[], ...);//在模块初始化函数里创建内核线程

    再运行wake_up_process(内核线程对象地址)

  或者kthread_run(threadfn, data, namefmt, ...);

   int kthread_stop(struct task_struct *k); // 请求线程退出, 堵塞到线程退出为止
   int kthread_should_stop(void);           // 判断当前线程是否要退出

    //在线程代码里...
    while (1)
    {   
        if (kthread_should_stop())
            break;
        ...
    }

5 使用struct timer_list也可作为中断的底半部

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值