一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

ArmSoM系列板卡 嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)實(shí)戰(zhàn)指南 之 字符設(shè)備驅(qū)動(dòng)

Rockchip系列教程 ? 來(lái)源:Rockchip系列教程 ? 作者:Rockchip系列教程 ? 2024-04-10 09:53 ? 次閱讀

字符設(shè)備驅(qū)動(dòng)

本章,我們將學(xué)習(xí)字符設(shè)備使用、字符設(shè)備驅(qū)動(dòng)相關(guān)的概念,理解字符設(shè)備驅(qū)動(dòng)程序的基本框架,并從源碼上分析字符設(shè)備驅(qū)動(dòng)實(shí)現(xiàn)和管理等。 主要分為下面五部分:

Linux設(shè)備分類;

字符設(shè)備的抽象,字符設(shè)備設(shè)計(jì)思路;

字符設(shè)備相關(guān)的概念以及數(shù)據(jù)結(jié)構(gòu),了解設(shè)備號(hào)等基本概念以及file_operations、file、inode相關(guān)數(shù)據(jù)結(jié)構(gòu);

字符字符設(shè)備驅(qū)動(dòng)程序框架,例如內(nèi)核是如何管理設(shè)備號(hào)的;系統(tǒng)關(guān)聯(lián)、調(diào)用file_operation接口,open函數(shù)所涉及的知識(shí)等等。

設(shè)備驅(qū)動(dòng)程序?qū)嶒?yàn)。

1. Linux設(shè)備分類?

linux是文件型系統(tǒng),所有硬件都會(huì)在對(duì)應(yīng)的目錄(/dev)下面用相應(yīng)的文件表示。 在windows系統(tǒng)中,設(shè)備大家很好理解,像硬盤,磁盤指的是實(shí)實(shí)在在硬件。 而在文件系統(tǒng)的linux下面,都有對(duì)于文件與這些設(shè)備關(guān)聯(lián)的,訪問(wèn)這些文件就可以訪問(wèn)實(shí)際硬件。 像訪問(wèn)文件那樣去操作硬件設(shè)備,一切都會(huì)簡(jiǎn)單很多,不需要再調(diào)用以前com,prt等接口了。 直接讀文件,寫(xiě)文件就可以向設(shè)備發(fā)送、接收數(shù)據(jù)。 按照讀寫(xiě)存儲(chǔ)數(shù)據(jù)方式,我們可以把設(shè)備分為以下幾種:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備。

字符設(shè)備:指應(yīng)用程序按字節(jié)/字符來(lái)讀寫(xiě)數(shù)據(jù)的設(shè)備。 這些設(shè)備節(jié)點(diǎn)通常為傳真、虛擬終端和串口調(diào)制解調(diào)器、鍵盤之類設(shè)備提供流通信服務(wù), 它通常不支持隨機(jī)存取數(shù)據(jù)。字符設(shè)備在實(shí)現(xiàn)時(shí),大多不使用緩存器。系統(tǒng)直接從設(shè)備讀取/寫(xiě)入每一個(gè)字符。 例如,鍵盤這種設(shè)備提供的就是一個(gè)數(shù)據(jù)流,當(dāng)你敲入“cnblogs”這個(gè)字 符串時(shí), 鍵盤驅(qū)動(dòng)程序會(huì)按照和輸入完全相同的順序返回這個(gè)由七個(gè)字符組成的數(shù)據(jù)流。它們是順序的,先返回c,最后是s。

塊設(shè)備:通常支持隨機(jī)存取和尋址,并使用緩存器。 操作系統(tǒng)為輸入輸出分配了緩存以存儲(chǔ)一塊數(shù)據(jù)。當(dāng)程序向設(shè)備發(fā)送了讀取或者寫(xiě)入數(shù)據(jù)的請(qǐng)求時(shí), 系統(tǒng)把數(shù)據(jù)中的每一個(gè)字符存儲(chǔ)在適當(dāng)?shù)木彺嬷?。?dāng)緩存被填滿時(shí),會(huì)采取適當(dāng)?shù)牟僮?把數(shù)據(jù)傳走), 而后系統(tǒng)清空緩存。它與字符設(shè)備不同之處就是,是否支持隨機(jī)存儲(chǔ)。字符型是流形式,逐一存儲(chǔ)。 典型的塊設(shè)備有硬盤、SD卡、閃存等,應(yīng)用程序可以尋址磁盤上的任何位置,并由此讀取數(shù)據(jù)。 此外,數(shù)據(jù)的讀寫(xiě)只能以塊的倍數(shù)進(jìn)行。

網(wǎng)絡(luò)設(shè)備:是一種特殊設(shè)備,它并不存在于/dev下面,主要用于網(wǎng)絡(luò)數(shù)據(jù)的收發(fā)。

Linux內(nèi)核中處處體現(xiàn)面向?qū)ο蟮脑O(shè)計(jì)思想,為了統(tǒng)一形形色色的設(shè)備,Linux系統(tǒng)將設(shè)備分別抽象為struct cdev, struct block_device,struct net_devce三個(gè)對(duì)象,具體的設(shè)備都可以包含著三種對(duì)象從而繼承和三種對(duì)象屬性和操作, 并通過(guò)各自的對(duì)象添加到相應(yīng)的驅(qū)動(dòng)模型中,從而進(jìn)行統(tǒng)一的管理和操作

字符設(shè)備驅(qū)動(dòng)程序適合于大多數(shù)簡(jiǎn)單的硬件設(shè)備,而且比起塊設(shè)備或網(wǎng)絡(luò)驅(qū)動(dòng)更加容易理解, 因此我們選擇從字符設(shè)備開(kāi)始,從最初的模仿,到慢慢熟悉,最終成長(zhǎng)為驅(qū)動(dòng)界的高手。

2. 字符設(shè)備抽象?

Linux內(nèi)核中將字符設(shè)備抽象成一個(gè)具體的數(shù)據(jù)結(jié)構(gòu)(struct cdev),我們可以理解為字符設(shè)備對(duì)象, cdev記錄了字符設(shè)備的相關(guān)信息(設(shè)備號(hào)、內(nèi)核對(duì)象),字符設(shè)備的打開(kāi)、讀寫(xiě)、關(guān)閉等操作接口(file_operations), 在我們想要添加一個(gè)字符設(shè)備時(shí),就是將這個(gè)對(duì)象注冊(cè)到內(nèi)核中,通過(guò)創(chuàng)建一個(gè)文件(設(shè)備節(jié)點(diǎn))綁定對(duì)象的cdev, 當(dāng)我們對(duì)這個(gè)文件進(jìn)行讀寫(xiě)操作時(shí),就可以通過(guò)虛擬文件系統(tǒng),在內(nèi)核中找到這個(gè)對(duì)象及其操作接口,從而控制設(shè)備。

C語(yǔ)言中沒(méi)有面向?qū)ο笳Z(yǔ)言的繼承的語(yǔ)法,但是我們可以通過(guò)結(jié)構(gòu)體的包含來(lái)實(shí)現(xiàn)繼承,這種抽象提取了設(shè)備的共性, 為上層提供了統(tǒng)一接口,使得管理和操作設(shè)備變得很容易。

wKgZomYVR9GANLMOAAFxQgXISF0915.png

在硬件層,我們可以通過(guò)查看硬件的原理圖、芯片的數(shù)據(jù)手冊(cè),確定底層需要配置的寄存器,這類似于裸機(jī)開(kāi)發(fā), 將對(duì)底層寄存器的配置,讀寫(xiě)操作放在文件操作接口里面,也就是實(shí)現(xiàn)file_operations結(jié)構(gòu)體; 在驅(qū)動(dòng)層,我們將文件操作接口注冊(cè)到內(nèi)核,內(nèi)核通過(guò)內(nèi)部散列表來(lái)登記記錄主次設(shè)備號(hào); 在文件系統(tǒng)層,新建一個(gè)文件綁定該文件操作接口,應(yīng)用程序通過(guò)操作指定文件的文件操作接口來(lái)設(shè)置底層寄存器。

實(shí)際上,在Linux上寫(xiě)驅(qū)動(dòng)程序,都是做一些“填空題”。因?yàn)長(zhǎng)inux給我們提供了一個(gè)基本的框架, 我們只需要按照這個(gè)框架來(lái)寫(xiě)驅(qū)動(dòng),內(nèi)核就能很好的接收并且按我們所要求的那樣工作。有句成語(yǔ)工欲善其事,必先利其器, 在理解這個(gè)框架之前我們得花點(diǎn)時(shí)間來(lái)學(xué)習(xí)字符設(shè)備驅(qū)動(dòng)相關(guān)概念及數(shù)據(jù)結(jié)構(gòu)。

3. 相關(guān)概念及數(shù)據(jù)結(jié)構(gòu)?

在linux中,我們使用設(shè)備編號(hào)來(lái)表示設(shè)備,主設(shè)備號(hào)區(qū)分設(shè)備類別,次設(shè)備號(hào)標(biāo)識(shí)具體的設(shè)備。 cdev結(jié)構(gòu)體被內(nèi)核用來(lái)記錄設(shè)備號(hào),而在使用設(shè)備時(shí),我們通常會(huì)打開(kāi)設(shè)備節(jié)點(diǎn),通過(guò)設(shè)備節(jié)點(diǎn)的inode結(jié)構(gòu)體、 file結(jié)構(gòu)體最終找到file_operations結(jié)構(gòu)體,并從file_operations結(jié)構(gòu)體中得到操作設(shè)備的具體方法。

3.1. 設(shè)備號(hào)?

對(duì)于字符的訪問(wèn)是通過(guò)文件系統(tǒng)的名稱進(jìn)行的,這些名稱被稱為特殊文件、設(shè)備文件,或者簡(jiǎn)單稱為文件系統(tǒng)樹(shù)的節(jié)點(diǎn), Linux根目錄下有/dev這個(gè)文件夾,專門用來(lái)存放設(shè)備中的驅(qū)動(dòng)程序,我們可以使用ls -l 以列表的形式列出系統(tǒng)中的所有設(shè)備。 其中,每一行表示一個(gè)設(shè)備,每一行的第一個(gè)字符表示設(shè)備的類型。

文件類型的字符及其意義如下:

-:普通文件(regular file)

d:目錄(directory)

l:符號(hào)鏈接(symbolic link)

c:字符設(shè)備(character device)

b:塊設(shè)備(block device)

p:管道(pipe)

s:套接字(socket)

