国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux設(shè)備驅(qū)動(dòng)——第三章字符驅(qū)動(dòng)

這篇具有很好參考價(jià)值的文章主要介紹了Linux設(shè)備驅(qū)動(dòng)——第三章字符驅(qū)動(dòng)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

當(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/devicessysfs中。

如同大部分內(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。countname參數(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_loadscull_unload的角色。

如果反復(fù)創(chuàng)建與銷(xiāo)毀/dev節(jié)點(diǎn),聽(tīng)起來(lái)過(guò)分了,有一個(gè)有用的辦法。如果你在加載和卸載單個(gè)驅(qū)動(dòng),你可以在你第一次使用你的腳本創(chuàng)建特殊文件之后,只使用rmmodinsmod,這樣動(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_operationsfileinode。需要對(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_READFMODE_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_NONBLOCKO_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);

這里,devcdev結(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);

majorname必須和傳遞給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)使用iminorinode結(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ā)生什么。畢竟,dupfork系統(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ù)。forkdup都不創(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_QUANTUMSCULL_QSET
  • 在模塊加載時(shí)設(shè)定整數(shù)值scull_quantumscull_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)的quantumqset分別代表設(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_devstruct 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方法做的:

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)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包