本文轉自 https://blog.csdn.net/sgmenghuo/article/details/37933821
在linux老版本的內核(2.6之前),系統中斷分爲頂半部和底半部。其實這不是linux的專利,很多現代操作系統都有類似的方法來平衡快速響應和執行復雜任務的關係。早期的有bottom half(BH)和task queue來實現底半部機制,但在遇到要求性能較高的子系統(如網絡部分),會導致系統性能嚴重下降,基於此,這2個從Linux內核中去除了,所以就不談了。於是軟中斷(softirq)、tasklet和工作隊列(work queue)這3個新貴粉墨登場,其實在我當前Android 4.x上的內核(linux 3.x)碰見最多的是work queue,所以可用會重點在這了。
softirq和tasklet很早就引入linux內核(Linux 2.3開始)的底半部機制。軟中斷是一組靜態(即編譯時創建的)定義的底半部接口,是Linux系統全局的,最多可掛32個底半部程序,可在多個CPU同時執行,類型相同也可以執行,而tasklet類型相同不可以執行。與軟中斷相關的函數只能和linux內核一起編譯,不能以模塊形式(.ko)編譯,否則會出現open_softirq、raise_softirq等函數未定義的編譯錯誤。tasklet和內核定時器都是基於軟中斷的,softirq在驅動程序中使用的較少,目前只有少數的設備如網絡設備、塊設備在使用。tasklet,也叫小任務,和軟中斷類似,基於軟中斷。需要在中斷處理程序(頂半部)中註冊,softirq和tasklet會在適當的時候喚醒ksoftirqd/n進行處理。
工作隊列(work queue)是另一種將工作推後執行的底半部機制,和前2種有所不同。工作隊列推後的工作交給內核線程處理,是進程上下文,不是中斷上下文,這樣就可以享受進程上下文的好處,可以sleep的,而softirq和tasklet不可以休眠,否則會系統崩潰。
相關文件:
kernel/include/linux/workqueue.h
Kernel/kernel/workqueue.c
基本的工作work_struct和workqueue_struct結構如下
struct work_struct {
atomic_long_t data; //傳給工作隊列處理函數的數據
struct list_head entry; //工作隊列鏈表頭指針
work_func_t func; //工作隊列的處理函數
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
struct workqueue_struct {
unsigned int flags; /* W: WQ_* flags */
union {
struct cpu_workqueue_struct __percpu *pcpu; //SMP使用
struct cpu_workqueue_struct *single; //單CPU使用
unsigned long v;
} cpu_wq; /* I: cwq's */
struct list_head list; /* W: list of all workqueues */ //指向所有工作隊列列表
struct mutex flush_mutex; /* protects wq flushing */
int work_color; /* F: current work color */
int flush_color; /* F: current flush color */
atomic_t nr_cwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* F: first flusher */
struct list_head flusher_queue; /* F: flush waiters */
struct list_head flusher_overflow; /* F: flush overflow list*/
mayday_mask_t mayday_mask; /* cpus requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* W: drain in progress */
int saved_max_active; /* W: saved cwq max_active */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[]; /* I: workqueue name */
};
工作隊列基本原理:
Linux內核會爲每個處理器創建一個線程(由worker_thread()創建),用來處理工作。此外內核還會創建一個全局的工作隊列(system_wq)。如果工作不緊急就直接掛在system_wq上,當然內核允許我們創建新的工作線程和隊列,接下來動態創建會說明。
work-queue可以靜態創建(使用系統工作隊列)和動態創建(自定義工作隊列)。
靜態創建:
1. 定義處理函數
typedef void (*work_func_t)(struct work_struct *work);
2. 定義初始化work_struct變量
可以用DECLARE_WORK宏,也可以直接對work_struct結構體直接賦值,不過比較煩。直接用宏吧,輸入name和第1步的function即可。或者先定義work_struct變量再調用INIT_WORK。
#define DECLARE_WORK(n, f) \ //靜態創建,輸入work_struct變量名和處理函數
struct work_struct n = __WORK_INITIALIZER(n, f)
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)
INIT_WORK(_work, _func) //動態創建,先定義一個work_struct變量,再輸入這個變量和處理函數
INIT_DELAYED_WORK(_work, _func)
3. 工作進行調度
調用就是將work_struct添加到work_queue中,這個工作由schedule_work函數完成。只要傳入第2步work_struct變量即可鏈入系統工作隊列system_wq。如果已經被調度過了返回0,否則返回非0。每個CPU都有一個workqueue,schedule_work()是將work_struct添加到當前的CPU的workqueue上。
/**
* schedule_work - put work task in global workqueue
* @work: job to be done
*
* Returns zero if @work was already on the kernel-global workqueue and
* non-zero otherwise.
*
* This puts a job in the kernel-global workqueue if it was not already
* queued and leaves it in the same position on the kernel-global
* workqueue otherwise.
*/
int schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
動態創建:
1. 定義並創建workquque
先定義一個workqueue_struct指針,使用create_workqueue(name)宏創建新的工作隊列,並返回隊列結構體指針。如下,可見create_workqueue宏調用alloc_workqueue宏,最後調用_alloc_workqueue(),這個函數中先創建一個workqueue_struct,然後爲每個CPU創建一個cpu_workqueue_struct,然後cpu_workqueue_struct.wq都指向workqueue_struct,最後創建1個工作線程。
#define alloc_workqueue(fmt, flags, max_active, args...) \
__alloc_workqueue_key((fmt), (flags), (max_active), \
NULL, NULL, ##args)
struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
unsigned int flags,
int max_active,
struct lock_class_key *key,
const char *lock_name, ...)
#define create_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
創建工作隊列有2個函數create_workqueue()和create_siglethread_workqueue(),他們的區別見下圖:
2. 定義處理函數
同靜態創建第1步
3. 定義工作節點work_struct
同靜態創建第2步
4. 初始化工作節點
實際上就是把第2步的處理函數放在第3步的work_struct節點上。
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
#define INIT_DELAYED_WORK(_work, _func) \
do { \
INIT_WORK(&(_work)->work, (_func)); \
init_timer(&(_work)->timer); \
} while (0)
5. 將工作節點鏈入工作隊列
將當前工作節點(work_struct)添加到當前CPU的工作隊列(workqueue_struct)中,如果想要指定放到其他CPU工作隊列,可以使用queue_work_on()。
6. 清理
一般Linux驅動卸載時,要銷燬destroy_workqueue()新創建的工作隊列,但銷燬前要先保證所有工作隊列中的工作都完成,使用flush_workqueue()完成此工作。當調度工作時,執行一次就結束了,如果需要再調度,則再次queue_work,取消調度時用
void flush_workqueue(struct workqueue_struct *wq);//確保隊列中的工作都完成了
void destroy_workqueue(struct workqueue_struct *wq);//銷燬工作隊列
API列表:
以下函數中,含有schedule關鍵字的表示與系統工作隊列system_wq關聯;含有on的關鍵字的表示與指定CPU/n上的工作隊列關聯,否則是當前cpu。
API |
類型 |
描 述 |
DECLARE_WORK(name, fun) |
宏 |
定義和初始化work_struct結構體變量 |
INIT_WORK(work, fun) |
宏 |
初始化work_struct結構體變量 |
int schedule_work(struct work_struct *work) |
函數 |
將當前工作添加到系統創建的工作隊列中(調度工作) |
workqueue_struct* create_workqueue(name) |
宏 |
創建一個新的工作隊列 |
create_singlethread_workqueue(name) |
宏 |
創建一個不與任何CPU綁定的工作線程。也就是說workqueue_struct.flags包含WQ_UNBOUND |
int queue_work(struct workqueue_struct *wq, struct work_struct *work) |
函數 |
將一個工作添加到當前CPU的wq指定的工作隊列中 |
int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work) |
函數 |
將一個工作添加到指定CPU的wq指定的工作隊列中 |
void flush_workqueue(struct workqueue_struct *wq) |
函數 |
等待wq指向的工作隊列中所有的 工作節點都處理完才返回 |
void flush_scheduled_work(void) |
函數 |
等待系統工作隊列中所有的 工作節點都處理完才返回 |
void destroy_workqueue(struct workqueue_struct *wq) |
函數 |
銷燬wq指向的工作隊列 |
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay) |
函數 | 延遲delay個時鐘週期(tick)後將dwork指向的工作添加到wq指向工作隊列中。 |
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay) |
函數 |
延遲delay個時鐘週期(tick)後將dwork添加到系統創建的工作隊列中 |
int cancel_delayed_work_sync(struct delayed_work *work) |
函數 |
取消正在等待添加到工作隊列的work |
示例:
來源於linux 3.4中的cm36283光強/距離傳感器。這個傳感器中存在中斷方式和poll方式,並且都是使用工作隊列,前者使用使用普通的及時工作隊列,後者使用delayed工作隊列。
中斷方式示例:
/*定義work_struct變量及處理函數*/
static DECLARE_WORK(sensor_irq_work, sensor_irq_do_work);
/*work_struct的處理函數*/
static void sensor_irq_do_work(struct work_struct *work)
{
struct cm36283_info *lpi = lp_info;
uint16_t intFlag;
_cm36283_I2C_Read_Word(lpi->slave_addr, INT_FLAG, &intFlag);
control_and_report(lpi, CONTROL_INT_ISR_REPORT, intFlag, 1);
enable_irq(lpi->irq);
}
/*cm36283的中斷函數*/
static irqreturn_t cm36283_irq_handler(int irq, void *data)
{
struct cm36283_info *lpi = data;
disable_irq_nosync(lpi->irq);
/*將work放入新創建的工作隊列進行調度,相當於之後要調用sensor_irq_do_work()函數*/
queue_work(lpi->lp_wq, &sensor_irq_work);
return IRQ_HANDLED;
}
static int cm36283_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
//...
/*創建自定義的工作隊列,且不與任何CPU綁定*/
lpi->lp_wq = create_singlethread_workqueue("cm36283_wq");
if (!lpi->lp_wq) {
pr_err("[PS][CM36283 error]%s: can't create workqueue\n", __func__);
ret = -ENOMEM;
goto err_create_singlethread_workqueue;
}
//...
err_cm36283_power_on:
/*銷燬前面創建的工作隊列*/
destroy_workqueue(lpi->lp_wq);
//...
}
poll方式示例:
/*
* poll方式使用delayed_work,未創建新的隊列,使用默認的系統工作隊列,system_wq
*/
/*定義delayed_work*/
struct delayed_work ldwork;
/*work的工作函數*/
static void lsensor_delay_work_handler(struct work_struct *work)
{
//...
/*經過ls_poll_delay時間後會再次調度ldwork的本函數*/
schedule_delayed_work(&ldwork,
msecs_to_jiffies(atomic_read(&lpi->ls_poll_delay)));
}
/*使能光強傳感器,就是要週期性調度work*/
static int lightsensor_enable(struct cm36283_info *lpi)
{
//...
if (lpi->polling)
schedule_delayed_work(&ldwork,
msecs_to_jiffies(delay));
return ret;
}
/*禁用光強傳感器,就是要停止調度work*/
static int lightsensor_disable(struct cm36283_info *lpi)
{
//...
if (lpi->polling)
cancel_delayed_work_sync(&ldwork);
//...
return ret;
}
/*將工作函數放在work節點上,以後只需對work進行調度和cancel即可讓handler運行和停止*/
static int cm36283_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
//...
INIT_DELAYED_WORK(&ldwork, lsensor_delay_work_handler);
//...
}