如下圖:ashmem 是一個(gè)字符設(shè)備c, 它的主設(shè)備號(hào)是10,次設(shè)備號(hào)是124;initctl是一個(gè)符號(hào)鏈接,隨后的權(quán)限字符 rwxrwxrwx 表示該鏈接的所有者、同組用戶和其他用戶均有讀(r)、寫(xiě)(w)和執(zhí)行(x)的權(quán)限;loop0 是一個(gè)塊設(shè)備,它的主設(shè)備號(hào)是7,次設(shè)備號(hào)為0,同時(shí)可以看到loop0-loop3共用一個(gè)主設(shè)備號(hào),次設(shè)備號(hào)由0開(kāi)始遞增。

wKgaomYVR9OAGVv_AAETnsc6MH0295.png

一般來(lái)說(shuō),主設(shè)備號(hào)指向設(shè)備的驅(qū)動(dòng)程序,次設(shè)備號(hào)指向某個(gè)具體的設(shè)備。如上圖,I2C-0,I2C-1屬于不同設(shè)備但是共用一套驅(qū)動(dòng)程序

3.1.1. 內(nèi)核中設(shè)備編號(hào)的含義?

在內(nèi)核中,dev_t用來(lái)表示設(shè)備編號(hào),dev_t是一個(gè)32位的數(shù),其中,高12位表示主設(shè)備號(hào),低20位表示次設(shè)備號(hào)。 也就是理論上主設(shè)備號(hào)取值范圍:0-2^12,次設(shè)備號(hào)0-2^20。 實(shí)際上在內(nèi)核源碼中__register_chrdev_region(…)函數(shù)中,major被限定在0-CHRDEV_MAJOR_MAX,CHRDEV_MAJOR_MAX是一個(gè)宏,值是512。 在kdev_t中,設(shè)備編號(hào)通過(guò)移位操作最終得到主/次設(shè)備號(hào)碼,同樣主/次設(shè)備號(hào)也可以通過(guò)位運(yùn)算變成dev_t類型的設(shè)備編號(hào), 具體實(shí)現(xiàn)參看下面代碼MAJOR(dev)、MINOR(dev)和MKDEV(ma,mi)。

dev_t定義 (內(nèi)核源碼/include/types.h)

typedef u32 __kernel_dev_t;typedef __kernel_dev_t               dev_t;

設(shè)備號(hào)相關(guān)宏 (內(nèi)核源碼/include/linux/kdev_t.h)

#define MINORBITS    20#define MINORMASK    ((1U > MINORBITS))#define MINOR(dev)   ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi) (((ma) 

第4-5行:內(nèi)核還提供了另外兩個(gè)宏定義MAJOR和MINOR,可以根據(jù)設(shè)備的設(shè)備號(hào)來(lái)獲取設(shè)備的主設(shè)備號(hào)和次設(shè)備號(hào)。

第6行:宏定義MKDEV,用于將主設(shè)備號(hào)和次設(shè)備號(hào)合成一個(gè)設(shè)備號(hào),主設(shè)備可以通過(guò)查閱內(nèi)核源碼的Documentation/devices.txt文件,而次設(shè)備號(hào)通常是從編號(hào)0開(kāi)始。

3.1.2. cdev結(jié)構(gòu)體?

內(nèi)核通過(guò)一個(gè)散列表(哈希表)來(lái)記錄設(shè)備編號(hào)。 哈希表由數(shù)組和鏈表組成,吸收數(shù)組查找快,鏈表增刪效率高,容易拓展等優(yōu)點(diǎn)。

以主設(shè)備號(hào)為cdev_map編號(hào),使用哈希函數(shù)f(major)=major%255來(lái)計(jì)算組數(shù)下標(biāo)(使用哈希函數(shù)是為了鏈表節(jié)點(diǎn)盡量平均分布在各個(gè)數(shù)組元素中,提高查詢效率); 主設(shè)備號(hào)沖突,則以次設(shè)備號(hào)為比較值來(lái)排序鏈表節(jié)點(diǎn)。 如下圖所示,內(nèi)核用struct cdev結(jié)構(gòu)體來(lái)描述一個(gè)字符設(shè)備,并通過(guò)struct kobj_map類型的 散列表cdev_map來(lái)管理當(dāng)前系統(tǒng)中的所有字符設(shè)備。

wKgZomYVR9WASZGkAAC9RMKyvw4820.jpg

cdev結(jié)構(gòu)體(內(nèi)核源碼/include/linux/cdev.h)

struct cdev {   struct kobject kobj;   struct module *owner;   const struct file_operations *ops;   struct list_head list;   dev_t dev;   unsigned int count;} __randomize_layout;

struct kobject kobj: 內(nèi)嵌的內(nèi)核對(duì)象,通過(guò)它將設(shè)備統(tǒng)一加入到“Linux設(shè)備驅(qū)動(dòng)模型”中管理(如對(duì)象的引用計(jì)數(shù)、電源管理、熱插拔、生命周期、與用戶通信等)。

struct module *owner: 字符設(shè)備驅(qū)動(dòng)程序所在的內(nèi)核模塊對(duì)象的指針。

const struct file_operations *ops: 文件操作,是字符設(shè)備驅(qū)動(dòng)中非常重要的數(shù)據(jù)結(jié)構(gòu),在應(yīng)用程序通過(guò)文件系統(tǒng)(VFS)呼叫到設(shè)備設(shè)備驅(qū)動(dòng)程序中實(shí)現(xiàn)的文件操作類函數(shù)過(guò)程中,ops起著橋梁紐帶作用,VFS與文件系統(tǒng)及設(shè)備文件之間的接口是file_operations結(jié)構(gòu)體成員函數(shù),這個(gè)結(jié)構(gòu)體包含了對(duì)文件進(jìn)行打開(kāi)、關(guān)閉、讀寫(xiě)、控制等一系列成員函數(shù)。

struct list_head list: 用于將系統(tǒng)中的字符設(shè)備形成鏈表(這是個(gè)內(nèi)核鏈表的一個(gè)鏈接因子,可以再內(nèi)核很多結(jié)構(gòu)體中看到這種結(jié)構(gòu)的身影)。

dev_t dev: 字符設(shè)備的設(shè)備號(hào),有主設(shè)備和次設(shè)備號(hào)構(gòu)成。

unsigned int count: 屬于同一主設(shè)備好的次設(shè)備號(hào)的個(gè)數(shù),用于表示設(shè)備驅(qū)動(dòng)程序控制的實(shí)際同類設(shè)備的數(shù)量。

3.2. 設(shè)備節(jié)點(diǎn)?

設(shè)備節(jié)點(diǎn)(設(shè)備文件):Linux中設(shè)備節(jié)點(diǎn)是通過(guò)“mknod”命令來(lái)創(chuàng)建的。一個(gè)設(shè)備節(jié)點(diǎn)其實(shí)就是一個(gè)文件, Linux中稱為設(shè)備文件。有一點(diǎn)必要說(shuō)明的是,在Linux中,所有的設(shè)備訪問(wèn)都是通過(guò)文件的方式, 一般的數(shù)據(jù)文件程序普通文件,設(shè)備節(jié)點(diǎn)稱為設(shè)備文件。

設(shè)備節(jié)點(diǎn)被創(chuàng)建在/dev下,是連接內(nèi)核與用戶層的樞紐,就是設(shè)備是接到對(duì)應(yīng)哪種接口的哪個(gè)ID 上。 相當(dāng)于硬盤的inode一樣的東西,記錄了硬件設(shè)備的位置和信息在Linux中,所有設(shè)備都以文件的形式存放在/dev目錄下, 都是通過(guò)文件的方式進(jìn)行訪問(wèn),設(shè)備節(jié)點(diǎn)是Linux內(nèi)核對(duì)設(shè)備的抽象,一個(gè)設(shè)備節(jié)點(diǎn)就是一個(gè)文件。 應(yīng)用程序通過(guò)一組標(biāo)準(zhǔn)化的調(diào)用執(zhí)行訪問(wèn)設(shè)備,這些調(diào)用獨(dú)立于任何特定的驅(qū)動(dòng)程序。而驅(qū)動(dòng)程序負(fù)責(zé)將這些標(biāo)準(zhǔn)調(diào)用映射到實(shí)際硬件的特有操作。

3.3. 數(shù)據(jù)結(jié)構(gòu)?

在驅(qū)動(dòng)開(kāi)發(fā)過(guò)程中,不可避免要涉及到三個(gè)重要的的內(nèi)核數(shù)據(jù)結(jié)構(gòu)分別包括文件操作方式(file_operations), 文件描述結(jié)構(gòu)體(struct file)以及inode結(jié)構(gòu)體,在我們開(kāi)始閱讀編寫(xiě)驅(qū)動(dòng)程序的代碼之前,有必要先了解這三個(gè)結(jié)構(gòu)體。

3.3.1. file_operations結(jié)構(gòu)體?

file_operation就是把系統(tǒng)調(diào)用和驅(qū)動(dòng)程序關(guān)聯(lián)起來(lái)的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。這個(gè)結(jié)構(gòu)的每一個(gè)成員都對(duì)應(yīng)著一個(gè)系統(tǒng)調(diào)用。 讀取file_operation中相應(yīng)的函數(shù)指針,接著把控制權(quán)轉(zhuǎn)交給函數(shù)指針指向的函數(shù),從而完成了Linux設(shè)備驅(qū)動(dòng)程序的工作。

下面是部分常用的字符操作介紹

