當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Linux底層驅(qū)動開發(fā)需要學(xué)習(xí)哪些內(nèi)容
Linux底層驅(qū)動開發(fā)需要學(xué)習(xí)哪些內(nèi)容想必這是很多學(xué)習(xí)Linux的朋友十分頭疼的問題,今天就讓我來告訴大家我們到底該學(xué)習(xí)哪些內(nèi)容呢?
1. 要會一些硬件知識,比如Arm接口編程
2. 學(xué)會寫簡單的makefile
3. 編一應(yīng)用程序,可以用makefile跑起來
4. 學(xué)會寫驅(qū)動的makefile
5. 寫一簡單char驅(qū)動,makefile編譯通過,可以insmod, lsmod, rmmod. 在驅(qū)動的init函數(shù)里打印hello world, insmod后應(yīng)該能夠通過dmesg看到輸出。
6. 寫一完整驅(qū)動, 加上read, write, ioctl, polling等各種函數(shù)的驅(qū)動實(shí)現(xiàn)。 在ioctl里完成從用戶空間向內(nèi)核空間傳遞結(jié)構(gòu)體的實(shí)現(xiàn)。
7. 寫一block驅(qū)動, 加上read,write,ioctl,poll等各種函數(shù)實(shí)現(xiàn)。
8. 簡單學(xué)習(xí)下內(nèi)存管理, 這個(gè)是難的,明白各種memory alloc的函數(shù)實(shí)現(xiàn)細(xì)節(jié)。這是Linux開發(fā)的基本功。
9. 學(xué)習(xí)鎖機(jī)制的應(yīng)用,這個(gè)不是難的但是容易犯錯(cuò)的,涉及到很多同步和并發(fā)的問題。
10. 看內(nèi)核中實(shí)際應(yīng)用的驅(qū)動代碼。 你會發(fā)現(xiàn)基本的你已經(jīng)知道了, 大的框架都是一樣的, 無非是read, write, ioctl等函數(shù)的實(shí)現(xiàn), 但里面包含了很多很多細(xì)小的實(shí)現(xiàn)細(xì)節(jié)是之前不知道的。 這時(shí)候就要考慮到很多別的問題而不僅僅是基本功能的實(shí)現(xiàn)。 推薦您看2.6.20中integrated的一個(gè)驅(qū)動 kvm, 記得是在driver/lguest下,很好玩的, 就是Linux下的虛擬機(jī)驅(qū)動, 代碼不長,但功能強(qiáng)大。有能力的可以自己寫一操作系統(tǒng)按照要求做成磁盤鏡像加載到虛擬機(jī)中, 然后客戶機(jī)可以有自己的4G虛擬地址空間。
11. 看完驅(qū)動歡迎您進(jìn)入Linux kernel學(xué)習(xí)中來。 簡單的方法,跟著ldd(Linux devive driver)做一遍。
1、 Makefile 是如何編寫
eg:
# 這是上面那個(gè)程序的 Makefile 文件
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
分析:
在 Makefile 中也#開始的行都是注釋行.Makefile 中重要的是描述文件的依賴關(guān)系的說
明.一般的格式是: Linux 操作系統(tǒng) C 語言編程入門
target: components //表示的是依賴關(guān)系
TAB rule //規(guī)則
main:main.o mytool1.o mytool2.o 表示我們的目標(biāo)(target)main 的依賴對象(components)是 main.o mytool1.o mytool2.o 當(dāng)倚賴的對象在目標(biāo)修改后修改的話,就要去執(zhí)行規(guī)則一行所指定的命令.就象我們的上
面那個(gè) Makefile 第3行所說的一樣要執(zhí)行 gcc -o main main.o mytool1.o mytool2.o
(注意規(guī)則一行中的 TAB表示那里是一個(gè) TAB 鍵)
Makefile 有三個(gè)非常有用的變量.分別是$@,$^,$<代表的意義分別是:
$@--目標(biāo)文件; $^--所有的依賴文件; $<--第一個(gè)依賴文件。
1、 字符設(shè)備驅(qū)動
Linux字符設(shè)備驅(qū)動的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)cdev及file_operations結(jié)構(gòu)體的操作方法,并分析了Linux字符設(shè)備的整體結(jié)構(gòu),給出了簡單的設(shè)計(jì)模板.
2.1、驅(qū)動結(jié)構(gòu)
1) cdev結(jié)構(gòu)體(cdev結(jié)構(gòu)體描述字符設(shè)備)
定義:
1 struct cdev {
3 struct kobject kobj; /* 內(nèi)嵌的kobject對象 */
4 struct module *owner; /*所屬模塊*/
5 struct file_operations *ops; /*文件操作結(jié)構(gòu)體*/
6 struct list_head list;
7 dev_t dev; /*設(shè)備號*/ 定義了設(shè)備號
8 unsigned int count;
9 };
dev_t 成員定義了設(shè)備號,為 32 位,其中高 12 位為主設(shè)備號,低20位為次設(shè)備號。使用下列宏可以從dev_t獲得主設(shè)備號和次設(shè)備號:
MAJOR(dev_t dev) //主設(shè)備號
MINOR(dev_t dev) //次設(shè)備號
而使用下列宏則可以通過主設(shè)備號和設(shè)備號生成 dev_t:
MKDEV(int major, int minor)
file_operations 定義了字符設(shè)備驅(qū)動提供給虛擬文件系統(tǒng)的接口函數(shù)
Linux 2.6內(nèi)核提供了一組函數(shù)用于操作 cdev結(jié)構(gòu)體
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()函數(shù)用于初始化 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; /*將傳入的文件操作結(jié)構(gòu)體指針賦值給cdev的ops*/
8 }
cdev_alloc()函數(shù)用于動態(tài)申請一個(gè)cdev內(nèi)存
1 struct cdev *cdev_alloc(void)
2 {
3 struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL); /*分配cdev的內(nèi)存*/
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()函數(shù)和 cdev_del()函數(shù)分別向系統(tǒng)添加和刪除一個(gè)cdev,完成字符設(shè)備的注冊和注銷。對 cdev_add()的調(diào)用通常發(fā)生在字符設(shè)備驅(qū)動模塊加載函數(shù)中,而對cdev_del()函數(shù)的調(diào)用則通常發(fā)生在字符設(shè)備驅(qū)動模塊卸載函數(shù)中。
2) 分配和釋放設(shè)備號
在 調(diào)用 cdev_add() 函 數(shù) 向系統(tǒng)注冊 字符 設(shè)備 之前 , 應(yīng)首先調(diào)用register_chrdev_region()或 alloc_chrdev_region()函數(shù)向系統(tǒng)申請?jiān)O(shè)備號。register_chrdev_region() 函 數(shù) 用 于 已 知 起 始 設(shè) 備的 設(shè)備 號 的 情 況; 而alloc_chrdev_region()用于設(shè)備號未知,向系統(tǒng)動態(tài)申請未被占用的設(shè)備號的情況,相反地 ,在 調(diào)用 cdev_del() 函 數(shù) 從系 統(tǒng) 注銷 字符設(shè)備 之 后,unregister_chrdev_region()應(yīng)該被調(diào)用以釋放原先申請的設(shè)備號。
3)file_operations結(jié)構(gòu)體
1 struct file_operations
2 {
3 struct module *owner; // 擁有該結(jié)構(gòu)的模塊的指針,一般為THIS_MODULES
5 loff_t(*llseek)(struct file *, loff_t, int); // 用來修改文件當(dāng)前的讀寫位置
7 ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*); // 從設(shè)備中同步讀取數(shù)據(jù)
9 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t); // 初始化一個(gè)異步的讀取操作
11 ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*); // 向設(shè)備發(fā)送數(shù)據(jù)
13 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t); // 初始化一個(gè)異步的寫入操作
15 int(*readdir)(struct file *, void *, filldir_t); // 僅用于讀取目錄,對于設(shè)備文件,該字段為 NULL
17 unsigned int(*poll)(struct file *, struct poll_table_struct*); // 輪詢函數(shù),判斷目前是否可以進(jìn)行非阻塞的讀取或?qū)懭?br style="padding: 0px; margin: 0px; list-style-type: none;" />
19 int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); // 執(zhí)行設(shè)備I/O控制命令
21 long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); // 不使用BLK文件系統(tǒng),將使用此種函數(shù)指針代替ioctl
23 long(*compat_ioctl)(struct file *, unsigned int, unsigned long); // 在64位系統(tǒng)上,32位的ioctl調(diào)用將使用此函數(shù)指針代替
25 int(*mmap)(struct file *, struct vm_area_struct*); // 用于請求將設(shè)備內(nèi)存映射到進(jìn)程地址空間
27 int(*open)(struct inode *, struct file*); // 打開
29 int(*flush)(struct file*);
30 int(*release)(struct inode *, struct file*); / 關(guān)閉
32 int(*synch)(struct file *, struct dentry *, int datasync); // 刷新待處理的數(shù)據(jù)
34 int(*aio_fsync)(struct kiocb *, int datasync); // 異步fsync
36 int(*fasync)(int, struct file *, int); // 通知設(shè)備FASYNC標(biāo)志發(fā)生變化
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); // 在進(jìn)程地址空間找到一個(gè)將底層設(shè)備中的內(nèi)存段映射的位置
49 int(*check_flags)(int); // 允許模塊檢查傳遞給fcntl(F_SETEL...)調(diào)用的標(biāo)志
51 int(*dir_notify)(struct file *filp, unsigned long arg); // 僅對文件系統(tǒng)有效,驅(qū)動程序不必實(shí)現(xiàn)
53 int(*flock)(struct file *, int, struct file_lock*);
54 };
llseek()函數(shù)用來修改一個(gè)文件的當(dāng)前讀寫位置,并將新位置返回,在出錯(cuò)時(shí),這個(gè)函數(shù)返回一個(gè)負(fù)值
read()函數(shù)用來從設(shè)備中讀取數(shù)據(jù),成功時(shí)函數(shù)返回讀取的字節(jié)數(shù),出錯(cuò)時(shí)返回一個(gè)負(fù)值。
write()函數(shù)向設(shè)備發(fā)送數(shù)據(jù),成功時(shí)該函數(shù)返回寫入的字節(jié)數(shù)。如果此函數(shù)未被實(shí)現(xiàn),當(dāng)用戶進(jìn)行write()系統(tǒng)調(diào)用時(shí),將得到-EINVAL返回值。
readdir()函數(shù)僅用于目錄,設(shè)備節(jié)點(diǎn)不需要實(shí)現(xiàn)它。
ioctl()提供設(shè)備相關(guān)控制命令的實(shí)現(xiàn) (既不是讀操作也不是寫操作) , 當(dāng)調(diào)用成功時(shí),返回給調(diào)用程序一個(gè)非負(fù)值。內(nèi)核本身識別部分控制命令,而不必調(diào)用設(shè)備驅(qū)動中的
ioctl()。如果設(shè)備不提供ioctl()函數(shù),對于內(nèi)核不能識別的命令,用戶進(jìn)行ioctl()系統(tǒng)調(diào)用時(shí)將獲得-EINVAL返回值。
mmap()函數(shù)將設(shè)備內(nèi)存映射到進(jìn)程內(nèi)存中,如果設(shè)備驅(qū)動未實(shí)現(xiàn)此函數(shù),用戶進(jìn)行 mmap()系統(tǒng)調(diào)用時(shí)將獲得-ENODEV返回值。 這個(gè)函數(shù)對于幀緩沖等設(shè)備特別有意義。
3)字符設(shè)備驅(qū)動的組成
A、字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
字符設(shè)備驅(qū)動模塊加載函數(shù)中應(yīng)該實(shí)現(xiàn)設(shè)備號的申請和cdev的注冊, 而在卸載函數(shù)中應(yīng)實(shí)現(xiàn)設(shè)備號的釋放和 cdev的注銷常見的設(shè)備結(jié)構(gòu)體、模塊加載和卸載函數(shù)形式如代碼清單:
1 //設(shè)備結(jié)構(gòu)體
2 struct xxx_dev_t
3 {
4 struct cdev cdev;
5 ...
6 } xxx_dev;
7 //設(shè)備驅(qū)動模塊加載函數(shù)
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; //獲取字符設(shè)備號
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); //注冊設(shè)備
24 ...
25 }
26 //設(shè)備驅(qū)動模塊卸載函數(shù)
27 static void _ _exit xxx_exit(void)
28 {
29 unregister_chrdev_region(xxx_dev_no, 1); //釋放占用的設(shè)備號
30 cdev_del(&xxx_dev.cdev); //注銷設(shè)備
31 ...
32 }
B、字符設(shè)備驅(qū)動的file_operations 結(jié)構(gòu)體中成員函數(shù)
file_operations 結(jié)構(gòu)體中成員函數(shù)是字符設(shè)備驅(qū)動與內(nèi)核的接口,是用戶空間對Linux進(jìn)行系統(tǒng)調(diào)用終的落實(shí)者。 大多數(shù)字符設(shè)備驅(qū)動會實(shí)現(xiàn)read()、 write()和 ioctl()函數(shù),常見的字符設(shè)備驅(qū)動的這3個(gè)函數(shù)的形式如代碼清單
1 /* 讀設(shè)備*/
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 }
設(shè)備驅(qū)動的讀函數(shù)中,filp是文件結(jié)構(gòu)體指針,buf是用戶空間內(nèi)存的地址,該地址在內(nèi)核空間不能直接讀寫,count是要讀的字節(jié)數(shù),f_pos是讀的位置相對于文件開頭的偏移。
9 /* 寫設(shè)備*/
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 }
設(shè)備驅(qū)動的寫函數(shù)中,filp是文件結(jié)構(gòu)體指針,buf是用戶空間內(nèi)存的地址,該地址在內(nèi)核空間不能直接讀寫,count是要寫的字節(jié)數(shù),f_pos是寫的位置相對于文件開頭的偏移
17 /* ioctl函數(shù) */
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 控制函數(shù)的cmd參數(shù)為事先定義的I/O 控制命令, 而 arg為對應(yīng)于該命令的參數(shù)。例如對于串行設(shè)備,如果SET_BAUDRATE 是一個(gè)設(shè)置波特率的命令,那后面的arg就應(yīng)該是波特率值。
讀和寫函數(shù)中的_ _user 是一個(gè)宏,表明其后的指針指向用戶空間
在字符設(shè)備驅(qū)動中,需要定義一個(gè) file_operations 的實(shí)例,并將具體設(shè)備驅(qū)動的函數(shù)賦值給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 };
华清图书馆
0元电子书,限时免费申领10本华清图书PDF版
扫码关注华清远见公众号