Linux底層驅動開發需要學(xue)習哪些內容
時間(jian):2018-01-09 來源:未知(zhi)
Linux底層驅(qu)動(dong)開(kai)發需要學(xue)習(xi)哪些內(nei)容想必(bi)這(zhe)是很(hen)多學(xue)習(xi)Linux的朋(peng)友十(shi)分頭疼的問題,今(jin)天就(jiu)讓我來告訴大家我們到(dao)底該學(xue)習(xi)哪些內(nei)容呢?
1. 要(yao)會一些硬件知識(shi),比(bi)如Arm接口編程
2. 學會寫簡(jian)單的makefile
3. 編(bian)一應用(yong)程序,可以用(yong)makefile跑起來
4. 學會寫(xie)驅動的(de)makefile
5. 寫一簡單char驅(qu)動,makefile編譯通過(guo),可以(yi)insmod, lsmod, rmmod. 在(zai)驅(qu)動的init函(han)數里(li)打印hello world, insmod后應該(gai)能夠通過(guo)dmesg看到輸出(chu)。
6. 寫一(yi)完整驅動(dong), 加上read, write, ioctl, polling等各種(zhong)函(han)數的(de)(de)驅動(dong)實(shi)現。 在(zai)ioctl里完成(cheng)從用戶空(kong)(kong)間向內核(he)空(kong)(kong)間傳遞結構體的(de)(de)實(shi)現。
7. 寫一block驅動, 加上read,write,ioctl,poll等各種函數實現。
8. 簡單(dan)學習下內存管理, 這個是難的,明白各種memory alloc的函(han)數實現細節。這是Linux開發的基本功。
9. 學習鎖機(ji)制的應用(yong),這個(ge)不是難的但是容(rong)易(yi)犯(fan)錯的,涉(she)及到很多同(tong)步和并發(fa)的問題(ti)。
10. 看內核中實(shi)(shi)際應用的(de)(de)(de)(de)(de)驅動代碼。 你會(hui)發(fa)現基(ji)本的(de)(de)(de)(de)(de)你已經知(zhi)道(dao)了(le), 大的(de)(de)(de)(de)(de)框(kuang)架都是一樣(yang)的(de)(de)(de)(de)(de), 無非是read, write, ioctl等函數的(de)(de)(de)(de)(de)實(shi)(shi)現, 但(dan)里(li)面包含了(le)很(hen)多很(hen)多細小的(de)(de)(de)(de)(de)實(shi)(shi)現細節是之前(qian)不(bu)(bu)知(zhi)道(dao)的(de)(de)(de)(de)(de)。 這時候就(jiu)要(yao)考慮到很(hen)多別的(de)(de)(de)(de)(de)問題而不(bu)(bu)僅(jin)僅(jin)是基(ji)本功(gong)能的(de)(de)(de)(de)(de)實(shi)(shi)現。 推薦您看2.6.20中integrated的(de)(de)(de)(de)(de)一個驅動 kvm, 記得(de)是在driver/lguest下(xia),很(hen)好玩的(de)(de)(de)(de)(de), 就(jiu)是Linux下(xia)的(de)(de)(de)(de)(de)虛(xu)(xu)擬(ni)機(ji)驅動, 代碼不(bu)(bu)長,但(dan)功(gong)能強(qiang)大。有(you)(you)能力的(de)(de)(de)(de)(de)可以自己(ji)(ji)寫一操作系統按照要(yao)求做成磁盤(pan)鏡像加(jia)載到虛(xu)(xu)擬(ni)機(ji)中, 然后客戶機(ji)可以有(you)(you)自己(ji)(ji)的(de)(de)(de)(de)(de)4G虛(xu)(xu)擬(ni)地址(zhi)空(kong)間。
11. 看完(wan)驅動歡迎您進入Linux kernel學習(xi)中來。 簡單的方(fang)法,跟著ldd(Linux devive driver)做(zuo)一遍。
1、 Makefile 是如何編寫(xie)
eg:
# 這是上面(mian)那(nei)個程序的(de) Makefile 文件(jian)
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
分析:
在(zai) Makefile 中也(ye)#開始的(de)行都是注(zhu)釋行.Makefile 中重要的(de)是描述文件的(de)依(yi)賴關(guan)系的(de)說
明.一般的格式是: Linux 操作系(xi)統 C 語言(yan)編程入門
target: components //表示的是(shi)依賴(lai)關系
TAB rule //規則
main:main.o mytool1.o mytool2.o 表(biao)示我們(men)的(de)(de)目標(target)main 的(de)(de)依賴(lai)對(dui)象(xiang)(components)是 main.o mytool1.o mytool2.o 當倚賴(lai)的(de)(de)對(dui)象(xiang)在目標修(xiu)改(gai)后修(xiu)改(gai)的(de)(de)話(hua),就要去執(zhi)行規(gui)則一行所指(zhi)定(ding)的(de)(de)命令.就象(xiang)我們(men)的(de)(de)上
面(mian)那個 Makefile 第3行(xing)所說的(de)一樣要執行(xing) gcc -o main main.o mytool1.o mytool2.o
(注意規則一(yi)行中的 TAB表示那里是一(yi)個 TAB 鍵)
Makefile 有(you)三個非(fei)常(chang)有(you)用(yong)的變(bian)量.分別是$@,$^,$<代表(biao)的意義分別是:
$@--目(mu)標文(wen)件(jian)(jian); $^--所有(you)的(de)依(yi)賴(lai)文(wen)件(jian)(jian); $<--第一(yi)個依(yi)賴(lai)文(wen)件(jian)(jian)。
1、 字符設備驅動(dong)
Linux字符(fu)(fu)設(she)備驅動的(de)關鍵數據(ju)結(jie)構(gou)cdev及(ji)file_operations結(jie)構(gou)體的(de)操作方法,并分析了Linux字符(fu)(fu)設(she)備的(de)整體結(jie)構(gou),給出了簡(jian)單的(de)設(she)計模(mo)板.
2.1、驅動結構
1) cdev結構(gou)體(ti)(cdev結構(gou)體(ti)描述字(zi)符設(she)備)
定義:
1 struct cdev {
3 struct kobject kobj; /* 內嵌的kobject對象 */
4 struct module *owner; /*所屬模塊(kuai)*/
5 struct file_operations *ops; /*文件操作(zuo)結構體*/
6 struct list_head list;
7 dev_t dev; /*設備號*/ 定義了(le)設備號
8 unsigned int count;
9 };
dev_t 成員定(ding)義了(le)設(she)備(bei)號,為 32 位(wei)(wei),其中高 12 位(wei)(wei)為主設(she)備(bei)號,低20位(wei)(wei)為次設(she)備(bei)號。使用下列(lie)宏可(ke)以從(cong)dev_t獲(huo)得(de)主設(she)備(bei)號和次設(she)備(bei)號:
MAJOR(dev_t dev) //主設備號(hao)
MINOR(dev_t dev) //次設備號
而使(shi)用下(xia)列宏則可以通過主(zhu)設備號和設備號生(sheng)成 dev_t:
MKDEV(int major, int minor)
file_operations 定義(yi)了(le)字符設備(bei)驅動提供給虛擬文件系統(tong)的(de)接(jie)口函數
Linux 2.6內核(he)提供了(le)一組函數用于操作 cdev結(jie)構體(ti)
Void cdev_init(struct cdev *, struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_init()函數用于初始化 cdev 的成員,并建立 cdev 和 file_operations 之間的連接。
1 void cdev_init(struct cdev *cdev, struct file_operations *fops)
2 {
3 memset(cdev, 0, sizeof *cdev);
4 INIT_LIST_HEAD(&cdev->list);
5 cdev->kobj.ktype = &ktype_cdev_default;
6 kobject_init(&cdev->kobj);
7 cdev->ops = fops; /*將傳入的文件操作結構體指針賦值給cdev的ops*/
8 }
cdev_alloc()函數用于動態申請一個cdev內存
1 struct cdev *cdev_alloc(void)
2 {
3 struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL); /*分配cdev的內存*/
4 if (p) {
5 memset(p, 0, sizeof(struct cdev));
6 p->kobj.ktype = &ktype_cdev_dynamic;
7 INIT_LIST_HEAD(&p->list);
8 kobject_init(&p->kobj);
9 }
10 return p;
11 }
cdev_add()函數(shu)(shu)(shu)(shu)和(he) cdev_del()函數(shu)(shu)(shu)(shu)分別向系統添加(jia)(jia)和(he)刪除一(yi)個(ge)cdev,完成(cheng)字(zi)符設(she)備的(de)注(zhu)冊(ce)和(he)注(zhu)銷。對(dui) cdev_add()的(de)調用(yong)通常(chang)(chang)發生(sheng)在字(zi)符設(she)備驅動模塊(kuai)加(jia)(jia)載函數(shu)(shu)(shu)(shu)中,而對(dui)cdev_del()函數(shu)(shu)(shu)(shu)的(de)調用(yong)則通常(chang)(chang)發生(sheng)在字(zi)符設(she)備驅動模塊(kuai)卸載函數(shu)(shu)(shu)(shu)中。
2) 分配和(he)釋放設(she)備號
在 調用 cdev_add() 函 數 向(xiang)系(xi)(xi)統(tong)注冊 字(zi)符 設(she)(she)備(bei)(bei) 之前 , 應首先調用register_chrdev_region()或(huo) alloc_chrdev_region()函數向(xiang)系(xi)(xi)統(tong)申(shen)(shen)請(qing)設(she)(she)備(bei)(bei)號(hao)。register_chrdev_region() 函 數 用 于 已(yi) 知(zhi) 起 始 設(she)(she) 備(bei)(bei)的(de) 設(she)(she)備(bei)(bei) 號(hao) 的(de) 情 況(kuang)(kuang); 而alloc_chrdev_region()用于設(she)(she)備(bei)(bei)號(hao)未知(zhi),向(xiang)系(xi)(xi)統(tong)動態(tai)申(shen)(shen)請(qing)未被(bei)占用的(de)設(she)(she)備(bei)(bei)號(hao)的(de)情況(kuang)(kuang),相反地 ,在 調用 cdev_del() 函 數 從(cong)系(xi)(xi) 統(tong) 注銷 字(zi)符設(she)(she)備(bei)(bei) 之 后(hou),unregister_chrdev_region()應該(gai)被(bei)調用以釋放(fang)原(yuan)先申(shen)(shen)請(qing)的(de)設(she)(she)備(bei)(bei)號(hao)。
3)file_operations結構體
1 struct file_operations
2 {
3 struct module *owner; // 擁有該結構的模塊的指針,一般為THIS_MODULES
5 loff_t(*llseek)(struct file *, loff_t, int); // 用來修改文件當前的讀寫位置
7 ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*); // 從設備中同步讀取數據
9 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t); // 初始化一個異步的讀取操作
11 ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*); // 向設備發送數據
13 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t); // 初始化一個異步的寫入操作
15 int(*readdir)(struct file *, void *, filldir_t); // 僅用于讀取目錄,對于設備文件,該字段為 NULL
17 unsigned int(*poll)(struct file *, struct poll_table_struct*); // 輪詢函數,判斷目前是否可以進行非阻塞的讀取或寫入
19 int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); // 執行設備I/O控制命令
21 long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); // 不使用BLK文件系統,將使用此種函數指針代替ioctl
23 long(*compat_ioctl)(struct file *, unsigned int, unsigned long); // 在64位系統上,32位的ioctl調用將使用此函數指針代替
25 int(*mmap)(struct file *, struct vm_area_struct*); // 用于請求將設備內存映射到進程地址空間
27 int(*open)(struct inode *, struct file*); // 打開
29 int(*flush)(struct file*);
30 int(*release)(struct inode *, struct file*); / 關閉
32 int(*synch)(struct file *, struct dentry *, int datasync); // 刷新待處理的數據
34 int(*aio_fsync)(struct kiocb *, int datasync); // 異步fsync
36 int(*fasync)(int, struct file *, int); // 通知設備FASYNC標志發生變化
38 int(*lock)(struct file *, int, struct file_lock*);
39 ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*);
40 ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*); // readv和writev:分散/聚集型的讀寫操作
42 ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void*); // 通常為NULL
44 ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int); // 通常為NULL
46 unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long, unsigned long, unsigned long); // 在進程地址空間找到一個將底層設備中的內存段映射的位置
49 int(*check_flags)(int); // 允許模塊檢查傳遞給fcntl(F_SETEL...)調用的標志
51 int(*dir_notify)(struct file *filp, unsigned long arg); // 僅對文件系統有效,驅動程序不必實現
53 int(*flock)(struct file *, int, struct file_lock*);
54 };
llseek()函數(shu)用(yong)來修改一個(ge)(ge)文件的當前讀寫位置,并將(jiang)新(xin)位置返(fan)回,在出錯時,這個(ge)(ge)函數(shu)返(fan)回一個(ge)(ge)負值
read()函數用(yong)來從設備中(zhong)讀取數據,成功時(shi)函數返(fan)回(hui)讀取的字節(jie)數,出錯時(shi)返(fan)回(hui)一個負值。
write()函數(shu)向設備發(fa)送數(shu)據(ju),成(cheng)功時該函數(shu)返回寫入的字節數(shu)。如果此函數(shu)未(wei)被實現,當用戶進行write()系統(tong)調用時,將得到-EINVAL返回值。
readdir()函數(shu)僅用于(yu)目錄,設備(bei)節點不(bu)需要實現它(ta)。
ioctl()提供(gong)設(she)備相關控(kong)制命(ming)令的(de)實現 (既不是(shi)讀操(cao)作也不是(shi)寫操(cao)作) , 當(dang)調用(yong)成(cheng)功時,返(fan)回給(gei)調用(yong)程序(xu)一(yi)個非負值。內核本身識別(bie)部分控(kong)制命(ming)令,而不必調用(yong)設(she)備驅(qu)動中的(de)
ioctl()。如果(guo)設備不(bu)提供ioctl()函(han)數,對于內核不(bu)能識別(bie)的(de)命令,用(yong)戶進行ioctl()系(xi)統調(diao)用(yong)時將獲得-EINVAL返回值。
mmap()函(han)數(shu)(shu)將設(she)備內(nei)存映射到進程(cheng)內(nei)存中(zhong),如果設(she)備驅動未實現(xian)此函(han)數(shu)(shu),用戶進行 mmap()系(xi)統調用時將獲得-ENODEV返回值(zhi)。 這個函(han)數(shu)(shu)對于(yu)幀緩沖等(deng)設(she)備特(te)別有(you)意義(yi)。
3)字符設備驅(qu)動的組成(cheng)
A、字(zi)符設備驅動模(mo)塊(kuai)加載與卸載函數
字符設備驅動模塊加載函數中應該實現設備號的申請和cdev的注冊, 而在卸載函數中應實現設備號的釋放和 cdev的注銷常見的設備結構體、模塊加載和卸載函數形式如代碼清單:
1 //設備結構體
2 struct xxx_dev_t
3 {
4 struct cdev cdev;
5 ...
6 } xxx_dev;
7 //設備驅動模塊加載函數
8 static int _ _init xxx_init(void)
9 {
10 ...
11 cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev
12 xxx_dev.cdev.owner = THIS_MODULE; //獲取字符設備號
14 if (xxx_major)
15 {
16 register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
17 }
18 else
19 {
20 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
21 }
22
23 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注冊設備
24 ...
25 }
26 //設備驅動模塊卸載函數
27 static void _ _exit xxx_exit(void)
28 {
29 unregister_chrdev_region(xxx_dev_no, 1); //釋放占用的設備號
30 cdev_del(&xxx_dev.cdev); //注銷設備
31 ...
32 }
B、字符設(she)備驅動的file_operations 結構(gou)體中成員(yuan)函數(shu)
file_operations 結構體中成員函數是字符設備驅動與內核的接口,是用戶空間對Linux進行系統調用終的落實者。 大多數字符設備驅動會實現read()、 write()和 ioctl()函數,常見的字符設備驅動的這3個函數的形式如代碼清單
1 /* 讀設備*/
2 ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count, loff_t*f_pos)
4 {
5 ...
6 copy_to_user(buf, ..., ...);
7 ...
8 }
設備驅動的讀函數中,filp是文件結構體指針,buf是用戶空間內存的地址,該地址在內核空間不能直接讀寫,count是要讀的字節數,f_pos是讀的位置相對于文件開頭的偏移。
9 /* 寫設備*/
10 ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_t count, loff_t *f_pos)
12 {
13 ...
14 copy_from_user(..., buf, ...);
15 ...
16 }
設備驅動的寫函數中,filp是文件結構體指針,buf是用戶空間內存的地址,該地址在內核空間不能直接讀寫,count是要寫的字節數,f_pos是寫的位置相對于文件開頭的偏移
17 /* ioctl函數 */
18 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,
unsigned long arg)
20 {
21 ...
22 switch (cmd)
23 {
24 case XXX_CMD1:
25 ...
26 break;
27 case XXX_CMD2:
28 ...
29 break;
30 default: /* 不能支持的命令 */
32 return - ENOTTY;
33 }
34 return 0;
35 }
I/O 控(kong)制函(han)數的cmd參數為事先定義的I/O 控(kong)制命令, 而 arg為對應(ying)于(yu)(yu)該命令的參數。例(li)如(ru)(ru)對于(yu)(yu)串(chuan)行設備,如(ru)(ru)果(guo)SET_BAUDRATE 是一個設置波特(te)率的命令,那后(hou)面的arg就應(ying)該是波特(te)率值。
讀和寫(xie)函(han)數中的_ _user 是(shi)一個宏,表明其后的指(zhi)針指(zhi)向用(yong)戶空(kong)間
在字符設備驅動中,需要定義一個 file_operations 的實例,并將具體設備驅動的函數賦值給file_operations的成員,如代碼清單:
1 struct file_operations xxx_fops =
2 {
3 .owner = THIS_MODULE,
4 .read = xxx_read,
5 .write = xxx_write,
6 .ioctl = xxx_ioctl,
7 ...
8 };