struct file_operations {    struct module *owner;   //擁有該結(jié)構(gòu)的模塊指針,一般有THIS_MODULES    loff_t (*llseek) (struct file *, loff_t, int);  //用來(lái)修改文件當(dāng)前讀寫(xiě)的位置    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);   //從設(shè)備中同步讀取數(shù)據(jù)    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向設(shè)備發(fā)送數(shù)據(jù)    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);   //初始化一個(gè)異步讀取操作    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);  //初始化一個(gè)異步寫(xiě)操作    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//執(zhí)行設(shè)備的I/O控制命令    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);//64bit系統(tǒng)上,32bit的ioctl調(diào)用將使用此函數(shù)指針代替    int (*mmap) (struct file *, struct vm_area_struct *);//用于請(qǐng)求將設(shè)備內(nèi)存映射到進(jìn)程地址空間    int (*open) (struct inode *, struct file *);//打開(kāi)設(shè)備,獲取設(shè)備描述符    int (*flush) (struct file *, fl_owner_t id);//刷新設(shè)備數(shù)據(jù)流    int (*release) (struct inode *, struct file *);//關(guān)閉設(shè)備    int (*fsync) (struct file *, loff_t, loff_t, int datasync);//刷新待處理的數(shù)據(jù)    int (*fasync) (int, struct file *, int);    //通知設(shè)備fasync標(biāo)志發(fā)生變化}

在系統(tǒng)內(nèi)部,I/O設(shè)備的存取操作通過(guò)特定的入口點(diǎn)來(lái)進(jìn)行,而這組特定的入口點(diǎn)恰恰是由設(shè)備驅(qū)動(dòng)程序提供的。 通常這組設(shè)備驅(qū)動(dòng)程序接口是由結(jié)構(gòu)file_operations結(jié)構(gòu)體向系統(tǒng)說(shuō)明的,它定義在ebf_buster_linux/include/linux/fs.h中。 傳統(tǒng)上, 一個(gè)file_operation結(jié)構(gòu)或者其一個(gè)指針?lè)Q為 fops( 或者它的一些變體). 結(jié)構(gòu)中的每個(gè)成員必須指向驅(qū)動(dòng)中的函數(shù), 這些函數(shù)實(shí)現(xiàn)一個(gè)特別的操作, 或者對(duì)于不支持的操作留置為NULL。當(dāng)指定為NULL指針時(shí)內(nèi)核的確切的行為是每個(gè)函數(shù)不同的。

上面,我們提到read和write函數(shù)時(shí),需要使用copy_to_user函數(shù)以及copy_from_user函數(shù)來(lái)進(jìn)行數(shù)據(jù)訪問(wèn),寫(xiě)入/讀取成 功函數(shù)返回0,失敗則會(huì)返回未被拷貝的字節(jié)數(shù)。

copy_to_user和copy_from_user函數(shù)(內(nèi)核源碼/include/asm-generic/uaccess.h)

static inline long copy_from_user(void *to, const void __user * from, unsigned long n)static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

函數(shù)參數(shù)和返回值如下:

參數(shù)

to:指定目標(biāo)地址,也就是數(shù)據(jù)存放的地址,

from:指定源地址,也就是數(shù)據(jù)的來(lái)源。

n:指定寫(xiě)入/讀取數(shù)據(jù)的字節(jié)數(shù)。

返回值

寫(xiě)入/讀取數(shù)據(jù)的字節(jié)數(shù)

3.3.2. file結(jié)構(gòu)體?

內(nèi)核中用file結(jié)構(gòu)體來(lái)表示每個(gè)打開(kāi)的文件,每打開(kāi)一個(gè)文件,內(nèi)核會(huì)創(chuàng)建一個(gè)結(jié)構(gòu)體,并將對(duì)該文件上的操作函數(shù)傳遞給 該結(jié)構(gòu)體的成員變量f_op,當(dāng)文件所有實(shí)例被關(guān)閉后,內(nèi)核會(huì)釋放這個(gè)結(jié)構(gòu)體。如下代碼中,只列出了我們本章需要了解的成員變量。

file結(jié)構(gòu)體(內(nèi)核源碼/include/fs.h)

struct file {{......}const struct file_operations *f_op;/* needed for tty driver, and maybe others */void *private_data;{......}};

f_op:存放與文件操作相關(guān)的一系列函數(shù)指針,如open、read、wirte等函數(shù)。

private_data:該指針變量只會(huì)用于設(shè)備驅(qū)動(dòng)程序中,內(nèi)核并不會(huì)對(duì)該成員進(jìn)行操作。因此,在驅(qū)動(dòng)程序中,通常用于指向描述設(shè)備的結(jié)構(gòu)體。

3.3.3. inode結(jié)構(gòu)體?

VFS inode 包含文件訪問(wèn)權(quán)限、屬主、組、大小、生成時(shí)間、訪問(wèn)時(shí)間、最后修改時(shí)間等信息。 它是Linux 管理文件系統(tǒng)的最基本單位,也是文件系統(tǒng)連接任何子目錄、文件的橋梁。 內(nèi)核使用inode結(jié)構(gòu)體在內(nèi)核內(nèi)部表示一個(gè)文件。因此,它與表示一個(gè)已經(jīng)打開(kāi)的文件描述符的結(jié)構(gòu)體(即file 文件結(jié)構(gòu))是不同的, 我們可以使用多個(gè)file文件結(jié)構(gòu)表示同一個(gè)文件的多個(gè)文件描述符,但此時(shí), 所有的這些file文件結(jié)構(gòu)全部都必須只能指向一個(gè)inode結(jié)構(gòu)體。 inode結(jié)構(gòu)體包含了一大堆文件相關(guān)的信息,但是就針對(duì)驅(qū)動(dòng)代碼來(lái)說(shuō),我們只要關(guān)心其中的兩個(gè)域即可:

inode結(jié)構(gòu)體(內(nèi)核源碼/include/linux/fs.h)

struct inode {   dev_t                     i_rdev;   {......}   union {      struct pipe_inode_info *i_pipe;   /* linux內(nèi)核管道 */      struct block_device    *i_bdev;      /* 如果這是塊設(shè)備,則設(shè)置并使用 */      struct cdev            *i_cdev;            /* 如果這是字符設(shè)備,則設(shè)置并使用 */      char                   *i_link;      unsigned               i_dir_seq;   };   {......}};

dev_t i_rdev: 表示設(shè)備文件的結(jié)點(diǎn),這個(gè)域?qū)嶋H上包含了設(shè)備號(hào)。

struct cdev *i_cdev: struct cdev是內(nèi)核的一個(gè)內(nèi)部結(jié)構(gòu),它是用來(lái)表示字符設(shè)備的,當(dāng)inode結(jié)點(diǎn)指向一個(gè)字符設(shè)備文件時(shí),此域?yàn)橐粋€(gè)指向inode結(jié)構(gòu)的指針。

4. 字符設(shè)備驅(qū)動(dòng)程序框架?

講了很多次字符設(shè)備驅(qū)動(dòng)程序框架,那到底什么是字符文件程序框架呢?我們可以從下面的思維導(dǎo)圖來(lái)解讀內(nèi)核源碼。

wKgaomYVR9qAcUPSAADjDuTB7TA823.png

我們創(chuàng)建一個(gè)字符設(shè)備的時(shí)候,首先要的到一個(gè)設(shè)備號(hào),分配設(shè)備號(hào)的途徑有靜態(tài)分配和動(dòng)態(tài)分配; 拿到設(shè)備的唯一ID,我們需要實(shí)現(xiàn)file_operation并保存到cdev中,實(shí)現(xiàn)cdev的初始化; 然后我們需要將我們所做的工作告訴內(nèi)核,使用cdev_add()注冊(cè)cdev; 最后我們還需要?jiǎng)?chuàng)建設(shè)備節(jié)點(diǎn),以便我們后面調(diào)用file_operation接口。

注銷設(shè)備時(shí)我們需釋放內(nèi)核中的cdev,歸還申請(qǐng)的設(shè)備號(hào),刪除創(chuàng)建的設(shè)備節(jié)點(diǎn)。

在實(shí)現(xiàn)設(shè)備操作這一段,我們可以看看open函數(shù)到底做了什么。

4.1. 驅(qū)動(dòng)初始化和注銷?

4.1.1. 設(shè)備號(hào)的申請(qǐng)和歸還?

Linux內(nèi)核提供了兩種方式來(lái)定義字符設(shè)備,如下所示。

定義字符設(shè)備

//第一種方式static struct cdev chrdev;//第二種方式struct cdev *cdev_alloc(void);

第一種方式,就是我們常見(jiàn)的變量定義;第二種方式,是內(nèi)核提供的動(dòng)態(tài)分配方式,調(diào)用該函數(shù)之 后,會(huì)返回一個(gè)struct cdev類型的指針,用于描述字符設(shè)備。

從內(nèi)核中移除某個(gè)字符設(shè)備,則需要調(diào)用cdev_del函數(shù),如下所示。

cdev_del函數(shù)

void cdev_del(struct cdev *p)

函數(shù)參數(shù)和返回值如下:

參數(shù):

p: 該函數(shù)需要將我們的字符設(shè)備結(jié)構(gòu)體的地址作為實(shí)參傳遞進(jìn)去,就可以從內(nèi)核中移除該字符設(shè)備了。

返回值: 無(wú)

register_chrdev_region函數(shù)

register_chrdev_region函數(shù)用于靜態(tài)地為一個(gè)字符設(shè)備申請(qǐng)一個(gè)或多個(gè)設(shè)備編號(hào)。函數(shù)原型如下所示。

register_chrdev_region函數(shù)

int register_chrdev_region(dev_t from, unsigned count, const char *name)

函數(shù)參數(shù)和返回值如下:

參數(shù):

from:dev_t類型的變量,用于指定字符設(shè)備的起始設(shè)備號(hào),如果要注冊(cè)的設(shè)備號(hào)已經(jīng)被其他的設(shè)備注冊(cè)了,那么就會(huì)導(dǎo)致注冊(cè)失敗。

count:指定要申請(qǐng)的設(shè)備號(hào)個(gè)數(shù),count的值不可以太大,否則會(huì)與下一個(gè)主設(shè)備號(hào)重疊。

name:用于指定該設(shè)備的名稱,我們可以在/proc/devices中看到該設(shè)備。

返回值: 返回0表示申請(qǐng)成功,失敗則返回錯(cuò)誤碼

alloc_chrdev_region函數(shù)

使用register_chrdev_region函數(shù)時(shí),都需要去查閱內(nèi)核源碼的Documentation/devices.txt文件, 這就十分不方便。因此,內(nèi)核又為我們提供了一種能夠動(dòng)態(tài)分配設(shè)備編號(hào)的方式:alloc_chrdev_region。

調(diào)用alloc_chrdev_region函數(shù),內(nèi)核會(huì)自動(dòng)分配給我們一個(gè)尚未使用的主設(shè)備號(hào)。 我們可以通過(guò)命令“cat /proc/devices”查詢內(nèi)核分配的主設(shè)備號(hào)。

alloc_chrdev_region函數(shù)原型

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

函數(shù)參數(shù)和返回值如下:

參數(shù):

dev:指向dev_t類型數(shù)據(jù)的指針變量,用于存放分配到的設(shè)備編號(hào)的起始值;

baseminor:次設(shè)備號(hào)的起始值,通常情況下,設(shè)置為0;

count、name:同register_chrdev_region類型,用于指定需要分配的設(shè)備編號(hào)的個(gè)數(shù)以及設(shè)備的名稱。

