上半部和下半部

  • 中断处理程序以异步方式执行,并且它有可能打断其他重要代码(甚至包括其他中断处理程序)的执行,因此中断处理程序应该越快越好。
  • 如果当前有一个中断处理程序正在执行,在最好的情况下(如果IRQF_DISABLED没有被设置),与该中断同级的其他中断被屏蔽,在最坏的情况下(设置了IRQF_DISABLED),当前处理器上所有其他中断都会被屏蔽。因为禁止中断后硬件与操作系统无法通信,因此,中断处理程序执行的越快越好。
  • 由于中断处理程序往往需要对硬件操作,它们通常有很高的时限要求。
  • 中断处理程序不能在进程上下文中进行,所以它们不能阻塞,这限制了它所做的事情。

上半部和下半部的工作划分

  • 如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
  • 如果一个任务和硬件相关,将其放在中断处理程序执行。
  • 如果一个任务要保证不被其它中断打断(特别是相同的中断),将其放在中断处理程序执行。
  • 其它所有任务,考虑放置在下半部执行。

软中断:

软中断是在编译器期间静态分配的,它不像 tasklet 那样能被动态地注册或注销。软中断由 softirq_action 结构表示,它定义在 linux/interrupt.h>中

struct softirq_action{
    void (*action)(struct softirq_action *);
}
  • 1
  • 2
  • 3

kernel/softirq.c 中定义了一个包含32个该结构体的数组

static struct softirq_action softirq_vec[NR_SOFTIRQS]
  • 1

每个被注册的软中断都占据该数组的一项,因此最多可能有32个软中断。在2.6版本的内核中,32项中只用到了9个。

上半部和下半部

使用软中断

1、分配索引

建立一个软中断必须在此枚举类型中加入新项,并注意优先级。

2、注册处理程序

在运行时通过 open_softirq() 注册软中断处理程序,该函数有两个参数:中断索引号和处理函数,如网络子系统,通过以下方式注册自己的软中断。

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  • 1
  • 2

注意:
软中断处理程序运行在中断上下文,允许中断相应,但它不能休眠。当一个处理程序运行的时候,当前处理器的软中断被禁止。但其它处理器仍可以执行别的软中断。实际上,如果同一个软中断在它被执行的同时再次触发了,那么另一个处理器可以同时运行其处理程序,这意味着任何的数据共享,都需要严格的锁保护。(tasklet更受青睐正是因此,tasklet处理程序正在执行时,在任何处理器都不会再执行)
一个软中断不会抢占另一个软中断。实际上,唯一可以抢占软中断的是中断处理程序。

3、触发软中断

raise_softirq()函数可以将一个软中断设置为挂起状态,让它在下次调用 do_softirq()函数时投入运行。例如:

raise_softirq[NET_TX_SOFTIRQ]
void raise_softirq(unsigned int nr)
{
    unsigned long flags;

    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

该函数在触发软中断之前会禁用中断,触发后,再回复原来的状态,如果中断本身已经被禁止了,可以直接调用

raise_softirq_irqoff(NET_TX_SOFTIRQ);
  • 1

在中断处理程序中触发软中断是最常见的形式,内核在执行完中断处理程序后,马上就会调用 do_softirq() 函数。

tasklet

因为 tasklet 是通过软中断实现的,因此它本质也是软中断。与软中断最大的区别是,如果是多处理器系统,tasklet在运行前会检查这个tasklet是否正在其它处理器上运行,如果是则不运行。

静态创建tasklet

DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);
  • 1
  • 2

动态创建tasklet

tasklet_init(t, tasklet_handler, dev);
  • 1

调度tasklet

tasklet_schedule(&my_tasklet);
  • 1

在 tasklet 被调度后,只要有机会它就会尽早的执行,如果它还没运行,又被调度了一次,那么它仍然只会运行一次。如果它正在执行,在另一个处理器上 tasklet 又被调度了一次,那么新的 tasklet 会被重新调度再运行一次(不是同时运行,再次调度,下次有机会运行时才运行)。

禁止指定的 tasklet

tasklet_disable(&my_tasklet);       //禁止tasklet等待处理程序执行完毕再返回
tasklet_disable_nosync(&my_tasklet);//禁止tasklet立即返回
tasklet_enable(&my_tasklet);        //使能该tasklet
tasklet_kill(&my_tasklet);          //从挂起的队列中去掉该tasklet,同样会等待执行完毕再返回。在处理经常重新调度它自身的tasklet的时候,该函数很有用
  • 1
  • 2
  • 3
  • 4

工作队列

工作队列会把工作推后,交由一个内核线程去执行——这个下半部总是在进程上下文中执行。

1、创建工作队列

静态创建
DECLARE_WORK(name, void (*func)(void *), void *data);
  • 1

这样就会静态的创建一个名为name, 处理函数为func,参数为data 的 work_struct

动态创建
INIT_WORK(struct work_struct *work, void (*func)(void *), void *data);
  • 1

2、工作队列处理函数

void work_handler(void *data);
  • 1

这个函数会有一个工作者线程执行,因此,函数会运行在进程上下文中。正常情况下相应中断,并且不持有任何锁。

3、调度工作队列

schedule_work(&work);    //立刻调度,等待执行
schedule_delay_work(&work, delay);    //delay 个 tick 之后,再调度
  • 1
  • 2

4、刷新操作

void flush_scheduled_work(void);
  • 1

等待系统默认工作者线程中所有对象都执行完毕才返回,该函数会休眠。

5、创建新的工作队列

创建一个新的工作者线程

struct workqueue_struct *create_workqueue(const char *name);
  • 1

比如缺省的工作者线程调用的是:

struct workqueue_struct *keventd_wq;
kevent_wq = create_workqueue("events");
  • 1
  • 2

这样就会创建所有的工作者线程,每个处理器一个。
创建工作时,无需考虑工作队列的类型,在调度时使用下列的函数,指定特定的工作队列。

ini queue_work(struct workqueue_struct *wq, struct work_struct * work);
int queue_delay_work(struct workqueue_struct *wq, struct work_struct * work, unsigned long delay);
  • 1
  • 2

刷新

flush_workqueue(struct workqueue_struct *wq);
  • 1

禁止下半部

void local_bh_disable();//禁止本处理器的软中断和tasklet
void local_bh_enable(); //使能本处理器的软中断和tasklet                            
                        

      

     
                 
                    
上半部和下半部上半部和下半部 太白醉客 发布了36 篇原创文章 · 获赞 6 · 访问量 2671 私信 关注
上一篇:底半部之工作队列和tasklet,内核定时器。


下一篇:Linux设备驱动程序 之 tasklet