linux字符设备开发
本篇基于ldd3中第三章,原書自帶的源碼隨著內(nèi)核版本更新已經(jīng)不能運(yùn)行,代碼需要進(jìn)行升級(jí),文章參考代碼能在內(nèi)核版本4.17.2運(yùn)行。
1.?? 分配設(shè)備編號(hào)
建立一個(gè)字符驅(qū)動(dòng)時(shí),需要做的第一件事是獲取一個(gè)或多個(gè)設(shè)備編號(hào)來使用.此目的必要的函數(shù)是 register_chrdev_region.
注冊(cè)字符設(shè)備函數(shù)執(zhí)行后會(huì)出現(xiàn)在/proc/devices和sysfs中:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
first是要分配的起始設(shè)備編號(hào). first 的次編號(hào)部分常常是 0, 但是沒有要求是那個(gè)效果. count 是請(qǐng)求的連續(xù)設(shè)備編號(hào)的總數(shù). 注意, 如果 count 太大,要求的范圍可能溢出到下一個(gè)次編號(hào); 但是只要要求的編號(hào)范圍可用, 一切都仍然會(huì)正確工作. name 是應(yīng)當(dāng)連接到這個(gè)編號(hào)范圍 的設(shè)備的名子; 它會(huì)出現(xiàn)在 /proc/devices 和 sysfs 中.
一些主設(shè)備編號(hào)是靜態(tài)分派給最普通的設(shè)備的. 一個(gè)這些設(shè)備的列表在內(nèi)核源碼樹的 Documentation/devices.txt 中.
對(duì)于新驅(qū)動(dòng),建議使用動(dòng)態(tài)分配來獲取你的主設(shè)備編號(hào), 而不是隨機(jī)選取一個(gè)當(dāng)前空閑的編號(hào).使用 alloc_chrdev_region, 不是 register_chrdev_region.
這個(gè)alloc_chrdev_region是動(dòng)態(tài)分配主設(shè)備號(hào)的,因?yàn)槟憧赡懿恢老到y(tǒng)中哪些主設(shè)備號(hào)可以給你的驅(qū)動(dòng)程序使用,動(dòng)態(tài)分配的一個(gè)缺點(diǎn)就是不能提前分配設(shè)備節(jié)點(diǎn)(注:通過MKNOD來創(chuàng)建節(jié)點(diǎn)的):
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
??????????? 兩個(gè)函數(shù)從名字上也可以區(qū)分,一個(gè)是注冊(cè),表示我有了主設(shè)備號(hào)只是想內(nèi)核注冊(cè),而alloc_則是分配,意味著讓內(nèi)核來幫著allocate一下。
??????????? 注冊(cè)對(duì)應(yīng)的是注銷,這個(gè)是驅(qū)動(dòng)程序卸載時(shí)候必須做的,從哪里來還那里去,對(duì)應(yīng)的函數(shù)是:
void unregister_chrdev_region(dev_t from, unsigned count)
另外,獲取設(shè)備主設(shè)備號(hào)和次設(shè)備號(hào),使用如下宏:
MAJOR(dev_t dev);
MINOR(dev_t dev);
將主、次設(shè)備號(hào)轉(zhuǎn)換成一個(gè)設(shè)備號(hào),如下:
MKDEV(int major, int minor);
2.?? 基礎(chǔ)性的驅(qū)動(dòng)操作
基礎(chǔ)性的驅(qū)動(dòng)操作包括 3 個(gè)重要的內(nèi)核數(shù) 據(jù)結(jié)構(gòu), 稱為 file_operations, file, 和 inode.
2.1???? file_operation
傳統(tǒng)上, 一個(gè) file_operation 結(jié)構(gòu)或者其一個(gè)指針稱為 fops( 或者它的一些變體). 結(jié)構(gòu)中的每個(gè)成員 必須指向驅(qū)動(dòng)中的函數(shù), 這些函數(shù)實(shí)現(xiàn)一個(gè)特別的操作
??????????? 其中定義的操作函數(shù)并不是需要全部實(shí)現(xiàn),根據(jù)具體驅(qū)動(dòng)實(shí)現(xiàn)針對(duì)的函數(shù)功能即可。字符設(shè)備主要有一下函數(shù)需要實(shí)現(xiàn):
owner,llseek,read,write,ioctl,open,release.
2.2???? file
file結(jié)構(gòu)表示一個(gè)打開的文件。在內(nèi)核中指向file的指針經(jīng)常叫做filp,就是file pointer,以免是file搞混。
2.3???? inode
inode結(jié)構(gòu)由內(nèi)核在內(nèi)部用來表示文件,代表打開文件描述符的文件結(jié)構(gòu)是不同的。多個(gè)打開的描述符可能指向一個(gè)單個(gè)inode結(jié)構(gòu)。
相對(duì)于字符設(shè)備驅(qū)動(dòng)程序,我們先使用i_rdev和i_cdev。
dev_t i_rdev;//實(shí)際設(shè)備的節(jié)點(diǎn)
Struct cdev *i_cdev; //指向字符設(shè)備驅(qū)動(dòng)程序指針
??????????? 現(xiàn)在可以通過宏如下,來獲取節(jié)點(diǎn)的主、次設(shè)備號(hào):
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
3.?? 字符設(shè)備注冊(cè)
內(nèi)核中使用cdev結(jié)構(gòu)體來表示字符設(shè)備。可以通過cdev_alloc來分配。
然后使用cdev_init來初始化。
?????? 我們可以將cdev結(jié)構(gòu)體嵌入到我們自己的設(shè)備結(jié)構(gòu)體中,這也正是例子所使用的方法。
??????????? 最后告訴內(nèi)核添加進(jìn)去,如果不告訴內(nèi)核就是空有一身資源而無施展之處,通過函數(shù)cdev_add。一旦添加,那么內(nèi)核就可能來騷擾設(shè)備,所以要確保所有都準(zhǔn)備好的時(shí)候調(diào)用cdev_add函數(shù)。
??????????? 去除設(shè)備調(diào)用函數(shù)cdev_del.
4.?? 設(shè)備布局
設(shè)備由內(nèi)存來模擬,其設(shè)備中的布局如下圖。
數(shù)據(jù)結(jié)構(gòu)如下,scull_qset結(jié)構(gòu)體非常簡(jiǎn)單,其實(shí)現(xiàn)一個(gè)鏈表的同時(shí),每個(gè)元素同時(shí)指向塊內(nèi)存:
struct scull_qset {
??????? void **data;
??????? struct scull_qset *next;
};
設(shè)備的結(jié)構(gòu)體如下:
struct scull_dev {
??????? struct scull_qset *data;? /* Pointer to first quantum set */
??????? int quantum;? ????????????/* the current quantum size */
??????? int qset;???????????????? /* the current array size */
??????? unsigned long size;?????? /* amount of data stored here */
??????? unsigned int access_key;? /* used by sculluid and scullpriv */
??????? struct semaphore sem;???? /* 互斥所*/
??????? struct cdev cdev;???????? /* Char device structure????????????? */
};
設(shè)備的大小為quantum*qset。
?
5.?? 代碼解析
5.1???? 初始化
初始化函數(shù)為scull_init_module
如果指定了主設(shè)備號(hào),調(diào)用register_chrdev_region,否則調(diào)用alloc_chrdev_region,并獲取主設(shè)備號(hào)。
然后分配設(shè)備scull_dev結(jié)構(gòu)體數(shù)組scull_devices,數(shù)量為SCULL_NR_DEVS子設(shè)備號(hào)數(shù)量,并初始化為0。接著根據(jù)需要分配的內(nèi)存空間大小正式初始化scull_devices,其中會(huì)調(diào)用scull_setup_cdev函數(shù)(該函數(shù)中會(huì)使用cdev_init,cdev_add函數(shù),初始化設(shè)備結(jié)構(gòu)中嵌入的cdev,同時(shí)綁定scull_fops),scull_fops結(jié)構(gòu)如下。
struct file_operations scull_fops = {
??????? .owner =??? THIS_MODULE,
??????? .llseek =?? scull_llseek,
?? ?????.read =???? scull_read,
??????? .write =??? scull_write,
??????? .unlocked_ioctl = scull_ioctl,
??????? .open =???? scull_open,
??????? .release =? scull_release,
};
??????????? 然后調(diào)用
初始化時(shí)候分配了指定數(shù)量的設(shè)備數(shù)據(jù)結(jié)構(gòu),并初始化后增加到了內(nèi)核,可以在/proc/devices中看到,此時(shí)其實(shí)并沒有分配設(shè)備的內(nèi)存空間,因?yàn)檫€不需要。
?
5.2???? 退出
退出函數(shù)是scull_cleanup_module,該函數(shù)先獲取設(shè)備號(hào)。然后根據(jù)設(shè)備數(shù)量循環(huán)調(diào)用scull_trim,cdev_del函數(shù)來刪除cdev設(shè)備,最后調(diào)用kfree釋放在初始化中分配的結(jié)構(gòu)體數(shù)據(jù)。
??????????? 其中scull_trim函數(shù)負(fù)責(zé)釋放分配的內(nèi)存空間。
5.3???? 操作函數(shù)集合
驅(qū)動(dòng)的open函數(shù)
通過inode獲取設(shè)備結(jié)構(gòu)體的指針,這個(gè)通過內(nèi)核中的container_of函數(shù)來實(shí)現(xiàn),并將其保存到文件對(duì)象的private_data中以備后用。
如果是寫模式打開,則將之前該設(shè)備分配的空間使用scull_trim函數(shù)清空。
?
llseek
返回當(dāng)前讀寫位置。
?
release
直接返回0,并不做操作。
read
先從filp->private_data中獲取設(shè)備地址。獲取設(shè)備總空間大小。
如果要讀的位置大于空間大小則退出。否則計(jì)算要讀取的正確位置,因?yàn)槊總€(gè)scull_qset結(jié)構(gòu)體指向的item空間大小是固定的,其相互之間是鏈表方式連接的。
然后會(huì)調(diào)用scull_follow函數(shù),該函數(shù)中會(huì)通過kmalloc函數(shù)動(dòng)態(tài)分配scull_qset結(jié)構(gòu)體(如果沒有被分配過),直到包含的item累計(jì)理論空間能包含要讀取的地址。然后返回最后一個(gè)scull_qset結(jié)構(gòu)體,如果返回null,說明系統(tǒng)內(nèi)存空間不夠了。
??????????? 最后調(diào)用copy_to_user函數(shù),將內(nèi)容復(fù)制到用戶的buf中。然后更新文件讀取位置,并返回所讀取字節(jié)大小。
write
獲取設(shè)備結(jié)構(gòu)體的指針,以及對(duì)應(yīng)的設(shè)備空間相關(guān)大小。如item,quantum。
計(jì)算文件讀取位置,調(diào)用scull_follow,返回位置所在的那個(gè)item的scull_qset結(jié)構(gòu)體,如果該結(jié)構(gòu)體對(duì)應(yīng)的數(shù)據(jù)指針為NULL,說明之前沒有給其分配內(nèi)存空間,則調(diào)用kmalloc分配qset指向quantum的指針數(shù)數(shù)組。
然后根據(jù)讀取位置,通過函數(shù)kmalloc函數(shù)分配quantum的內(nèi)存空間。
最后調(diào)用用copy_from_user函數(shù)將數(shù)據(jù)復(fù)制到內(nèi)存中的quantum片段。
?
unlocked_ioctl
對(duì)ioctl的實(shí)現(xiàn)。
6.?? 使用測(cè)試
加載驅(qū)動(dòng)后,執(zhí)行如下,其中247是主設(shè)備號(hào),在/proc/devices中可以查看到:
mknod /dev/scull0 c 247 0
mknod /dev/scull1 c 247 1
mknod /dev/scull2 c 247 2
mknod /dev/scull3 c 247 3
然后可以使用dd命令或者cp命令復(fù)制內(nèi)容到設(shè)備中。
# echo "hello" > scull0
# cat scull0
hello
7.?? 代碼
https://github.com/kernel-z/ldd3/tree/master/scull
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的linux字符设备开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Heartbeat VIP/IP 与 别
- 下一篇: 【VMware vSAN 6.6】5.8