返回值: 返回0表示申請(qǐng)成功,失敗則返回錯(cuò)誤碼

unregister_chrdev_region函數(shù)

當(dāng)我們刪除字符設(shè)備時(shí)候,我們需要把分配的設(shè)備編號(hào)交還給內(nèi)核,對(duì)于使用register_chrdev_region函數(shù) 以及alloc_chrdev_region函數(shù)分配得到的設(shè)備編號(hào),可以使用unregister_chrdev_region函數(shù)實(shí)現(xiàn)該功能。

unregister_chrdev_region函數(shù)(內(nèi)核源碼/fs/char_dev.c)

void unregister_chrdev_region(dev_t from, unsigned count)

函數(shù)參數(shù)和返回值如下:

參數(shù):

from:指定需要注銷的字符設(shè)備的設(shè)備編號(hào)起始值,我們一般將定義的dev_t變量作為實(shí)參。

count:指定需要注銷的字符設(shè)備編號(hào)的個(gè)數(shù),該值應(yīng)與申請(qǐng)函數(shù)的count值相等,通常采用宏定義進(jìn)行管理。

返回值: 無(wú)

register_chrdev函數(shù)

除了上述的兩種,內(nèi)核還提供了register_chrdev函數(shù)用于分配設(shè)備號(hào)。該函數(shù)是一個(gè)內(nèi)聯(lián)函數(shù),它不 僅支持靜態(tài)申請(qǐng)?jiān)O(shè)備號(hào),也支持動(dòng)態(tài)申請(qǐng)?jiān)O(shè)備號(hào),并將主設(shè)備號(hào)返回,函數(shù)原型如下所示。

register_chrdev函數(shù)原型(內(nèi)核源碼/include/linux/fs.h文件)

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops){   return __register_chrdev(major, 0, 256, name, fops);}

函數(shù)參數(shù)和返回值如下:

參數(shù):

major:用于指定要申請(qǐng)的字符設(shè)備的主設(shè)備號(hào),等價(jià)于register_chrdev_region函數(shù),當(dāng)設(shè)置為0時(shí),內(nèi)核會(huì)自動(dòng)分配一個(gè)未使用的主設(shè)備號(hào)。

name:用于指定字符設(shè)備的名稱

fops:用于操作該設(shè)備的函數(shù)接口指針。

返回值: 主設(shè)備號(hào)

我們從以上代碼中可以看到,使用register_chrdev函數(shù)向內(nèi)核申請(qǐng)?jiān)O(shè)備號(hào),同一類字 符設(shè)備(即主設(shè)備號(hào)相同),會(huì)在內(nèi)核中申請(qǐng)了256個(gè),通常情況下,我們不需要用到這么多個(gè)設(shè)備,這就造成了極大的資源浪費(fèi)。

unregister_chrdev函數(shù)

使用register函數(shù)申請(qǐng)的設(shè)備號(hào),則應(yīng)該使用unregister_chrdev函數(shù)進(jìn)行注銷。

unregister_chrdev函數(shù)(內(nèi)核源碼/include/linux/fs.h)

static inline void unregister_chrdev(unsigned int major, const char *name){__unregister_chrdev(major, 0, 256, name);}

函數(shù)參數(shù)和返回值如下:

參數(shù):

major:指定需要釋放的字符設(shè)備的主設(shè)備號(hào),一般使用register_chrdev函數(shù)的返回值作為實(shí)參。

name:執(zhí)行需要釋放的字符設(shè)備的名稱。

返回值: 無(wú)

4.1.2. 初始化cdev?

前面我們已經(jīng)提到過(guò)了,編寫(xiě)一個(gè)字符設(shè)備最重要的事情,就是要實(shí)現(xiàn)file_operations這個(gè)結(jié)構(gòu)體中的函數(shù)。 實(shí)現(xiàn)之后,如何將該結(jié)構(gòu)體與我們的字符設(shè)備結(jié)構(gòu)體相關(guān)聯(lián)呢??jī)?nèi)核提供了cdev_init函數(shù),來(lái)實(shí)現(xiàn)這個(gè)過(guò)程。

cdev_init函數(shù)(內(nèi)核源碼/fs/char_dev.c)

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

函數(shù)參數(shù)和返回值如下:

參數(shù):

cdev:struct cdev類型的指針變量,指向需要關(guān)聯(lián)的字符設(shè)備結(jié)構(gòu)體;

fops:file_operations類型的結(jié)構(gòu)體指針變量,一般將實(shí)現(xiàn)操作該設(shè)備的結(jié)構(gòu)體file_operations結(jié)構(gòu)體作為實(shí)參。

返回值: 無(wú)

4.2. 設(shè)備注冊(cè)和注銷?

cdev_add函數(shù)用于向內(nèi)核的cdev_map散列表添加一個(gè)新的字符設(shè)備,如下所示。

cdev_add函數(shù)(內(nèi)核源碼/fs/char_dev.c)

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

函數(shù)參數(shù)和返回值如下:

參數(shù):

p:struct cdev類型的指針,用于指定需要添加的字符設(shè)備;

dev:dev_t類型變量,用于指定設(shè)備的起始編號(hào);

count:指定注冊(cè)多少個(gè)設(shè)備。

返回值: 錯(cuò)誤碼

從系統(tǒng)中刪除cdev,cdev設(shè)備將無(wú)法再打開(kāi),但任何已經(jīng)打開(kāi)的cdev將保持不變, 即使在cdev_del返回后,它們的FOP仍然可以調(diào)用。

cdev_del函數(shù)(內(nèi)核源碼/fs/char_dev.c)

void cdev_del(struct cdev *p)

函數(shù)參數(shù)和返回值如下:

參數(shù):

p:struct cdev類型的指針,用于指定需要?jiǎng)h除的字符設(shè)備;

返回值: 無(wú)

4.3. 設(shè)備節(jié)點(diǎn)的創(chuàng)建和銷毀?

創(chuàng)建一個(gè)設(shè)備并將其注冊(cè)到文件系統(tǒng)

device_create函數(shù)(內(nèi)核源碼/drivers/base/core.c)

struct device *device_create(struct class *class, struct device *parent,            dev_t devt, void *drvdata, const char *fmt, ...)

函數(shù)參數(shù)和返回值如下:

參數(shù):

class:指向這個(gè)設(shè)備應(yīng)該注冊(cè)到的struct類的指針;

parent:指向此新設(shè)備的父結(jié)構(gòu)設(shè)備(如果有)的指針;

devt:要添加的char設(shè)備的開(kāi)發(fā);

drvdata:要添加到設(shè)備進(jìn)行回調(diào)的數(shù)據(jù);

fmt:輸入設(shè)備名稱。

返回值: 成功時(shí)返回 struct device 結(jié)構(gòu)體指針, 錯(cuò)誤時(shí)返回ERR_PTR().

刪除使用device_create函數(shù)創(chuàng)建的設(shè)備

device_destroy函數(shù)(內(nèi)核源碼/drivers/base/core.c)

void device_destroy(struct class *class, dev_t devt)

函數(shù)參數(shù)和返回值如下:

參數(shù):

class:指向注冊(cè)此設(shè)備的struct類的指針;

devt:以前注冊(cè)的設(shè)備的開(kāi)發(fā);

返回值: 無(wú)

除了使用代碼創(chuàng)建設(shè)備節(jié)點(diǎn),還可以使用mknod命令創(chuàng)建設(shè)備節(jié)點(diǎn)。

用法:mknod 設(shè)備名 設(shè)備類型 主設(shè)備號(hào) 次設(shè)備號(hào)

當(dāng)類型為”p”時(shí)可不指定主設(shè)備號(hào)和次設(shè)備號(hào),否則它們是必須指定的。 如果主設(shè)備號(hào)和次設(shè)備號(hào)以”0x”或”0X”開(kāi)頭,它們會(huì)被視作十六進(jìn)制數(shù)來(lái)解析;如果以”0”開(kāi)頭,則被視作八進(jìn)制數(shù); 其余情況下被視作十進(jìn)制數(shù)??捎玫念愋桶ǎ?/p>

b 創(chuàng)建(有緩沖的)區(qū)塊特殊文件

c, u 創(chuàng)建(沒(méi)有緩沖的)字符特殊文件

p 創(chuàng)建先進(jìn)先出(FIFO)特殊文件

如:mkmod /dev/test c 2 0

創(chuàng)建一個(gè)字符設(shè)備/dev/test,其主設(shè)備號(hào)為2,次設(shè)備號(hào)為0。

wKgZomYVR92AUt-UAACJG4BzABI739.png

當(dāng)我們使用上述命令,創(chuàng)建了一個(gè)字符設(shè)備文件時(shí),實(shí)際上就是創(chuàng)建了一個(gè)設(shè)備節(jié)點(diǎn)inode結(jié)構(gòu)體, 并且將該設(shè)備的設(shè)備編號(hào)記錄在成員i_rdev,將成員f_op指針指向了def_chr_fops結(jié)構(gòu)體。 這就是mknod負(fù)責(zé)的工作內(nèi)容,具體代碼見(jiàn)如下。

mknod調(diào)用關(guān)系 (內(nèi)核源碼/mm/shmem.c)

static struct inode *shmem_get_inode(struct super_block *sb, const struct inode *dir,umode_t mode, dev_t dev, unsigned long flags){   inode = new_inode(sb);   if (inode) {      ......      switch (mode & S_IFMT) {         default:         inode->i_op = &shmem_special_inode_operations;         init_special_inode(inode, mode, dev);         break;         ......      }   } else   shmem_free_inode(sb);   return inode;}

第10行:mknod命令最終執(zhí)行init_special_inode函數(shù)

init_special_inode函數(shù)(內(nèi)核源碼/fs/inode.c)

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev){   inode->i_mode = mode;   if (S_ISCHR(mode)) {      inode->i_fop = &def_chr_fops;      inode->i_rdev = rdev;   } else if (S_ISBLK(mode)) {      inode->i_fop = &def_blk_fops;      inode->i_rdev = rdev;   } else if (S_ISFIFO(mode))      inode->i_fop = &pipefifo_fops;   else if (S_ISSOCK(mode))      ;      /* leave it no_open_fops */   else      printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"            " inode %s:%lun", mode, inode->i_sb->s_id,            inode->i_ino);}

第4-17行:判斷文件的inode類型,如果是字符設(shè)備類型,則把def_chr_fops作為該文件的操作接口,并把設(shè)備號(hào)記錄在inode->i_rdev。

inode上的file_operation并不是自己構(gòu)造的file_operation,而是字符設(shè)備通用的def_chr_fops, 那么自己構(gòu)建的file_operation等在應(yīng)用程序調(diào)用open函數(shù)之后,才會(huì)綁定在文件上。接下來(lái)我們?cè)倏磑pen函數(shù)到底做了什么。

