天天看點

中斷下半部——工作隊列、軟體中斷、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

繼續閱讀