欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第八章 下半部和推后执行的工作

程序员文章站 2022-03-30 10:42:34
...

上半部的局限性:

  • 中断处理程序以异步方式执行,并且它有可能打断其他重要代码(甚至包括其他中断处理程序)的执行,因此中断处理程序应该越快越好。
  • 如果当前有一个中断处理程序正在执行,在最好的情况下(如果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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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
相关标签: 内核