5. open函數(shù)到底做了什么?

使用設(shè)備之前我們通常都需要調(diào)用open函數(shù),這個(gè)函數(shù)一般用于設(shè)備專有數(shù)據(jù)的初始化,申請(qǐng)相關(guān)資源及進(jìn)行設(shè)備的初始化等工作, 對(duì)于簡(jiǎn)單的設(shè)備而言,open函數(shù)可以不做具體的工作,你在應(yīng)用層通過(guò)系統(tǒng)調(diào)用open打開(kāi)設(shè)備時(shí), 如果打開(kāi)正常,就會(huì)得到該設(shè)備的文件描述符,之后,我們就可以通過(guò)該描述符對(duì)設(shè)備進(jìn)行read和write等操作; open函數(shù)到底做了些什么工作?下圖中列出了open函數(shù)執(zhí)行的大致過(guò)程。

wKgaomYVR-OAKo6uAAF_H6aO4oA236.png

用戶空間使用open()系統(tǒng)調(diào)用函數(shù)打開(kāi)一個(gè)字符設(shè)備時(shí)(int fd = open(“dev/xxx”, O_RDWR))大致有以下過(guò)程:

在虛擬文件系統(tǒng)VFS中的查找對(duì)應(yīng)與字符設(shè)備對(duì)應(yīng) struct inode節(jié)點(diǎn)

遍歷散列表cdev_map,根據(jù)inod節(jié)點(diǎn)中的 cdev_t設(shè)備號(hào)找到cdev對(duì)象

創(chuàng)建struct file對(duì)象(系統(tǒng)采用一個(gè)數(shù)組來(lái)管理一個(gè)進(jìn)程中的多個(gè)被打開(kāi)的設(shè)備,每個(gè)文件秒速符作為數(shù)組下標(biāo)標(biāo)識(shí)了一個(gè)設(shè)備對(duì)象)

初始化struct file對(duì)象,將 struct file對(duì)象中的 file_operations成員指向 struct cdev對(duì)象中的 file_operations成員(file->fops = cdev->fops)

回調(diào)file->fops->open函數(shù)

我們使用的open函數(shù)在內(nèi)核中對(duì)應(yīng)的是sys_open函數(shù),sys_open函數(shù)又會(huì)調(diào)用do_sys_open函數(shù)。在do_sys_open函數(shù)中, 首先調(diào)用函數(shù)get_unused_fd_flags來(lái)獲取一個(gè)未被使用的文件描述符fd,該文件描述符就是我們最終通過(guò)open函數(shù)得到的值。 緊接著,又調(diào)用了do_filp_open函數(shù),該函數(shù)通過(guò)調(diào)用函數(shù)get_empty_filp得到一個(gè)新的file結(jié)構(gòu)體,之后的代碼做了許多復(fù)雜的工作, 如解析文件路徑,查找該文件的文件節(jié)點(diǎn)inode等,直接來(lái)到了函數(shù)do_dentry_open函數(shù),如下所示。

do_dentry_open函數(shù)(位于 ebf-busrer-linux/fs/open.c)

static int do_dentry_open(struct file *f,struct inode *inode,int (*open)(struct inode *, struct file *),const struct cred *cred){   //……   f->f_op = fops_get(inode->i_fop);   //……   if (!open)   open = f->f_op->open;   if (open) {      error = open(inode, f);      if (error)      goto cleanup_all;   }   //……}

第4行:使用fops_get函數(shù)來(lái)獲取該文件節(jié)點(diǎn)inode的成員變量i_fop,在上圖中我們使用mknod創(chuàng)建字符設(shè)備文件時(shí),將def_chr_fops結(jié)構(gòu)體賦值給了該設(shè)備文件inode的i_fop成員。

第7行:到了這里,我們新建的file結(jié)構(gòu)體的成員f_op就指向了def_chr_fops。

def_chr_fops結(jié)構(gòu)體(內(nèi)核源碼/fs/char_dev.c)

const struct file_operations def_chr_fops = {   .open = chrdev_open,   .llseek = noop_llseek,};

最終,會(huì)執(zhí)行def_chr_fops中的open函數(shù),也就是chrdev_open函數(shù),可以理解為一個(gè)字符設(shè)備的通用初始化函數(shù),根據(jù)字符設(shè)備的設(shè)備號(hào), 找到相應(yīng)的字符設(shè)備,從而得到操作該設(shè)備的方法,代碼實(shí)現(xiàn)如下。

wKgZomYVR-WADrHQAAB5XtbvGRs826.jpg

chrdev_open函數(shù)(內(nèi)核源碼/fs/char_dev.c)

static int chrdev_open(struct inode *inode, struct file *filp){   const struct file_operations *fops;   struct cdev *p;   struct cdev *new = NULL;   int ret = 0;   spin_lock(&cdev_lock);   p = inode->i_cdev;   if (!p) {      struct kobject *kobj;      int idx;      spin_unlock(&cdev_lock);      kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);      if (!kobj)         return -ENXIO;      new = container_of(kobj, struct cdev, kobj);      spin_lock(&cdev_lock);      /* Check i_cdev again in case somebody beat us to it whilewe dropped the lock.*/      p = inode->i_cdev;      if (!p) {         inode->i_cdev = p = new;         list_add(&inode->i_devices, &p->list);         new = NULL;      } else if (!cdev_get(p))         ret = -ENXIO;   } else if (!cdev_get(p))      ret = -ENXIO;   spin_unlock(&cdev_lock);   cdev_put(new);   if (ret)      return ret;   ret = -ENXIO;   fops = fops_get(p->ops);   if (!fops)   goto out_cdev_put;   replace_fops(filp, fops);   if (filp->f_op->open) {      ret = filp->f_op->open(inode, filp);      if (ret)      goto out_cdev_put;   }   return 0;   out_cdev_put:   cdev_put(p);   return ret;}

在Linux內(nèi)核中,使用結(jié)構(gòu)體cdev來(lái)描述一個(gè)字符設(shè)備。

第8行:inode->i_rdev中保存了字符設(shè)備的設(shè)備編號(hào),

第13行:通過(guò)函數(shù)kobj_lookup函數(shù)便可以找到該設(shè)備文件cdev結(jié)構(gòu)體的kobj成員,

第16行:再通過(guò)函數(shù)container_of便可以得到該字符設(shè)備對(duì)應(yīng)的結(jié)構(gòu)體cdev。函數(shù)container_of的作用就是通過(guò)一個(gè)結(jié)構(gòu)變量中一個(gè)成員的地址找到這個(gè)結(jié)構(gòu)體變量的首地址。同時(shí),將cdev結(jié)構(gòu)體記錄到文件節(jié)點(diǎn)inode中的i_cdev,便于下次打開(kāi)該文件。

第38-43行:函數(shù)chrdev_open最終將該文件結(jié)構(gòu)體file的成員f_op替換成了cdev對(duì)應(yīng)的ops成員,并執(zhí)行ops結(jié)構(gòu)體中的open函數(shù)。

最后,調(diào)用上圖的fd_install函數(shù),完成文件描述符和文件結(jié)構(gòu)體file的關(guān)聯(lián),之后我們使用對(duì)該文件描述符fd調(diào)用read、write函數(shù), 最終都會(huì)調(diào)用file結(jié)構(gòu)體對(duì)應(yīng)的函數(shù),實(shí)際上也就是調(diào)用cdev結(jié)構(gòu)體中ops結(jié)構(gòu)體內(nèi)的相關(guān)函數(shù)。

總結(jié)一下整個(gè)過(guò)程,當(dāng)我們使用open函數(shù),打開(kāi)設(shè)備文件時(shí),會(huì)根據(jù)該設(shè)備的文件的設(shè)備號(hào)找到相應(yīng)的設(shè)備結(jié)構(gòu)體, 從而得到了操作該設(shè)備的方法。也就是說(shuō)如果我們要添加一個(gè)新設(shè)備的話,我們需要提供一個(gè)設(shè)備號(hào), 一個(gè)設(shè)備結(jié)構(gòu)體以及操作該設(shè)備的方法(file_operations結(jié)構(gòu)體)。

6. 字符設(shè)備驅(qū)動(dòng)程序?qū)嶒?yàn)?

6.1. 硬件介紹?

本節(jié)實(shí)驗(yàn)使用到armsom-sige系列板。

6.2. 實(shí)驗(yàn)代碼講解?

結(jié)合前面所有的知識(shí)點(diǎn),首先,字符設(shè)備驅(qū)動(dòng)程序是以內(nèi)核模塊的形式存在的, 我們要向系統(tǒng)注冊(cè)一個(gè)新的字符設(shè)備,需要這幾樣?xùn)|西:字符設(shè)備結(jié)構(gòu)體cdev,設(shè)備編號(hào), 以及最重要的操作方式結(jié)構(gòu)體file_operations。

下面,我們開(kāi)始編寫(xiě)我們自己的字符設(shè)備驅(qū)動(dòng)程序。

6.2.1. 內(nèi)核模塊框架?

既然我們的設(shè)備程序是以內(nèi)核模塊的方式存在的,那么就需要先寫(xiě)出一個(gè)基本的內(nèi)核框架,見(jiàn)如下所示。

內(nèi)核模塊加載函數(shù)

#define DEV_NAME "EmbedCharDev"#define DEV_CNT (1)#define BUFF_SIZE 128//定義字符設(shè)備的設(shè)備號(hào)static dev_t devno;//定義字符設(shè)備結(jié)構(gòu)體chr_devstatic struct cdev chr_dev;static int __init chrdev_init(void){   int ret = 0;   printk("chrdev initn");   //第一步   //采用動(dòng)態(tài)分配的方式,獲取設(shè)備編號(hào),次設(shè)備號(hào)為0,   //設(shè)備名稱為EmbedCharDev,可通過(guò)命令cat /proc/devices查看   //DEV_CNT為1,當(dāng)前只申請(qǐng)一個(gè)設(shè)備編號(hào)   ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);   if (ret < 0) {   printk("fail to alloc devnon");   goto alloc_err; } //第二步 //關(guān)聯(lián)字符設(shè)備結(jié)構(gòu)體cdev與文件操作結(jié)構(gòu)體file_operations cdev_init(&chr_dev, &chr_dev_fops); //第三步 //添加設(shè)備至cdev_map散列表中 ret = cdev_add(&chr_dev, devno, DEV_CNT); if (ret < 0) {   printk("fail to add cdevn");   goto add_err; } return 0; add_err: //添加設(shè)備失敗時(shí),需要注銷設(shè)備號(hào) unregister_chrdev_region(devno, DEV_CNT); alloc_err: return ret; } module_init(chrdev_init);

