當(dāng)對(duì)幸福的憧憬過(guò)于急切,那痛苦就在人的心靈深處升起。——加繆
本章的目的是編寫(xiě)一個(gè)完整的字符設(shè)備驅(qū)動(dòng)。我們開(kāi)發(fā)一個(gè)字符驅(qū)動(dòng)是因?yàn)檫@一類(lèi)適合大部分簡(jiǎn)單的硬件設(shè)備。字符驅(qū)動(dòng)也比塊驅(qū)動(dòng)易于理解。本章的最終目的是編寫(xiě)一個(gè)模塊化的字符驅(qū)動(dòng),但是我們不會(huì)在本章討論模塊化的事情。
貫穿本章,我們展示從一個(gè)真實(shí)設(shè)備驅(qū)動(dòng)提取的代碼片段:scull(Simple Character Utility for Loading Localities)。scull是一個(gè)字符驅(qū)動(dòng),操作一塊內(nèi)存區(qū)域好像它是一個(gè)設(shè)備。在本章,因?yàn)閟cull的這個(gè)怪特性,我們可互換地使用設(shè)備這個(gè)詞和“scull使用的內(nèi)存區(qū)”。
scull的優(yōu)勢(shì)在于它不依賴(lài)硬件。scull只是操作一些從內(nèi)核分配的內(nèi)存。任何人都可以編譯和運(yùn)行scull,并且scull在Linux運(yùn)行的體系結(jié)構(gòu)體中可移植。另一方面,這個(gè)設(shè)備除了演示內(nèi)核與字符驅(qū)動(dòng)的接口和允許用戶(hù)運(yùn)行一些測(cè)試之外,不做任何有用的事情。
3.1 scull的設(shè)計(jì)
編寫(xiě)驅(qū)動(dòng)的第一步是定義驅(qū)動(dòng)將要提供給用戶(hù)程序的能力(機(jī)制)。因?yàn)槲覀兊摹霸O(shè)備”是計(jì)算機(jī)內(nèi)存的一部分,我們可以自由做我們想做的事情。它可以是一個(gè)順序的或者隨機(jī)存取的設(shè)備,一個(gè)或多個(gè)設(shè)備,等等。
為使scull作為一個(gè)模板來(lái)編寫(xiě)真實(shí)設(shè)備的真實(shí)驅(qū)動(dòng),我們將展示給你如何在計(jì)算機(jī)內(nèi)存上實(shí)現(xiàn)幾個(gè)設(shè)備的抽象,每個(gè)有不同的個(gè)性。
scull源碼實(shí)現(xiàn)下面的設(shè)備,模塊實(shí)現(xiàn)的每種設(shè)備都被引用做一種類(lèi)型。
scull0到scull3
4個(gè)設(shè)備,每個(gè)由一個(gè)全局永久內(nèi)存區(qū)組成。全局意味著如果設(shè)備被多次打開(kāi),設(shè)備中含有的數(shù)據(jù)由所有打開(kāi)它的文件描述符共享。永久意味著如果設(shè)備關(guān)閉又重新打開(kāi),數(shù)據(jù)不會(huì)丟失。這個(gè)設(shè)備用起來(lái)有意思,因?yàn)樗梢杂脩T常的命令來(lái)存取和測(cè)試,例如cp
、cat
、以及I/O重定向。
scullpipe0 到scullpipe3
4個(gè)FIFO(先入先出)設(shè)備,行為像管道。一個(gè)進(jìn)程度的內(nèi)容來(lái)自另一個(gè)進(jìn)程所寫(xiě)的。如果多個(gè)進(jìn)程讀同一個(gè)設(shè)備,它們競(jìng)爭(zhēng)數(shù)據(jù)。scullpipe的內(nèi)部將展示阻塞讀寫(xiě)與非阻塞讀寫(xiě)如何實(shí)現(xiàn),而不必采取中斷。盡管真實(shí)的驅(qū)動(dòng)使用硬件中斷來(lái)同步它們的設(shè)備,阻塞和非阻塞操作的主題是重要的,并且與中斷處理是分開(kāi)的。(在第十章涉及)
scullsingle
scullpriv
sculluid
scullwuid
這些設(shè)備與scull0相似,但是在什么時(shí)候允許打開(kāi)上有一些限制。第一個(gè)(scullsingle)只允許一次一個(gè)進(jìn)程使用驅(qū)動(dòng),而scullpriv對(duì)每個(gè)虛擬終端(或X中斷會(huì)話)是私有的,因?yàn)槊總€(gè)控制臺(tái)/終端上的進(jìn)程有不同的內(nèi)存區(qū)。sculluid和scullwuid可以多次打開(kāi),但是一次只能是一個(gè)用戶(hù);前者返回一個(gè)“設(shè)備忙”錯(cuò)誤,如果另一個(gè)用戶(hù)鎖著設(shè)備,而后者實(shí)現(xiàn)阻塞打開(kāi)。這些scull的變體可能看來(lái)混淆了策略和機(jī)制,但是它們值得看看,因?yàn)橐恍?shí)際設(shè)備需要這類(lèi)管理。
每個(gè)scull設(shè)備演示了驅(qū)動(dòng)的不同特色,并且呈現(xiàn)了不同的難度。本章涉及scull0到scull3的內(nèi)部;更高級(jí)的設(shè)備在第6章涉及。scullpipe在“一個(gè)阻塞I/O例子”一節(jié)中描述,其他的在“設(shè)備文件上的存取控制”中描述。
3.2 主次編號(hào)
字符設(shè)備通過(guò)文件系統(tǒng)中的名字。那些名字成為文件系統(tǒng)的特殊文件或者設(shè)備文件,或者文件系統(tǒng)的簡(jiǎn)單結(jié)點(diǎn);慣例上它們位于/dev目錄。字符驅(qū)動(dòng)的特殊文件由使用ls -l
的輸出的第一列“c”標(biāo)識(shí)。塊設(shè)備也出現(xiàn)在/dev中,但是它們由“b”表示。本章集中在字符設(shè)備,但是下面的很多信息也使用于塊設(shè)備。
如果你發(fā)出ls -l
命令,你會(huì)看到在設(shè)備文件項(xiàng)中有2個(gè)數(shù)(由一個(gè)逗號(hào)分隔),在最后修改日期前面,這里通常是文件長(zhǎng)度出現(xiàn)的地方。這些數(shù)字是給特殊設(shè)備的主次設(shè)備編號(hào)。下面的列表顯示了一個(gè)典型系統(tǒng)上出現(xiàn)的幾個(gè)設(shè)備。它們的主編號(hào)是1,4,7,10,而次編號(hào)是1,3,5,64,65和129。
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw------- 1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1
crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw--w---- 1 vcsa tty 7,129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
傳統(tǒng)上,主編號(hào)標(biāo)識(shí)設(shè)備相連的驅(qū)動(dòng)。例如,/dev/null
和 /dev/zero
都由驅(qū)動(dòng)1來(lái)管理,而虛擬控制臺(tái)和串口終端都由驅(qū)動(dòng)4管理;同樣vcs1和vcsa1設(shè)備都由驅(qū)動(dòng)7管理?,F(xiàn)代Linux內(nèi)核允許多個(gè)驅(qū)動(dòng)共享主編號(hào),但是你看到的大部分設(shè)備仍然按照一個(gè)主編號(hào)一個(gè)驅(qū)動(dòng)的原則來(lái)組織。
次編號(hào)被內(nèi)核用來(lái)決定引用哪個(gè)設(shè)備。依據(jù)你的驅(qū)動(dòng)是如何編寫(xiě)的(如同我們下面見(jiàn)到的),你可以從內(nèi)核得到一個(gè)你的設(shè)備的直接指針,或者可以自己使用次編號(hào)作為本地設(shè)備數(shù)組的索引。不論哪個(gè)方法,內(nèi)核自己幾乎不知道次編號(hào)的任何事情,除了它們指向你的驅(qū)動(dòng)實(shí)現(xiàn)的設(shè)備。
3.2.1 設(shè)備編號(hào)的內(nèi)部表示
在內(nèi)核中,dev_t
類(lèi)型(在<linux/types.h>
中定義)用來(lái)持有設(shè)備編號(hào)——主次部分都包括。對(duì)于2.6.0內(nèi)核,dev_t是32位的量,12位用作主編號(hào),20位用作次編號(hào)。你的代碼應(yīng)當(dāng)對(duì)于設(shè)備編號(hào)的內(nèi)部組織從不做任何假設(shè);相反,應(yīng)當(dāng)利用在<linux/kdev_t.h>
中的一套宏定義獲得一個(gè)dev_t
的主編號(hào)或次編號(hào),使用。
MAJOR(dev_t dev)
MINOR(dev_t dev)
相反,如果你有主次編號(hào),需要將其轉(zhuǎn)換為一個(gè)dev_t
使用:
MKDEV(int major, int minor)
注意,2.6內(nèi)核能容納有大量設(shè)備,而以前的內(nèi)核版本限制在255個(gè)主編號(hào)和255個(gè)次編號(hào)。有人認(rèn)為這么寬的范圍在很長(zhǎng)時(shí)間內(nèi)是足夠的,但是計(jì)算機(jī)領(lǐng)域被這個(gè)特性的錯(cuò)誤假設(shè)搞亂了。因此,你應(yīng)當(dāng)希望dev_t
的格式將來(lái)可能再次改變;但是,如果你仔細(xì)寫(xiě)你的驅(qū)動(dòng),這些變化不會(huì)是一個(gè)問(wèn)題。
3.2.2分配和釋放設(shè)備編號(hào)
在建立一個(gè)字符驅(qū)動(dòng)時(shí)你的驅(qū)動(dòng)需要做的第一件事是獲取一個(gè)或多個(gè)設(shè)備編號(hào)來(lái)使用。為此目的的必要的函數(shù)是register_chrdev_region
,在<linux/fs.h>
中聲明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
這里first
是你要分配的起始設(shè)備編號(hào)。first
的次編號(hào)部分常常是0,但是沒(méi)有要求是那個(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
中。
如同大部分內(nèi)核函數(shù),如果分配成功進(jìn)行,resigister_chrdev_region
的返回值是0,出錯(cuò)的情況下,返回一個(gè)負(fù)的錯(cuò)誤碼,你不能存取請(qǐng)求的區(qū)域。
如果你確實(shí)事先知道你需要哪個(gè)設(shè)備編號(hào),register_chrdev_region工作得好。然而,你常常不會(huì)知道你的設(shè)備使用哪個(gè)主編號(hào);在Linux內(nèi)核開(kāi)發(fā)社團(tuán)中一直努力使用動(dòng)態(tài)分配設(shè)備編號(hào)。內(nèi)核會(huì)樂(lè)于動(dòng)態(tài)為你分配一個(gè)主編號(hào),但是你必須使用一個(gè)不同的函數(shù)來(lái)請(qǐng)求這個(gè)分配。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, usigned int count, char *name);
使用這個(gè)函數(shù),dev是一個(gè)只輸出的參數(shù),它在函數(shù)成功完成時(shí)持有你的分配范圍的第一個(gè)數(shù)。firstminor
應(yīng)當(dāng)是請(qǐng)求的第一個(gè)要用的次編號(hào);它常常是0。count
和name
參數(shù)如同給request_chrdev_region
的一樣。
不管你以任何方式分配你的設(shè)備編號(hào),你應(yīng)當(dāng)在不使用它們的時(shí)候釋放它。設(shè)備編號(hào)的釋放使用:
void unregister_chrdev_region(dev_t first, unsigned int count);
調(diào)用unregister_chrdev_region
的地方常常是你的模塊的cleanup
函數(shù)。
上面的函數(shù)分配設(shè)備編號(hào)給你的驅(qū)動(dòng)使用,但是它們不告訴內(nèi)核你實(shí)際上會(huì)對(duì)這些編號(hào)做什么。在用戶(hù)空間程序能夠存取這些設(shè)備號(hào)中一個(gè)之前,你的驅(qū)動(dòng)需要連接它們到它的實(shí)現(xiàn)設(shè)備操作的內(nèi)部函數(shù)上。我們將描述如何簡(jiǎn)短完成這個(gè)連接,但首先估計(jì)一些必要的枝節(jié)問(wèn)題。
3.2.3 主編號(hào)的動(dòng)態(tài)分配
一些主設(shè)備編號(hào)是靜態(tài)分派給最普通的設(shè)備的。一個(gè)這些設(shè)備的列表在內(nèi)核源碼樹(shù)的Documentation/deviecs.txt
中。分配給你的新驅(qū)動(dòng)使用一個(gè)已經(jīng)分配的靜態(tài)編號(hào)的機(jī)會(huì)很小,但是,并且新編號(hào)沒(méi)在分配。因此,作為一個(gè)驅(qū)動(dòng)編寫(xiě)者,你有一個(gè)選擇:你可以簡(jiǎn)單地?fù)煲粋€(gè)看起來(lái)沒(méi)有用的編號(hào),或者你以動(dòng)態(tài)方式分配主編號(hào)。只要你是你的驅(qū)動(dòng)的唯一用戶(hù)就可以建一個(gè)編號(hào)用;一旦你的驅(qū)動(dòng)更廣泛的被使用了,一個(gè)隨機(jī)撿來(lái)的主編號(hào)將導(dǎo)致沖突和麻煩。
因此,對(duì)于新驅(qū)動(dòng),我們強(qiáng)烈建議你使用動(dòng)態(tài)分配來(lái)獲取你的主設(shè)備編號(hào),而不是隨機(jī)選取一個(gè)當(dāng)前空閑的編號(hào)。換句話說(shuō),你的驅(qū)動(dòng)應(yīng)當(dāng)幾乎肯定地使用alloc_chrdev_region
,不是register_chrdev_region
。
動(dòng)態(tài)分配的缺點(diǎn)是你無(wú)法提前創(chuàng)建設(shè)備節(jié)點(diǎn),因?yàn)榉峙浣o你的模塊的主編號(hào)會(huì)變化,對(duì)于驅(qū)動(dòng)的正常使用,這不是問(wèn)題,因?yàn)橐坏┚幪?hào)分配了,你可以從/proc/devices
中讀取它。
為使用動(dòng)態(tài)主編號(hào)來(lái)加載一個(gè)驅(qū)動(dòng),因此,可使用一個(gè)簡(jiǎn)單的腳本來(lái)代替調(diào)用insmod
,在調(diào)用insmod
后,讀取/proc/devices
來(lái)創(chuàng)建特殊文件。
一個(gè)典型的/proc/devices
文件看來(lái)如下:
Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
180 usb
Block devices:
2 fd
8 sd
11 sr
65 sd
66 sd
因此,加載一個(gè)已經(jīng)安排了一個(gè)動(dòng)態(tài)編號(hào)的模塊的腳本,可以使用一個(gè)工具來(lái)編寫(xiě),如awk,來(lái)從/proc/devices
獲取信息以創(chuàng)建/dev
中的文件。
下面的腳本,snull_load
,是scull
發(fā)布的一部分。以模塊發(fā)布的驅(qū)動(dòng)的用戶(hù)可以從系統(tǒng)的rc.local
文件中調(diào)用這樣一個(gè)腳本,或者在需要模塊時(shí)手工調(diào)用它。
#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1
#remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices)
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff"
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]
這個(gè)腳本可以通過(guò)重定義變量和調(diào)整mknod
行來(lái)適用于另外的驅(qū)動(dòng)。這個(gè)腳本僅僅展示了創(chuàng)建4各設(shè)備,因?yàn)?是scull源碼中缺省的。
腳本的最后幾行可能有些模糊:為什么改變?cè)O(shè)備的和模式?理由是這個(gè)腳本必須由超級(jí)用戶(hù)運(yùn)行,因此新建的特殊文件由root
擁有。許可位缺省的是只有root
有寫(xiě)權(quán)限,而任何人可以讀。通常,一個(gè)設(shè)備節(jié)點(diǎn)需要一個(gè)不同的存取策略,因此在某些方面別人的存取權(quán)限必須改變。我們的腳本缺省是給一個(gè)用戶(hù)組存取,但是你的需求可能不同。在第6章的“設(shè)備文件的存取控制”一節(jié)中,sculluid
的代碼演示了驅(qū)動(dòng)如何能夠強(qiáng)制它自己的對(duì)設(shè)備存取的授權(quán)。
還有一個(gè)scull_unload
腳本來(lái)清理/dev
目錄并去除模塊。
除了用一對(duì)腳本實(shí)現(xiàn)加載與卸載,還可編寫(xiě)一個(gè)init腳本,放在發(fā)布的腳本目錄中。作為scull源碼的一部分,這里提供了一個(gè)相當(dāng)完整且可配置的init腳本示例,稱(chēng)為scull.init
。(init腳本scull.init
不在命令行中接收驅(qū)動(dòng)選項(xiàng),但是它支持一個(gè)配置文件,因?yàn)樗辉O(shè)計(jì)為在啟動(dòng)與關(guān)機(jī)時(shí)自動(dòng)使用)該腳本接收傳統(tǒng)的參數(shù)start,stop與restart,并且完成scull_load
與scull_unload
的角色。
如果反復(fù)創(chuàng)建與銷(xiāo)毀/dev節(jié)點(diǎn),聽(tīng)起來(lái)過(guò)分了,有一個(gè)有用的辦法。如果你在加載和卸載單個(gè)驅(qū)動(dòng),你可以在你第一次使用你的腳本創(chuàng)建特殊文件之后,只使用rmmod
與insmod
,這樣動(dòng)態(tài)編號(hào)不是隨機(jī)的。并且你每次都可以使用所選的同一個(gè)編號(hào),如果你不加載任何別的動(dòng)態(tài)模塊。在開(kāi)發(fā)中避免長(zhǎng)腳本是有用的。但是這個(gè)技巧,顯然不能擴(kuò)展到一次多于一個(gè)驅(qū)動(dòng)。
安排主編號(hào)最好的方式,我們認(rèn)為是缺省使用動(dòng)態(tài)分配,而留給自己加載時(shí)指定主編號(hào)的選項(xiàng)權(quán),或者甚至在編譯時(shí)。scull實(shí)現(xiàn)以這種方式工作;它使用一個(gè)全局變量scull_major
來(lái)持有特定的編號(hào)(還有一個(gè)scull_minor
給次編號(hào))。這個(gè)變量初始化為SCULL_MAJOR
,定義在scull.h
。發(fā)布的源碼中的SCULL_MAJOR
的缺省值是0,意思是“使用動(dòng)態(tài)分配”。用戶(hù)可以接受缺省值或者選擇一個(gè)特殊的主編號(hào),或者在編譯前修改宏定義或者在insmod
命令行指定一個(gè)scull_load
的命令行傳遞參數(shù)給insmod
。
這是我們用在scull的源碼中獲取主編號(hào)的代碼:
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
3.3 一些重要的數(shù)據(jù)結(jié)構(gòu)
注冊(cè)設(shè)備編號(hào)僅僅是驅(qū)動(dòng)代碼的諸多任務(wù)中的第一項(xiàng)。我們將很快看到其他重要的驅(qū)動(dòng)組件,但在這之前需要一些其他準(zhǔn)備。大部分的基礎(chǔ)性的驅(qū)動(dòng)操作包括3個(gè)重要的內(nèi)核數(shù)據(jù)結(jié)構(gòu),成為file_operations
、file
與inode
。需要對(duì)這些結(jié)構(gòu)有基本了解才能夠做大量有意思的事情。因此我們?cè)谔骄炕A(chǔ)性驅(qū)動(dòng)操作的實(shí)現(xiàn)細(xì)節(jié)之前,快速地查看這三個(gè)內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
3.3.1 文件操作
至此,我們已保留了一些設(shè)備編號(hào)來(lái)使用,但是還沒(méi)有連接任何我們?cè)O(shè)備的操作到這些編號(hào)上。file_operations
結(jié)構(gòu)是一個(gè)字符驅(qū)動(dòng)建立這種連接的結(jié)構(gòu),定義在<linux/fs.h>中,是一個(gè)函數(shù)指針的集合。每個(gè)打開(kāi)文件(內(nèi)部用一個(gè)file結(jié)構(gòu)來(lái)代表,稍后我們會(huì)查看)與它自身的函數(shù)集合相關(guān)聯(lián)(通過(guò)包含一個(gè)稱(chēng)為f_op的成員,它指向一個(gè)file_operations結(jié)構(gòu))。這些操作大部分負(fù)責(zé)實(shí)現(xiàn)系統(tǒng)調(diào)用,因此命名為open、read等等。我們可以認(rèn)為文件是一個(gè)“對(duì)象”并且其上的函數(shù)操作稱(chēng)為它的“方法”,使用面向?qū)ο缶幊痰男g(shù)語(yǔ)類(lèi)表示一個(gè)對(duì)象聲明的用來(lái)操作對(duì)象的動(dòng)作。這是我們?cè)趌inux內(nèi)核中看到的第一個(gè)面向?qū)ο缶幊痰默F(xiàn)象,后續(xù)章節(jié)中我們會(huì)看到更多。
傳統(tǒng)上,一個(gè)file_operations結(jié)構(gòu)或者其一個(gè)指針成為fops(或者它的一些變體)。結(jié)構(gòu)中的每個(gè)成員必須指向驅(qū)動(dòng)中的函數(shù),這些函數(shù)實(shí)現(xiàn)一個(gè)特別的操作,或者對(duì)于不支持的操作留置為NULL。當(dāng)指定為NULL指針時(shí)內(nèi)核的確切的行為對(duì)于每個(gè)函數(shù)來(lái)說(shuō)是不同的,如同本節(jié)后面的列表所示。
下面的列表介紹了一個(gè)應(yīng)用程序能夠在設(shè)備上調(diào)用的所有操作。我們已經(jīng)試圖保持列表簡(jiǎn)短,這樣它可以作為一個(gè)參考,只是總結(jié)每個(gè)操作和在NULL指針使用時(shí)的缺省內(nèi)核行為。
在你通讀file_operations方法的列表時(shí),你會(huì)注意到不少包含字串__user。這種注解是一種文檔形式,注意,一個(gè)指針是一個(gè)不能被直接解引用的用戶(hù)空間地址。對(duì)于正常的編譯,__user沒(méi)有效果,到那時(shí)它可被外部檢查軟件使用來(lái)找出對(duì)用戶(hù)空間地址的錯(cuò)誤使用。
本章剩下的部分,在描述一些其他重要數(shù)據(jù)結(jié)構(gòu)后,解釋了最重要的操作的角色并且給了提示、告誡和真實(shí)代碼例子。我們推遲討論更復(fù)雜的操作到后面章節(jié),因?yàn)槲覀冞€不準(zhǔn)備深入如內(nèi)存管理、阻塞操作和異步通知。
struct module *owner
第一個(gè)file_operations成員根本不是一個(gè)操作,它是一個(gè)指向擁有這個(gè)結(jié)構(gòu)的模塊的指針。這個(gè)成員用來(lái)在它的操作還在被使用時(shí)阻止模塊被卸載。幾乎所有時(shí)間中,它被簡(jiǎn)單初始化為T(mén)HIS_MODULE,一個(gè)在<linux/module.h>中定義的宏。
loff_t (*llseek) (struct file *, loff_t, int);
llseek方法用作改變文件的當(dāng)前讀寫(xiě)位置,并且新位置作為(正數(shù))返回值。loff_t參數(shù)時(shí)以個(gè)long offset
,并且在32位平臺(tái)上也至少64位寬。錯(cuò)誤由一個(gè)負(fù)返回值指示。如果這個(gè)函數(shù)指針是NULL,seek調(diào)用會(huì)以潛在地?zé)o法預(yù)知的方式修改file結(jié)構(gòu)中的位置計(jì)數(shù)器(在"file"結(jié)構(gòu)一節(jié)中描述)。
ssize_t (*read) (struct file *, char __user*, size_t, loff_t *);
用來(lái)從設(shè)備中獲取數(shù)據(jù),在這個(gè)位置的一個(gè)空指針導(dǎo)致read系統(tǒng)調(diào)用以-EINVAL (“Invalid argument”)失敗。一個(gè)非負(fù)返回值代表了成功讀取的字節(jié)數(shù)(返回值是一個(gè)"signed size"類(lèi)型,常常是目標(biāo)平臺(tái)本地的整數(shù)類(lèi)型)。
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t *);
初始化一個(gè)異步讀,可能在函數(shù)返回前不結(jié)束讀操作。如果這個(gè)方法是NULL,所有的操作會(huì)由read代替進(jìn)行(同步的)。
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
發(fā)送數(shù)據(jù)給設(shè)備。如果NULL, -EINVAL返回給調(diào)用write系統(tǒng)調(diào)用的程序。如果非負(fù),返回值帶你表成功寫(xiě)的字節(jié)數(shù)。
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化設(shè)備上的一個(gè)異步寫(xiě)。
int (readdir)(struct file *, void *, filldir_t);
對(duì)于設(shè)備文件這個(gè)成員應(yīng)當(dāng)位NULL, 它用來(lái)讀取目錄,并且僅對(duì)文件系統(tǒng)有用。
unsigned int (*poll)(struct file *, struct poll_table_struct *);
poll方法是3個(gè)系統(tǒng)調(diào)用的后端:poll、epoll與select,都用作查詢(xún)對(duì)一個(gè)或多個(gè)文件描述符的讀或?qū)懯欠駮?huì)阻塞。poll方法一個(gè)當(dāng)返回一個(gè)位掩碼指示是否非阻塞的讀或?qū)懯强赡艿?。并且,可能地,提供給內(nèi)核信息用來(lái)使調(diào)用進(jìn)程睡眠知道I/O變?yōu)榭赡?。如果一個(gè)驅(qū)動(dòng)的poll方法為NULL,設(shè)備假定為不阻塞的可讀可寫(xiě)。
int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);
ioctl系統(tǒng)調(diào)用提供了發(fā)出設(shè)備特定命令的方法(例如格式化軟盤(pán)的一個(gè)磁道,這不是讀,也不是寫(xiě))。另外,幾個(gè)ioctl命令被內(nèi)核識(shí)別而不必引用fops表。如果設(shè)備不提供ioctl方法,對(duì)于任何未事先定義的請(qǐng)求(-ENOTTY,“設(shè)備五這樣的ioctl”),系統(tǒng)調(diào)用返回一個(gè)錯(cuò)誤。
int (*mmap)(struct file *, struct vm_area_struct *);
mmap用來(lái)請(qǐng)求將設(shè)備內(nèi)存映射到進(jìn)程的地址空間。如果這個(gè)方法是NULL,mmap系統(tǒng)調(diào)用返回-ENODEV。
int (*open)(struct inode *, struct file *);
盡管這常常是對(duì)設(shè)備文件進(jìn)行的第一個(gè)操作,不要求驅(qū)動(dòng)聲明一個(gè)對(duì)應(yīng)的方法,如果這個(gè)項(xiàng)時(shí)NULL,設(shè)備打開(kāi)一直成功,但是你的驅(qū)動(dòng)不會(huì)得到通知。
int (*flush)(struct file *);
flush操作在進(jìn)程關(guān)閉它的設(shè)備文件描述符的拷貝時(shí)調(diào)用,它應(yīng)當(dāng)執(zhí)行(并且等待)設(shè)備的任何未完成的操作。這個(gè)必須不要和用戶(hù)查詢(xún)請(qǐng)求的fsync操作混淆了。當(dāng)前,flush在很少驅(qū)動(dòng)中使用,SCSI磁帶驅(qū)動(dòng)使用它,例如,為了確保所有寫(xiě)的數(shù)據(jù)在設(shè)備關(guān)閉前寫(xiě)道磁帶上。如果flush為NULL,內(nèi)核簡(jiǎn)單地忽略用戶(hù)應(yīng)用程序的請(qǐng)求。
int (*release)(struct inode *, struct file *);
在文件結(jié)構(gòu)被釋放是應(yīng)用這個(gè)操作。如同open,release也可以為NULL。
int (*fsync)(struct file *, struct dentry *, int);
這個(gè)方法時(shí)fsync系統(tǒng)調(diào)用的后端,用戶(hù)調(diào)用來(lái)刷新任何掛著的數(shù)據(jù)。如果這個(gè)指針時(shí)NULL,系統(tǒng)調(diào)用返回-EINVAL。
int (*aio_fsync)(struct kiocb *, int);
這時(shí)fsync方法的異步版本。
int (*fasync)(int, struct file *, int);
這個(gè)操作用來(lái)通知設(shè)備它的FASYNC標(biāo)志的改變。異步通知時(shí)一個(gè)高級(jí)的主題,在第6章中描述。這個(gè)成員可以是NULL,如果驅(qū)動(dòng)不支持異步通知。
int (*lock)(struct file *, int, struct file_lock *);
lock方法用來(lái)實(shí)現(xiàn)文件加鎖,加鎖對(duì)常規(guī)文件時(shí)必不可少的特性,但是設(shè)備驅(qū)動(dòng)幾乎從不實(shí)現(xiàn)它。
ssize_t (*readv)(struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev)(struct file *, const struct iovec *, unsigned long, loff_t *);
這些方法實(shí)現(xiàn)發(fā)散/匯聚讀和寫(xiě)操作,應(yīng)用程序偶爾需要做一個(gè)包含多個(gè)內(nèi)存區(qū)的單個(gè)讀或?qū)懖僮?;這些系統(tǒng)調(diào)用允許它們這樣做而不必對(duì)數(shù)據(jù)進(jìn)行額外拷貝。如果這些函數(shù)指針為NULL,read與write方法被調(diào)用(可能多于一次)。
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
這個(gè)方法實(shí)現(xiàn)sendfile系統(tǒng)調(diào)用的讀,是用最少的拷貝從一個(gè)文件描述符搬移數(shù)據(jù)到另一個(gè)。例如,它被一個(gè)需要發(fā)送文件內(nèi)容到一個(gè)網(wǎng)絡(luò)連接的web服務(wù)器使用。設(shè)備驅(qū)動(dòng)常常使sendfile為NULL。
ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
sendpage是sendfile的另一半。它由內(nèi)核調(diào)用來(lái)發(fā)送數(shù)據(jù),一次一頁(yè),到對(duì)應(yīng)的文件。設(shè)備驅(qū)動(dòng)實(shí)際上不是先sendpage。
unsigned long (*get_unmmaped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
這個(gè)方法的目的是在進(jìn)程的地址空間找一個(gè)合適的位置來(lái)映射在底層設(shè)備上的內(nèi)存段中就。這個(gè)任務(wù)通常由內(nèi)存管理代碼進(jìn)行,這個(gè)方法存在為了使驅(qū)動(dòng)能強(qiáng)制特殊設(shè)備可能有的任何的對(duì)齊請(qǐng)求。大部分驅(qū)動(dòng)可以置這個(gè)方法為NULL。
int (*check_flags)(int);
這個(gè)方法允許模塊檢查傳遞給fnctl(F_SETGL…)調(diào)用的標(biāo)志。
int (*dir_notify)(struct file *, unsigned long);
這個(gè)方法在應(yīng)用層使用fcntl來(lái)請(qǐng)求目錄改變通知時(shí)調(diào)用。只對(duì)文件系統(tǒng)有用,驅(qū)動(dòng)不需要實(shí)現(xiàn)dir_notify。
scull設(shè)備驅(qū)動(dòng)只實(shí)現(xiàn)最重要的設(shè)備方法。它的file_operations結(jié)構(gòu)是如下初始化的:
struct file_operations scull_fops {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
這個(gè)聲明使用標(biāo)準(zhǔn)的C標(biāo)記式結(jié)構(gòu)初始化語(yǔ)法。這個(gè)語(yǔ)法是首選的,因?yàn)樗跪?qū)動(dòng)在結(jié)構(gòu)定義的改變之間更加可移植,并且,有爭(zhēng)議地,使代碼更加緊湊和可讀。標(biāo)記式初始化允許結(jié)構(gòu)成員重新排序,在某種情況下,真實(shí)的性能提高已經(jīng)實(shí)現(xiàn),通過(guò)安放經(jīng)常使用的成員的指針在相同硬件高速存儲(chǔ)行中。
3.3.2 文件結(jié)構(gòu)
struct file,定義于 <linux/fs.h>
是設(shè)備驅(qū)動(dòng)中第二個(gè)最重要的數(shù)據(jù)結(jié)構(gòu)。注意file與用戶(hù)空間長(zhǎng)須的FILE指針沒(méi)有任何關(guān)系。一個(gè)FILE
定義在C庫(kù)中,從不出現(xiàn)在內(nèi)核代碼中。一個(gè)struct file
, 另一方面,是一個(gè)內(nèi)核結(jié)構(gòu),從不出現(xiàn)在用戶(hù)程序中。
文件結(jié)構(gòu)代表一個(gè)打開(kāi)的文件。(它不特定給設(shè)備驅(qū)動(dòng),系統(tǒng)中每個(gè)打開(kāi)的文件有一個(gè)掛念的struct file
在內(nèi)核空間)。它由內(nèi)核在open
時(shí)創(chuàng)建,并傳遞給在文件上操作的任何函數(shù),直到最后的關(guān)閉。在文件的所有的實(shí)例都關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)核源碼中,struct file
的指針常常稱(chēng)為file或者filp(“file pointer”)。我們將一直稱(chēng)這個(gè)指針為filp
以避免和結(jié)構(gòu)自身混淆。因此file
指的是結(jié)構(gòu),而filp
是結(jié)構(gòu)指針。
struct file的最重要成員在這展示,如同在前一節(jié),第一次閱讀可以跳過(guò)這個(gè)列表。但是,在本章后面,當(dāng)我們面對(duì)一些真實(shí)C代碼時(shí),我們將更詳細(xì)討論這些成員。
mode_t f_mode;
文件模式確定文件是可讀、可寫(xiě)或兩者皆可,通過(guò)位FMODE_READ
和FMODE_WRITE
。你可能想在你的open或者ioctl函數(shù)中檢查這個(gè)成員函數(shù)的讀寫(xiě)許可,但是你不需要檢查讀寫(xiě)許可,因?yàn)閮?nèi)核在調(diào)用你的方法之前檢查。當(dāng)文件還沒(méi)有為那種存取而打開(kāi)時(shí)讀或?qū)懙钠髨D被拒絕,驅(qū)動(dòng)甚至不知道這個(gè)情況。
loff_t f_pos;
當(dāng)前讀寫(xiě)位置。loff_t
在所有平臺(tái)都是64位(在gcc術(shù)語(yǔ)里是long long
)。驅(qū)動(dòng)可以讀這個(gè)值,若它需要知道文件中的當(dāng)前位置,正常地不應(yīng)改變它;讀與寫(xiě)應(yīng)當(dāng)使用它們作為最后參數(shù)而收到的指針來(lái)更新一個(gè)位置,代替直接作用于filp->f_pos
。這個(gè)規(guī)則地一個(gè)例外是在llseek
方法中,它的目的就是改變文件位置。
unsigned int f_flags;
這些是文件標(biāo)志,例如O_RDONLY
, O_NONBLOCK
與O_SYNC
。驅(qū)動(dòng)應(yīng)當(dāng)檢查O_NONBLOCK
標(biāo)志類(lèi)來(lái)看是否是請(qǐng)求非阻塞操作(我們?cè)诘谝徽碌摹白枞头亲枞僮鳌币还?jié)中討論非阻塞I/O);其他標(biāo)志很少使用。特別地,應(yīng)當(dāng)檢查讀/寫(xiě)許可,使用f_mode
而不是f_flags
。所有的標(biāo)志在頭文件<linux/fcntl.h>
中定義。
struct file_operations *f_op;
和文件關(guān)聯(lián)的操作。內(nèi)核安排指針作為它的open實(shí)現(xiàn)的一部分,接著讀取它當(dāng)它需要分派任何的操作時(shí)。file->f_op
中的值從不由內(nèi)核保存為后面的引用,這意味著你可改變你的文件關(guān)聯(lián)的文件操作,在你返回調(diào)用者之后新方法會(huì)起作用。例如,關(guān)聯(lián)到主編號(hào)1(/dev/null
, /dev/zero
等等)的open代碼根據(jù)打開(kāi)的次編號(hào)來(lái)代替filp->f_op中的操作。這個(gè)做法允許實(shí)現(xiàn)幾種行為。在用一個(gè)主編號(hào)下而不必再每個(gè)系統(tǒng)調(diào)用中引入開(kāi)銷(xiāo)。替換文件操作的能力時(shí)面向?qū)ο缶幊痰摹胺椒ㄖ剌d”的內(nèi)核對(duì)等體。
void *private_data
open
系統(tǒng)調(diào)用設(shè)置這個(gè)指針為NULL
,在為驅(qū)動(dòng)調(diào)用open
方法之前。你可自由使用這個(gè)成員或者忽略它,你可以使用這個(gè)成員來(lái)指向分配的數(shù)據(jù),但是接著你必須記住在內(nèi)核銷(xiāo)毀文件結(jié)構(gòu)之前,在release
方法中釋放那個(gè)內(nèi)存。private_data
是一個(gè)有用的資源,在系統(tǒng)調(diào)用間保留狀態(tài)信息,我們大部分例子模塊都使用它。
struct dentry *f_dentry;
關(guān)聯(lián)到文件的目錄入口(dentry
)結(jié)構(gòu)。設(shè)備驅(qū)動(dòng)編寫(xiě)者正常地不需要關(guān)心dentry
結(jié)構(gòu),除了作為filp->f_dentry->d_inode
存取inode
結(jié)構(gòu)。
真實(shí)結(jié)構(gòu)有多幾個(gè)成員,但是它們對(duì)設(shè)備驅(qū)動(dòng)沒(méi)有用處。我們可以安全地忽略這些成員,因?yàn)轵?qū)動(dòng)從不創(chuàng)建文件結(jié)構(gòu),它們真實(shí)存取別處創(chuàng)建的結(jié)構(gòu)。
3.3.3 inode 結(jié)構(gòu)
inode結(jié)構(gòu)由內(nèi)核在內(nèi)部表示文件。因此,它和代表打開(kāi)文件描述符的文件結(jié)構(gòu)是不同的??赡苡写韱蝹€(gè)文件的多個(gè)打開(kāi)打開(kāi)描述符的許多文件結(jié)構(gòu),但是它們都指向一個(gè)單個(gè)inode結(jié)構(gòu)。
inode結(jié)構(gòu)包含大量關(guān)于文件的信息。作為一個(gè)通過(guò)用的規(guī)則,這個(gè)結(jié)構(gòu)只有2個(gè)成員對(duì)于編寫(xiě)驅(qū)動(dòng)代碼有用:
dev_t i_rdev;
對(duì)于代表設(shè)備文件的節(jié)點(diǎn),這個(gè)成員包含實(shí)際的設(shè)備編號(hào)。
struct cdev *i_cdev;
struct cdev
是內(nèi)核的內(nèi)部結(jié)構(gòu),代表字符設(shè)備。這個(gè)成員包含一個(gè)指針,指向這個(gè)結(jié)構(gòu),當(dāng)節(jié)點(diǎn)指的是一個(gè)字符設(shè)備文件時(shí)。
i_rdev
類(lèi)型在2.5開(kāi)發(fā)系列中改變了,破壞了大量的驅(qū)動(dòng)。作為一個(gè)鼓勵(lì)更可移植編程的方法,內(nèi)核開(kāi)發(fā)者已經(jīng)增加了2個(gè)宏,可用來(lái)從一個(gè)inode
中獲取主次編號(hào):
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
為了不要被下一次改動(dòng)抓住,應(yīng)當(dāng)使用這些宏代替直接操作i_cdev
。
注意,release
不是每次進(jìn)程調(diào)用close時(shí)都被調(diào)用。無(wú)論何時(shí)共享一個(gè)文件結(jié)構(gòu)(例如,在一個(gè)fork
或者dup
之后),release
不會(huì)調(diào)用直到所有的拷貝都關(guān)閉了。如果你需要在任一拷貝關(guān)閉時(shí)刷新掛著的數(shù)據(jù),你應(yīng)當(dāng)實(shí)現(xiàn)flush
方法。
是什么掛著的數(shù)據(jù)?待解決
3.4 字符設(shè)備注冊(cè)
如我們提過(guò)的,內(nèi)核在內(nèi)部使用類(lèi)型struct cdev
的結(jié)構(gòu)來(lái)代表字符設(shè)備。在內(nèi)核調(diào)用你的設(shè)備操作前,你編寫(xiě)分配并注冊(cè)一個(gè)或幾個(gè)這些結(jié)構(gòu)。為此,你的代碼應(yīng)當(dāng)包含<linux/cdev.h>
,這個(gè)結(jié)構(gòu)和它的關(guān)聯(lián)幫助函數(shù)定義在這里。
有2種方法來(lái)分配和初始化一個(gè)這些結(jié)構(gòu)。如果你想在運(yùn)行時(shí)獲得一個(gè)獨(dú)立的cdev
結(jié)構(gòu),你可以為此使用這樣的代碼:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
但是,偶爾你會(huì)想將cdev
結(jié)構(gòu)嵌入一個(gè)你自己的設(shè)備特定的結(jié)構(gòu);scull這樣做了。在這種情況下,你應(yīng)當(dāng)初始化你已經(jīng)分配的結(jié)構(gòu),使用:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
任一方法,有一個(gè)其他的struct cdev
成員你需要初始化。像file_operarions
結(jié)構(gòu),struct cdev
有一個(gè)擁有者成員,應(yīng)當(dāng)設(shè)置為THIS_MODULE
。一旦cdev
結(jié)構(gòu)建立,最后的步驟是把它告訴內(nèi)核,調(diào)用:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
這里,dev
是cdev
結(jié)構(gòu),num
是這個(gè)設(shè)備相應(yīng)的第一個(gè)設(shè)備號(hào),count
是應(yīng)當(dāng)關(guān)聯(lián)到設(shè)備的設(shè)備號(hào)的數(shù)目。常常count
是1,但是有多個(gè)設(shè)備號(hào)對(duì)應(yīng)于一個(gè)特定的設(shè)備的情形。例如,設(shè)想SCSI磁帶驅(qū)動(dòng),它允許用戶(hù)空間來(lái)選擇操作模式(例如密度),通過(guò)安排多個(gè)次編號(hào)給每一個(gè)物理設(shè)備。
在使用cdev_add
是由幾個(gè)重要事情要記住。第一個(gè)是這個(gè)調(diào)用可能失敗。如果它返回一個(gè)負(fù)的錯(cuò)誤碼,你的設(shè)備沒(méi)有增加到系統(tǒng)中,它幾乎會(huì)一直成功,但是它的操作,除非你的驅(qū)動(dòng)完全準(zhǔn)備好處理設(shè)備上的操作,你不應(yīng)當(dāng)調(diào)用cdev_add
。為從系統(tǒng)中去除一個(gè)字符設(shè)備,調(diào)用
void cdev_del(struct cdev *dev);
顯然,你不應(yīng)當(dāng)在傳遞給cdev_del
后存取cdev
結(jié)構(gòu)。
3.4.1 scull 中的設(shè)備注冊(cè)
在內(nèi)部,scull
使用一個(gè)struct scull_dev
類(lèi)型的結(jié)構(gòu)表示每個(gè)設(shè)備。這個(gè)結(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 */
unsigned long size; /*amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device struct */
};
我們?cè)谟龅浇Y(jié)構(gòu)的各個(gè)成員時(shí)討論它們,但是現(xiàn)在,我們關(guān)注cdev
,我們的設(shè)備與內(nèi)核接口的struct cdev
。這個(gè)結(jié)構(gòu)必須初始化并且如上所述添加到系統(tǒng)中;處理這個(gè)任務(wù)的scull代碼是:
static void scull_setup_cdev(struct scull_dev *dev, int index) {
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev, devno, 1);
/* Fail fracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
因?yàn)?code>cdev結(jié)構(gòu)嵌在struct scull_dev
里面,cdev_init
必須調(diào)用來(lái)進(jìn)行那個(gè)結(jié)構(gòu)的初始化。
3.4.2 老方法
如果你深入瀏覽2.6內(nèi)核的大量驅(qū)動(dòng),你可能注意到有許多字符驅(qū)動(dòng)不適用剛剛描述的cdev
接口。你見(jiàn)到的是還未更新到2.6內(nèi)核接口的老代碼。因?yàn)槟莻€(gè)代碼實(shí)際上能用,這個(gè)更新可能很長(zhǎng)時(shí)間不會(huì)發(fā)生。為完整,我們描述老的字符設(shè)備注冊(cè)接口,但是新代碼不應(yīng)當(dāng)使用它。這個(gè)機(jī)制在將來(lái)內(nèi)核中可能會(huì)消失。
注冊(cè)一個(gè)字符設(shè)備的經(jīng)典方法是使用:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
這里major
是感興趣的主編號(hào),name
是驅(qū)動(dòng)的名字(出現(xiàn)在/proc/devices
),fops
是缺省的file_operations
結(jié)構(gòu)。一個(gè)對(duì)register_chrdev
的調(diào)用為給定的主編號(hào)注冊(cè)0-255的次編號(hào),并且為每一個(gè)建立一個(gè)缺省的cdev
結(jié)構(gòu)。使用這個(gè)接口的驅(qū)動(dòng)必須準(zhǔn)備好處理對(duì)所有256個(gè)次編號(hào)的open
調(diào)用(不管讓們是否對(duì)應(yīng)真實(shí)設(shè)備),他們不能使用大于255的主或次編號(hào)。
如果你使用register_chrdev,從系統(tǒng)中去除你的設(shè)備的正確的函數(shù)是:
int unregister_chrdev(unsigned int major, const char *name);
major
與name
必須和傳遞給register_chrdev
的相同,否則調(diào)用會(huì)失敗。
3.5 open和release
到此我們已經(jīng)快速瀏覽了這些成員,我們開(kāi)始在真實(shí)的scull函數(shù)中使用它們。
3.5.1 open方法
open方法提供給驅(qū)動(dòng)來(lái)做任何的初始化來(lái)準(zhǔn)備后續(xù)的操作。再大部分驅(qū)動(dòng)中,open應(yīng)當(dāng)進(jìn)行下面的工作:
檢查設(shè)備特定的錯(cuò)誤(例如設(shè)備沒(méi)準(zhǔn)備好,或者類(lèi)似的硬件錯(cuò)誤)
如果它第一次打開(kāi),初始化設(shè)備
如果需要,更新f_op
指針。
分配并填充要放進(jìn)filp->private_data
的任何數(shù)據(jù)結(jié)構(gòu)
但是,事情的第一步常常是去頂打開(kāi)哪個(gè)設(shè)備,記住open方法的原型是:
int (*open)(struct inode *inode, struct file *filp);
inode
參數(shù)有我們需要的信息,以它的i_cdev
成員的形式,里面包含我們之前建立的cdev
結(jié)構(gòu)。唯一的問(wèn)題是通常我們不想要cdev
結(jié)構(gòu)本身,我們需要的是包含cdev結(jié)構(gòu)的scull_dev
。C語(yǔ)言使程序員玩弄各種技巧來(lái)做這種轉(zhuǎn)換,但是,這種技巧編程是易出錯(cuò)的,并且導(dǎo)致別人難于閱讀和理解代碼。幸運(yùn)的是,在這種情況下,內(nèi)核hacker已經(jīng)為我們實(shí)現(xiàn)了這個(gè)技巧,以container_of
宏的形式,在<linux/kernel.h>
中定義:
container_of(pointer, container_type, container_field);
這個(gè)宏使用一個(gè)指向container_field
類(lèi)型的成員的指針,它在一個(gè)container_type
類(lèi)型的結(jié)構(gòu)中,并且返回一個(gè)指針指向包含結(jié)構(gòu)。在scull_open
,這個(gè)宏用來(lái)找到適當(dāng)?shù)脑O(shè)備結(jié)構(gòu)。
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
一旦它找到scull_dev
結(jié)構(gòu),scull在文件結(jié)構(gòu)的private_data
成員中存儲(chǔ)一個(gè)它的指針,為以后更易存取。
識(shí)別打開(kāi)的設(shè)備的另外的方法是查看存儲(chǔ)在inode
結(jié)構(gòu)的次編號(hào)。如果你使用register_chrdev
注冊(cè)你的設(shè)備,你必須使用這個(gè)技術(shù)。確認(rèn)使用iminor
從inode
結(jié)構(gòu)中獲取次編號(hào),并且確定它對(duì)應(yīng)一個(gè)你的驅(qū)動(dòng)真正準(zhǔn)備好處理的設(shè)備。
scull_open的代碼(稍微簡(jiǎn)化過(guò))是:
int scull_open(struct inode *inode, struct file *filp) {
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
scull_trim(dev); /* ignore errors */
}
return 0; /* success */
}
代碼看起來(lái)相當(dāng)稀疏,因?yàn)樵谡{(diào)用open時(shí)它沒(méi)有做任何特別的設(shè)備處理。。它不需要,因?yàn)閟cull設(shè)備設(shè)計(jì)為全局的和永久的。特別地,沒(méi)有如“在第一次打開(kāi)時(shí)初始化設(shè)備”等動(dòng)作,因?yàn)槲覀儾粸閟cull保持打開(kāi)計(jì)數(shù)。
唯一在設(shè)備上的真實(shí)操作是當(dāng)設(shè)備為寫(xiě)而打開(kāi)時(shí)將它截取為長(zhǎng)度為0。這樣做是因?yàn)?,在設(shè)計(jì)上,用一個(gè)短的文件覆蓋一個(gè)scull設(shè)備導(dǎo)致一個(gè)短的設(shè)備數(shù)據(jù)區(qū)。這類(lèi)似于為寫(xiě)而打開(kāi)一個(gè)常規(guī)文件,將其截短為0。如果設(shè)備為讀而打開(kāi),這個(gè)操作什么都不做。
在我們查看其他scull特性的代碼時(shí)將看到一個(gè)真實(shí)的初始化如何起作用的。
3.5.2 release方法
release方法的角色是open的反面。又是你會(huì)發(fā)現(xiàn)方法的實(shí)現(xiàn)稱(chēng)為device_close
,而不是device_release
。任一方式,設(shè)備方法應(yīng)當(dāng)進(jìn)行下面的任務(wù):
釋放open
分配在filp->private_data
中的任何東西
在最后的close關(guān)閉設(shè)備
scull的基本形式?jīng)]有硬件去關(guān)閉,因此需要的代碼是最少的:
int scull_release(struct inode *inode, struct file *filp) {
return 0;
}
你可能想知道當(dāng)一個(gè)設(shè)備文件關(guān)閉次數(shù)超過(guò)它被打開(kāi)的次數(shù)會(huì)發(fā)生什么。畢竟,dup
和fork
系統(tǒng)調(diào)用不調(diào)用open
來(lái)創(chuàng)建打開(kāi)文件的拷貝。每個(gè)拷貝接著在程序終止時(shí)被關(guān)閉。例如,大部分程序不打開(kāi)它們的stdin
文件(或設(shè)備),但是他們都以關(guān)閉它結(jié)束。當(dāng)1個(gè)打開(kāi)的設(shè)備文件已經(jīng)真正被關(guān)閉時(shí),驅(qū)動(dòng)如何知道?
答案簡(jiǎn)單:
不是每個(gè)close
系統(tǒng)調(diào)用引起調(diào)用release
方法,只有真正釋放設(shè)備數(shù)據(jù)結(jié)構(gòu)的調(diào)用會(huì)調(diào)用這個(gè)方法。內(nèi)核維持一個(gè)文件被使用多少次的計(jì)數(shù)。fork
和dup
都不創(chuàng)建新文件(只有open
這樣);它們只遞增正存在的結(jié)構(gòu)中的計(jì)數(shù)。close
系統(tǒng)調(diào)用僅在文件結(jié)構(gòu)計(jì)數(shù)掉到0時(shí)執(zhí)行release
方法,這在結(jié)構(gòu)被銷(xiāo)毀時(shí)發(fā)生。release
方法和close
系統(tǒng)調(diào)用之間的這種關(guān)系保證了你的驅(qū)動(dòng)一次open
只看到一次release
。
注意,flush
方法在每次應(yīng)用程序調(diào)用close
時(shí)都被調(diào)用。但是,很少驅(qū)動(dòng)實(shí)現(xiàn)flush
,因?yàn)槌3T?code>close時(shí)沒(méi)有什么要做的,除非調(diào)用release
。
如你會(huì)想到的,前面的討論即便是應(yīng)用程序沒(méi)有明顯地關(guān)閉它打開(kāi)的文件也適用:內(nèi)核在進(jìn)程exit
時(shí)自動(dòng)關(guān)閉了任何文件,通過(guò)在內(nèi)部使用close
系統(tǒng)調(diào)用。
3.6 scull的內(nèi)存使用
在介紹讀寫(xiě)操作前,我們最好看看如何以及為什么scull
內(nèi)存分配。
- “如何”是需要全面理解代碼本身
-
“為什么”演示了驅(qū)動(dòng)編寫(xiě)者需要做的選擇,盡管
scull
明確地不是典型設(shè)備。
本節(jié)只處理scull
中的內(nèi)存分配策略,不展示編寫(xiě)真正驅(qū)動(dòng)需要的硬件管理技能。這些技能在第9章和第10章介紹。因此,你可跳過(guò)本章,如果你不感興趣于理解面向內(nèi)存的scull
驅(qū)動(dòng)的內(nèi)部工作。
scull
使用的內(nèi)存區(qū),也成為一個(gè)設(shè)備,長(zhǎng)度可變。你寫(xiě)的越多,它增長(zhǎng)越多;通過(guò)使用一個(gè)短文件覆蓋設(shè)備來(lái)進(jìn)行進(jìn)行修整。
scull
驅(qū)動(dòng)引入2個(gè)核心函數(shù)來(lái)管理Linux
內(nèi)核中的內(nèi)存。這些函數(shù)定義在<linux/slab.h>
中,分別是:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
對(duì)kmalloc
的調(diào)用試圖分配size
字節(jié)的內(nèi)存;返回值使指向那個(gè)內(nèi)存的指針或者如果分配失敗為NULL
。flags
參數(shù)用來(lái)描述內(nèi)存應(yīng)當(dāng)如何分配;我們?cè)?mark>第8章詳細(xì)查看這些標(biāo)志。對(duì)于現(xiàn)在,我們一直使用GFP_KERNEL
。分配的內(nèi)存應(yīng)當(dāng)用kfree
來(lái)釋放。你應(yīng)當(dāng)從不傳遞任何不是從kmalloc
獲得的東西給kfree
,但是傳遞一個(gè)NULL
指針給kfree
是合法的。
kmalloc
不是最有效的分配大內(nèi)存區(qū)的方法(見(jiàn)第8章),所以挑選給scull的實(shí)現(xiàn)不是一個(gè)特別巧妙的。一個(gè)巧妙的源碼實(shí)現(xiàn)可能更難閱讀,而本節(jié)的目標(biāo)是展示讀和寫(xiě),不是內(nèi)存管理。因此,這里只是使用kmalloc和kfree而不依靠整頁(yè)的分配,盡管這個(gè)方法會(huì)更有效。
在filp一邊,我們不想限制“設(shè)備”區(qū)的大小。但由于理論上的與實(shí)踐上的理由。理論上,給在被管理的數(shù)據(jù)項(xiàng)施加武斷的限制總是個(gè)壞想法。實(shí)踐上,scull可用來(lái)暫時(shí)地吃光你系統(tǒng)的內(nèi)存,以便運(yùn)行在低內(nèi)存條件下的測(cè)試。運(yùn)行這樣的測(cè)試可能會(huì)幫助你理解系統(tǒng)的內(nèi)部。你可以使用命令cp /dev/zero /dev/scull0 來(lái)用scull吃掉所有的真實(shí)RAM,并且你可以使用dd工具來(lái)選擇拷貝多少數(shù)據(jù)給scull設(shè)備。
在scull,每個(gè)設(shè)備是一個(gè)指針鏈表,每個(gè)都指向一個(gè)scull_dev結(jié)構(gòu)。每個(gè)這樣的結(jié)構(gòu),缺省地,指向最多4兆字節(jié),通過(guò)一個(gè)中間指針數(shù)組。發(fā)行代碼使用一個(gè)1000個(gè)指針地?cái)?shù)組指向每個(gè)4000字節(jié)的區(qū)域。我們稱(chēng)每個(gè)內(nèi)存區(qū)域?yàn)橐粋€(gè)量子,數(shù)組(或者它的長(zhǎng)度)為一個(gè)量子集。一個(gè)scull設(shè)備和它的內(nèi)存區(qū)如圖所示:
選定的數(shù)字是這樣,在scull中寫(xiě)單個(gè)一個(gè)字節(jié)消耗8000或12000KB內(nèi)存:4000是量子,4000或8000是量子集(根據(jù)指針在目標(biāo)平臺(tái)上是用32位還是64位表示)。相反,如果你寫(xiě)入大量數(shù)據(jù),鏈表的開(kāi)銷(xiāo)不是太壞。每4MB數(shù)據(jù)只有一個(gè)鏈表元素,設(shè)備的最大尺寸受限于計(jì)算機(jī)內(nèi)存的大小。
為量子與量子集選擇合適的值是一個(gè)策略問(wèn)題,而不是機(jī)制,并且優(yōu)化的值依賴(lài)于設(shè)備如何使用。因此,scull驅(qū)動(dòng)不應(yīng)當(dāng)強(qiáng)制給量子與量子集使用任何特別的值。在scull中,用戶(hù)可以掌管改變這些值,有幾個(gè)途徑:
- 編譯時(shí)間通過(guò)改變
scull.h
中的宏SCULL_QUANTUM
和SCULL_QSET
; - 在模塊加載時(shí)設(shè)定整數(shù)值
scull_quantum
和scull_qset
; - 使用ioctl在運(yùn)行時(shí)改變當(dāng)前值與缺省值。
使用宏定義和一個(gè)整數(shù)值來(lái)進(jìn)行編譯時(shí)和加載時(shí)的配置,是對(duì)于如何選擇主編號(hào)的回憶。我們?cè)隍?qū)動(dòng)中任何與策略相關(guān)或?qū)嗟闹瞪线\(yùn)用這個(gè)技術(shù)。
余下的唯一問(wèn)題是如果選擇缺省值。在這個(gè)特殊情況下,問(wèn)題是找到最好的平衡,由填充了一半的量子和量子集導(dǎo)致內(nèi)存浪費(fèi),如果量子和量子集小的情況下分配釋放和指針連接引起開(kāi)銷(xiāo)。另外,kmalloc
的內(nèi)部設(shè)計(jì)應(yīng)當(dāng)考慮進(jìn)去。(現(xiàn)在我們不追求這點(diǎn),不過(guò);kmalloc
的內(nèi)部在第8章探索。)缺省值的選擇來(lái)自假設(shè)測(cè)試時(shí)可能有大量數(shù)據(jù)寫(xiě)進(jìn)scull,盡管設(shè)備正常使用最可能只傳送幾KB數(shù)據(jù)。
我們已經(jīng)見(jiàn)過(guò)內(nèi)部代表我們?cè)O(shè)備的scull_dev
結(jié)構(gòu)。結(jié)構(gòu)的quantum
與qset
分別代表設(shè)備的量子與量子集大小。實(shí)際數(shù)據(jù),但是,是由一個(gè)不同的結(jié)構(gòu)跟蹤,我們稱(chēng)為struct scull_qset
:
struct scull_qset {
void **data;
struct scull_qset *next;
}
下一個(gè)代碼片段展示了實(shí)際中struct scull_dev
與struct scull_qset
是如何被用來(lái)持有數(shù)據(jù)的。scull_trim
函數(shù)負(fù)責(zé)釋放整個(gè)數(shù)據(jù)區(qū),由scull_open
在文件為寫(xiě)而打開(kāi)時(shí)調(diào)用。它簡(jiǎn)單地遍歷列表并且釋放它發(fā)現(xiàn)的任何量子與量子集。
int scull_trim(struct scull) {
struct scull_qset *next, *dptr;
int qset = dev->qset;
int i;
for (dptr = dev->data; dptr; dptr = next) {
/* all the list items */
if (dptr->data) {
for (i = 0; i < qset; i++) {
kfree(dptr->data[i]);
}
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
scull_trim也用在模塊清理函數(shù)中,來(lái)歸還scull使用的內(nèi)存給系統(tǒng)。
3.7 讀和寫(xiě)
讀和寫(xiě)的方法都進(jìn)行類(lèi)似的任務(wù),就是從和到應(yīng)用程序代碼拷貝數(shù)據(jù)。因此它們的原型相當(dāng)相似,可以同時(shí)介紹它們:
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
對(duì)于兩個(gè)方法,filp是文件指針,count是請(qǐng)求的傳輸數(shù)據(jù)大小,buff參數(shù)指向持有被寫(xiě)入數(shù)據(jù)的緩存,或者放入新數(shù)據(jù)的空緩存。最后,offp是一個(gè)指針指向一個(gè)“l(fā)ong offset type”對(duì)象,它指出用戶(hù)正在存取的文件位置。返回值是一個(gè)signed size type,它的使用在后面討論。
讓我們重復(fù)一下,read與write的buff參數(shù)是用戶(hù)空間指針。因此,它不能被內(nèi)核代碼直接解引用。這個(gè)限制有幾個(gè)理由:
- 依賴(lài)于你的驅(qū)動(dòng)運(yùn)行的體系,以及內(nèi)核被如何配置的,用戶(hù)空間指針當(dāng)運(yùn)行于內(nèi)核模式可能根本是無(wú)效的??赡軟](méi)有那個(gè)地址的映射,或者它可能只想一些其他的隨機(jī)數(shù)據(jù)。
- 就算這個(gè)指針在內(nèi)核空間是同樣的東西,用戶(hù)空間內(nèi)存是分頁(yè)的,在做系統(tǒng)調(diào)用時(shí)這個(gè)內(nèi)存可能沒(méi)有在RAM中,試圖直接應(yīng)用用戶(hù)空間內(nèi)存可能產(chǎn)生一個(gè)頁(yè)面錯(cuò),這是內(nèi)核代碼不允許做的事情,結(jié)果可能是一個(gè)“oops”,導(dǎo)致進(jìn)行系統(tǒng)調(diào)用的進(jìn)程死亡。
- 置疑中的指針由一個(gè)用戶(hù)程序提供,它可能是錯(cuò)誤的或者惡意的。如果你的驅(qū)動(dòng)盲目地解引用一個(gè)用戶(hù)提供的指針,它提供了一個(gè)打開(kāi)的門(mén)路使用戶(hù)空間程序存取或覆蓋系統(tǒng)任何地方的內(nèi)存。如果你不想負(fù)責(zé)你的用戶(hù)的系統(tǒng)的安全危險(xiǎn),你就不能直接解引用用戶(hù)空間指針。
顯然,你的驅(qū)動(dòng)必須能夠存取用戶(hù)空間緩存以完成它的工作。但是,為安全起見(jiàn)這個(gè)存取必須使用特殊的,內(nèi)核提供的函數(shù)。我們介紹幾個(gè)這樣的函數(shù)(定義于<asm/uaccess.h>
),剩下的在第一章“使用ioctl參數(shù)”一節(jié)中。它們使用一種特殊的、依賴(lài)體系的技巧來(lái)確保內(nèi)核和用戶(hù)空間的數(shù)據(jù)傳輸安全和正確。
scull中的讀寫(xiě)代碼需要拷貝一診斷數(shù)據(jù)到或者從用戶(hù)地址空間。這個(gè)能力由下列內(nèi)核函數(shù)提供,它們拷貝一個(gè)任意的字節(jié)數(shù)組,并且位于大部分讀寫(xiě)實(shí)現(xiàn)的核心中。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
盡管這些函數(shù)表現(xiàn)像正常的memcpy
函數(shù),必須加一點(diǎn)小心在從內(nèi)核代碼中從存取用戶(hù)空間。尋址的用戶(hù)也當(dāng)前可能不在內(nèi)存,虛擬內(nèi)存子系統(tǒng)會(huì)是進(jìn)程睡眠在這個(gè)頁(yè)被傳送到位時(shí)。例如,這發(fā)生在必須從交換空間獲取頁(yè)的時(shí)候。對(duì)于驅(qū)動(dòng)編寫(xiě)者來(lái)說(shuō),最終結(jié)果是任何存取用戶(hù)空間的函數(shù)必須是可重入的,必須能夠和其他驅(qū)動(dòng)函數(shù)并行執(zhí)行,并且,特別的,必須在一個(gè)它能夠合法地水面的位置。我們?cè)?mark>第5章再回到這個(gè)主題。
這兩個(gè)函數(shù)的角色不限于內(nèi)核空間與用戶(hù)空間之間來(lái)回拷貝數(shù)據(jù),它們還檢查用戶(hù)空間指針是否有效。如果指針無(wú)效,不進(jìn)行拷貝; 如果再拷貝中遇到一個(gè)無(wú)效地址,另一方面,只拷貝部分?jǐn)?shù)據(jù)。在2種情況下,返回值時(shí)還要拷貝的數(shù)據(jù)量。scull代碼查看這個(gè)錯(cuò)誤返回,并且如果它不是0就返回-EFAULT
給用戶(hù)。
用戶(hù)空間存取和無(wú)效用戶(hù)空間指針的主題有些高級(jí),在第6章討論。如果無(wú)需檢查用戶(hù)空間指針,可以調(diào)用__copy_to_user
和__copy_from_user
來(lái)代替。這是有用處的,例如,如果你確信你已經(jīng)檢查好了這些參數(shù)。但是要小心,如果不檢查傳遞給這些函數(shù)的用戶(hù)空間指針,那么可能造成內(nèi)核崩潰或安全漏洞。
至于實(shí)際的設(shè)備方法,read方法的任務(wù)是從設(shè)備拷貝數(shù)據(jù)到用戶(hù)空間,而write方法必須從用戶(hù)空間拷貝數(shù)據(jù)到設(shè)備(使用copy_from_user)。每個(gè)read或write系統(tǒng)調(diào)用請(qǐng)求特定數(shù)目字節(jié)的傳送,但是驅(qū)動(dòng)可自由傳送較少數(shù)據(jù),對(duì)讀和寫(xiě)這確切的規(guī)則稍微不同,在本章后面描述。
不管這些方法傳送多少數(shù)據(jù),他們通常應(yīng)當(dāng)更新*offp中的文件位置來(lái)表示在系統(tǒng)調(diào)用成功完成后當(dāng)前的文件位置。內(nèi)核接著在適當(dāng)時(shí)候傳播文件位置的改變到文件結(jié)構(gòu)。pread和pwrite系統(tǒng)調(diào)用有不同的語(yǔ)義,他們從一個(gè)給定的文件偏移操作,并且不改變其他的系統(tǒng)調(diào)用看到的文件位置。這些調(diào)用傳遞一個(gè)指向用戶(hù)提供的位置的指針,并且放棄你的驅(qū)動(dòng)所作的改變。
read與write方法都在發(fā)生錯(cuò)誤時(shí)返回一個(gè)負(fù)值。相反,大于等于0的返回值告知調(diào)用程序有多少字節(jié)已經(jīng)成功傳送。如果一些數(shù)據(jù)成功傳送接著發(fā)生錯(cuò)誤,返回值必須是成功傳送的字節(jié)數(shù),錯(cuò)誤不報(bào)告知道函數(shù)下一次調(diào)用。實(shí)現(xiàn)這個(gè)傳統(tǒng),當(dāng)然要求你的驅(qū)動(dòng)記住錯(cuò)誤已經(jīng)發(fā)生,以便它們可以在以后返回錯(cuò)誤狀態(tài)。
盡管內(nèi)核函數(shù)返回一個(gè)負(fù)數(shù)指示一個(gè)錯(cuò)誤,這個(gè)數(shù)的值指出所發(fā)生的錯(cuò)誤類(lèi)型(如第2章介紹),用戶(hù)空間運(yùn)行的程序常常看到-1作為錯(cuò)誤返回值。它們需要存取errno變量來(lái)找出發(fā)生了什么。用戶(hù)空間的行為由POSIX標(biāo)準(zhǔn)來(lái)規(guī)定,但是這個(gè)標(biāo)準(zhǔn)沒(méi)有規(guī)定內(nèi)核內(nèi)部如何操作。
3.7.1 read方法
read的返回值由調(diào)用的應(yīng)用程序解釋?zhuān)?/p>
- 如果這個(gè)值等于傳遞給read系統(tǒng)調(diào)用的count參數(shù),請(qǐng)求的字節(jié)數(shù)已經(jīng)被傳送,這是最好的情況。
- 如果是正數(shù),但是小于count,只有部分?jǐn)?shù)據(jù)被傳送,這可能由于幾個(gè)原因,依賴(lài)于設(shè)備。常常,應(yīng)用程序重新試著讀取,例如,如果你使用fread函數(shù)來(lái)讀取,庫(kù)函數(shù)重新發(fā)出系統(tǒng)調(diào)用直到請(qǐng)求的數(shù)據(jù)傳送完成
- 如果值為0,到達(dá)了文件末尾(沒(méi)有讀取數(shù)據(jù))。
- 一個(gè)負(fù)值表示有一個(gè)錯(cuò)誤。這個(gè)值指出了什么錯(cuò)誤,根據(jù)
<linux/errno.h>
,出錯(cuò)的典型返回值包括-EINTR(被打斷的系統(tǒng)調(diào)用)
或者-EFAULT(壞地址)
。
前面的列表中漏掉的是這種情況“沒(méi)有數(shù)據(jù),但是可能后來(lái)到達(dá)”。在這種情況下,read系統(tǒng)調(diào)用應(yīng)當(dāng)阻塞。我們將在第6章談到阻塞。
scull代碼利用來(lái)這些規(guī)則。特別地,它利用了部分讀規(guī)則。每個(gè)scull_read調(diào)用只處理單個(gè)數(shù)據(jù)量子,不實(shí)現(xiàn)一個(gè)循環(huán)來(lái)收集所有的數(shù)據(jù);這使得代碼更短更易讀。如果程序確實(shí)需要更多數(shù)據(jù),它重新調(diào)用。如果標(biāo)準(zhǔn)I/O庫(kù)(例如,fread)用來(lái)讀取設(shè)備,應(yīng)用程序甚至不會(huì)注意到數(shù)據(jù)傳送的量子化。
如果當(dāng)前讀取位置大于設(shè)備大小,scull的read方法返回0來(lái)表示沒(méi)有可用的數(shù)據(jù)(換句話說(shuō),我們?cè)谖募玻?,這個(gè)情況發(fā)生在如果進(jìn)程A 在讀設(shè)備,同時(shí)進(jìn)程B打開(kāi)它寫(xiě),這樣將設(shè)備截短為0。進(jìn)程A突然發(fā)現(xiàn)自己過(guò)了文件尾,下一個(gè)讀調(diào)用返回0。
這是read的代碼(忽略對(duì)down_interruptible的調(diào)用并且現(xiàn)在為up;我們?cè)谙乱徽轮杏懻撍鼈儯?/p>
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset; /* how many bytes in the listitem */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_post = rest / quantum;
q_post = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
3.7.2 write方法
write像read,可以傳送少于要求的數(shù)據(jù),根據(jù)返回值的下列規(guī)則:
- 如果值等于count,要求的字節(jié)數(shù)已被傳送。
- 如果為正數(shù),但是小于count,只有部分?jǐn)?shù)據(jù)被傳送,程序最可能重試寫(xiě)入剩下的數(shù)據(jù)。
- 如果值為0,什么沒(méi)有寫(xiě)。這個(gè)結(jié)果不是一個(gè)錯(cuò)誤,沒(méi)有理由返回一個(gè)錯(cuò)誤碼。再一次,標(biāo)準(zhǔn)庫(kù)重試寫(xiě)調(diào)用。我們將在第6章查看這種情況的確切含義,那里介紹了阻塞。
- 一個(gè)負(fù)值表示發(fā)生一個(gè)錯(cuò)誤;如同對(duì)于讀,有效的錯(cuò)誤值是定義于
<linux/errno.h>
中。
不幸的是,仍然可能有發(fā)出錯(cuò)誤消息的不當(dāng)行為程序,它在進(jìn)行了部分傳送時(shí)中止。這是因?yàn)橐恍┏绦騿T習(xí)慣看寫(xiě)調(diào)用要么完全失敗要么完全成功,這實(shí)際上是大部分時(shí)間的情況,應(yīng)當(dāng)也被設(shè)備支持。scull實(shí)現(xiàn)的這個(gè)限制可以修改,但是我們不想使代碼不必要地復(fù)雜。
write的scull代碼一次處理單個(gè)量子,如read方法做的:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-479138.html
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* find listitem, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pocs = rest % quantum;
/* follow the list up to the right position */
dptr = scull_follow(dev, item);
if (dptr == NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/* write only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos) dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
3.7.3 readv與writev
Unix系統(tǒng)已經(jīng)長(zhǎng)時(shí)間支持名為readv和writev的2個(gè)系統(tǒng)調(diào)用。這些read和write的“矢量”版本使用一個(gè)結(jié)構(gòu)數(shù)據(jù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-479138.html
到了這里,關(guān)于Linux設(shè)備驅(qū)動(dòng)——第三章字符驅(qū)動(dòng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!