• Linux中断底半部机制总结


    linux实现底半部的机制主要有tasklet、workqueue、softirq和线程化irq。

    1.tasklet

    tasklet的使用较为简单,它的执行上下文是软中断,所以在tasklet中不能睡眠,它的执行时机通常是中断顶半部返回的时候。我们只需要定义tasklet及其处理函数,并将两者关联起来即可,例如:

    1 void my_tasklet_func(unsigned long); /* 定义一个处理函数 */
    2 DECLARE_TASKLET(my_tasklet, my_tasklet_func, data); /* 定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联 */

    代码 DECLEARE_TASKLET(my_tasklet, my_tasklet_func, data)实现了定义名称为my_tasklet的tasklet,并将其与my_tasklet_func()这个函数绑定,而传入这个函数的参数为data。在需要调度tasklet的时候引用一个tasklet_schedule()函数就能使系统在适当的时候进行调度运行:

    tasklet_schedule(&my_tasklet);

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

    struct xxx_struct {
      int xxx_irq;
      ...
      ... 
    };
    
    static unsigned long data;
    
    /* 定义 tasklet 和底半部函数并将它们关联 */
    DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, (unsigned long)&data); 
    /*  中断处理底半部 */
    void xxx_do_tasklet(unsigned long data)
    {
       struct xxx_struct *pdata = (void *)*(unsigned long *)data;
       ...  
    }
     
    /* 中断处理顶半部 */
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
      ...
      data = (unsigned long)dev_id;
      tasklet_schedule(&xxx_tasklet);
      ...  
    }
     
    /* 设备驱动模块探测函数 */
    static int xxx_probe(struct platform_device *pdev)
    {
      ...
      struct xxx_struct *pdata = devm_kzalloc(&pdev->dev, sizeof(struct xxx_struct), GFP_KERNEL);
      if (!pdata) {
        dev_err(&pdev->dev, "Out of memory\n");
        return -ENOMEM; 
      } 
      
      platform_set_drvdata(pdev, pdata);
      /* 申请中断 */
      result = request_irq(pdata->xxx_irq, xxx_interrupt, 0, "xxx", pdata);
      ...
      return IRQ_HANDLED; } /* 设备驱动模块remove函数 */ static int xxx_remove(struct platforn_device *pdev) {   struct xxx_struct *pdata = platform_get_drvdata(pdev);   ...   /* 释放中断 */   free_irq(pdata->xxx_irq, NULL);   ... }

    上述程序在模块加载函数中申请中断,并在模块卸载函数中释放它。对应于xxx_irq的中断处理程序被设置为xxx_interrupt()函数,在这个函数中,tasklet_schedule(&xxx_tasklet)调度被定义的tasklet函数xxx_do_tasklet()在适当的时候执行。

    2.工作队列

    工作队列的使用方法和tasklet非常相似,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。下面的代码用于定义一个工作队列和一个底半部执行函数:

    struct work_struct my_wq;    /* 定义一个工作队列 */
    void my_wq_func(struct work_struct *work); /* 定义一个处理函数 */

    通过INIT_WORK()可以初始化这个工作队列并将工作队列与处理函数绑定:

    INIT_WORK(&my_wq, my_wq_func); /* 初始化工作队列并将其与处理函数绑定 */

    与tasklet_schedule()对应的用于调度工作队列执行的函数为 schedule_work(),如:

    schedule_work(&my_wq); /* 调度工作队列执行 */

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

    struct xxx_struct {
      int xxx_irq;
      struct work_struct xxx_wq; /* 定义工作队列 */
      ...
      ...
    };
    
    /* 中断处理底半部 */
    void xxx_do_work(struct work_struct *work)
    {
      struct xxx_struct *pdata = container_of(work, struct xxx_struct, xxx_wq);
      ...
    }
    
    /* 中断处理顶半部 */
    irqreturn_t xxx_interrupt(int irq, void *dev_id)
    {
      struct xxx_struct *pdata = (struct xxx_struct *)dev_id;
      ...
      schedule_work(&pdata->xxx_wq);
      ...
      return IRQ_HANDLED;
    }
    
    /* 设备驱动模块探测函数 */
    int xxx_probe(struct platform_device *pdev)
    {
      ...
      struct xxx_struct *pdata = devm_kzalloc(&pdev->dev, sizeof(struct xxx_struct ), GFP_KERNEL);
    
      /*申请中断*/
      result = request_irq(pdata->xxx_irq, xxx_interrupt, 0, "xxx", pdata);
      ...
      /* 初始化工作队列 */
      INIT_WORK(&pdata->xxx_wq, xxx_do_work);
      platform_set_drvdata(pdev, pdata);
      ...
    }
    
    /* 设备驱动模块remove函数 */
    int xxx_remove(struct platform_device *pdev)
    {
      struct xxx_struct *pdata = platform_get_drvdata(pdev);
      ...
      cancel_work_sync(&pdata->xxx_wq);   
    /* 释放中断 */   free_irq(pdata->xxx_irq, NULL);   ... }

    工作队列早期的实现是在每个CPU核上创建一个worker内核线程,所有在这个核上调度的工作都在该worker线程中执行,其并发性显然差强人意。在linux 2.6.36以后,转而实现了 “Concurrency-managed workqueues”,简称"cmwq",cmwq会自动维护工作队列的线程池以提高并发性,同时保持了API的向后兼容。

    延时工作队列

      1. 数据结构delayed_work用于处理延迟执行

    struct delayed_work {
        struct work_struct work;
        struct timer_list timer;
    
        /* target workqueue and CPU ->timer uses to queue ->work */
        struct workqueue_struct *wq;
        int cpu;
    };

      2.在工作队列中被调用的函数原形如下

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

      3. 初始化数据结构

    INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func)

      4. 提交延时任务到工作队列

    int schedule_delayed_work(struct delayed_work *work, unsigned long delay);

      5.删除提交到工作队列的任务

    int cancel_delayed_work(strcut delayed_work *work);

      6. 刷新默认工作队列(常跟cancle_delayed_work一起使用)

    void flush_schedlue_work(void);
    或者
    int cancel_delayed_work_sync(strcut delayed_work *work);

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

    struct xxx_struct {  
      int xxx_irq;   struct delayed_work xxx_dwork; /* 定义延时工作队列 */   ...   ... }; /* 中断处理底半部 */ void xxx_do_work(struct work_struct *work) {   struct xxx_struct *pdata = container_of(work, struct xxx_struct, xxx_dwork.work);   ... } /* 中断处理顶半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id) {   struct xxx_struct *pdata = (struct xxx_struct *)dev_id;   ...   
      /*   * delay 2000ms   */   schedule_delayed_work(&pdata->xxx_dwork, msesc_to_jiffies(2000));   ...
      
    return IRQ_HANDLED; } /* 设备驱动模块探测函数 */ int xxx_probe(struct platform_device *pdev) {   ...   struct xxx_struct *pdata = devm_kzalloc(&pdev->dev, sizeof(struct xxx_struct ), GFP_KERNEL);   /*申请中断*/   result = request_irq(pdata->xxx_irq, xxx_interrupt, 0, "xxx", pdata);   ...   /* 初始化工作队列 */   INIT_DELAYED_WORK(&pdata->xxx_dwork, xxx_do_work);   platform_set_drvdata(pdev, pdata);   ... } /* 设备驱动模块remove函数 */ int xxx_remove(struct platform_device *pdev) {   struct xxx_struct *pdata = platform_get_drvdata(pdev);   ...
      cancel_delayed_work(&pdata->xxx_dwork);
      cancel_delayed_work_sync(&pdata->xxx_dwork);
      
    /* 释放中断 */   free_irq(pdata->xxx_irq, NULL);   ... }

    3.软中断

    软中断(Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,tasklet是基于软中断实现的,因此也运行于软中断上下文。

    在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。

    软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,在软中断和tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。

    local_bh_disable() 和 local_bh_enable() 是内核中用于禁止和使能软中断及tasklet底半部机制的函数。

    内核中采用softirq的地方包括 HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ等,一般来说,驱动的编写者不会也不宜直接使用softirq。

    硬中断、软中断和信号的区别:

      硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核(或其他进程)对某个进程的中断。在设计系统调用的场合,人们也常说通过软中断(例ARM为swi)陷入内核,此时软中断的概念是指由软件指令引发的中断,和我们这个地方所说的softirq是两个完全不同的概念,一个是software,一个是soft。

    需要特别说明的是,软中断以及基于软中断的tasklet如果在某段时间内大量出现的话,内核会把后续软中断放入 ksoftirqd 内核线程中执行。总的来说,中断优先级高于软中断,软中断优先级又高于任何一个线程。软中断适度线程化,可以缓解高负载情况下系统的响应。

    4.threaded_irq

    在内核中除了可以通过request_irq()、devm_request_irq()申请中断以外,还可以通过request_threaded_irq() 和 devm_request_threaded_irq() 申请。这两个函数的原型为:

    /**
     *    request_threaded_irq - allocate an interrupt line
     *    @irq: Interrupt line to allocate
     *    @handler: Function to be called when the IRQ occurs.
     *          Primary handler for threaded interrupts.
     *          If handler is NULL and thread_fn != NULL
     *          the default primary handler is installed.
     *    @thread_fn: Function called from the irq handler thread
     *            If NULL, no irq thread is created
     *    @irqflags: Interrupt type flags
     *    @devname: An ascii name for the claiming device
     *    @dev_id: A cookie passed back to the handler function
     *
     *    This call allocates interrupt resources and enables the
     *    interrupt line and IRQ handling. From the point this
     *    call is made your handler function may be invoked. Since
     *    your handler function must clear any interrupt the board
     *    raises, you must take care both to initialise your hardware
     *    and to set up the interrupt handler in the right order.
     *
     *    If you want to set up a threaded irq handler for your device
     *    then you need to supply @handler and @thread_fn. @handler is
     *    still called in hard interrupt context and has to check
     *    whether the interrupt originates from the device. If yes it
     *    needs to disable the interrupt on the device and return
     *    IRQ_WAKE_THREAD which will wake up the handler thread and run
     *    @thread_fn. This split handler design is necessary to support
     *    shared interrupts.
     *
     *    Dev_id must be globally unique. Normally the address of the
     *    device data structure is used as the cookie. Since the handler
     *    receives this value it makes sense to use it.
     *
     *    If your interrupt is shared you must pass a non NULL dev_id
     *    as this is required when freeing the interrupt.
     *
     *    Flags:
     *
     *    IRQF_SHARED        Interrupt is shared
     *    IRQF_TRIGGER_*        Specify active edge(s) or level
     *    IRQF_ONESHOT        Run thread_fn with interrupt line masked
     */
    int request_threaded_irq(unsigned int irq, irq_handler_t handler,
                 irq_handler_t thread_fn, unsigned long irqflags,
                 const char *devname, void *dev_id);
    
    /**
     *    devm_request_threaded_irq - allocate an interrupt line for a managed device
     *    @dev: device to request interrupt for
     *    @irq: Interrupt line to allocate
     *    @handler: Function to be called when the IRQ occurs
     *    @thread_fn: function to be called in a threaded interrupt context. NULL
     *            for devices which handle everything in @handler
     *    @irqflags: Interrupt type flags
     *    @devname: An ascii name for the claiming device, dev_name(dev) if NULL
     *    @dev_id: A cookie passed back to the handler function
     *
     *    Except for the extra @dev argument, this function takes the
     *    same arguments and performs the same function as
     *    request_threaded_irq().  IRQs requested with this function will be
     *    automatically freed on driver detach.
     *
     *    If an IRQ allocated with this function needs to be freed
     *    separately, devm_free_irq() must be used.
     */
    int devm_request_threaded_irq(struct device *dev, unsigned int irq,
                      irq_handler_t handler, irq_handler_t thread_fn,
                      unsigned long irqflags, const char *devname,
                      void *dev_id);

    由此可见,它们比request_irq()、devm_request_irq()多了一个参数 thread_fn。用这两个API申请中断的时候,内核会为相应的中断号分配一个对应的内核线程。注意这个线程只针对这个中断号,如果其他中断也通过request_threaded_irq()申请,自然会得到新的内核线程。

    参数handler对应的函数执行于中断上下文,thread_fn参数对应的函数则执行于内核线程。如果handler结束的时候,返回值是 IRQ_WAKE_THREAD,内核会调度对应线程执行 thread_fn 对应的函数。

    request_threaded_irq() 和 devm_request_threaded_irq() 支持在 irqflags 中设置 IRQF_ONESHOT标记,这样内核会自动帮助我们在中断上下文中屏蔽对应的中断号,而在内核调度 thread_fn 执行后,重新使能该中断号。对于我们无法在上半部清除中断的情况, IRQ_ONESHOT 特别有用,避免了中断服务程序一退出,中断就洪泛的情况。

    handler 参数可以设置为NULL,这种情况下,内核会用默认的 irq_default_primary_handler() 代替 handler,并会使用 IRQ_ONESHOT标记。 irq_default_primary_handler() 定义为:

    /*
     * Default primary interrupt handler for threaded interrupts. Is
     * assigned as primary handler when request_threaded_irq is called
     * with handler == NULL. Useful for oneshot interrupts.
     */
    static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
    {
        return IRQ_WAKE_THREAD;
    }
  • 相关阅读:
    flash 自定义右键功能
    本地和VMware虚拟主机之间的网络访问
    java: org.luaj.vm2.LuaError:XXX module not found lua脚本初始化出错
    火狐 提示“此连接是不受信任的” 可能是因为开启了其它抓包代理软件导致的
    批量导入数据库
    引用Interop.SQLDMO.dll后的注意事项。
    c# Invoke和BeginInvoke 区别
    闭包的7种形式
    C#使用ICSharpCode.SharpZipLib压缩后进行web批量下载文件
    C# Socket编程笔记
  • 原文地址:https://www.cnblogs.com/wanglouxiaozi/p/16028892.html
Copyright © 2020-2023  润新知