第16行:使用動(dòng)態(tài)分配(alloc_chrdev_region)的方式來(lái)獲取設(shè)備號(hào),指定設(shè)備的名稱為“EmbedCharDev”,只申請(qǐng)一個(gè)設(shè)備號(hào),并且次設(shè)備號(hào)為0。

第19行:這里使用C語(yǔ)言的goto語(yǔ)法,當(dāng)獲取失敗時(shí),直接返回對(duì)應(yīng)的錯(cuò)誤碼。成功獲取到設(shè)備號(hào)之后,我們還缺字符設(shè)備結(jié)構(gòu)體以及文件的操作方式。

第23行:以上代碼中使用定義變量的方式定義了一個(gè)字符設(shè)備結(jié)構(gòu)體chr_dev,調(diào)用cdev_init函數(shù)將chr_dev結(jié)構(gòu)體和文件操作結(jié)構(gòu)體相關(guān)聯(lián),該結(jié)構(gòu)體的具體實(shí)現(xiàn)下節(jié)見(jiàn)分曉。

第26行:最后我們只需要調(diào)用cdev_add函數(shù)將我們的字符設(shè)備添加到字符設(shè)備管理列表cdev_map即可。

第29行:此處也使用了goto語(yǔ)法,當(dāng)添加設(shè)備失敗的話,需要將申請(qǐng)的設(shè)備號(hào)注銷掉。

模塊的卸載函數(shù)就相對(duì)簡(jiǎn)單一下,只需要完成注銷設(shè)備號(hào),以及移除字符設(shè)備,如下所示。

內(nèi)核模塊卸載函數(shù)

static void __exit chrdev_exit(void){   printk("chrdev exitn");   unregister_chrdev_region(devno, DEV_CNT);   cdev_del(&chr_dev);}module_exit(chrdev_exit);

6.2.2. 文件操作方式的實(shí)現(xiàn)?

下面,我們開(kāi)始實(shí)現(xiàn)字符設(shè)備最重要的部分:文件操作方式結(jié)構(gòu)體file_operations,見(jiàn)如下所示。

file_operations結(jié)構(gòu)體

#define BUFF_SIZE 128//數(shù)據(jù)緩沖區(qū)static char vbuf[BUFF_SIZE];static struct file_operations chr_dev_fops = {   .owner = THIS_MODULE,   .open = chr_dev_open,   .release = chr_dev_release,   .write = chr_dev_write,   .read = chr_dev_read, };

由于這個(gè)字符設(shè)備是一個(gè)虛擬的設(shè)備,與硬件并沒(méi)有什么關(guān)聯(lián),因此,open函數(shù)與release直接返回0即可,我們重點(diǎn)關(guān)注write以及read函數(shù)的實(shí)現(xiàn)

chr_dev_open函數(shù)與chr_dev_release函數(shù)

static int chr_dev_open(struct inode *inode, struct file *filp){   printk("nopenn");   return 0;}static int chr_dev_release(struct inode *inode, struct file *filp){   printk("nreleasen");   return 0; }

我們?cè)趏pen函數(shù)與release函數(shù)中打印相關(guān)的調(diào)試信息,如上方代碼所示。

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   int tmp = count ;   if (p > BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_from_user(vbuf, buf, tmp);   *ppos += tmp;   return tmp;}

當(dāng)我們的應(yīng)用程序調(diào)用write函數(shù),最終就調(diào)用我們的gpio_write函數(shù)。

第3行:變量p記錄了當(dāng)前文件的讀寫(xiě)位置,

第6-9行:如果超過(guò)了數(shù)據(jù)緩沖區(qū)的大小(128字節(jié))的話,直接返回0。并且如果要讀寫(xiě)的數(shù)據(jù)個(gè)數(shù)超過(guò)了數(shù)據(jù)緩沖區(qū)剩余的內(nèi)容的話,則只讀取剩余的內(nèi)容。

第10-11行:使用copy_from_user從用戶空間拷貝tmp個(gè)字節(jié)的數(shù)據(jù)到數(shù)據(jù)緩沖區(qū)中,同時(shí)讓文件的讀寫(xiě)位置偏移同樣的字節(jié)數(shù)。

chr_dev_read函數(shù)

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   int tmp = count ;   if (p >= BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_to_user(buf, vbuf+p, tmp);   *ppos +=tmp;   return tmp;}

同樣的,當(dāng)我們應(yīng)用程序調(diào)用read函數(shù),則會(huì)執(zhí)行chr_dev_read函數(shù)的內(nèi)容。 該函數(shù)的實(shí)現(xiàn)與chr_dev_write函數(shù)類似,區(qū)別在于,使用copy_to_user從數(shù)據(jù)緩沖區(qū)拷貝tmp個(gè)字節(jié)的數(shù)據(jù)到用戶空間中。

6.2.3. 簡(jiǎn)單測(cè)試程序?

下面,我們開(kāi)始編寫(xiě)應(yīng)用程序,來(lái)讀寫(xiě)我們的字符設(shè)備,如下所示。

main.c函數(shù)

#include #include #include #include char *wbuf = "Hello Worldn";char rbuf[128];int main(void){   printf("EmbedCharDev testn");   //打開(kāi)文件   int fd = open("/dev/chrdev", O_RDWR);   //寫(xiě)入數(shù)據(jù)   write(fd, wbuf, strlen(wbuf));   //寫(xiě)入完畢,關(guān)閉文件   close(fd);   //打開(kāi)文件   fd = open("/dev/chrdev", O_RDWR);   //讀取文件內(nèi)容   read(fd, rbuf, 128);   //打印讀取的內(nèi)容   printf("The content : %s", rbuf);   //讀取完畢,關(guān)閉文件   close(fd);   return 0;}

第11行:以可讀可寫(xiě)的方式打開(kāi)我們創(chuàng)建的字符設(shè)備驅(qū)動(dòng)

第12-15行:寫(xiě)入數(shù)據(jù)然后關(guān)閉

第17-21行:再次打開(kāi)設(shè)備將數(shù)據(jù)讀取出來(lái)

6.3. 實(shí)驗(yàn)準(zhǔn)備?

6.3.1. makefile修改說(shuō)明?

makefile

KERNEL_DIR=/home/lhd/project/3588/linux5.10-rkr6/kernelCROSS_COMPILE=/home/lhd/project/3588/linux5.10-rkr6/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gccobj-m := chrdev.oout =  chrdev_testall:    $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules    $(CROSS_COMPILE) -o $(out) main.c.PHONY:cleanclean:    $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean    rm $(out)

Makefile與此前相比,增加了編譯測(cè)試程序部分。

第1行:該Makefile定義了變量KERNEL_DIR,來(lái)保存內(nèi)核源碼的目錄。

第2行: 指定了工具鏈

第5行:變量obj-m保存著需要編譯成模塊的目標(biāo)文件名。

第6行:變量out保存著需要編譯成測(cè)試程序的目標(biāo)文件名。

第8行:’$(MAKE)modules’實(shí)際上是執(zhí)行Linux頂層Makefile的偽目標(biāo)modules。通過(guò)選項(xiàng)’-C’,可以讓make工具跳轉(zhuǎn)到源碼目錄下讀取頂層Makefile?!疢=$(CURDIR)’表明返回到當(dāng)前目錄,讀取并執(zhí)行當(dāng)前目錄的Makefile,開(kāi)始編譯內(nèi)核模塊。CURDIR是make的內(nèi)嵌變量,自動(dòng)設(shè)置為當(dāng)前目錄。

第9行:交叉編譯工具鏈編譯測(cè)試程序。

6.3.2. 編譯命令說(shuō)明?

make

編譯成功后,實(shí)驗(yàn)?zāi)夸浵聲?huì)生成兩個(gè)名為”chrdev.ko”驅(qū)動(dòng)模塊文件和” chrdev_test”測(cè)試程序。

6.4. 程序運(yùn)行結(jié)果?

通過(guò)我們?yōu)榇蠹揖帉?xiě)好的Makefile,執(zhí)行make,會(huì)生成chrdev.ko文件和驅(qū)動(dòng)測(cè)試程序chrdev_test, 通過(guò)nfs網(wǎng)絡(luò)文件系統(tǒng)或者scp,將文件拷貝到開(kāi)發(fā)板。 執(zhí)行以下命令:

sudo insmod chrdev.kocat /proc/devices

wKgaomYVR-eAesRFAABlv1S12rA617.png

我們從/proc/devices文件中,可以看到我們注冊(cè)的字符設(shè)備EmbedCharDev的主設(shè)備號(hào)為234。 注意此主設(shè)備號(hào)下面會(huì)用到,大家開(kāi)發(fā)板根據(jù)實(shí)際指調(diào)整

mknod /dev/chrdev c 234 0

以root權(quán)限使用mknod命令來(lái)創(chuàng)建一個(gè)新的設(shè)備chrdev,見(jiàn)下圖。

wKgaomYVR5uAciciAAAZskFo9sY352.png

以root權(quán)限運(yùn)行chrdev_test,測(cè)試程序,效果見(jiàn)下圖。

wKgZomYVR5uASIGjAAAVPnqgWaY321.png

實(shí)際上,我們也可以通過(guò)echo或者cat命令,來(lái)測(cè)試我們的設(shè)備驅(qū)動(dòng)程序。

echo "EmbedCharDev test" > /dev/chrdev

當(dāng)我們不需要該內(nèi)核模塊的時(shí)候,我們可以執(zhí)行以下命令:

rmmod chrdev.korm /dev/chrdev

使用命令rmmod,卸載內(nèi)核模塊,并且刪除相應(yīng)的設(shè)備文件。

7. 一個(gè)驅(qū)動(dòng)支持多個(gè)設(shè)備?

