這幾天開始研究linux下的驅動程式編寫了,遇到的問題也挺多的,好在linux是開源的,很多高人編寫的技巧和思路都會在他們的源**中體現,我也在他們的原始碼中學到了很多好東西,我歸納了下貼出來,希望自己的**能幫到別人。
今天就來介紹一下linux的字元裝置驅動程式:
字元驅動應該是驅動學習的第一站了,在《linux裝置驅動程式第三版》這本書的第三章介紹了乙個簡單的字元裝置scull的程式設計,這一章很詳細的介紹了字元裝置驅動的一些概念,建議使用的事項和設計時要用到一些結構體和函式,所以我就不需要再吧這些介紹一遍了。我為了學起來方便,簡化了書裡的scull例子,將儲存裝置定義為一塊連續的資料段。
現在讓我們邊看**邊分析這簡單的scull字元裝置驅動設計要做些什麼,怎麼做。
1 - 39行
/* scull v1.0 */
#include #include #include // file_operations
#include #include // err
#include // memcpy memset
#include // kmalloc
#include // container_of
#include // copy_***x_user
//#include "c_device.h"
#define debug_switch
#ifdef debug_switch
#define p_debug(fmt, args...) printk(kern_warning"kdebug-%s[%s]: "fmt, __file__, __function__, ##args)
#else
#define p_debug(fmt, args...) printk(kern_debug"kdebug-%s[%s]: "fmt, __file__, __function__, ##args)
#endif
#define rw_buf_size 20 // 每個次裝置分配到的位元組寬度
#define seek_set 0 // llseek
#define seek_cur 1
#define seek_end 2
#define d_count 2 // 定義的值與常量d_count一致,存在的理由下面會講
unsigned int major = 0; // 自定義主裝置號
unsigned int minor = 0; // 自定義次裝置號
const unsigned int d_count = 2; // 次裝置數
dev_t devno; // dev_t裝置號描述
struct _dev_sct;
struct _dev_sct *devp; // 建立全域性的裝置結構指標
首先來看看包括的頭函式,後面的注釋是這個頭函式包含的作用,其中被我注釋掉的c_device.h這個檔案不存在,我原本的想法是將檔案的一些宣告部分放到c_device.h中的,這樣可以減少原始檔**量,但是為了修改方便所以沒這麼做。
下面的rw_buf_size是我定義的這個記憶體裝置的長度,每個次裝置將分配到20位元組寬度的記憶體,分配策略會在後面**中實現。
seek_***將給llseek使用,由於核心空間不能呼叫c庫,所以不能享受c庫的好處了,自己定義吧。
d_count與常量d_count都是為了描述次裝置個數的,本來編寫的時候是沒有d_count,但編譯的時候報錯error: variably modified 'kbuf' at file scope,上網一查發現gcc編譯器不允許常量來做陣列的引數(vc是可以這麼幹的)。
重點來談談_dev_sct這個結構吧,《linux裝置驅動程式第三版》將它解釋為描述裝置的結構,即它代表裝置的主體,這個結構是程式設計師定義的,裡面要新增什麼與具體要編寫的驅動相關,存在這個結構的好處是這個結構包含了裝置所有需要關注的資訊,這使後面的呼叫變得簡單,起碼思路上簡單了很多,不會因為包含了不同重要資訊的變數變得雜亂無章。
我的_dev_sct結構描述了d_count個,每個大小為rw_buf_size位元組的記憶體buffer,這個buffer的主體是乙個[d_count][rw_buf_size]的陣列,_dev_sct結構儲存了kbuf這個指向陣列的陣列指標。所以在分配完_dev_sct結構後還需分配buffer主體,然後將其與kbuf關聯,這個策略將在後面**中貼出。
_dev_sct結構裡嵌入cdev是乙個很nice的做法,這樣_dev_sct就能通過cdev結構被別的函式更方便的呼叫了,策略在後。
165-227
static int __init mycdev_init(void)
else
if(result < 0)
devp = (struct _dev_sct *)kmalloc(sizeof(struct _dev_sct), gfp_kernel);
if(!devp)
memset(devp, 0, sizeof(struct _dev_sct));
devp->kbuf = (char (*)[d_count][rw_buf_size])kmalloc(d_count * rw_buf_size, gfp_kernel);
if(!devp->kbuf)
memset(devp->kbuf, 0, d_count * rw_buf_size);
cdev_init(&devp->c_dev, &f_opts);
devp->c_dev.owner = this_module;
devp->c_dev.ops = &f_opts;
result = cdev_add(&devp->c_dev, devno, d_count);
if(result < 0)
p_debug("major = [%d]\n", major(devno));
return result;
err0:
return result;
err2:
unregister_chrdev_region(devno, d_count);
return result;
err3:
kfree(devp);
unregister_chrdev_region(devno, d_count);
return result;
err1:
kfree(devp->kbuf);
kfree(devp);
unregister_chrdev_region(devno, d_count);
return result;
}
這裡先貼出init**,why? 大家注意,分析核心模組和驅動**都應該從init開始,包括以後看行家裡手寫的核心模組**,如果不從init慢慢開始看,會暈的。
字元裝置首先分配和獲取裝置號,register_chrdev_region代表用自己配置的裝置號,alloc_chrdev_region表示從系統獲取空閒裝置號(推薦)。
為了讓裝置被系統控制就要註冊裝置,cdev相關函式來完成註冊,在註冊以前就要使裝置已在工作狀態,意思就是說呼叫cdev_add以前就要把裝置初始化ok了,這裡就是記憶體裝置,用kmalloc來分配記憶體吧,我首先分配的是_dev_sct結構,kmalloc分配的記憶體是不清零的,需要手動memset,然後分配記憶體裝置主體,接著把它們拼起來:
devp->kbuf = (char (*)[d_count][rw_buf_size])kmalloc(d_count * rw_buf_size, gfp_kernel);
出錯處理這裡我發現很多資料裡面都用了goto,應該是個不流行的東西,為什麼這裡用的比較多呢,我的理解是:核心和驅動**在某個地方失敗後需要將前面執行成功的比如記憶體分配之類的資源釋放,而這樣的出錯處理如果寫在函式裡面就不直觀,因為這部分出錯處理的**有相似性,釋放順序也很有規律。而且一般都是與資源分配順序相反,這樣goto的**寫在一起就好看很多。
上半部分就講到這裡,下半部分會繼續講解file_opt的相關函式,我的實驗**和操作步驟也會打包到網上。
下半部分《簡單linux字元裝置驅動程式與程式設計小技巧(下)》
源**鏈結
就這樣 公尺娜桑 貴安。
linux裝置驅動程式 字元裝置驅動程式
先留個 有一起學習驅動程式的加qq295699450 字元裝置驅動 這篇比較惱火。載入成功,但是讀不出來資料,有知道怎麼回事的,留個言,一起討論下 資料結構 struct scull mem struct scull dev dev 整個驅動程式 如下 include include include...
Linux裝置驅動程式 字元裝置驅動程式
1.檢視主裝置號,次裝置號 進入 dev目錄執行ls l,第四,五列分別為主次裝置號,10,180,1,5,這些是主裝置號,而60,63這些就是次裝置號 130 shell android dev ls l crw rw r system radio 10,60 1969 12 31 21 00 a...
Linux裝置驅動程式 字元裝置驅動程式設計
linux對裝置的操作與對檔案的操作是一樣的,可以看到乙個裝置所對應的檔案。我們平時用的read write等函式也可以用於裝置檔案。字元裝置 以字元為單位 按照順利操作 沒有快取區,故不支援隨機讀寫 例外 幀快取裝置,如顯示卡,是可以隨機訪問的 裝置號由主裝置號與次裝置號組成。主裝置號標識裝置對應...