大部分學(xué)習(xí)者的最終目的就是學(xué)習(xí) Linux驅(qū)動(dòng)開(kāi)發(fā),Linux中的外設(shè)驅(qū)動(dòng)可以分為:字符設(shè)備驅(qū)動(dòng)、塊設(shè)備驅(qū)動(dòng)和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)。
|?字符設(shè)備驅(qū)動(dòng)簡(jiǎn)介
字符設(shè)備是 Linux 驅(qū)動(dòng)中最基本的一類設(shè)備驅(qū)動(dòng),字符設(shè)備就是一個(gè)一個(gè)字節(jié),按照字節(jié)流進(jìn)行讀寫操作的設(shè)備,讀寫數(shù)據(jù)是分先后順序的。比如最常見(jiàn)的點(diǎn)燈、按鍵、IIC、SPI,LCD 等等都是字符設(shè)備,這些設(shè)備的驅(qū)動(dòng)就叫做字符設(shè)備驅(qū)動(dòng)。
Linux 應(yīng)用程序?qū)︱?qū)動(dòng)程序的調(diào)用流程
在 Linux 中一切皆為文件,驅(qū)動(dòng)加載成功以后會(huì)在“/dev”目錄下生成一個(gè)相應(yīng)的文件,應(yīng)用程序通過(guò)對(duì)這個(gè)名為“/dev/xxx”(xxx 是具體的驅(qū)動(dòng)文件名字)的文件進(jìn)行相應(yīng)的操作即可實(shí)現(xiàn)對(duì)硬件的操作。 比如現(xiàn)在有個(gè)叫做/dev/led 的驅(qū)動(dòng)文件,此文件是 led 燈的驅(qū)動(dòng)文件。應(yīng)用程序使用 open 函數(shù)來(lái)打開(kāi)文件/dev/led,使用完成以后使用 close 函數(shù)關(guān)閉/dev/led 這個(gè)文件。open和 close 就是打開(kāi)和關(guān)閉 led 驅(qū)動(dòng)的函數(shù),如果要點(diǎn)亮或關(guān)閉 led,那么就使用 write 函數(shù)來(lái)操作,也就是向此驅(qū)動(dòng)寫入數(shù)據(jù),這個(gè)數(shù)據(jù)就是要關(guān)閉還是要打開(kāi) led 的控制參數(shù)。如果要獲取led 燈的狀態(tài),就用 read 函數(shù)從驅(qū)動(dòng)中讀取相應(yīng)的狀態(tài)。
應(yīng)用程序運(yùn)行在用戶空間,而Linux驅(qū)動(dòng)屬于內(nèi)核的一部分,因此驅(qū)動(dòng)運(yùn)行于內(nèi)核空間。當(dāng)應(yīng)用層通過(guò)open函數(shù)打開(kāi)/dev/led 這個(gè)驅(qū)動(dòng)時(shí),因用戶空間不能直接操作內(nèi)核,因此會(huì)使用“系統(tǒng)調(diào)用”的方法來(lái)從用戶空間“陷入”到內(nèi)核空間,實(shí)現(xiàn)對(duì)底層驅(qū)動(dòng)的操作。
比如應(yīng)用程序調(diào)用了open這個(gè)函數(shù),則在驅(qū)動(dòng)程序中也應(yīng)有一個(gè)對(duì)應(yīng)的open的函數(shù)。
Linux內(nèi)核驅(qū)動(dòng)操作函數(shù)
每一個(gè)系統(tǒng)調(diào)用,在驅(qū)動(dòng)中都有與之對(duì)應(yīng)的一個(gè)驅(qū)動(dòng)函數(shù),在 Linux 內(nèi)核文件 include/linux/fs.h 中有個(gè)叫做 file_operations 的結(jié)構(gòu)體,此結(jié)構(gòu)體就是 Linux 內(nèi)核驅(qū)動(dòng)操作函數(shù)集合:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, constchar __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsignedint, unsignedlong); long (*compat_ioctl) (struct file *, unsignedint, unsignedlong); int (*mmap) (struct file *, struct vm_area_struct *); int (*mremap)(struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); /*省略若干行...*/ };其中常用的函數(shù)有以下這幾種:
?
owner:擁有該結(jié)構(gòu)體的模塊的指針,一般設(shè)置為THIS_MODULE。
llseek函數(shù):用于修改文件當(dāng)前的讀寫位置。
read函數(shù):用于讀取設(shè)備文件。
write函數(shù):用于向設(shè)備文件寫入(發(fā)送)數(shù)據(jù)。
poll函數(shù):是個(gè)輪詢函數(shù),用于查詢?cè)O(shè)備是否可以進(jìn)行非阻塞的讀寫。
unlocked_ioctl函數(shù):提供對(duì)于設(shè)備的控制功能, 與應(yīng)用程序中的 ioctl 函數(shù)對(duì)應(yīng)。
compat_ioctl函數(shù):與 unlocked_ioctl功能一樣,區(qū)別在于在 64 位系統(tǒng)上,32 位的應(yīng)用程序調(diào)用將會(huì)使用此函數(shù)。在 32 位的系統(tǒng)上運(yùn)行 32 位的應(yīng)用程序調(diào)用的是unlocked_ioctl。
mmap函數(shù):用于將將設(shè)備的內(nèi)存映射到進(jìn)程空間中(也就是用戶空間),一般幀緩沖設(shè)備會(huì)使用此函數(shù), 比如 LCD 驅(qū)動(dòng)的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應(yīng)用程序就可以直接操作顯存了,這樣就不用在用戶空間和內(nèi)核空間之間來(lái)回復(fù)制。
open函數(shù):用于打開(kāi)設(shè)備文件。
release函數(shù):用于釋放(關(guān)閉)設(shè)備文件,與應(yīng)用程序中的 close 函數(shù)對(duì)應(yīng)。
fasync函數(shù):用于刷新待處理的數(shù)據(jù),用于將緩沖區(qū)中的數(shù)據(jù)刷新到磁盤中。
aio_fsync函數(shù):與fasync功能類似,只是 aio_fsync 是異步刷新待處理的
Linux 驅(qū)動(dòng)有兩種運(yùn)行方式
Linux 驅(qū)動(dòng)有兩種運(yùn)行方式,第一種就是將驅(qū)動(dòng)編譯進(jìn) Linux 內(nèi)核中,這樣當(dāng) Linux 內(nèi)核啟動(dòng)的時(shí)候就會(huì)自動(dòng)運(yùn)行驅(qū)動(dòng)程序。第二種就是將驅(qū)動(dòng)編譯成模塊(Linux 下模塊擴(kuò)展名為.ko),在Linux 內(nèi)核啟動(dòng)以后使用“insmod”命令加載驅(qū)動(dòng)模塊。在驅(qū)動(dòng)開(kāi)發(fā)階段一般都將其編譯為模塊,不需要編譯整個(gè)Linux代碼,方便調(diào)試驅(qū)動(dòng)程序。當(dāng)驅(qū)動(dòng)開(kāi)發(fā)完成后,根據(jù)實(shí)際需要,可以選擇是否將驅(qū)動(dòng)編譯進(jìn)Linux內(nèi)核中。 Linux 設(shè)備號(hào) 為了方便管理,Linux 中每個(gè)設(shè)備都有一個(gè)設(shè)備號(hào),設(shè)備號(hào)由主設(shè)備號(hào)和次設(shè)備號(hào)兩部分組成,主設(shè)備號(hào)表示某一個(gè)具體的驅(qū)動(dòng),次設(shè)備號(hào)表示使用這個(gè)驅(qū)動(dòng)的各個(gè)設(shè)備。Linux 提供了一個(gè)名為 dev_t 的數(shù)據(jù)類型表示設(shè)備號(hào),dev_t 定義在文件 include/linux/types.h 里面,定義如下:
typedef __u32 __kernel_dev_t; ...... typedef __kernel_dev_t dev_t;dev_t 其實(shí)就是 unsigned int 類型,是一個(gè) 32 位的數(shù)據(jù)類型。這 32 位的數(shù)據(jù)構(gòu)成了主設(shè)備號(hào)和次設(shè)備號(hào)兩部分,其中高 12 位為主設(shè)備號(hào),低 20 位為次設(shè)備號(hào)。因此 Linux系統(tǒng)中主設(shè)備號(hào)范圍為 0~4095,所以大家在選擇主設(shè)備號(hào)的時(shí)候一定不要超過(guò)這個(gè)范圍。 在文件 include/linux/kdev_t.h 中提供了幾個(gè)關(guān)于設(shè)備號(hào)的操作函數(shù)(本質(zhì)是宏),如下所示:
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) 8 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
?
MINORBITS:表示次設(shè)備號(hào)位數(shù),一共20位
MINORMASK:表示次設(shè)備號(hào)掩碼
MAJOR:用于從dev_t中獲取主設(shè)備號(hào),將dev_t右移20位即可
MINOR:用于從dev_t中獲取次設(shè)備號(hào),取dev_t的低20位的值即可
MKDEV:用于將給定的主設(shè)備號(hào)和次設(shè)備號(hào)的值組合成dev_t類型的設(shè)備號(hào)
設(shè)備號(hào)的分配有兩種方式,第一種是靜態(tài)分配設(shè)備號(hào),需要開(kāi)發(fā)者手動(dòng)指定設(shè)備號(hào),并且要注意不能與已有的重復(fù),一些常用的設(shè)備號(hào)已經(jīng)被Linux內(nèi)核開(kāi)發(fā)者給分配掉了,使用“cat /proc/devices”命令可查看當(dāng)前系統(tǒng)中所有已經(jīng)使用了的設(shè)備號(hào)。;第二種是動(dòng)態(tài)分配設(shè)備號(hào),靜態(tài)分配設(shè)備號(hào)很容易帶來(lái)沖突問(wèn)題,Linux 社區(qū)推薦使用動(dòng)態(tài)分配設(shè)備號(hào),在注冊(cè)字符設(shè)備之前先申請(qǐng)一個(gè)設(shè)備號(hào),系統(tǒng)會(huì)自動(dòng)給你一個(gè)沒(méi)有被使用的設(shè)備號(hào),這樣就避免了沖突。 設(shè)備號(hào)的申請(qǐng)函數(shù)
/* dev:保存申請(qǐng)到的設(shè)備號(hào)。 baseminor:次設(shè)備號(hào)起始地址,一般 baseminor 為?0,也就是說(shuō)次設(shè)備號(hào)從?0?開(kāi)始。 count:要申請(qǐng)的設(shè)備號(hào)數(shù)量。 name:設(shè)備名字。 */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)? 設(shè)備號(hào)釋放函數(shù)
/* from:要釋放的設(shè)備號(hào)。 count:表示從 from 開(kāi)始,要釋放的設(shè)備號(hào)數(shù)量。 */ void unregister_chrdev_region(dev_t from, unsigned count)
?
| 字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)模板
加載與卸載
在編寫驅(qū)動(dòng)的時(shí)候需要注冊(cè)模塊加載和卸載這兩種函數(shù):
?
module_init(xxx_init); //注冊(cè)模塊加載函數(shù) module_exit(xxx_exit); //注冊(cè)模塊卸載函數(shù)
?
module_init()用來(lái)向Linux內(nèi)核注冊(cè)一個(gè)模塊加載函數(shù),參數(shù)xxx_init就是需要注冊(cè)的具體函數(shù),當(dāng)使用 “insmod” 命令加載驅(qū)動(dòng)的時(shí)候,xxx_init這個(gè)函數(shù)就會(huì)被調(diào)用。
module_exit()用來(lái)向Linux內(nèi)核注冊(cè)一個(gè)模塊卸載函數(shù),參數(shù)xxx_exit就是需要注冊(cè)的具體函數(shù),當(dāng)使用“rmmod”命令卸載具體驅(qū)動(dòng)的時(shí)候 xxx_exit函數(shù)就會(huì)被調(diào)用。
字符設(shè)備驅(qū)動(dòng)模塊加載和卸載模板如下所示:
?
/* 驅(qū)動(dòng)入口函數(shù) */ static int __init xxx_init(void) { /*入口函數(shù)內(nèi)容 */ return0; } /* 驅(qū)動(dòng)出口函數(shù) */ static void __exit xxx_exit(void) { /*出口函數(shù)內(nèi)容*/ } /*指定為驅(qū)動(dòng)的入口和出口函數(shù) */ module_init(xxx_init); module_exit(xxx_exit);
?
驅(qū)動(dòng)編譯完成以后擴(kuò)展名為.ko, 有兩種命令可以加載驅(qū)動(dòng)模塊:
insmod:最簡(jiǎn)單的模塊加載命令,用于加載指定的.ko模塊,此命令不能解決模塊的依賴關(guān)系
modprobe:該命令會(huì)分析模塊的依賴關(guān)系,將所有的依賴模塊都加載到內(nèi)核中,因此更智能
modprobe 命令默認(rèn)會(huì)去/lib/modules/
卸載驅(qū)動(dòng)也有兩種命令:
rmmod:例如使用rmmod drv.ko來(lái)卸載 drv.ko這一個(gè)模塊
modprobe -r:該命令除了卸載指定的驅(qū)動(dòng),還卸載其所依賴的其他模塊,若這些依賴模塊還在被其它模塊使用,就不能使用 modprobe來(lái)卸載驅(qū)動(dòng)模塊! ?
注冊(cè)與注銷
對(duì)于字符設(shè)備驅(qū)動(dòng)而言,當(dāng)驅(qū)動(dòng)模塊加載成功以后需要注冊(cè)字符設(shè)備,同樣,卸載驅(qū)動(dòng)模塊的時(shí)候也需要注銷掉字符設(shè)備。
字符設(shè)備的注冊(cè)函數(shù)原型如下所示:
?
/* major:主設(shè)備號(hào) name:設(shè)備名字,指向一串字符串 fops:結(jié)構(gòu)體 file_operations 類型指針,指向設(shè)備的操作函數(shù)集合變量 */ static?inline?int?register_chrdev(unsigned?int?major,?const?char?*name,const?struct?file_operations?*fops)
?
字符設(shè)備的注銷函數(shù)原型如下所示:
?
/* major:要注銷的設(shè)備對(duì)應(yīng)的主設(shè)備號(hào)。 name:要注銷的設(shè)備對(duì)應(yīng)的設(shè)備名。 */ static inline void unregister_chrdev(unsigned int major, const char *name)
?
一般字符設(shè)備的注冊(cè)在驅(qū)動(dòng)模塊的入口函數(shù)?xxx_init 中進(jìn)行,字符設(shè)備的注銷在驅(qū)動(dòng)模塊的出口函數(shù)?xxx_exit 中進(jìn)行。
?
staticstruct file_operations test_fops; /* 驅(qū)動(dòng)入口函數(shù) */ static int __init xxx_init(void) { ??/*?入口函數(shù)具體內(nèi)容?*/ ??int?retvalue?=?0;? ??/*?注冊(cè)字符設(shè)備驅(qū)動(dòng)?*/ ??retvalue?=?register_chrdev(200,?"chrtest",?&test_fops);? if(retvalue < 0) { /* 字符設(shè)備注冊(cè)失敗, 自行處理 */ } return0; } /* 驅(qū)動(dòng)出口函數(shù) */ static void __exit xxx_exit(void) { /* 注銷字符設(shè)備驅(qū)動(dòng) */ unregister_chrdev(200, "chrtest"); } /* 將上面兩個(gè)函數(shù)指定為驅(qū)動(dòng)的入口和出口函數(shù) */ module_init(xxx_init); module_exit(xxx_exit);要注意的一點(diǎn)就是,選擇沒(méi)有被使用的主設(shè)備號(hào);
// 查看當(dāng)前已經(jīng)被使用掉的設(shè)備號(hào) cat /proc/devices
?
實(shí)現(xiàn)設(shè)備的具體操作函數(shù)
file_operations 結(jié)構(gòu)體就是設(shè)備的具體操作函數(shù),在上圖代碼中定義了file_operations結(jié)構(gòu)體類型的變量test_fops,但是還沒(méi)對(duì)其進(jìn)行初始化,也就是初始化其中的 open、release、read 和 write 等具體的設(shè)備操作函數(shù)。
假設(shè)對(duì)chrtest這個(gè)設(shè)備有如下兩個(gè)要求:
能夠?qū)崿F(xiàn)打開(kāi)和關(guān)閉操作:需要實(shí)現(xiàn) file_operations 中的open和release?這兩個(gè)函數(shù)
能夠?qū)崿F(xiàn)進(jìn)行讀寫操作:需要實(shí)現(xiàn) file_operations 中的read和write這兩個(gè)函數(shù)
?
/*打開(kāi)設(shè)備*/ static int chrtest_open(struct inode *inode, struct file *filp) { ??/*用戶實(shí)現(xiàn)具體功能*/ return0; } /*從設(shè)備讀取*/ static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { ??/*用戶實(shí)現(xiàn)具體功能*/ return0; } /*向設(shè)備寫數(shù)據(jù)*/ static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { ??/*用戶實(shí)現(xiàn)具體功能*/ return0; } /*關(guān)閉釋放設(shè)備*/ static int chrtest_release(struct inode *inode, struct file *filp) { ??/*用戶實(shí)現(xiàn)具體功能*/ return0; } /*文件操作結(jié)構(gòu)體*/ staticstruct file_operations test_fops = { .owner = THIS_MODULE, .open = chrtest_open, .read = chrtest_read, .write = chrtest_write, .release = chrtest_release, }; /*驅(qū)動(dòng)入口函數(shù)*/ static int __init xxx_init(void) { /*入口函數(shù)具體內(nèi)容*/ int retvalue = 0; /*注冊(cè)字符設(shè)備驅(qū)動(dòng)*/ retvalue = register_chrdev(200, "chrtest", &test_fops); if(retvalue < 0) { /*字符設(shè)備注冊(cè)失敗*/ } return0; } /*驅(qū)動(dòng)出口函數(shù)*/ static void __exit xxx_exit(void) { /*注銷字符設(shè)備驅(qū)動(dòng)*/ unregister_chrdev(200, "chrtest"); } /*指定為驅(qū)動(dòng)的入口和出口函數(shù)*/ module_init(xxx_init); module_exit(xxx_exit);
?
一開(kāi)始編寫了四個(gè)函數(shù):chrtest_open、chrtest_read、chrtest_write和 chrtest_release。這四個(gè)函數(shù)就是 chrtest 設(shè)備的 open、read、write 和 release 操作函數(shù)。結(jié)構(gòu)體配置就是初始化 test_fops 的 open、read、write 和 release 這四個(gè)成員變量。
? 添加 LICENSE 和作者信息 最后需要在驅(qū)動(dòng)中加入 LICENSE(許可) 信息和作者信息,其中 LICENSE 是必須添加的,否則的話編譯的時(shí)候會(huì)報(bào)錯(cuò),作者信息可以添加也可以不添加。 LICENSE 和作者信息的添加使用如下兩個(gè)函數(shù):
MODULE_LICENSE() //添加模塊 LICENSE 信息 MODULE_AUTHOR() //添加模塊作者信息?
/* 打開(kāi)設(shè)備 */ static int chrtest_open(struct inode *inode, struct file *filp) { /* 用戶實(shí)現(xiàn)具體功能 */ return 0; } ...... /* 將上面兩個(gè)函數(shù)指定為驅(qū)動(dòng)的入口和出口函數(shù) */ module_init(xxx_init); module_exit(xxx_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");? LICENSE 采用 GPL 協(xié)議,有時(shí)候協(xié)議是很有必要的,特別是開(kāi)源的項(xiàng)目,對(duì)于常用的協(xié)議還是要有一定的了解。?
?
評(píng)論
查看更多