天天看点

中断下半部——工作队列、软件中断、tasklet前言一、workqueue二、软件中断三、tasklet四、总结

工作队列、软件中断、tasklet

  • 前言
  • 一、workqueue
    • 1、使用默认工作队列
    • 2、自己创建工作队列api
  • 二、软件中断
  • 三、tasklet
    • 1、编写tasklet处理函数
    • 2、初始化结构体tasklet_struct
    • 3、在中断返回前调度tasklet
    • 4、在模块卸载中,将tasklet_struct结构体移除
  • 四、总结

前言

中断上半部和下半部

上半部:中断服务函数

下半部:工作队列、软件中断、tasklet

上半部执行紧急的任务不能休眠、下半部为可延迟后执行的任务。

优先级:中断>tasklet>进程

一、workqueue

工作队列可以把工作推后,交由一个内核线程去执行。这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。

最重要的特点是工作队列允许重新调度甚至是睡眠。

内核中有默认的工作队列keventd_wq也叫共享队列 、还有自己创建的队列

1、使用默认工作队列

①、创建工作

//动态创建
INIT_WORK(struct work_struct *work,(void (*)(void *)) xxx_do_work);
INIT_DELAY_WORK(struct delay_work *de_work,(void (*)(void *)) xxx_do_work,delay);
//静态创建
DECLARE_WORK(struct work_struct *work,(void (*)(void *)) xxx_do_work);
DECLARE_DELAYED_WORK(struct delay_work *de_work,(void (*)(void *)) xxx_do_work,delay);

INIT_WORK(&xxx_wq,  xxx_do_work);
INIT_WORK(&xxx_de_wq,  xxx_do_work,msecs_to_jiffies(10));
           

②、下半部

void xxx_do_work(struct work_struct *work)
{
	struct xxx_struct *temp = container_of(ptr, type, member);
	.......
}
根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针
           

③、在中断返回前提交工作

schedule_work(&work);
//使得调用内核进程,并不是马上调用,要等待调度器去调用
//跟INIT_WORK 联用
schedule_delayed_work(&work,delay);
//&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行
//跟INIT_DELAY_WORK 联用
           

④、终止工作

int cancel_work(struct delayed_work *work);
//取消相应的ork。直接返回  
int cancel_work_sync(struct work_struct *work);
//取消相应的work。但是,如果这个work已经在运行,那么,
//cancel_work_sync会阻塞,直到work完成并取消相应的work。
int cancel_delayed_work(struct delayed_work *dwork) 
//返回非 0:内核会确保不会初始化给定入口项的执行,即终止该工作。
//返回0:则说明该工作已经在其他处理器上运行
int cancel_delayed_work_sync(struct delayed_work *dwork)    
//取消相应的dwork。但是,如果这个dwork已经在运行,那么,
//cancel_delayed_work_sync会阻塞,直到dwork完成并取消相应的dwork。
           

⑤、刷新工作

int flush_work(struct work_struct *work)
//立即刷新调度执行指定的work。并阻塞直到该work执行完毕后,返回退出。                                      
//如果指定的work在调用flush_work之前已经终止了,则flush_work直接返回错误。
void flush_delayed_work(struct delayed_work *dwork)
 //刷新调度执行指定的delaed_work(注意,不需要再等待delay时间到了,才执
 //行工作。而是马上刷新调度执行。)。并阻塞直到该delayed_work执行完毕
 //后,返回退出。  如果指定的delayed_work在调用flush_work之前已经终止
 //了,则flush_delayed_work直接返回。
           

2、自己创建工作队列api

通过自己创建工作队列,避免共享队列被一直占用

API:

struct workqueue_struct;
struct work_struct;

//创建工作队列  返回队列指针
struct workqueue_struct *create_workqueue(const char *name);
//摧毁工作队列
void destroy_workqueue(struct workqueue_struct *queue);

INIT_WORK(_work, _func);
INIT_DELAYED_WORK(_work, _func);
//在中断返回前提交工作  类似于schedule_work
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
//延时提交工作  类似于schedule_delayed_work
int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay);
//延时提交并指定那个cpu
int queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
            struct delayed_work *dwork, unsigned long delay);

int cancel_work_sync(struct work_struct *work);
int cancel_delayed_work_sync(struct delayed_work *dwork);

void flush_workqueue(struct workqueue_struct *wq);
           

demo:

struct mini2440_data{
		struct work_struct mini2440_work;
		struct delayed_work mini2440_delayed_work;
		int value;
};
struct mini2440_work_dev{	
	struct cdev cdev;
	struct mini2440_data pri_data[2];
};
struct workqueue_struct *mini2440_wq=NULL;
static struct mini2440_work_dev *p_mini2440_work_dev;


//工作处理
static void mini2440_work_handle(struct work_struct *work)
{
	struct mini2440_data *private_data = container_of(work,struct mini2440_data,mini2440_work);

    printk("mini2440_work_handle--current jiffies=%lu,value=%d\n",jiffies,private_data->value);
    msleep(3000);
     //若没有下面这条语句,则只运行一次mini2440_work_handle
    queue_work(mini2440_wq,&p_mini2440_work_dev->pri_data[1].mini2440_work);
    
}
irqreturn_t irq_jandler(int irq_no,void *dev_id)
{
	queue_work(mini2440_wq,&p_mini2440_work_dev->pri_data[1].mini2440_work);
	
	return IRQ_HANDLED;
}
static int __init xxxx_init(void)
{
	mini2440_wq = create_workqueue("mini2440_wq_test");
	INIT_WORK(&p_mini2440_work_dev->pri_data[1].mini2440_work,mini2440_work_handle);

	queue_work(mini2440_wq,&p_mini2440_work_dev->pri_data[1].mini2440_work);  
}

