上半部和下半部
作者:互联网
- 中断处理程序以异步方式执行,并且它有可能打断其他重要代码(甚至包括其他中断处理程序)的执行,因此中断处理程序应该越快越好。
- 如果当前有一个中断处理程序正在执行,在最好的情况下(如果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
私信
关注
标签:处理程序,struct,中断,work,半部,上半部,tasklet,softirq 来源: https://blog.csdn.net/lx123010/article/details/104109819