在Linux內(nèi)核中,主設(shè)備號(hào)用于標(biāo)識(shí)設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)程序,告訴Linux內(nèi)核使用哪一個(gè)驅(qū)動(dòng)程序?yàn)樵撛O(shè)備服務(wù)。但是, 次設(shè)備號(hào)表示了同類設(shè)備的各個(gè)設(shè)備。每個(gè)設(shè)備的功能都是不一樣的。如何能夠用一個(gè)驅(qū)動(dòng)程序去控制各種設(shè)備呢? 很明顯,首先,我們可以根據(jù)次設(shè)備號(hào),來(lái)區(qū)分各種設(shè)備;其次,就是前文提到過(guò)的file結(jié)構(gòu)體的私有數(shù)據(jù)成員private_data。 我們可以通過(guò)該成員來(lái)做文章,不難想到為什么只有open函數(shù)和close函數(shù)的形參才有file結(jié)構(gòu)體, 因?yàn)轵?qū)動(dòng)程序第一個(gè)執(zhí)行的是操作就是open,通過(guò)open函數(shù)就可以控制我們想要驅(qū)動(dòng)的底層硬件。

7.1. 硬件介紹?

本節(jié)實(shí)驗(yàn)使用到armsom-sige7

7.2. 實(shí)驗(yàn)代碼講解?

7.2.1. 實(shí)現(xiàn)方式一 管理各種的數(shù)據(jù)緩沖區(qū)?

下面介紹第一種實(shí)現(xiàn)方式,將我們的上一節(jié)程序改善一下,生成了兩個(gè)設(shè)備,各自管理各自的數(shù)據(jù)緩沖區(qū)。

chrdev.c修改

#define DEV_NAME "EmbedCharDev"#define DEV_CNT (2) (1)#define BUFF_SIZE 128//定義字符設(shè)備的設(shè)備號(hào)static dev_t devno;//定義字符設(shè)備結(jié)構(gòu)體chr_devstatic struct cdev chr_dev;//數(shù)據(jù)緩沖區(qū)static char vbuf1[BUFF_SIZE];static char vbuf2[BUFF_SIZE];

第2行:修改了宏定義DEV_CNT,將原本的個(gè)數(shù)1改為2,這樣的話,我們的驅(qū)動(dòng)程序便可以管理兩個(gè)設(shè)備。

第9-10行:處修改為兩個(gè)數(shù)據(jù)緩沖區(qū)。

chr_dev_open函數(shù)修改

static int chr_dev_open(struct inode *inode, struct file *filp){   printk("nopenn ");   switch (MINOR(inode->i_rdev)) {      case 0 : {         filp->private_data = vbuf1;         break;      }      case 1 : {         filp->private_data = vbuf2;         break;      }   }   return 0;}

我們知道inode結(jié)構(gòu)體中,對(duì)于設(shè)備文件的設(shè)備號(hào)會(huì)被保存到其成員i_rdev中。

第4行:在chr_dev_open函數(shù)中,我們使用宏定義MINOR來(lái)獲取該設(shè)備文件的次設(shè)備號(hào),使用private_data指向各自的數(shù)據(jù)緩沖區(qū)。

第5-12行:對(duì)于次設(shè)備號(hào)為0的設(shè)備,負(fù)責(zé)管理vbuf1的數(shù)據(jù),對(duì)于次設(shè)備號(hào)為1的設(shè)備,則用于管理vbuf2的數(shù)據(jù),這樣就實(shí)現(xiàn)了同一個(gè)設(shè)備驅(qū)動(dòng),管理多個(gè)設(shè)備了。

接下來(lái),我們的驅(qū)動(dòng)只需要對(duì)private_data進(jìn)行讀寫(xiě)即可。

chr_dev_write函數(shù)

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   char *vbuf = filp->private_data;   int tmp = count ;   if (p > BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_from_user(vbuf, buf, tmp);   *ppos += tmp;   return tmp;}

可以看到,我們的chr_dev_write函數(shù)改動(dòng)很小,只是增加了第5行的代碼,將原先vbuf數(shù)據(jù)指向了private_data,這樣的話, 當(dāng)我們往次設(shè)備號(hào)為0的設(shè)備寫(xiě)數(shù)據(jù)時(shí),就會(huì)往vbuf1中寫(xiě)入數(shù)據(jù)。次設(shè)備號(hào)為1的設(shè)備寫(xiě)數(shù)據(jù),也是同樣的道理。

chr_dev_read函數(shù)

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   int tmp = count ;   char *vbuf = filp->private_data;   if (p >= BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_to_user(buf, vbuf+p, tmp);   *ppos +=tmp;   return tmp;}

同樣的,chr_dev_read函數(shù)也只是增加了第6行的代碼,將原先的vbuf指向了private_data成員。

7.2.2. 實(shí)現(xiàn)方式二 i_cdev變量?

我們回憶一下,我們前面講到的文件節(jié)點(diǎn)inode中的成員i_cdev,為了方便訪問(wèn)設(shè)備文件,在打開(kāi)文件過(guò)程中, 將對(duì)應(yīng)的字符設(shè)備結(jié)構(gòu)體cdev保存到該變量中,那么我們也可以通過(guò)該變量來(lái)做文章。

定義設(shè)備

/*虛擬字符設(shè)備*/struct chr_dev {struct cdev dev;char vbuf[BUFF_SIZE];};//字符設(shè)備1static struct chr_dev vcdev1;//字符設(shè)備2static struct chr_dev vcdev2;

以上代碼中定義了一個(gè)新的結(jié)構(gòu)體struct chr_dev,它有兩個(gè)結(jié)構(gòu)體成員:字符設(shè)備結(jié)構(gòu)體dev以及設(shè)備對(duì)應(yīng)的數(shù)據(jù)緩沖區(qū)。 使用新的結(jié)構(gòu)體類型struct chr_dev定義兩個(gè)虛擬設(shè)備vcdev1以及vcdev2。

chrdev_init函數(shù)

static int __init chrdev_init(void){   int ret;   printk("4 chrdev initn");   ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);   if (ret < 0)      goto alloc_err;   //關(guān)聯(lián)第一個(gè)設(shè)備:vdev1   cdev_init(&vcdev1.dev, &chr_dev_fops);   ret = cdev_add(&vcdev1.dev, devno+0, 1);   if (ret < 0) {      printk("fail to add vcdev1 ");      goto add_err1;   }   //關(guān)聯(lián)第二個(gè)設(shè)備:vdev2   cdev_init(&vcdev2.dev, &chr_dev_fops);   ret = cdev_add(&vcdev2.dev, devno+1, 1);   if (ret < 0) {      printk("fail to add vcdev2 ");      goto add_err2;   }   return 0;   add_err2:      cdev_del(&(vcdev1.dev));   add_err1:      unregister_chrdev_region(devno, DEV_CNT);   alloc_err:      return ret;}

chrdev_init函數(shù)的框架仍然沒(méi)有什么變化。

第10、17行:在添加字符設(shè)備時(shí),使用cdev_add依次添加。

第23-24行:當(dāng)虛擬設(shè)備1添加失敗時(shí),直接返回的時(shí)候,只需要注銷申請(qǐng)到的設(shè)備號(hào)即可。

第25-26行:若虛擬設(shè)備2添加失敗,則需要把虛擬設(shè)備1移除,再將申請(qǐng)的設(shè)備號(hào)注銷。

chrdev_exit函數(shù)(位于../linux_driver/EmbedCharDev/2_SupportMoreDev/chrdev.c)

static void __exit chrdev_exit(void){   printk("chrdev exitn");   unregister_chrdev_region(devno, DEV_CNT);   cdev_del(&(vcdev1.dev));   cdev_del(&(vcdev2.dev));}

chrdev_exit函數(shù)注銷了申請(qǐng)到的設(shè)備號(hào),使用cdev_del移除兩個(gè)虛擬設(shè)備。

chr_dev_open以及chr_dev_release函數(shù)

static int chr_dev_open(struct inode *inode, struct file *filp){   printk("openn");   filp->private_data = container_of(inode->i_cdev, struct chr_dev, dev);   return 0;}static int chr_dev_release(struct inode *inode, struct file *filp){   printk("releasen");   return 0;}

我們知道inode中的i_cdev成員保存了對(duì)應(yīng)字符設(shè)備結(jié)構(gòu)體的地址,但是我們的虛擬設(shè)備是把cdev封裝起來(lái)的一個(gè)結(jié)構(gòu)體, 我們要如何能夠得到虛擬設(shè)備的數(shù)據(jù)緩沖區(qū)呢?為此,Linux提供了一個(gè)宏定義container_of,該宏可以根據(jù)結(jié)構(gòu)體的某個(gè)成員的地址, 來(lái)得到該結(jié)構(gòu)體的地址。該宏需要三個(gè)參數(shù),分別是代表結(jié)構(gòu)體成員的真實(shí)地址,結(jié)構(gòu)體的類型以及結(jié)構(gòu)體成員的名字。 在chr_dev_open函數(shù)中,我們需要通過(guò)inode的i_cdev成員,來(lái)得到對(duì)應(yīng)的虛擬設(shè)備結(jié)構(gòu)體,并保存到文件指針filp的私有數(shù)據(jù)成員中。 假如,我們打開(kāi)虛擬設(shè)備1,那么inode->i_cdev便指向了vcdev1的成員dev,利用container_of宏, 我們就可以得到vcdev1結(jié)構(gòu)體的地址,也就可以操作對(duì)應(yīng)的數(shù)據(jù)緩沖區(qū)了。

chr_dev_write函數(shù)

static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   //獲取文件的私有數(shù)據(jù)   struct chr_dev *dev = filp->private_data;   char *vbuf = dev->vbuf;   int tmp = count ;   if (p > BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_from_user(vbuf, buf, tmp);   *ppos += tmp;   return tmp;}

對(duì)比第一種方法,實(shí)際上只是新增了第6行代碼,通過(guò)文件指針filp的成員private_data得到相應(yīng)的虛擬設(shè)備。 修改第7行的代碼,定義了char類型的指針變量,指向?qū)?yīng)設(shè)備的數(shù)據(jù)緩沖區(qū)。

chr_dev_read函數(shù)

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos){   unsigned long p = *ppos;   int ret;   int tmp = count ;   //獲取文件的私有數(shù)據(jù)   struct chr_dev *dev = filp->private_data;   char *vbuf = dev->vbuf;   if (p >= BUFF_SIZE)      return 0;   if (tmp > BUFF_SIZE - p)      tmp = BUFF_SIZE - p;   ret = copy_to_user(buf, vbuf+p, tmp);   *ppos +=tmp;   return tmp;}

讀函數(shù),與寫(xiě)函數(shù)的改動(dòng)部分基本一致,這里就只貼出代碼,不進(jìn)行講解。

7.3. 實(shí)驗(yàn)準(zhǔn)備?

分別獲取兩個(gè)種方式的內(nèi)核模塊源碼,將使用配套代碼 /linux_driver放到內(nèi)核同級(jí)目錄下,進(jìn)入到EmbedCharDev目錄,找到1_SupportMoreDev和2_SupportMoreDev。

7.3.1. makefile說(shuō)明?

至于Makefile文件,與上一小節(jié)的相同,這里便不再羅列出來(lái)了。

7.3.2. 編譯命令說(shuō)明?

在實(shí)驗(yàn)?zāi)夸浵螺斎肴缦旅顏?lái)編譯驅(qū)動(dòng)模塊:

make

編譯成功后,實(shí)驗(yàn)?zāi)夸浵聲?huì)分別生成驅(qū)動(dòng)模塊文件

7.4. 程序運(yùn)行結(jié)果?

通過(guò)NFS或者SCP將編譯好的驅(qū)動(dòng)模塊拷貝到開(kāi)發(fā)板中

下面我們 使用cat以及echo命令,對(duì)我們的驅(qū)動(dòng)程序進(jìn)行測(cè)試。

insmod 1_SupportMoreDev.kosudo mknod /dev/chrdev1 c 234 0sudo mknod /dev/chrdev2 c 234 1

通過(guò)以上命令,加載了新的內(nèi)核模塊,手動(dòng)創(chuàng)建了兩個(gè)新的字符設(shè)備,主設(shè)備號(hào)根據(jù)/proc/devices中描述設(shè)置,分 別是/dev/chrdev1和/dev/chrdev2,開(kāi)始進(jìn)行讀寫(xiě)測(cè)試:

echo "hello world" > /dev/chrdev1# 或者sudo sh -c "echo 'hello world' > /dev/chrdev1"echo "123456" > /dev/chrdev2# 或者sudo sh -c "echo '123456' > /dev/chrdev2"cat /dev/chrdev1cat /dev/chrdev2

可以看到設(shè)備chrdev1中保存了字符串“hello world”,而設(shè)備chrdev2中保存了字符串“123456”。 只需要幾行代碼,就可以實(shí)現(xiàn)一個(gè)驅(qū)動(dòng)程序,控制多個(gè)設(shè)備。

總結(jié)一下,一個(gè)驅(qū)動(dòng)支持多個(gè)設(shè)備的具體實(shí)現(xiàn)方式的重點(diǎn)在于如何運(yùn)用file的私有數(shù)據(jù)成員。 第一種方法是通過(guò)將各自的數(shù)據(jù)緩沖區(qū)放到該成員中,在讀寫(xiě)函數(shù)的時(shí)候,直接就可以對(duì)相應(yīng)的數(shù)據(jù)緩沖區(qū)進(jìn)行操作; 第二種方法則是通過(guò)將我們的數(shù)據(jù)緩沖區(qū)和字符設(shè)備結(jié)構(gòu)體封裝到一起,由于文件結(jié)構(gòu)體inode的成員i_cdev保存了對(duì)應(yīng)字符設(shè)備結(jié)構(gòu)體, 使用container_of宏便可以獲得封裝后的結(jié)構(gòu)體的地址,進(jìn)而得到相應(yīng)的數(shù)據(jù)緩沖區(qū)。

到這里,字符設(shè)備驅(qū)動(dòng)就已經(jīng)講解完畢了。如果你發(fā)現(xiàn)自己有好多不理解的地方,學(xué)完本章之后,建議重新梳理一下整個(gè)過(guò)程, 有助于加深對(duì)整個(gè)字符設(shè)備驅(qū)動(dòng)框架的理解。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5125

    文章

    19438

    瀏覽量

    313070
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11420

    瀏覽量

    212319
  • 板卡
    +關(guān)注

    關(guān)注

    3

    文章

    129

    瀏覽量

    17081
  • Rockchip
    +關(guān)注

    關(guān)注

    0

    文章

    76

    瀏覽量

    18958
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    【15年重磅】嵌入式入門及項(xiàng)目實(shí)戰(zhàn)開(kāi)發(fā)【菜鳥(niǎo)必學(xué)項(xiàng)目】

    ://pan.baidu.com/s/1i3mVybb【嵌入式linux驅(qū)動(dòng)開(kāi)發(fā)系列】第六節(jié)Linux
    發(fā)表于 01-24 00:00

    零基礎(chǔ)學(xué)習(xí)嵌入式開(kāi)發(fā)以及項(xiàng)目實(shí)戰(zhàn)開(kāi)發(fā)

    linux下PWM驅(qū)動(dòng)實(shí)現(xiàn)過(guò)程以及代碼詳解(2)http://pan.baidu.com/s/1i3mVybb【嵌入式linux驅(qū)動(dòng)
    發(fā)表于 05-19 21:34

    嵌入式驅(qū)動(dòng)開(kāi)發(fā) Linux字符設(shè)備驅(qū)動(dòng)

    1.嵌入式設(shè)備驅(qū)動(dòng)概述2.字符設(shè)備驅(qū)動(dòng)框架3.GPIO驅(qū)動(dòng)
    發(fā)表于 10-09 17:21

    嵌入式 linux字符設(shè)備驅(qū)動(dòng)的設(shè)計(jì)與應(yīng)用,看完你就懂了

    本文通過(guò)實(shí)現(xiàn)對(duì) PXA255開(kāi)發(fā)板外圍字符設(shè)備(電機(jī)、數(shù)碼管、串口和 mini鍵盤)的操作和控制,詳細(xì)討論了嵌入式 linux
    發(fā)表于 04-26 06:35

    嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)

    想講好嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)并不容易,各位業(yè)界大神最基礎(chǔ)的字符驅(qū)動(dòng)到中斷并發(fā)再到
    發(fā)表于 11-04 09:02

    Linux嵌入式驅(qū)動(dòng)開(kāi)發(fā)

    嵌入式驅(qū)動(dòng)開(kāi)發(fā)04——應(yīng)用層和內(nèi)核層數(shù)據(jù)傳輸Linux嵌入式驅(qū)動(dòng)
    發(fā)表于 12-17 06:22

    嵌入式驅(qū)動(dòng)開(kāi)發(fā)字符驅(qū)動(dòng)

    嵌入式 驅(qū)動(dòng)開(kāi)發(fā)基礎(chǔ)2》 字符驅(qū)動(dòng) 2008年畢業(yè)于沈陽(yáng)航空航天大學(xué)電子...
    發(fā)表于 12-23 06:05

    嵌入式Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)

    嵌入式Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā) Linux 設(shè)備
    發(fā)表于 09-10 13:10 ?82次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b><b class='flag-5'>設(shè)備</b><b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>開(kāi)發(fā)</b>

    嵌入式Linux字符設(shè)備驅(qū)動(dòng)的設(shè)計(jì)與應(yīng)用

    描述了基于嵌入式Linux字符設(shè)備驅(qū)動(dòng)程序的設(shè)計(jì)方法和實(shí)現(xiàn)過(guò)程。以電機(jī)、數(shù)碼管、串口和mini鍵盤的驅(qū)
    發(fā)表于 02-23 15:45 ?24次下載

    嵌入式Linux字符設(shè)備驅(qū)動(dòng)的設(shè)計(jì)與應(yīng)用

    描述了基于嵌入式Linux字符設(shè)備驅(qū)動(dòng)程序的設(shè)計(jì)方法和實(shí)現(xiàn)過(guò)程。以電機(jī)、數(shù)碼管、串口和mini鍵盤的驅(qū)
    發(fā)表于 07-14 17:31 ?31次下載

    基于PXA255開(kāi)發(fā)板外圍字符設(shè)備嵌入式Linux字符設(shè)備驅(qū)動(dòng)設(shè)計(jì)與應(yīng)用

    驅(qū)動(dòng)程序和應(yīng)用程序的需求在成倍增長(zhǎng)。本文通過(guò)實(shí)現(xiàn)對(duì) PXA255開(kāi)發(fā)板外圍字符設(shè)備(電機(jī)、數(shù)碼管、串口和 mini鍵盤)的操作和控制,詳細(xì)討論了
    發(fā)表于 08-21 10:19 ?1278次閱讀
    基于PXA255<b class='flag-5'>開(kāi)發(fā)</b>板外圍<b class='flag-5'>字符</b><b class='flag-5'>設(shè)備</b>的<b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b><b class='flag-5'>字符</b><b class='flag-5'>設(shè)備</b><b class='flag-5'>驅(qū)動(dòng)</b>設(shè)計(jì)與應(yīng)用

    嵌入式Linux系統(tǒng)和驅(qū)動(dòng)開(kāi)發(fā)

    linux的中斷系統(tǒng)、Linux內(nèi)核的移植等。有了內(nèi)核的基礎(chǔ),就可以學(xué)習(xí)嵌入式Linux設(shè)備驅(qū)動(dòng)
    發(fā)表于 10-11 11:11 ?894次閱讀

    嵌入式Linux設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)基礎(chǔ)知識(shí)總結(jié)免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是嵌入式Linux設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)基礎(chǔ)知識(shí)總結(jié)免費(fèi)下載 嵌入式
    發(fā)表于 10-23 16:10 ?13次下載

    嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)從基礎(chǔ)到框架

    想講好嵌入式Linux驅(qū)動(dòng)開(kāi)發(fā)并不容易,各位業(yè)界大神最基礎(chǔ)的字符驅(qū)動(dòng)到中斷并發(fā)再到
    發(fā)表于 11-01 16:58 ?15次下載
    <b class='flag-5'>嵌入式</b><b class='flag-5'>Linux</b><b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>開(kāi)發(fā)</b>從基礎(chǔ)到框架

    迅為基于RK3568開(kāi)發(fā)板的嵌入式學(xué)習(xí)Linux驅(qū)動(dòng)視頻

    迅為基于RK3568開(kāi)發(fā)板的嵌入式學(xué)習(xí)Linux驅(qū)動(dòng)視頻
    的頭像 發(fā)表于 05-19 16:30 ?1194次閱讀
    迅為基于RK3568<b class='flag-5'>開(kāi)發(fā)</b>板的<b class='flag-5'>嵌入式</b>學(xué)習(xí)<b class='flag-5'>之</b><b class='flag-5'>Linux</b><b class='flag-5'>驅(qū)動(dòng)</b>視頻