2009年5月19日 星期二

Linux Kernel時序的三種機制

最近在寫Driver時,常常遇到需要「等待一段時間」再處理的動作,以往我都傻傻的用msleep()或mdelay(),殊不知這種busy waiting會hold住cpu資源,在這段期間內都無法讓給其他process執行,時間短(10ms以下等級)或許還可以,太長就不行了,所以需要Kernel本身就有提供的「時序」機制來做處理,於是我漸漸學會了如何使用Timer、Tasklet和Workqueue的用法,在O'ReillyLinux Device Drivers第七章有詳細的講解,我將書上的精華茲簡單整理如下:

##CONTINUE##
一、Timer


#include <linux/timer.h>

struct timer_list
{
/* ... */
unsigned long expires;//期望運行的 jiffies 值
void (*function)(unsigned long);
unsigned long data;//傳給function的參數
};//使用前必須初始化

void init_timer(struct timer_list *timer);
//初始化一個timer_list
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
//初始化一個靜態的timer_list
void add_timer(struct timer_list * timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
int del_timer(struct timer_list * timer);
int del_timer_sync(struct timer_list *timer);
//同 del_timer 一樣,但保證函數return時,定時器函數不在任何 CPU 上運行
//避免在SMP 系統上競爭

int timer_pending(const struct timer_list * timer);
//返回真或假來指示是否定時器已被運行

二、Tasklet


#include <linux/interrupt.h>

struct tasklet_struct {
/* ... */

void (*func)(unsigned long);
unsigned long data;
};

void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
//將定義好的結構體初始化,才可用

DECLARE_TASKLET(name, func, data);
//直接申明就可用了

DECLARE_TASKLET_DISABLED(name, func, data);
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
//對正在運行的tasklet無效,當它返回時,這個 tasklt 被禁止並且不會在以後被調度,直到重新enable.

void tasklet_enable(struct tasklet_struct *t);
//如果這個 tasklet 已經被調度, 它會很快運行. 一個對 tasklet_enable 的調用必須匹配每個對tasklet_disable 的調用, 因為內核跟蹤每個 tasklet 的"禁止次數".

void tasklet_schedule(struct tasklet_struct *t);
//調度 tasklet 執行.一般只運行一次,但可在一個tasklet中再調用,到達多次運行的目的

void tasklet_hi_schedule(struct tasklet_struct *t);
//調度 tasklet 在更高優先級執行.高於一般的tasklet優先級

void tasklet_kill(struct tasklet_struct *t);
//當一個設備正被關閉或者模塊卸載時,被調用。若tasklet被調度,則從運行的list中去掉 tasklet.若tasklet在另一個 CPU 上運行,則阻塞等待tasklet終止.

注意:tasklet是不可中斷的,一個tasklet只在一個CPU上運行

三、Workqueue

非共享隊列

#include <linux/workqueue.h>

struct workqueue_struct {

unsigned long pending;
struct list_head entry;
void (*func)(void *);
void *data;
void *wq_data;
struct timer_list timer;
};

1.創建工作隊列
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
2.創建工作
DECLARE_WORK(name, void (*function)(void *), void *data);
//一個函數搞定定義和初始化
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
//要先定義work_struct,再調用這兩個函數
//INIT_WORK做更全的初始化,若需要改變工作隊列,則用PREPARE_WORK


3.提交工作給一個工作隊列
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
//返回0,添加成功,非0表明已存在此工作
int cancel_delayed_work(struct work_struct *work);
//返回非0,取消成功,返回0,取消不成功,工作仍在運行
void flush_workqueue(struct workqueue_struct *queue);
//確保cancel_delayed_work調用返回0後,停止運行工作
4.刪除隊列
void destroy_workqueue(struct workqueue_struct *queue);
//用完後刪掉工作隊列

共享隊列

1.創建工作
同非共享
2.提交工作給一個工作隊列
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
void flush_scheduled_work(void);
//刷新 同flush_workqueue一樣

注意:工作隊列可休眠

2 則留言:

  1. msleep()裡面是使用schedule_timeout()來達成延時, 所以應該不是busy waiting, 當然也不會hold住cpu資源; mdelay()和udelay()才屬於busy wauting

    回覆刪除
  2. 是如同一樓說的沒錯,但是msleep()使用上有許多限制,例如他是不能在中斷中使用的(一用就當),在Driver很多情況下就只能被迫用mdelay(),但是busy waiting會造成畫面、聲音等停頓而讓使用者發覺,也所以才會需要研究這些Timer、Tasklet和Workqueue的用法。

    回覆刪除

寫在參加309反核遊行之前

圖片出自:日本藝術家 奈良美智(Yoshitomo Nara) 的經典反核作品 一開始,先來聽首好聽的【 棉花糖 katncandix2 - 深黑的河 】 吧! 我一直以來都犯個毛病,就是有時會過於理性,相信「證據會說話」,換句話說就是愛當「認真魔人」,...