linux 驅動知識點總結
1. 什麼是workqueue
Linux中的Workqueue機制就是為了簡化核心執行緒的建立。通過呼叫workqueue的介面就能建立核心執行緒。並且可以根據當前系統CPU的個 數建立執行緒的數量,使得執行緒處理的事務能夠並行化。workqueue是核心中實現簡單而有效的機制,他顯然簡化了核心daemon的建立,方便了使用者的 程式設計.
工作佇列(workqueue)是另外一種將工作推後執行的形式.工作佇列可以把工作推後,交由一個核心執行緒去執行,也就是說,這個下半部分可以在程序上下文中執行。最重要的就是工作佇列允許被重新排程甚至是睡眠。
那麼,什麼情況下使用工作佇列,什麼情況下使用tasklet。如果推後執行的任務需要睡眠,那麼就選擇工作佇列。如果推後執行的任務不需要睡眠,那麼就 選擇tasklet。另外,如果需要用一個可以重新排程的實體來執行你的下半部處理,也應該使用工作佇列。它是唯一能在程序上下文執行的下半部實現的機 制,也只有它才可以睡眠。這意味著在需要獲得大量的記憶體時、在需要獲取訊號量時,在需要執行阻塞式的I/O操作時,它都會非常有用。如果不需要用一個核心 執行緒來推後執行工作,那麼就考慮使用tasklet。
2. 資料結構
我們把推後執行的任務叫做工作(work),描述它的資料結構為work_struct:
struct work_struct { atomic_long_t data; /*工作處理函式func的引數*/ #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */ #define WORK_STRUCT_STATIC 1 /* static initializer (debugobjects) */ #define WORK_STRUCT_FLAG_MASK (3UL) #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK) struct list_head entry; /*連線工作的指標*/ work_func_t func; /*工作處理函式*/ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
這些工作以佇列結構組織成工作佇列(workqueue),其資料結構為workqueue_struct:
struct workqueue_struct { struct cpu_workqueue_struct *cpu_wq; struct list_head list; const char *name; /*workqueue name*/ int singlethread; /*是不是單執行緒 - 單執行緒我們首選第一個CPU -0表示採用預設的工作者執行緒event*/ int freezeable; /* Freeze threads during suspend */ int rt; };
如果是多執行緒,Linux根據當前系統CPU的個數建立cpu_workqueue_struct 其結構體就是:
truct cpu_workqueue_struct {
spinlock_t lock;/*因為工作者執行緒需要頻繁的處理連線到其上的工作,所以需要枷鎖保護*/
struct list_head worklist;
wait_queue_head_t more_work;
struct work_struct *current_work; /*當前的work*/
struct workqueue_struct *wq; /*所屬的workqueue*/
struct task_struct *thread; /*任務的上下文*/
} ____cacheline_aligned;
在該結構主要維護了一個任務佇列,以及核心執行緒需要睡眠的等待佇列,另外還維護了一個任務上下文,即task_struct。
三者之間的關係如下:
3. 建立工作
3.1 建立工作queue
a. create_singlethread_workqueue(name)
該函式的實現機制如下圖所示,函式返回一個型別為struct workqueue_struct的指標變數,該指標變數所指向的記憶體地址在函式內部呼叫kzalloc動態生成。所以driver在不再使用該work queue的情況下呼叫:
void destroy_workqueue(struct workqueue_struct *wq)來釋放此處的記憶體地址。
圖中的cwq是一per-CPU型別的地址空間。對於create_singlethread_workqueue而言,即使是對於多CPU系統,核心也 只負責建立一個worker_thread核心程序。該核心程序被建立之後,會先定義一個圖中的wait節點,然後在一迴圈體中檢查cwq中的 worklist,如果該佇列為空,那麼就會把wait節點加入到cwq中的more_work中,然後休眠在該等待佇列中。
Driver呼叫queue_work(struct workqueue_struct *wq, struct work_struct *work)向wq中加入工作節點。work會依次加在cwq->worklist所指向的連結串列中。queue_work向 cwq->worklist中加入一個work節點,同時會呼叫wake_up來喚醒休眠在cwq->more_work上的 worker_thread程序。wake_up會先呼叫wait節點上的autoremove_wake_function函式,然後將wait節點從 cwq->more_work中移走。
worker_thread再次被排程,開始處理cwq->worklist中的所有work節點...當所有work節點處理完 畢,worker_thread重新將wait節點加入到cwq->more_work,然後再次休眠在該等待佇列中直到Driver呼叫 queue_work...
b. create_workqueue
相對於create_singlethread_workqueue, create_workqueue同樣會分配一個wq的工作佇列,但是不同之處在於,對於多CPU系統而言,對每一個CPU,都會為之建立一個per- CPU的cwq結構,對應每一個cwq,都會生成一個新的worker_thread程序。但是當用queue_work向cwq上提交work節點時, 是哪個CPU呼叫該函式,那麼便向該CPU對應的cwq上的worklist上增加work節點。
c.小結
當用戶呼叫workqueue的初始化介面create_workqueue或者create_singlethread_workqueue對 workqueue佇列進行初始化時,核心就開始為使用者分配一個workqueue物件,並且將其鏈到一個全域性的workqueue佇列中。然後 Linux根據當前CPU的情況,為workqueue物件分配與CPU個數相同的cpu_workqueue_struct物件,每個 cpu_workqueue_struct物件都會存在一條任務佇列。緊接著,Linux為每個cpu_workqueue_struct物件分配一個內 核thread,即核心daemon去處理每個佇列中的任務。至此,使用者呼叫初始化介面將workqueue初始化完畢,返回workqueue的指標。
workqueue初始化完畢之後,將任務執行的上下文環境構建起來了,但是具體還沒有可執行的任務,所以,需要定義具體的work_struct物件。然後將work_struct加入到任務佇列中,Linux會喚醒daemon去處理任務。
上述描述的workqueue核心實現原理可以描述如下:
3.2 建立工作
要使用工作佇列,首先要做的是建立一些需要推後完成的工作。可以通過DECLARE_WORK在編譯時靜態地建該結構:
DECLARE_WORK(name,void (*func) (void *), void *data);
這樣就會靜態地建立一個名為name,待執行函式為func,引數為data的work_struct結構。
同樣,也可以在執行時通過指標建立一個工作:
INIT_WORK(structwork_struct *work, woid(*func) (void *), void *data);
4. 排程
a. schedule_work
在大多數情況下, 並不需要自己建立工作佇列,而是隻定義工作, 將工作結構掛接到核心預定義的事件工作佇列中排程, 在kernel/workqueue.c中定義了一個靜態全域性量的工作佇列static struct workqueue_struct *keventd_wq;預設的工作者執行緒叫做events/n,這裡n是處理器的編號,每個處理器對應一個執行緒。比如,單處理器的系統只有events /0這樣一個執行緒。而雙處理器的系統就會多一個events/1執行緒。
排程工作結構, 將工作結構新增到全域性的事件工作佇列keventd_wq,呼叫了queue_work通用模組。對外遮蔽了keventd_wq的介面,使用者無需知道此 引數,相當於使用了預設引數。keventd_wq由核心自己維護,建立,銷燬。這樣work馬上就會被排程,一旦其所在的處理器上的工作者執行緒被喚醒, 它就會被執行。
b. schedule_delayed_work(&work,delay);
有時候並不希望工作馬上就被執行,而是希望它經過一段延遲以後再執行。在這種情況下,同時也可以利用timer來進行延時排程,到期後才由預設的定時器回撥函式進行工作註冊。延遲delay後,被定時器喚醒,將work新增到工作佇列wq中。
工作佇列是沒有優先順序的,基本按照FIFO的方式進行處理。
5. work queue API
1. create_workqueue用於建立一個workqueue佇列,為系統中的每個CPU都建立一個核心執行緒。輸入引數:
@name:workqueue的名稱
2. create_singlethread_workqueue用於建立workqueue,只建立一個核心執行緒。輸入引數:
@name:workqueue名稱
3. destroy_workqueue釋放workqueue佇列。輸入引數:
@ workqueue_struct:需要釋放的workqueue佇列指標
4. schedule_work排程執行一個具體的任務,執行的任務將會被掛入Linux系統提供的workqueue——keventd_wq輸入引數:
@ work_struct:具體任務物件指標
5. schedule_delayed_work延遲一定時間去執行一個具體的任務,功能與schedule_work類似,多了一個延遲時間,輸入引數:
@work_struct:具體任務物件指標
@delay:延遲時間
6. queue_work排程執行一個指定workqueue中的任務。輸入引數:
@ workqueue_struct:指定的workqueue指標
@work_struct:具體任務物件指標
7. queue_delayed_work延遲排程執行一個指定workqueue中的任務,功能與queue_work類似,輸入引數多了一個delay。
6. 示例
- //宣告
- static struct workqueue_struct *mdp_pipe_ctrl_wq; /* mdp mdp pipe ctrl wq */
- static struct delayed_work mdp_pipe_ctrl_worker;
- mdp_pipe_ctrl_wq = create_singlethread_workqueue("mdp_pipe_ctrl_wq");//建立工作佇列
- INIT_DELAYED_WORK(&mdp_pipe_ctrl_worker,mdp_pipe_ctrl_workqueue_handler);//delayed_work與task_func繫結。
- //處理函式
- static void mdp_pipe_ctrl_workqueue_handler(struct work_struct *work)
- {
- mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE);
- }
- //開始呼叫工作佇列,delay時間到了就執行處理函式。
- unsigned long mdp_timer_duration = (HZ/20); /* 50 msecond */
- /* send workqueue to turn off mdp power */
- queue_delayed_work(mdp_pipe_ctrl_wq,&mdp_pipe_ctrl_worker, mdp_timer_duration);
- /* cancel pipe ctrl worker */
- cancel_delayed_work(&mdp_pipe_ctrl_worker);
- /* for workder can't be cancelled... */
- flush_workqueue(mdp_pipe_ctrl_wq);
- /* for workder can't be cancelled... */
- flush_workqueue(mdp_pipe_ctrl_wq);
在driver 程式中許多很多情況需要設定延後執行的,這樣工作佇列就很好幫助我們實現。
複雜型:
1:linux中netfilter的實現機制?是如何實現對特定資料包進行處理(如過濾,NAT之類的)及HOOK點的註冊?
2:linux中系統呼叫過程?如:應用程式中read()在linux中執行過程即從使用者空間到核心空間?
3:linux核心的啟動過程(原始碼級)?
4:linux排程原理?
5:linux網路子系統的認識?
三: sample
1:二分法查詢
2:大小端轉化及判斷
3: 二維陣列最外邊個元素之和?
4:特定位元位置0和1
5:字串中的第一個和最後一個元素交換(字串反轉)?
6:linux driver 小例子
Linux裝置驅動分為:字元裝置、塊裝置和網路裝置。原理圖如下:
二、示例
示例主要轉載自部落格園的部落格,見上。只是我採用的的Linux核心版本比那篇博文的新,有小許改動,貼上程式碼如下:
核心版本:
[email protected]:/$ uname -a
Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
檔案:hello_mod.c
- /*
- * =====================================================================================
- *
- * Filename: hello.c
- *
- * Description: hello_mod
- *
- * Version: 1.0
- * Created: 01/28/2011 05:07:55 PM
- * Revision: none
- * Compiler: gcc
- *
- * Author: Tishion (shion), [email protected]
- * Company: LIM
- *
- * =====================================================================================
- */
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/uaccess.h>
- #include <linux/semaphore.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/ioctl.h>
- #include <linux/slab.h>
- #include <linux/errno.h>
- #include <linux/string.h>
- #include "hello_mod_ioctl.h"
- #define MAJOR_NUM 250
- #define MINOR_NUM 0
- #define IN_BUF_LEN 256
- #define OUT_BUF_LEN 512
- MODULE_AUTHOR("Tishion");
- MODULE_DESCRIPTION("Hello_mod driver by tishion");
- staticstructclass * hello_class;
- staticstruct cdev hello_cdev;
- static dev_t devnum = 0;
- staticchar * modname = "hello_mod";
- staticchar * devicename = "hello";
- staticchar * classname = "hello_class";
- staticint open_count = 0;
- staticstruct semaphore sem;
- static DEFINE_SPINLOCK(spin);
- staticchar * inbuffer = NULL;
- staticchar * outbuffer = NULL;
- static lang_t langtype;
- staticint hello_mod_open(struct inode *, struct file *);
- staticint hello_mod_release(struct inode *, struct file *);
- static ssize_t hello_mod_read(struct file *, char *, size_t, loff_t *);
- static ssize_t hello_mod_write(struct file *, constchar *, size_t, loff_t *);
- staticlong hello_mod_ioctl(struct file *, unsigned int, unsigned long);
- struct file_operations hello_mod_fops =
- {
- .owner = THIS_MODULE,
- .open = hello_mod_open,
- .read = hello_mod_read,
- .write = hello_mod_write,
- .unlocked_ioctl = hello_mod_ioctl,
- .release = hello_mod_release,
- };
- staticint hello_mod_open(struct inode *inode, struct file *pfile)
- {
- printk("+hello_mod_open()!/n");
- spin_lock(&spin);