八、下半部和推后执行的工作-创新互联

中断处理程序的局限:八、下半部和推后执行的工作

1、中断处理程序以异步的方式执行,并且它有可能会打断其他重要代码的执行。

创新互联公司是一家集网站建设,湘西土家族企业网站建设,湘西土家族品牌网站建设,网站定制,湘西土家族网站建设报价,网络营销,网络优化,湘西土家族网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。

2、如果当前有一个中断处理程序正在执行,最好的情况是,与该中断同级的其他中断会被屏蔽,最坏情况下,当前处理器上所有其他中断都会被屏蔽。

3、中断处理程序往往需要对硬件进行操作,所以他们不能阻塞。这限制了他们所作的事情。

8.1 下半部

下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。中断处理程序注定要完成一部分工作:几乎都需要通过操作硬件对中断的到达进行确认,有时他还会从硬件拷贝数据。

8.1.2 下半部的环境

3、软中断和tasklet

软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行——及时两个类型相同也可以。

tasklet:两个不同类型的tasklet可以在不同的处理器上同时执行,但类型相同的tasklet不能同时执行。软中断必须在静态编译期间就进行注册。如此相反,tasklet可以通过代码动态注册。

内核定时器把操作推迟到某个确定的时间段之后执行。

8.2 软中断

8.2.1 软中断的实现

软中断是在编译期间静态分配的。它不像tasklet那样能被动态的注册或注销。软中断由softirq_action结构表示。

一个软中断不会抢占另外一个软中断。唯一可以抢占软中断的是中断处理程序。其他的软中断甚至是相同类型的软中断可以在其他处理器上同时执行。

在下列地方,待处理的软中断会被检查和执行:

1、从一个硬件中断代码处返回时

2、在ksoftirqd内核线程中

3、在那些显示检查和执行待处理的软中断代码中,如网络子系统中

不管用什么办法唤起,软中断都要在do_softirq()中执行。

8.2.2 使用软中断

软中断保留给系统中对时间要求最严格以及最重要的下半部使用。目前,只有两个子系统(网络和SCSI)直接使用软中断。此外,内核定时器和tasklet都是简历在软中断上的。软中断处理程序执行时,允许响应中断,但它自己不能休眠。在一个处理程序运行的时候,当前处理器上的软中断被禁止。但其他处理器仍可以执行别的软中断。如果同一个软中断在它被执行的同时再次触发了,那么另一个处理器可以同时运行这个处理程序。这意味着任何共享数据都需要严格保护。因此,大部分软中断处理冲虚,都通过采取单处理器数据或其他一些技巧来避免枷锁。

raise_softirq()函数可以讲一个软中断设置为挂起状态,让他在下次调用do_softirq()的时候投入运行。

在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后推出。内核在执行完中断处理程序后会马上执行do_softirq()函数。

8.3 tasklet

8.3.1

因为tasklet是通过软中断实现的,所以其本身也是软中断。tasklt有两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。HI_SOFTIRQ优先执行。

8.3.2 使用tasklet

1、声明tasklet

静态:DECLARE_TASKLET(name,func,data);tasklet处于激活状态

DELCARE_TASKLET_DISABLE(name,func,data);tasklet禁止状态

动态:struct tasklet_struct my_tasklet={null,0,ATOMIC_INIT(0),my_tasklet_handler,dev)

tasklet_init(t,tasklet_handler,dev);

2、编写tasklet处理程序

void tasklet_handler(unsigned long data)

因为是靠软中断实现,所以tasklet不能睡眠。不能使用阻塞式函数,由于tasklet运行时允许响应中断,所以必须做好预防工作。

3、调度tasklet

tasklet_schedule(&my_tasklet);

在tasklet被调度后,只要有机会就会尽可能的执行。在它还没有得到运行机会之前,如果有一个相同的tasklet又被调度了,那么它仍然只能运行一次。作为一种优化措施,一个tasklet总在调度它的处理器上运行——更好的利用处理器缓存。

tasklet_disable();

tasklet_disable_nosync();

tasklet_enable();

tasklet_kill();从挂起的队列中去掉一个tasklet。如果tasklet正在执行,该函数等待其执行完毕后,从队列中去掉tasklet。

4、ksoftirqd

内核不会立即处理重新触发的软中断。当大量软中断出现后,内核会唤醒一组内核线程来处理这些负载。这些线程在最低优先级上运行(nice 19),这能避免与其他重要的任务抢夺资源。但他们最终肯定会执行,所以这个方案能够保证在软中断负担很重的时候,用户程序不会因为得不到处理器时间而处于饥饿状态。相应的,也能保证“过量”的软中断终究会得到处理。在空闲系统上,这个方案表现良好,软中断处理的非常迅速(因为内核线程会马上调度)。

8.4 工作队列

工作队列可以把工作推后,交由一个内核线程去执行——这个下半部分总是会在进程上下文中执行。工作队列允许重新调度和睡眠。

8.4.1 工作队列的实现

工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由其他内核其他部分排到队列中的人物。它创建的这些内核线程称作工作者线程。工作队列子系统提供了一个缺省的工作者线程来处理这些工作。

8.4.2 使用工作队列

1、创建推后的工作

DECLARE_WORK(name,void(*func)(void*),void *data);

INIT_WORK(struct work_struct *work,void(*func)(void *),void *data);

2、工作队列处理函数

void work_handler(void *data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管操作处理函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常发生在系统调用时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

3、对工作进行调度

想要把给定的工作的处理函数提交给缺省的events工作者线程:

schedule_work(&work);

schedule_delayed_work(&work);

4、刷新操作

void flush_scheduled_work(void);

函数会一直等待,知道队列中所有对象都被执行以后才返回。在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所以只能在进程上下文使用它。

int cancel_delayed_wrok(struct work_struct *work);

5、创建新的工作队列

struct workqueue_struct *create_workqueue(const char *name);

int queue_work(struct workqueue_struct *wq,struct work_struct *work);

int queue_delayed_work(struct wrokqueue_struct *wq,struct work_struct *work,unsigned long delay);

flush_workqueue(struct workqueue_struct *wq);

8.5 下半部机制的选择

8.6 在下半部之间加锁

使用tasklet的好处在于,他自己负责执行的序列化保障:两个相同的tasklet不允许同时执行,即使在不同的处理器上也不行。

8.7 禁止下半部

一般的单纯禁止下半部是不够的,为了保证共享数据的安全,更常见的做法是,先得到一个锁然后再禁止下半部的处理。驱动程序中通常使用的都是这种方法。

local_bh_disable();函数通过preempt_count(内核抢占的时候使用的也是它)为每一个进程维护一个计数器。当计数器变为0时,下半部才能够被处理。


本文标题:八、下半部和推后执行的工作-创新互联
网页链接:http://scyanting.com/article/cehoci.html