static void __exit xxx_exit(void)
{
/*
	1、如果mini2440_work_handle已经运行,则cancel_work_sync会等待mini2440_work_handle运行结束后才返回
	2、如果mini2440_work_handle还没有被调用,则cancel_work_sync会取消mini2440_work_handle的调用,并返回
	*/
	cancel_work_sync(&p_mini2440_work_dev->pri_data[1].mini2440_work);
	//释放我们所创建的工作队列
	destroy_workqueue(mini2440_wq);
}

           

对于多个work时,当前一个work执行时间很长就会导致下一个work的执行,

解决办法:

①、创一个属于自己work的内核进程(创建自已自己的工作队列,不用默认的)

②、中断的线程化处理 中断线程化

二、软件中断

一般很少用于实现下半部,但tasklet是通过软终端实现的,软件中断就是软件实现的异步中断,优先级比硬件低,但比普通进程优先级高,同时和硬件不能休眠

softirq一般用在对实时性要求比较强的地方,当前的Linux内核中,只有两个子系统直接使用了softirq:网络子系统和块设备子系统

软件中断是在编译时候静态分配的,必须修改内核代码

内核实现过程:在内核kernel/softirq.c中—>添加自己的中断号到softirq_action数组中---->

向外export open_softirq 和 raise_softirq

驱动:编写中断处理函数 ---->open_softirq 将中断号与处理函数进行绑定(类似于INIT_WORK)---->提交中断 raise_softirq (类似于schedule_work)

参考:http://www.360doc.com/content/12/0228/19/7891085_190357505.shtml

三、tasklet

Taskletsk类似工作队列,是软件中断实现的,允许内核代码请求在将来某个时间调用一个函数,不同在于:

1、tasklet 在软件中断上下文中运行,所以不能睡眠。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。

2、tasklet 只能在最初被提交的处理器上运行,这只是工作队列默认工作方式(比如,可通过函数queue_delayed_work_on实现把任务挂载到指定CPU的工作队列。)。

3、内核代码可以请求工作队列函数被延后一个给定的时间间隔。而tasklet没有提供这种机制。

4、tasklet 执行的很快, 短时期, 并且在原子态;而工作队列函数可能是长周期且不需要是原子的

1、编写tasklet处理函数

内核是通过tasklet_struct来维护tasklet
struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);  //taskelt处理函数
	unsigned long data;		//给tasklet处理函数传参
};
static void tasklet_handle(unsigned long arg)
{
	printk("tasklet_handle--current jiffies=%lu,value=%lu\n",jiffies,arg);
	mdelay(1000);
	tasklet_schedule(&tasklet_test);
}
           

2、初始化结构体tasklet_struct

①、静态定义并初始化

#define DECLARE_TASKLET(name,func,data) \
struct tasklet_struct name = {NULL,0,ATOMIC_INIT(0),func,data}

#define DECLARE_TASKLET_DISABLED(name,func,data)\

都是定义一个叫name的tasklet_struct,并指定它的处理函数和传参分别是func和data
区别是,DECLARE_TASKLET_DISABLED初始化后处于禁止状态,暂时不被使用
           

②、动态定义并初始化

void tasklet_init(struct tasklet_struct *t,void(*func)(unsigned long),unsigned long data )

定义一个struct  tasklet_struct 结构,然后把结构体指针传给tasklet_init来动态初始化

struct  tasklet_struct  tasklet_test;
tasklet_init(&tasklet_test,tasklet_handle,(unsigned long)2);

也相当于
DECLARE_TASKLET(tasklet_test,tasklet_handle,(unsigned long)2)
           

3、在中断返回前调度tasklet

void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
区别在于第一个使用TASKLET_SOFTIRQ,第二个使用HI_SOFTIRQ的中断号

tasklet_schedule(&tasklet_test);
           

调度的过程中将tasklet结构体放入内核的tasklet队列里。

当在执行下半部是又有一个硬件中断打断后,再次调度下半部时从原先被打断时继续进行。

硬件中断 软件中断是多对一的 即多次调用tasklet_schedule并不会每次都把tasklet结果体放入队列里。

4、在模块卸载中,将tasklet_struct结构体移除

void tasklet_kill(struct tasklet_struct *t)
如果tasklet正在运行,程序会休眠等待直到执行完毕

tasklet_kill(&tasklet_test)
           

最后还有禁止和激活tasklet的函数,被禁止的tasklet不能被调用,直到被激活

void tasklet_disable(struct tasklet_struct *t);
void tasklet_enable(struct tasklet_struct *t);

           

四、总结

软中断优点:运行在中断上下文,优先级高于普通进程,调度快

缺点:不能休眠

一般需要休眠用工作队列 其次tasklet

参考:http://www.360doc.com/content/12/0228/19/7891085_190357505.shtml

继续阅读