- 軟中斷
- tasklet
- 工做隊列
- 內核定時器,也能夠將工做退後一段時間(精準的)執行
tasklet是中斷處理下半部最多見的一種方式,驅動程序通常先申請中斷,在中斷處理函數內完成中斷上半部分的工做以後再調用tasklet,tasklet有以下優勢:linux
HI_SOFTIRQ
和ACKLET_SOFTIRQ
,本質上沒什麼區別,只不過HI_SOFTIRQ
的優先級更高一些/* tasklet結構體 */ struct tasklet_struct { struct tasklet_struct *next; //鏈表節點 unsigned long state; //屬性 atomic_t count; //計數,用於禁止使能,每禁止一次計數加1,每使能一次計數減1,只有禁止次數和使能次數同樣(count=0)的時候tasklet纔會執行調用函數 void (*func)(unsigned long); //中斷下半部實現方法,不能阻塞,不能睡眠 unsigned long data; //中斷服務函數的參數 }; /* tasklet結構體變量是tasklet_vec鏈表下的一個節點,next是鏈表下一個節點,status使用了2個位,以下:*/ enum { TASKLET_STATE_SCHED, //1表示已經被調度,0表示還沒調度 TASKLET_STATE_RUN //1表示tasklet正在執行,0表示還沒有執行,只針對SMP有效,單處理器無心義 }; /* tasklet初始化 */ /* 1.定義時初始化:定義變量名爲name的tasklet_struct結構體變量,並初始化調用函數func,參數爲data,使能tasklet */ DECLARE_TASKLET(name, func, data); #define DECLARE_TASKLET(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(0), func, data} /* 2.定義時初始化:定義變量名爲name的tasklet_struct結構體變量,並初始化調用函數func,參數爲data,禁止tasklet */ DECLARE_TASKLET_DISABLED(name, func, data); #define DECLARE_TASKLET_DISABLED(name, func, data) struct tasklet_struct name = {NULL, 0, ATOMIC_INIT(1), func, data} /* 3.運行中初始化,先定義struct tasklet_struct name,後初始化 */ void tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data) { t->next = NULL; t->state = 0; //設置爲未調度,未執行 atomic_set(&t->count, 0); //默認使能 t->func = func; t->data = data; } /* 啓動中斷下半部 */ static inline void tasklet_schedule(struct tasklet_struct * t); static inline int tasklet_trylock(struct tasklet_struct * t); static inline void tasklet_unlock(struct tasklet_struct * t); static inline void tasklet_unlock_wait(struct tasklet_struct * t); /* 使能以前一個被disable的tasklet;若這個tasklet已經被調度,他會很快運行。tasklet_enable和task_disable必須匹配使用,由於內核跟蹤每一個tasklet的「禁止次數」 */ static inline void tasklet_enable(struct tasklet_struct * t); /* 與tasklet_schedule相似,只是在更高優先級執行。當軟中斷處理運行時,它處理高優先級tasklet,在其餘軟中斷以前,只有具備低響應週期要求的驅動(實時性不強)才應使用這個函數,可避免其餘軟件中斷處理引入的附加週期 */ void tasklet_hi_schedule(struct tasklet_struct * t); /* 確保了tasklet不會被再次調度運行,即執行此函數以後若再次調用tasklet_schedule函數,中斷下半部也不會執行。一般當一個設備正在被關閉或者模塊卸載時被調用。若是tasklet正在運行,那麼這個函數需等待直到tasklet執行完畢。若tasklet從新調度它本身,則必須阻止在調用tasklet_kill以前它從新調度本身,如同使用del_timer_sync。注意:該函數不是真的去殺掉被調度的tasklet,而是保證tasklet再也不調用 */ void tasklet_kill(struct tasklet_struct *t);
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ cat tasklet.c #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/interrupt.h> struct message { int id; int val; }; struct tasklet_struct mytask; void mytask_func(unsigned long data) { struct message *msg = (struct message *)data; printk("id = %d, val = %d\n", msg->id, msg->val); kfree(msg); msg = NULL; } static int __init task_init(void) { struct message *msg; printk(KERN_INFO "%s start\n", __func__); msg = (struct message *)kzalloc(sizeof(struct message), GFP_KERNEL); if (msg == NULL) { printk(KERN_ERR "failed to kmalloc\n"); return -ENOMEM; } msg->id = 7; msg->val = 77; tasklet_init(&mytask, mytask_func, (unsigned long)msg); tasklet_schedule(&mytask); printk(KERN_INFO "%s end\n", __func__); return 0; } static void __exit task_exit(void) { printk(KERN_INFO "%s start\n", __func__); tasklet_kill(&mytask); printk(KERN_INFO "%s end\n", __func__); } module_init(task_init); module_exit(task_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo insmod tasklet.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ sudo rmmod tasklet.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/tasklet$ dmesg [ 583.990890] task_init start [ 583.990893] task_init end [ 583.990897] id = 7, val = 77 [ 593.107581] task_exit start [ 593.107582] task_exit end
工做隊列是一種將工做退後執行的形式,交由一個內核線程去執行進在程上下文執行,其不能訪問用戶空間。最重要的特色是工做隊列容許從新調度甚至是睡眠。工做隊列子系統提供了一個默認的工做線程來處理這些工做。默認的工做者線程叫作/events/n
,這裏的n是處理器編號,每一個處理器對應一個線程,也能夠本身建立工做者線程編程
/* 工做隊列work_struct結構體 */ struct work_struct { /* atomic_long_t data; */ unsigned long data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; typedef void (*work_func_t)(struct work_struct *work); /* 初始化工做隊列 */ INIT_WORK(struct work_struct *work, work_func_t func); DECLARE_WORK(n, f); /* 開啓中斷下半部,即將給定工做的處理函數提交給缺省的工做隊列和工做線程 */ int schedule_work(struct work_struct *work); /* 確保沒有工做隊列入口在系統中任何地方運行 */ void flush_scheduled_word(void); /* 延時執行一個任務 */ int schedule_delayed_work(struct delayed_struct *work, unsigned long delay); /* 從一個工做隊列中去除入口 */ int cancel_dalayed_work(struct delayed_struct *work);
workqueue_struct *
指針,並建立了工做線程。三個宏主要區別在於後面兩個參數singlethread
和freezable
,singlethread
爲0時會爲每一個CPU上建立一個工做者線程,爲1時只在當前運行的cpu上建立一個工做者線程。freezable
會影響內核線程結構體thread_info
的PF_NOFREEZE
標記/* 多處理器時會爲每一個cpu建立一個工做線程 */ #define create_workqueue(name) __create_workqueue((name), 0, 0) /* 只建立一個工做線程,系統掛起時線程也掛起 */ #define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1) /* 只建立一個工做線程,系統掛起時線程不會掛起 */ #define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0) /* 釋放建立的工做隊列資源 */ void destroy_workqueue(struct workqueue_struct *wq); /* 延時調用指定工做隊列的工做 */ queue_delayed_work(struct workqueue_struct *wq, struct delay_struct *work, unsigned long delay); /* 取消指定工做隊列的延時工做 */ cancel_delayed_work(struct delay_struct *work); /* 將工做加入到工做隊列中進行調度 */ queue_work(struct workqueue_struct *wq, struct work_struct *work); /* 等待隊列中的任務所有執行完畢 */ void flush_workqueue(struct workqueue_struct *wq);
#include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h> void my_func(struct work_struct *ws); DECLARE_WORK(my_work, my_func); void my_func(struct work_struct *ws) { printk(KERN_INFO "doing work\n"); } static int __init work_queue_init(void) { schedule_work(&my_work); return 0; } static void __exit work_queue_exit(void) { } module_init(work_queue_init); module_exit(work_queue_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg [ 4097.029387] doing work
#include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h> struct work_struct my_work; struct workqueue_struct *my_wq; void my_func(struct work_struct *ws) { printk(KERN_INFO "doing work\n"); } static int __init work_queue_init(void) { /* create my work queue */ my_wq = create_workqueue("my_wq"); if (!my_wq) { printk(KERN_ERR "failed to create workqueue\n"); return -1; } /* init my work */ INIT_WORK(&my_work, my_func); /* schedule my work to my work queue */ queue_work(my_wq, &my_work); return 0; } static void __exit work_queue_exit(void) { /* destroy my work queue */ destroy_workqueue(my_wq); my_wq = NULL; } module_init(work_queue_init); module_exit(work_queue_exit); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ sudo insmod work_queue.ko hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/workqueue$ dmesg \ [ 5472.402008] doing work
find -name "*.c" | xargs grep "create_workqueue"
mod_timer()
函數動態的改變定時器到達時間struct timer_list
實現,須要的頭文件包括#include <linux/timer.h>
,可是實際開發過程當中不須要包含該頭文件,由於在sched.h
中包含了該頭文件struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; }
expires
:定義定時器到期時間,一般採用jiffies
和HZ
這個兩個全局變量來配合設置該參數的值,如expires = jiffies + n * HZ
,其中jiffies
是自啓動以來的滴答數,HZ
是一秒鐘的滴答數。假設系統1秒的滴答數爲1,咱們但願定時10秒鐘,並且咱們在啓動定時器的時候jiffies
數值爲5,那麼10秒後滴答總數將爲5 + 10 * 1 = 15
function
:即一個函數指針,就是定時器處理函數,當定時時間到達以後就會觸發該函數的執行data
:一般實現參數的傳遞,從function
的參數類型值咱們能夠看到,data
能夠做爲定時器處理函數的參數,其餘的元素可經過內核的函數來初始化
/* 定時器初始化 */ init_timer(struct timer_list *timer); /* 定時器初始化 */ #define DEFINE_TIMER(_name, _function, _expires, _data) struct timer_list_name = TIMER_INITIALIZER(_function, _expires, _data) /* 添加定時器到內核 */ void add_timer(struct timer_list *timer) { BUG_ON(timer_pending(timer)); mod_timer(timer, timer->expires; } /* 刪除定時器,若是定時器的定時時間尚未到達,那麼才能夠刪除定時器 */ int del_timer(struct timer_list *timer); /* 修改定時器到達時間,該函數特色是:無論定時器是否到達定時時間,都會從新添加一個定時器到內核,因此能夠在定時器出路函數中調用該函數去修改新的定時時間 */ int mode_timer(struct timer_list *timer, unsigned long expires);
#include <linux/module.h> #include <linux/init.h> #include <linux/timer.h> struct timer_list g_timer; void timer_func(unsigned long data) { printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies); mod_timer(&g_timer, jiffies + HZ); } static int __init timer_init(void) { /* init timer old kernel can use init_timer(), but new kernel can not use this function */ init_timer(&g_timer); // DEFINE_TIMER(g_timer, timer_func); /* set timer parameters */ printk(KERN_INFO "%s->current jiffies = %ld\n", __func__, jiffies); g_timer.expires = jiffies + 2 * HZ; g_timer.function = timer_func; /* add timer */ add_timer(&g_timer); return 0; } static void __exit timer_exit(void) { del_timer(&g_timer); } module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL");