手把手教Linux驅(qū)動3-之字符設(shè)備架構(gòu)詳解,建議收藏

<h1>一步淹、Linux設(shè)備分類</h1><p>Linux系統(tǒng)為了管理方便贾富,將設(shè)備分成三種基本類型:</p><ul><li><p>字符設(shè)備</p></li><li><p>塊設(shè)備</p></li><li><p>網(wǎng)絡(luò)設(shè)備</p></li></ul><p/><p>字符(char)設(shè)備是個能夠像字節(jié)流(類似文件)一樣被訪問的設(shè)備,由字符設(shè)備驅(qū)動程序來實(shí)現(xiàn)這種特性崇决。字符設(shè)備驅(qū)動程序通常至少要實(shí)現(xiàn)open材诽、close、read和write的系統(tǒng)調(diào)用恒傻。</p><p>字符終端(/dev/console)和串口(/dev/ttyS0以及類似設(shè)備)就是兩個字符設(shè)備脸侥,它們能很好的說明“流”這種抽象概念。</p><p>字符設(shè)備可以通過文件節(jié)點(diǎn)來訪問盈厘,比如/dev/tty1和/dev/lp0等睁枕。這些設(shè)備文件和普通文件之間的唯一差別在于對普通文件的訪問可以前后移動訪問位置,而大多數(shù)字符設(shè)備是一個只能順序訪問的數(shù)據(jù)通道沸手。然而外遇,也存在具有數(shù)據(jù)區(qū)特性的字符設(shè)備,訪問它們時可前后移動訪問位置契吉。例如framebuffer就是這樣的一個設(shè)備跳仿,app可以用mmap或lseek訪問抓取的整個圖像。</p><p class="image-package">在/dev下執(zhí)行l(wèi)s -l ,可以看到很多創(chuàng)建好的設(shè)備節(jié)點(diǎn):<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-4f8fd6dbe83d309a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>字符設(shè)備文件(類型為c)栅隐,設(shè)備文件是沒有文件大小的塔嬉,取而代之的是兩個號碼:主設(shè)備號5 +次設(shè)備號1 玩徊。</p><p/><p>和字符設(shè)備類似,塊設(shè)備也是通過/dev目錄下的文件系統(tǒng)節(jié)點(diǎn)來訪問谨究。塊設(shè)備(例如磁盤)上能夠容納filesystem恩袱。在大多數(shù)的Unix系統(tǒng)中,進(jìn)行I/O操作時塊設(shè)備每次只能傳輸一個或多個完整的塊胶哲,而每塊包含512字節(jié)(或2的更高次冪字節(jié)的數(shù)據(jù))畔塔。</p><p>Linux可以讓app像字符設(shè)備一樣地讀寫塊設(shè)備,允許一次傳遞任意多字節(jié)的數(shù)據(jù)鸯屿。因此澈吨,塊設(shè)備和字符設(shè)備的區(qū)別僅僅在于內(nèi)核內(nèi)部管理數(shù)據(jù)的方式,也就是內(nèi)核及驅(qū)動程序之間的軟件接口寄摆,而這些不同對用戶來講是透明的谅辣。在內(nèi)核中,和字符驅(qū)動程序相比婶恼,塊驅(qū)動程序具有完全不同的接口桑阶。</p><p class="image-package">塊設(shè)備文件(類型為b):<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-7893ee2dfb9d320b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p/><p>任何網(wǎng)絡(luò)事物都需要經(jīng)過一個網(wǎng)絡(luò)接口形成,網(wǎng)絡(luò)接口是一個能夠和其他主機(jī)交換數(shù)據(jù)的設(shè)備勾邦。接口通常是一個硬件設(shè)備蚣录,但也可能是個純軟件設(shè)備,比如回環(huán)(loopback)接口眷篇。</p><p>網(wǎng)絡(luò)接口由內(nèi)核中的網(wǎng)絡(luò)子系統(tǒng)驅(qū)動萎河,負(fù)責(zé)發(fā)送和接收數(shù)據(jù)包。許多網(wǎng)絡(luò)連接(尤其是使用TCP協(xié)議的連接)是面向流的蕉饼,但網(wǎng)絡(luò)設(shè)備卻圍繞數(shù)據(jù)包的傳送和接收而設(shè)計虐杯。網(wǎng)絡(luò)驅(qū)動程序不需要知道各個連接的相關(guān)信息,它只要處理數(shù)據(jù)包即可椎椰。</p><p>由于不是面向流的設(shè)備厦幅,因此將網(wǎng)絡(luò)接口映射到filesystem中的節(jié)點(diǎn)(比如/dev/tty1)比較困難。</p><p>Unix訪問網(wǎng)絡(luò)接口的方法仍然是給它們分配一個唯一的名字(<strong>比如eth0</strong>)慨飘,但這個名字在filesystem中不存在對應(yīng)的節(jié)點(diǎn)确憨。內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動程序間的通信,完全不同于內(nèi)核和字符以及塊驅(qū)動程序之間的通信瓤的,內(nèi)核調(diào)用一套和數(shù)據(jù)包相關(guān)的函數(shù)socket休弃,也叫套接字。</p><p class="image-package">查看網(wǎng)絡(luò)設(shè)備使用命令ifconfig:<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-1f90d722710a60e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><h1>二圈膏、字符設(shè)備架構(gòu)是如何實(shí)現(xiàn)的塔猾?</h1><p>在Linux的世界里面一切皆文件,所有的硬件設(shè)備操作到應(yīng)用層都會被抽象成文件的操作稽坤。我們知道如果應(yīng)用層要訪問硬件設(shè)備丈甸,它必定要調(diào)用到硬件對應(yīng)的驅(qū)動程序糯俗。Linux內(nèi)核中有那么多驅(qū)動程序,應(yīng)用層怎么才能精確的調(diào)用到底層的驅(qū)動程序呢睦擂?</p><p>在這里我們字符設(shè)備為例得湘,來看一下應(yīng)用程序是如何和底層驅(qū)動程序關(guān)聯(lián)起來的。必須知道的基礎(chǔ)知識:</p><ul><li><p>1.在Linux文件系統(tǒng)中顿仇,每個文件都用一個struct inode結(jié)構(gòu)體來描述淘正,這個結(jié)構(gòu)體里面記錄了這個文件的所有信息,例如:文件類型臼闻,訪問權(quán)限等鸿吆。</p></li><li><p>2.在Linux操作系統(tǒng)中,每個驅(qū)動程序在應(yīng)用層的/dev目錄下都會有一個設(shè)備文件和它對應(yīng)述呐,并且該文件會有對應(yīng)的主設(shè)備號和次設(shè)備號惩淳。</p></li><li><p>3.在Linux操作系統(tǒng)中,每個驅(qū)動程序都要分配一個主設(shè)備號市埋,字符設(shè)備的設(shè)備號保存在struct cdev結(jié)構(gòu)體中黎泣。</p></li><li><p>?struct cdev {</p></li><li><p>? ? ? ? struct kobject kobj;</p></li><li><p>? ? ? ? struct module owner;</p></li><li><p>? ? ? ? const struct file_operations ops;//接口函數(shù)集合</p></li><li><p>? ? ? ? struct list_head list;//內(nèi)核鏈表</p></li><li><p>? ? ? ? dev_t dev;? ? //設(shè)備號</p></li><li><p>? ? ? ? unsigned int count;//次設(shè)備號個數(shù)</p></li><li><p>? ? };</p></li><li><p>4.在Linux操作系統(tǒng)中缤谎,每打開一次文件,Linux操作系統(tǒng)在VFS層都會分配一個struct file結(jié)構(gòu)體來描述打開的這個文件褐着。該結(jié)構(gòu)體用于維護(hù)文件打開權(quán)限坷澡、文件指針偏移值、私有內(nèi)存地址等信息含蓉。</p></li></ul><p><strong>注意:</strong></p><p>常常我們認(rèn)為struct inode描述的是文件的靜態(tài)信息频敛,即這些信息很少會改變。而struct file描述的是動態(tài)信息馅扣,即在對文件的操作的時候斟赚,struct file里面的信息經(jīng)常會發(fā)生變化。典型的是struct file結(jié)構(gòu)體里面的f_pos(記錄當(dāng)前文件的位移量),每次讀寫一個普通文件時f_ops的值都會發(fā)生改變差油。</p><p class="image-package">這幾個結(jié)構(gòu)體關(guān)系如下圖所示:<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-03ae21539f02faf4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>通過上圖我們可以知道拗军,如果想訪問底層設(shè)備,就必須打開對應(yīng)的設(shè)備文件蓄喇。也就是在這個打開的過程中发侵,Linux內(nèi)核將應(yīng)用層和對應(yīng)的驅(qū)動程序關(guān)聯(lián)起來。</p><ul><li><p>1.當(dāng)open函數(shù)打開設(shè)備文件時妆偏,可以根據(jù)設(shè)備文件對應(yīng)的struct inode結(jié)構(gòu)體描述的信息刃鳄,可以知道接下來要操作的設(shè)備類型(字符設(shè)備還是塊設(shè)備)。還會分配一個struct file結(jié)構(gòu)體钱骂。</p></li><li><p>2.根據(jù)struct inode結(jié)構(gòu)體里面記錄的設(shè)備號叔锐,可以找到對應(yīng)的驅(qū)動程序挪鹏。這里以字符設(shè)備為例。在Linux操作系統(tǒng)中每個字符設(shè)備有一個struct cdev結(jié)構(gòu)體愉烙。此結(jié)構(gòu)體描述了字符設(shè)備所有的信息讨盒,其中最重要一項(xiàng)的就是字符設(shè)備的操作函數(shù)接口。</p></li><li><p>3.找到struct cdev結(jié)構(gòu)體后齿梁,Linux內(nèi)核就會將struct cdev結(jié)構(gòu)體所在的內(nèi)存空間首地記錄在struct inode結(jié)構(gòu)體的i_cdev成員中催植。將struct cdev結(jié)構(gòu)體的中記錄的函數(shù)操作接口地址記錄在struct file結(jié)構(gòu)體的f_op成員中。</p></li><li><p>4.任務(wù)完成勺择,VFS層會給應(yīng)用層返回一個文件描述符(fd)创南。這個fd是和struct file結(jié)構(gòu)體對應(yīng)的。接下來上層的應(yīng)用程序就可以通過fd來找到strut file,然后在由struct file找到操作字符設(shè)備的函數(shù)接口了省核。</p></li></ul><h1>三稿辙、字符驅(qū)動相關(guān)函數(shù)分析</h1><p>
</p><p>
</p><p>/
</p><p>?* cdev_init() - initialize a cdev structure</p><p>?* @cdev: the structure to initialize</p><p>?* @fops: the file_operations for this device</p><p>?</p><p>? Initializes @cdev, remembering @fops, making it ready to add to the</p><p>?* system with cdev_add().</p><p>?/</p><p>void cdev_init(struct cdev cdev, const struct file_operations fops)</p><p>功能:</p><p>? 初始化cdev結(jié)構(gòu)體</p><p>參數(shù):</p><p>? @cdev cdev結(jié)構(gòu)體地址</p><p>? @fops 操作字符設(shè)備的函數(shù)接口地址</p><p>返回值:</p><p>? 無</p><p>
</p><p>/
</p><p>?
register_chrdev_region() - register a range of device numbers</p><p>?* @from: the first in the desired range of device numbers; must include</p><p>?? ? ? ? the major number.</p><p>? @count: the number of consecutive device numbers required</p><p>?* @name: the name of the device or driver.</p><p>?</p><p>? Return value is zero on success, a negative error code on failure.</p><p>?/? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??</p><p>int register_chrdev_region(dev_t from, unsigned count, const char name)</p><p>功能:</p><p>? 注冊一個范圍()的設(shè)備號</p><p>參數(shù):</p><p>? @from 設(shè)備號</p><p>? @count 注冊的設(shè)備個數(shù)</p><p>? @name 設(shè)備的名字</p><p>返回值:</p><p>? 成功返回0,失敗返回錯誤碼(負(fù)數(shù))</p><p>
</p><p>/
</p><p>?* cdev_add() - add a char device to the system</p><p>?* @p: the cdev structure for the device</p><p>?* @dev: the first device number for which this device is responsible</p><p>?* @count: the number of consecutive minor numbers corresponding to this</p><p>?? ? ? ? ?device</p><p>?</p><p>?* cdev_add() adds the device represented by @p to the system, making it</p><p>?* live immediately.? A negative error code is returned on failure.</p><p>?/</p><p>int cdev_add(struct cdev p, dev_t dev, unsigned count)</p><p>功能:</p><p>? 添加一個字符設(shè)備到操作系統(tǒng)</p><p>參數(shù):</p><p>? @p cdev結(jié)構(gòu)體地址</p><p>? @dev 設(shè)備號</p><p>? @count 次設(shè)備號個數(shù)</p><p>返回值:</p><p>? 成功返回0,失敗返回錯誤碼(負(fù)數(shù))</p><p>
</p><p>/
</p><p>?* cdev_del() - remove a cdev from the system</p><p>?* @p: the cdev structure to be removed</p><p>?</p><p>? cdev_del() removes @p from the system, possibly freeing the structure</p><p>?* itself.</p><p>?/</p><p>void cdev_del(struct cdev p)</p><p>功能:</p><p>? 從系統(tǒng)中刪除一個字符設(shè)備</p><p>參數(shù):</p><p>? @p cdev結(jié)構(gòu)體地址</p><p>返回值:</p><p>? 無</p><p>
</p><p>static inline int register_chrdev(unsigned int major, const char name,</p><p>? ? ? ? ? const struct file_operations fops)</p><p>
</p><p>功能:</p><p>? 注冊或者分配設(shè)備號,并注冊fops到cdev結(jié)構(gòu)體气忠,</p><p>? 如果major>0邻储,功能為注冊該主設(shè)備號,</p><p>? 如果major=0旧噪,功能為動態(tài)分配主設(shè)備號吨娜。</p><p>參數(shù):</p><p>? @major : 主設(shè)備號</p><p>? @name : 設(shè)備名稱,執(zhí)行 cat /proc/devices顯示的名稱</p><p>? @fops? : 文件系統(tǒng)的接口指針</p><p>返回值</p><p>? 如果major>0? ?成功返回0淘钟,失敗返回負(fù)的錯誤碼</p><p>? 如果major=0? 成功返回主設(shè)備號宦赠,失敗返回負(fù)的錯誤碼</p><p>
</p><p>
</p><p>?該函數(shù)實(shí)現(xiàn)了對cdev的初始化和注冊的封裝,所以調(diào)用該函數(shù)之后就不需要自己操作cdev了米母。</p><p>
</p><p>
</p><p>?相對的注銷函數(shù)為unregister_chrdev</p><p>
</p><p>
</p><p>static inline void unregister_chrdev(unsigned int major, const char name)</p><h1>四勾扭、如何編寫字符設(shè)備驅(qū)動</h1><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-402c9f57e884d9c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/>在這里插入圖片描述</p><p>參考上圖,編寫字符設(shè)備驅(qū)動步驟如下:</p><p/><pre>module_init?(hello_init);module_exit?(hello_exit);</pre><p/><p>申請主設(shè)備號 ?(內(nèi)核中用于區(qū)分和管理不同字符設(shè)備)</p><pre>register_chrdev_region?(devno,?number_of_devices,?"hello");</pre><p/><p>創(chuàng)建設(shè)備節(jié)點(diǎn)文件 (為用戶提供一個可操作到文件接口--open())
創(chuàng)建設(shè)備節(jié)點(diǎn)有兩種方式:手動方式創(chuàng)建铁瞒,函數(shù)自動創(chuàng)建妙色。<strong>手動創(chuàng)建:</strong></p><pre>mknod?/dev/hello?c?250?0</pre><p><strong>自動創(chuàng)建設(shè)備節(jié)點(diǎn)</strong></p><p class="image-package">除了使用mknod命令手動創(chuàng)建設(shè)備節(jié)點(diǎn),還可以利用linux的udev慧耍、mdev機(jī)制身辨,而我們的ARM開發(fā)板上移植的busybox有mdev機(jī)制,那么就使用mdev機(jī)制來自動創(chuàng)建設(shè)備節(jié)點(diǎn)蜂绎。</p><p>在etc/init.d/rcS文件里有一句:</p><pre>echo?/sbin/mdev?>?/proc/sys/kernel/hotplug</pre><p>該名命令就是用來自動創(chuàng)建設(shè)備節(jié)點(diǎn)栅表。</p><p>udev 是一個工作在用戶空間的工具,它能根據(jù)系統(tǒng)中硬件設(shè)備的狀態(tài)動態(tài)的更新設(shè)備文件师枣,包括設(shè)備文件的創(chuàng)建怪瓶,刪除,權(quán)限等。這些文件通常都定義在/dev 目錄下洗贰,但也可以在配置文件中指定找岖。udev 必須有內(nèi)核中的sysfs和tmpfs支持,sysfs 為udev 提供設(shè)備入口和uevent 通道敛滋,tmpfs 為udev 設(shè)備文件提供存放空間许布。</p><p class="image-package">udev 運(yùn)行在用戶模式,而非內(nèi)核中绎晃。udev 的初始化腳本在系統(tǒng)啟動時創(chuàng)建設(shè)備節(jié)點(diǎn)蜜唾,并且當(dāng)插入新設(shè)備——加入驅(qū)動模塊——在sysfs上注冊新的數(shù)據(jù)后,udev會創(chuàng)新新的設(shè)備節(jié)點(diǎn)庶艾。</p><p>注意袁余,udev 是通過對內(nèi)核產(chǎn)生的設(shè)備文件修改,或增加別名的方式來達(dá)到自定義設(shè)備文件的目的咱揍。但是颖榜,udev 是用戶模式程序,其不會更改內(nèi)核行為煤裙。也就是說掩完,內(nèi)核仍然會創(chuàng)建sda,sdb等設(shè)備文件硼砰,而udev可根據(jù)設(shè)備的唯一信息來區(qū)分不同的設(shè)備且蓬,并產(chǎn)生新的設(shè)備文件(或鏈接)。</p><p><strong>例如:</strong></p><p class="image-package">如果驅(qū)動模塊可以將自己的設(shè)備號作為內(nèi)核參數(shù)導(dǎo)出题翰,在sysfs文件中就有一個叫做uevent文件記錄它的值缅疟。<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-f5d3d95cd60c7f7f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>由上圖可知,uevent中包含了主設(shè)備號和次設(shè)備號的值以及設(shè)備名字遍愿。</p><p>在Linux應(yīng)用層啟動一個udev程序,這個程序的第一次運(yùn)行的時候耘斩,會遍歷/sys目錄沼填,尋找每個子目錄的uevent文件,從這些uevent文件中獲取創(chuàng)建設(shè)備節(jié)點(diǎn)的信息括授,然后調(diào)用mknod程序在/dev目錄下創(chuàng)建設(shè)備節(jié)點(diǎn)坞笙。結(jié)束之后,udev就開始等待內(nèi)核空間的event荚虚。這個設(shè)備模型的東西薛夜,我們在后面再詳細(xì)說。這里大就可以這樣理解版述,在Linux內(nèi)核中提供了一些函數(shù)接口梯澜,通過這些函數(shù)接口,我們可在sysfs文件系統(tǒng)中導(dǎo)出我們的設(shè)備號的值渴析,導(dǎo)出值之后晚伙,內(nèi)核還會向應(yīng)用層上報event吮龄。此時udev就知道有活可以干了,它收到這個event后咆疗,就讀取event對應(yīng)的信息漓帚,接下來就開始創(chuàng)建設(shè)備節(jié)點(diǎn)啦。</p><p>如何創(chuàng)建一個設(shè)備類午磁?</p><p>
</p><p>第一步 :通過宏class_create() 創(chuàng)建一個class類型的對象尝抖;</p><p>
</p><p>?</p><p>/
This is a #define to keep the compiler from merging different</p><p>?
instances of the __key variable /</p><p>#define class_create(owner, name)? ? </p><p>({? ? ? ? ? ? </p><p>? static struct lock_class_key __key;? </p><p>? __class_create(owner, name, &__key);? </p><p>})</p><p>
</p><p>參數(shù):</p><p>? @owner? THIS_MODULE</p><p>? @name? ?類名字</p><p>返回值</p><p>? 可以定義一個struct class的指針變量cls接受返回值,然后通過IS_ERR(cls)判斷</p><p>? 是否失敗迅皇,如果成功這個宏返回0昧辽,失敗返回非9值(可以通過PTR_ERR(cls)來獲得</p><p>? 失敗返回的錯誤碼)</p><p>?</p><p>在Linux內(nèi)核中,把設(shè)備進(jìn)行了分類喧半,同一類設(shè)備可以放在同一個目錄下奴迅,該函數(shù)啟示就是創(chuàng)建了一個類,例如:</p><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-b9be7bce345b4b61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p/><p>
</p><p>?第二步:導(dǎo)出我們的設(shè)備信息到用戶空間</p><p>
</p><p>?</p><p>/
</p><p>?
device_create - creates a device and registers it with sysfs</p><p>?
@class: pointer to the struct class that this device should be registered to</p><p>?* @parent: pointer to the parent struct device of this new device, if any</p><p>?* @devt: the dev_t for the char device to be added</p><p>?* @drvdata: the data to be added to the device for callbacks</p><p>?* @fmt: string for the device's name</p><p>?</p><p>? This function can be used by char device classes.? A struct device</p><p>?* will be created in sysfs, registered to the specified class.</p><p>?</p><p>? A "dev" file will be created, showing the dev_t for the device, if</p><p>?* the dev_t is not 0,0.</p><p>?* If a pointer to a parent struct device is passed in, the newly created</p><p>?* struct device will be a child of that device in sysfs.</p><p>?* The pointer to the struct device will be returned from the call.</p><p>?* Any further sysfs files that might be required can be created using this</p><p>?* pointer.</p><p>?</p><p>? Returns &struct device pointer on success, or ERR_PTR() on error.</p><p>?</p><p>? Note: the struct class passed to this function must have previously</p><p>?* been created with a call to class_create().</p><p>?/</p><p>struct device device_create(struct class class, struct device parent,</p><p>? ? ? ? ? ?dev_t devt, void drvdata, const char fmt, ...)</p><p>```</p><p>
</p><p>
</p><p>自動創(chuàng)建設(shè)備節(jié)點(diǎn)使用實(shí)例:</p><p>
</p><p>?</p><p>static struct class cls;</p><p>static struct device test_device;</p><p>
</p><p>? devno = MKDEV(major,minor);</p><p>? cls = class_create(THIS_MODULE,"helloclass");</p><p>? if(IS_ERR(cls))</p><p>? {</p><p>? ? unregister_chrdev(major,"hello");</p><p>? ? return result;</p><p>? }</p><p>? test_device = device_create(cls,NULL,devno,NULL,"hellodevice");</p><p>? if(IS_ERR(test_device ))</p><p>? {</p><p>? ? class_destroy(cls);</p><p>? ? unregister_chrdev(major,"hello");</p><p>? ? return result;</p><p>? }</p><p>?</p><p>
</p><p>
</p><p>
</p><p>?4 實(shí)現(xiàn)file_operations</p><p>
</p><p>
</p><p>?</p><p>static const struct file_operations fifo_operations = {</p><p>? ? .owner =? ?THIS_MODULE,</p><p>? ? .open =? ?dev_fifo_open,</p><p>? ? .read =? ?dev_fifo_read,</p><p>? ? .write =? ?dev_fifo_write,</p><p>? ? .unlocked_ioctl =? ?dev_fifo_unlocked_ioctl,</p><p>};</p><p>?</p><p>open挺据、release對應(yīng)應(yīng)用層的open()取具、close()函數(shù)。實(shí)現(xiàn)比較簡單扁耐,
直接返回0即可暇检。
其中read、write婉称、unloched_ioctrl 函數(shù)的實(shí)現(xiàn)需要涉及到用戶空間
和內(nèi)存空間的數(shù)據(jù)拷貝块仆。</p><p>在Linux操作系統(tǒng)中,用戶空間和內(nèi)核空間是相互獨(dú)立的王暗。也就是說內(nèi)核空間是不能直接訪問用戶空間內(nèi)存地址悔据,同理用戶空間也不能直接訪問內(nèi)核空間內(nèi)存地址。</p><p>如果想實(shí)現(xiàn)俗壹,將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間或?qū)?nèi)核空間數(shù)據(jù)拷貝到用戶空間科汗,就必須借助內(nèi)核給我們提供的接口來完成。</p><p><strong>read接口實(shí)現(xiàn)</strong></p><p>
</p><p><strong>用戶空間-->內(nèi)核空間</strong></p><p>字符設(shè)備的write接口定義如下:</p><p>ssize_t (
write)(struct file filp, const char __user buf, size_t count, loff_t f_pos);</p><p>參數(shù):</p><p>? filp:待操作的設(shè)備文件file結(jié)構(gòu)體指針</p><p>? buf:待寫入所讀取數(shù)據(jù)的用戶空間緩沖區(qū)指針</p><p>? count:待讀取數(shù)據(jù)字節(jié)數(shù)</p><p>? f_pos:待讀取數(shù)據(jù)文件位置绷雏,寫入完成后根據(jù)實(shí)際寫入字節(jié)數(shù)重新定位</p><p>返回:</p><p>? 成功實(shí)際寫入的字節(jié)數(shù)头滔,失敗返回負(fù)值</p><p>如果該操作為空,將使得write系統(tǒng)調(diào)用返回負(fù)EINVAL失敗涎显,正常返回實(shí)際寫入的字節(jié)數(shù)坤检。</p><p>用戶空間向內(nèi)核空間拷貝數(shù)據(jù)需要使用copy_from_user函數(shù),該函數(shù)定義在arch/arm/include/asm/uaccess.h中期吓。</p><p>static inline int copy_from_user(void to, const void __user volatile from,unsigned long n)</p><p>參數(shù):</p><p>? to:目標(biāo)地址(內(nèi)核空間)</p><p>? from:源地址(用戶空間)</p><p>? n:將要拷貝數(shù)據(jù)的字節(jié)數(shù)</p><p>返回:</p><p>? 成功返回0早歇,失敗返回沒有拷貝成功的數(shù)據(jù)字節(jié)數(shù)</p><pre>[object?Object]</pre><p>?</p><p>還可以使用get_user宏:</p><p/><p>int get_user(data, ptr);</p><p>參數(shù):</p><p>? data:可以是字節(jié)、半字、字缺前、雙字類型的內(nèi)核變量</p><p>? ptr:用戶空間內(nèi)存指針</p><p>返回:</p><p>? 成功返回0蛀醉,失敗返回非0</p><p><strong>
</strong></p><p><strong>write接口實(shí)現(xiàn)</strong></p><p><strong>內(nèi)核空間-->用戶空間</strong></p><p>字符設(shè)備的read接口定義如下:</p><pre>ssize_t?(
read)(struct?file?
filp,?char?__user?
buf,?size_t??count,?lofft?
f_pos);
參數(shù):
??filp:?待操作的設(shè)備文件file結(jié)構(gòu)體指針
??buf:??待寫入所讀取數(shù)據(jù)的用戶空間緩沖區(qū)指針
??count:待讀取數(shù)據(jù)字節(jié)數(shù)
??f_pos:待讀取數(shù)據(jù)文件位置,讀取完成后根據(jù)實(shí)際讀取字節(jié)數(shù)重新定位
??__user?:是一個空的宏衅码,主要用來顯示的告訴程序員它修飾的指針變量存放的是用戶空間的地址拯刁。
返回值:
??成功實(shí)際讀取的字節(jié)數(shù),失敗返回負(fù)值</pre><p><strong>注意</strong>:如果該操作為空逝段,將使得read系統(tǒng)調(diào)用返回負(fù)EINVAL失敗垛玻,正常返回實(shí)際讀取的字節(jié)數(shù)。</p><p>用戶空間從內(nèi)核空間讀取數(shù)據(jù)需要使用copy_to_user函數(shù):</p><pre>?static?inline?int?copy_to_user(void?__user?volatile?
to,?const?void?
from,unsigned?long?n)
參數(shù):
??to:目標(biāo)地址(用戶空間)
??from:源地址(內(nèi)核空間)
??n:將要拷貝數(shù)據(jù)的字節(jié)數(shù)
返回:
??成功返回0奶躯,失敗返回沒有拷貝成功的數(shù)據(jù)字節(jié)數(shù)</pre><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-6362360ce65b02d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/>?</p><p>還可以使用put_user宏:</p><pre>int?put_user(data,?prt)
參數(shù):
??data:可以是字節(jié)帚桩、半字、字嘹黔、雙字類型的內(nèi)核變量
??ptr:用戶空間內(nèi)存指針
返回:
??成功返回0账嚎,?失敗返回非0</pre><p>這樣我們就可以實(shí)現(xiàn)read、write函數(shù)了儡蔓,實(shí)例如下:</p><pre>ssize_t?hello_read?(struct?file?
filp,?char?
buff,???size_t?count,?loff_t?
offp)
{
??ssize_t???result?=?0;
??if?(count???>?127)?
????count?=?127;
??if???(copy_to_user?(buff,?data,?count))
??{
????result?=???-EFAULT;
??}
??else
??{
????printk???(KERN_INFO?"wrote?%d?bytes\n",?count);
????result?=???count;
??}?
??return???result;
}
ssize_t?hello_write?(struct?file?
filp,const?char?
buf,?size_t?count,?loff_t?
f_pos)
{
??ssize_t?ret???=?0;
??//printk???(KERN_INFO?"Writing?%d?bytes\n",?count);
??if?(count???>?127)?return?-ENOMEM;
??if???(copy_from_user?(data,?buf,?count))?{
????ret?=???-EFAULT;
??}
??else?{
????data[count]?=?'\0';
????printk???(KERN_INFO"Received:?%s\n",?data);
????ret?=???count;
??}
??return?ret;
}</pre><p/><p/><p><strong>unlocked_ioctl接口實(shí)現(xiàn)</strong></p><p>
</p><p><strong>(1)為什么要實(shí)現(xiàn)xxx_ioctl ?</strong></p><p>前面我們在驅(qū)動中已經(jīng)實(shí)現(xiàn)了讀寫接口郭蕉,通過這些接口我們可以完成對設(shè)備的讀寫。但是很多時候我們的應(yīng)用層工程師除了要對設(shè)備進(jìn)行讀寫數(shù)據(jù)之外喂江,還希望可以對設(shè)備進(jìn)行控制召锈。例如:針對串口設(shè)備,驅(qū)動層除了需要提供對串口的讀寫之外获询,還需提供對串口波特率涨岁、奇偶校驗(yàn)位、終止位的設(shè)置吉嚣,這些配置信息需要從應(yīng)用層傳遞一些基本數(shù)據(jù)梢薪,僅僅是數(shù)據(jù)類型不同。</p><p>通過xxx_ioctl函數(shù)接口尝哆,可以提供對設(shè)備的控制能力,增加驅(qū)動程序的靈活性沮尿。</p><p><strong>(2)如何實(shí)現(xiàn)xxx_ioctl函數(shù)接口?</strong></p><p>增加xxx_ioctl函數(shù)接口,應(yīng)用層可以通過ioctl系統(tǒng)調(diào)用较解,根據(jù)不同的命令來操作dev_fifo。</p><p>kernel 2.6.35 及之前的版本中struct file_operations 一共有3個ioctl :ioctl,unlocked_ioctl和compat_ioctl 現(xiàn)在只有unlocked_ioctl和compat_ioctl 了</p><p>在kernel 2.6.36 中已經(jīng)完全刪除了struct file_operations 中的ioctl 函數(shù)指針赴邻,取而代之的是unlocked_ioctl 印衔。</p><p>· ? ? ? ? 2.6.36 之前的內(nèi)核</p><pre>long?(ioctl)?(struct?inode?node?,struct?file?filp,?unsigned?int?cmd,unsigned?long?arg)</pre><p>· ? ? ? ? 2.6.36之后的內(nèi)核</p><pre>long?(unlocked_ioctl)?(struct?file?filp,?unsigned?int?cmd,?unsigned?long?arg)</pre><p>參數(shù)cmd: 通過應(yīng)用函數(shù)ioctl傳遞下來的命令</p><p class="image-package">先來看看應(yīng)用層的ioctl和驅(qū)動層的xxx_ioctl對應(yīng)關(guān)系:<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-04f6beeb2e0223e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/><strong><1>應(yīng)用層ioctl參數(shù)分析</strong></p><pre>int?ioctl(int?fd,?int?cmd,?...);
參數(shù):
@fd:打開設(shè)備文件的時候獲得文件描述符?
@?cmd:第二個參數(shù):給驅(qū)動層傳遞的命令,需要注意的時候姥敛,驅(qū)動層的命令和應(yīng)用層的命令一定要統(tǒng)一
@第三個參數(shù):?"..."在C語言中奸焙,很多時候都被理解成可變參數(shù)。
返回值
???????成功:0
???????失敗:-1与帆,同時設(shè)置errno</pre><p>小貼士:</p><pre>當(dāng)我們通過ioctl調(diào)用驅(qū)動層xxx_ioctl的時候了赌,有三種情況可供選擇:
1:?不傳遞數(shù)據(jù)給xxx_ioctl?
2:?傳遞數(shù)據(jù)給xxx_ioctl,希望它最終能把數(shù)據(jù)寫入設(shè)備(例如:設(shè)置串口的波特率)
3:?調(diào)用xxxx_ioctl希望獲取設(shè)備的硬件參數(shù)(例如:獲取當(dāng)前串口設(shè)備的波特率)
這三種情況中,有些時候需要傳遞數(shù)據(jù)玄糟,有些時候不需要傳遞數(shù)據(jù)勿她。在C語言中,是
無法實(shí)現(xiàn)函數(shù)重載的阵翎。那怎么辦?用"..."來欺騙編譯器了逢并,"..."本來的意思是傳
遞多參數(shù)。在這里的意思是帶一個參數(shù)還是不帶參數(shù)郭卫。
參數(shù)可以傳遞整型值砍聊,也可以傳遞某塊內(nèi)存的地址,內(nèi)核接口函數(shù)必須根據(jù)實(shí)際情況
提取對應(yīng)的信息贰军。</pre><p><strong><2>驅(qū)動層xxx_ioctl參數(shù)分析</strong></p><pre>long?(
unlocked_ioctl)?(struct?file?file,?unsigned?int?cmd,?unsigned?long?arg);
參數(shù):
@file:???vfs層為打開字符設(shè)備文件的進(jìn)程創(chuàng)建的結(jié)構(gòu)體玻蝌,用于存放文件的動態(tài)信息?
@?cmd:?用戶空間傳遞的命令,可以根據(jù)不同的命令做不同的事情
@第三個參數(shù):?用戶空間的數(shù)據(jù)词疼,主要這個數(shù)據(jù)可能是一個地址值(用戶空間傳遞的是一個地址)俯树,也可能是一個數(shù)值,也可能沒值
返回值
???????成功:0
???????失敽:帶錯誤碼的負(fù)值</pre><p><strong><3>如何確定cmd 的值聘萨。</strong></p><p>該值主要用于區(qū)分命令的類型,雖然我只需要傳遞任意一個整型值即可童太,但是我們盡量按照內(nèi)核規(guī)范要求米辐,充分利用這32bite的空間,如果大家都沒有規(guī)矩书释,又如何能成方圓翘贮?</p><p class="image-package">現(xiàn)在我就來看看,在Linux 內(nèi)核中這個cmd是如何設(shè)計的吧!<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-f013ad9738628c3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p>具體含義如下:</p><p class="image-package"><img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-eb5a6d0feb738a3b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p class="image-package">由上可以一個命令由4個部分組成爆惧,每個部分需要的bite都不完全一樣狸页,制作一個命令需要在不同的位域?qū)懖煌臄?shù)字,Linux 系統(tǒng)已經(jīng)給我們封裝好了宏扯再,我們只需要直接調(diào)用宏來設(shè)計命令即可芍耘。<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-b579fd05d3a0f6c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/></p><p class="image-package">通過Linux 系統(tǒng)給我們提供的宏,我們在設(shè)計命令的時候熄阻,只需要指定設(shè)備類型斋竞、命令序號,數(shù)據(jù)類型三個字段就可以了秃殉。<img class="uploaded-img" src="https://upload-images.jianshu.io/upload_images/23850874-4fd2e533e2653a09.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="auto" height="auto"/>Linux 系統(tǒng)中已經(jīng)設(shè)計了一場用的命令坝初,可以通過查閱Linux 源碼中的Documentation/ioctl/ioctl-number.txt文件浸剩,看哪些命令已經(jīng)被使用過了。</p><p><strong><4> 如何檢查命令鳄袍?</strong></p><p>可以通過宏_IOC_TYPE(nr)來判斷應(yīng)用程序傳下來的命令type是否正確绢要;</p><p>可以通過宏_IOC_DIR(nr)來得到命令是讀還是寫,然后再通過宏access_ok(type,addr,size)來判斷用戶層傳遞的內(nèi)存地址是否合法拗小。</p><p>使用方法如下:</p><pre>??if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){
????pr_err("cmd???%u,bad?magic?0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);
????return-ENOTTY;
??}
??if(_IOC_DIR(cmd)&_IOC_READ)
????ret=!access_ok(VERIFY_WRITE,(void?__user
)arg,_IOC_SIZE(cmd));
??else?if(?_IOC_DIR(cmd)&_IOC_WRITE?)
????ret=!access_ok(VERIFY_READ,(void???__user*)arg,_IOC_SIZE(cmd));
??if(ret){
????pr_err("bad???access?%ld.\n",ret);
????return-EFAULT;
??}</pre><p><strong>5 注冊cdev</strong></p><p>定義好file_operations結(jié)構(gòu)體重罪,就可以通過函數(shù)cdev_init()、cdev_add()注冊字符設(shè)備驅(qū)動了。</p><p>實(shí)例如下:</p><pre>static?struct?cdev?cdev;
cdev_init(&cdev,&hello_ops);
error?=?cdev_add(&cdev,devno,1);</pre><p>注意如果使用了函數(shù)register_chrdev(),就不用了執(zhí)行上述操作,因?yàn)樵摵瘮?shù)已經(jīng)實(shí)現(xiàn)了對cdev的封裝喻杈。</p><h1>五、實(shí)例</h1><p>好了惨篱,現(xiàn)在我們可以來實(shí)現(xiàn)一個完整的字符設(shè)備框架的實(shí)例,包括打開围俘、關(guān)閉砸讳、讀寫、ioctrl界牡、自動創(chuàng)建設(shè)備節(jié)點(diǎn)等功能簿寂。</p><pre>#include?<linux/init.h>

include?<linux/module.h>

include?<linux/cdev.h>

include?<linux/fs.h>

include?<linux/device.h>

include?<linux/slab.h>

include?<asm/uaccess.h>

include?"dev_fifo_head.h"

//指定的主設(shè)備號

define???MAJOR_NUM?250

//自己的字符設(shè)備
struct?mycdev
{
????int?len;
????unsigned???char?buffer[50];
????struct???cdev?cdev;
};
MODULE_LICENSE("GPL");
//設(shè)備號
static?dev_t???dev_num?=?{0};
//全局gcd
struct?mycdev?gcd;
//設(shè)備類
struct?class?
cls;
//獲得用戶傳遞的數(shù)據(jù),根據(jù)它來決定注冊的設(shè)備個數(shù)
static?int?ndevices?=?1;
module_param(ndevices,?int,?0644);
MODULE_PARM_DESC(ndevices,?"The?number?of?devices?for?register.\n");
//打開設(shè)備
static?int?dev_fifo_open(struct???inode?inode,???struct?file?file)
{
????struct???mycdev?cd;??
????printk("dev_fifo_open???success!\n");??
????//用struct?file的文件私有數(shù)據(jù)指針保存struct?mycdev結(jié)構(gòu)體指針
????cd???=?container_of(inode->i_cdev,struct???mycdev,cdev);
????file->private_data?=???cd;??
????return???0;
}
//讀設(shè)備
static?ssize_t???dev_fifo_read(struct?file?
file,?char???__user?ubuf,???size_t
size,?loff_t?
ppos)
{
????int?n;
????int?ret;
????char???kbuf;
????struct???mycdev?
mycd?=???file->private_data;
????printk("read?ppos?:???%lld\n",ppos);?
????if(ppos?==?mycd->len)
????????return???0;
????//請求大大小?>?buffer剩余的字節(jié)數(shù)???:讀取實(shí)際記得字節(jié)數(shù)
????if(size?>?mycd->len?-?
ppos)
????????n?=?mycd->len?-?ppos;
????else
????????n?=?size;
????printk("n?=???%d\n",n);
????//從上一次文件位置指針的位置開始讀取數(shù)據(jù)
????kbuf???=?mycd->buffer???+?
ppos;
????//拷貝數(shù)據(jù)到用戶空間
????ret???=?copy_to_user(ubuf,kbuf,?n);
????if(ret?!=?0)
????????return???-EFAULT;
????//更新文件位置指針的值
????ppos?+=?n;
????printk("dev_fifo_read???success!\n");
????return???n;
}
//寫設(shè)備
static?ssize_t???dev_fifo_write(struct?file?
file,?const?char?__user?ubuf,size_t?size,?loff_t?ppos)
{
????int?n;
????int?ret;
????char???kbuf;
????struct???mycdev?
mycd?=???file->private_data;
????printk("write?ppos?:???%lld\n",ppos);
????//已經(jīng)到達(dá)buffer尾部了
????if(ppos?==?sizeof(mycd->buffer))
???????return???-1;
????//請求大大小?>?buffer剩余的字節(jié)數(shù)(有多少空間就寫多少數(shù)據(jù))
????if(size?>?sizeof(mycd->buffer)?-?
ppos)
????????n?=?sizeof(mycd->buffer)?-?ppos;
????else
????????n?=?size;
????//從上一次文件位置指針的位置開始寫入數(shù)據(jù)
????kbuf???=?mycd->buffer???+?
ppos;
????//拷貝數(shù)據(jù)到內(nèi)核空間
????ret???=?copy_from_user(kbuf,?ubuf,?n);
????if(ret?!=?0)
????????return???-EFAULT;
????//更新文件位置指針的值
????ppos?+=?n;
????//更新dev_fifo.len
????mycd->len?+=?n;
????printk("dev_fifo_write???success!\n");
????return???n;
}
//linux?內(nèi)核在2.6以后宿亡,已經(jīng)廢棄了ioctl函數(shù)指針結(jié)構(gòu)常遂,取而代之的是
long???dev_fifo_unlocked_ioctl(struct?file?
file,???unsigned?int?cmd,
????unsigned???long?arg)
{
??int?ret?=?0;
??struct?mycdev?mycd???=?file->private_data;
??if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){
????pr_err("cmd???%u,bad?magic?0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);
????return-ENOTTY;
??}
??if(_IOC_DIR(cmd)&_IOC_READ)
????ret=!access_ok(VERIFY_WRITE,(void?__user
)arg,_IOC_SIZE(cmd));
??else?if(?_IOC_DIR(cmd)&_IOC_WRITE?)
????ret=!access_ok(VERIFY_READ,(void???__user)arg,_IOC_SIZE(cmd));
??if(ret){
????pr_err("bad???access?%ld.\n",ret);
????return-EFAULT;
??}?
????switch(cmd)
????{
??????case?DEV_FIFO_CLEAN:
?????????printk("CMD:CLEAN\n");
??????memset(mycd->buffer,?0,?sizeof(mycd->buffer));
?????????break;
??????case?DEV_FIFO_SETVALUE:
?????????printk("CMD:SETVALUE\n");
?????????mycd->len?=?arg;
?????????break;
??????case?DEV_FIFO_GETVALUE:
?????????printk("CMD:GETVALUE\n");
?????????ret???=?put_user(mycd->len,?(int?
)arg);
?????????break;
??????default:
?????????return???-EFAULT;
????}
????return???ret;
}
//設(shè)備操作函數(shù)接口
static?const?struct?file_operations?fifo_operations?=?{
????.owner?=???THIS_MODULE,
????.open?=???dev_fifo_open,
????.read?=???dev_fifo_read,
????.write?=???dev_fifo_write,
????.unlocked_ioctl?=???dev_fifo_unlocked_ioctl,
};
//模塊入口
int?__init?dev_fifo_init(void)
{
????int?i?=?0;
????int?n?=?0;
????int?ret;
????struct???device?device;
??gcd???=?kzalloc(ndevices???
?sizeof(struct???mycdev),?GFP_KERNEL);
????if(!gcd){
????????return???-ENOMEM;
????}
????//設(shè)備號?:?主設(shè)備號(12bit)?|?次設(shè)備號(20bit)
????dev_num???=?MKDEV(MAJOR_NUM,?0);
????//靜態(tài)注冊設(shè)備號
????ret???=?register_chrdev_region(dev_num,ndevices,"dev_fifo");
????if(ret?<?0){
????//靜態(tài)注冊失敗,進(jìn)行動態(tài)注冊設(shè)備號
?????ret???=alloc_chrdev_region(&dev_num,0,ndevices,"dev_fifo");
??????if(ret?<?0){
????????printk("Fail?to?register_chrdev_region\n");
????????goto???err_register_chrdev_region;
??????}
????}
????//創(chuàng)建設(shè)備類
????cls???=?class_create(THIS_MODULE,?"dev_fifo");
????if(IS_ERR(cls)){
????????ret???=?PTR_ERR(cls);
????????goto???err_class_create;
????}
????printk("ndevices?:???%d\n",ndevices);
????for(n?=?0;n?<?ndevices;n???++)
????{
??????//初始化字符設(shè)備
??????cdev_init(&gcd[n].cdev,&fifo_operations);
??????//添加設(shè)備到操作系統(tǒng)
??????ret???=?cdev_add(&gcd[n].cdev,dev_num?+?n,1);
??????if?(ret?<?0)
??????{
?????????goto???err_cdev_add;
??????}
?????//導(dǎo)出設(shè)備信息到用戶空間(/sys/class/類名/設(shè)備名)
??????device???=?device_create(cls,NULL,dev_num?+n,NULL,"dev_fifo%d",n);
??????if(IS_ERR(device)){
?????????ret???=?PTR_ERR(device);
?????????printk("Fail?to?device_create\n");
?????????goto???err_device_create;????
??????}
????}
????printk("Register???dev_fito?to?system,ok!\n");
????return???0;
err_device_create:
????//將已經(jīng)導(dǎo)出的設(shè)備信息除去
????for(i?=?0;i?<?n;i?++)
????{
???????device_destroy(cls,dev_num?+?i);????
????}
err_cdev_add:
????//將已經(jīng)添加的全部除去
????for(i?=?0;i?<?n;i?++)
????{
???????cdev_del(&gcd[i].cdev);
????}
err_class_create:
????unregister_chrdev_region(dev_num,???ndevices);
err_register_chrdev_region:
????return???ret;
}
void?__exit?dev_fifo_exit(void)
{
????int?i;
????//刪除sysfs文件系統(tǒng)中的設(shè)備
????for(i?=?0;i?<?ndevices;i???++)
????{
????????device_destroy(cls,dev_num?+?i);????
????}
????//刪除系統(tǒng)中的設(shè)備類
????class_destroy(cls);
????//從系統(tǒng)中刪除添加的字符設(shè)備
????for(i?=?0;i?<?ndevices;i???++)
????{
???????cdev_del(&gcd[i].cdev);
????}?
????//釋放申請的設(shè)備號
????unregister_chrdev_region(dev_num,???ndevices);
????return;
}
module_init(dev_fifo_init);
module_exit(dev_fifo_exit);</pre><p>頭文件內(nèi)容:</p><p>dev_fifo_head.h</p><pre>#ifndef?_DEV_FIFO_HEAD_H

define?_DEV_FIFO_HEAD_H

define?DEV_FIFO_TYPE?'k'

define?DEV_FIFO_CLEAN?_IO(DEV_FIFO_TYPE,0x10)

define?DEV_FIFO_GETVALUE?_IOR(DEV_FIFO_TYPE,0x11,int)

define?DEV_FIFO_SETVALUE?_IOW(DEV_FIFO_TYPE,0x12,int)

endif</pre><p>Makefile
</p><pre>```bash

ifeq?((KERNELRELEASE),) KERNEL_DIR&nbsp;?=/lib/modules/(shell?uname?-r)/build??
PWD?:=(shell&nbsp;pwd) modules: &nbsp;&nbsp;&nbsp;&nbsp;(MAKE)?-C?(KERNEL_DIR)&nbsp;&nbsp;&nbsp;M=(PWD)?modules
.PHONY:modules?clean
clean:
????(MAKE)&nbsp;-C&nbsp;(KERNEL_DIR)???M=$(PWD)?clean
else
????obj-m?:=?dev_fifo.o??
endif</pre><p>
</p><p>
</p><p>應(yīng)用程序:</p><pre>#include?<stdio.h>

include?<stdlib.h>

include?<sys/types.h>

include?<string.h>

include?<sys/stat.h>

include?<fcntl.h>

int?main(int?argc,?const?char?*argv[])
{
????int?fd?;
????int?n;
????char?buf[1024]?=?"hello???word";
????
????fd?=?open("/dev/dev_fifo0",O_RDWR);
????if(fd?<?0){
????????perror("Fail???ot?open");
????????return???-1;
????}
????printf("open???successful?,fd?=?%d\n",fd);
????n?=?write(fd,buf,strlen(buf));
????if(n?<?0){
????????perror("Fail???to?write");
????????return???-1;
????}
????printf("write???%d?bytes!\n",n);
????n?=?write(fd,buf,strlen(buf));
????if(n?<?0){
????????perror("Fail???to?write");
????????return???-1;
????}
????printf("write???%d?bytes!\n",n);
????return?0;
}</pre><p>
</p><p>測試步驟:</p><p>(1) ? 加載模塊</p><pre>sudo?insmod?hello.ko</pre><p>(2) ? 創(chuàng)建設(shè)備節(jié)點(diǎn)</p><pre>sudo?mknod?/dev/hello?c?250?0</pre><p>如果代碼中增加了自動創(chuàng)建設(shè)備節(jié)點(diǎn)的功能挽荠,這個步驟不要執(zhí)行克胳。</p><p>(3) ? 測試字符設(shè)備</p><pre>gcc?test.c?-o?runsudo?./run</pre><p>更多嵌入式資料,請關(guān)注公眾號: <strong><strong>一口Linux</strong></strong>圈匆。</p><p>
</p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漠另,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跃赚,更是在濱河造成了極大的恐慌笆搓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纬傲,死亡現(xiàn)場離奇詭異满败,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)叹括,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門葫录,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人领猾,你說我怎么就攤上這事米同。” “怎么了摔竿?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵面粮,是天一觀的道長。 經(jīng)常有香客問我继低,道長熬苍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任袁翁,我火速辦了婚禮柴底,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粱胜。我一直安慰自己柄驻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布焙压。 她就那樣靜靜地躺著鸿脓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涯曲。 梳的紋絲不亂的頭發(fā)上野哭,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音幻件,去河邊找鬼拨黔。 笑死,一個胖子當(dāng)著我的面吹牛绰沥,可吹牛的內(nèi)容都是我干的篱蝇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼揪利,長吁一口氣:“原來是場噩夢啊……” “哼态兴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疟位,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瞻润,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后甜刻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绍撞,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年得院,在試婚紗的時候發(fā)現(xiàn)自己被綠了傻铣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡祥绞,死狀恐怖非洲,靈堂內(nèi)的尸體忽然破棺而出鸭限,到底是詐尸還是另有隱情,我是刑警寧澤两踏,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布败京,位于F島的核電站,受9級特大地震影響梦染,放射性物質(zhì)發(fā)生泄漏赡麦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一帕识、第九天 我趴在偏房一處隱蔽的房頂上張望泛粹。 院中可真熱鬧,春花似錦肮疗、人聲如沸晶姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帽借。三九已至,卻和暖如春超歌,著一層夾襖步出監(jiān)牢的瞬間砍艾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工巍举, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脆荷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓懊悯,卻偏偏與公主長得像蜓谋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炭分,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345