第八章 設備驅動程式

作業系統的目的之一就是掩蓋掉各種硬體的特殊性。使得系統中的硬體設備對於用
戶而言是透明的,例如,不管底層是什麼樣的實體設備,虛擬檔案系統提供一個一致的,
安裝好的檔案系統。本章將描述Linux核心如何管理系統中的實體設備。
系統中CPU不是唯一的智能設備,每一個實體週邊都有其設備控制器。鍵盤,滑鼠和
序列介面由多功能卡(SuperIO)控制,IDE磁碟由IDE控制器掌握,SCSI磁碟有SCSI控制器
控制。每一個於硬體控制器都有其自己的控制和狀態暫存器(CSR)。這些CSR在不同的設備
中是不一樣的。一個Adaptec 2940 SCSI控制器的CSR與NCR810 SCSI控制器差別很大。CSR用
來啟動和停止一個設備,用來初始化一個設備和檢測故障。用來管理系統中硬體控
制器的程式碼位於Linux核心中,而不是在每個應用程式中。用來管理硬體控制器的軟體通
常叫做設備驅動程式。Linux核心的設備驅動程式基本上是一些共享函式庫(Shared Library),在
函式庫中含有一些特權的,常住記憶體的,一些用來處理底層硬體的例程。Linux的設備驅動程
序用來處理各種硬體的多樣性。
作業系統的基本功能之一是對設備處理的抽像化。所有的實體設備被當做正規的檔案
來處理,可以被“打開”,“關閉”,“讀”和”寫“,就像我們用系統調用處理檔
案一樣。(譯者注:“檔案”是一個邏輯上的概念﹔設備是一個實體。這裡談的是把設備
抽像在/dev檔案系統下。)系統中每一個設備都對應一個設備特殊檔案(device special
file),例如,系統中的第一個IDE磁碟的設備檔案名是/dev/hda。對於區塊設備(如,磁碟)
和字元設備,它們的的設備特殊檔案通常是通過mknod命令用主設備號和次設備號來描述和
創建。(譯者注:主設備號和次設備號用來定位系統中兩個表。一個主設備對應一個設備驅
動程式。次設備的含義是系統中可以存在多個設備屬於同一類,比如多個IDE磁碟。 但它們
只需要一個同樣的設備驅動程式來管理。)網路設備也同樣是一個設備特殊檔案,但它是由
Linux核心來創建當系統發現並初始化網路控制器的時候。被同樣一個設備驅動程式所管理的
所有設備擁有一個同樣的主設備號。次設備號用來區分不同的設備和設備控制器。例如,每個
IDE磁碟主設備的每個分區都有個不同的次設備號。所以,/dev/hda2,這個第2個分區的主設備
號是3,次設備號是2。Linux將系統調用中(比如將一個檔案系統安裝在一個區塊設備上)傳遞過來
的設備特殊檔案名映射到相應的設備驅動程式(根據其相應的主設備名)和許多系統表中,如字
元設備表,chrdevs。
Linux支援三種硬體設備類型:字元,區塊和網路設備。字元設備的讀寫不需要緩衝,
例如系統的序列介面/dev/cua0和/dev/cua1。區塊設備的讀和寫只能以區塊的單位來進行,區塊的大
小一般是512位元或1024位元。區塊設備的讀寫是通過緩衝Cache並且可以被隨機存取。
隨機存取意味著你可以定位區塊設備的任一個塊並進行讀取﹔區塊設備的存取可以通過
其設備特殊檔案,但更通常的是通過檔案系統。只有區塊設備支援檔案系統的安裝(Mount)
。網路設備的存取是通過BSD的Socket介面和網路子系統(請參閱網路章節)。
Linux支援許多不同的設備驅動程式(Linux的優點之一)。它們都具備一些共同的屬
性:
核心模式:
設備驅動程式是核心的一部份,就像核心中其他程式碼一樣,如果不正確運行,會嚴
重地毀壞系統。一個寫的不好的驅動程式 可能使系統崩潰,並可能將檔案系統打亂
丟失資料. (譯者注:作者在這提“核心模式”的目的是指核心模式下運行的程式碼可以幾
乎完全控制一個系統。)
核心介面:
設備驅動程式必須提供一個標準的介面給Linux核心或相應的子系統。例如,終端
驅動程式提供一個檔案I/O介面給Linux核心﹔SCSI設備驅動程式提供一個SCSI設備
介面給SCSI子系統。SCSI設備介面提供檔案I/O,SCSI子系統提供緩衝機制。
核心機制和服務:
設備驅動程式利用標準的核心服務,如記憶體分配,中斷傳送,等待隊列來運行。
可裝卸的:
大多數的Linux設備驅動程式可以在需要時被載進系統作為核心的一個模組﹔可以
被卸下當不再被使用。這使的核心的自適應性非常好,系統的資源可以有效地被利
用。(譯者注:讀者可以聯想一下Windows 作業系統中的DLL(Dynamic Link Library)的
概念)
可重構的:
Linux設備驅動程式可以被構造進核心。當核心重新編譯時,那些設備就是可重構
的。
動態的:
當系統啟動時,每一個設備驅動程式進行初始化,尋找其控制的設備。如果核心中
一個設備驅動程式所對應的控制設備不存在(譯者注:例如沒有安裝SCSI磁碟雖然系
統有SCSI驅動程式),也沒有關系。這種情況下,系統中只不過是多了一個“多余的”
驅動程式,占用了一些系統記憶體而已。對系統本身無礙。
8.1 檢測與中斷
每次設備接受一個命令,例如,“移動讀磁頭到軟碟的第42sector”,為了知道這個
命令是否完成,設備驅動程式有兩種選擇:(不斷地)檢測這個設備或使用中斷。(譯
者注:“不斷地”可以理解為:“while(!(read_device_status_register()));”
)
檢測一個設備意味著頻繁地讀(設備的)狀態暫存器直到狀態暫存器值的變化顯示該
設備已經完次昐求。如果一個設備驅動程式是核心的一部份,上述行為將是一種災
難性的因為核心什麼其他的也不能作直到設備完成服務請求(譯者注:這種方法極大
地犧牲了系統的並發性。例如,其他程序全部被阻塞因為在核心模式時,程序是不可
被搶先的(或被剝奪的。)。一個替代的方法是使用一個系統定時器,設備驅動程式
每隔一定時間調用設備驅動程式中的一個例程去檢測服務命令是否完成。Linux的軟
盤驅動程式就是這樣工作的(譯者注:不知道這種方法的優點何在?)。一種更有效
的方法是使用中斷。
中斷驅動的設備驅動程式意味著:任何時候,它所管理的設備需要被處理時,該設
備會發出一個中斷。例如,每當一個Ethernet網路卡控制器從網路上接收一個Ethernet數
據包時,系統將會接收到一個中斷。Linux核心需要能夠傳送這個來自設備的中斷到相應的設
備驅動程式。這是通過該設備驅動程式(在初始化時)登記它所管理的中斷號來達到
的(譯者注:請參閱中斷處理章節)。它並且登記對應該中斷的中斷處理程式的位址。
讀者可以通過/proc/interrupts來查閱哪一個中斷別哪一個設備驅動程式所使用和
其中斷的類型。
0: 727432 timer
1: 20534 keyboard
2: 0 cascade
3: 79691 + serial
4: 28258 + serial
5: 1 sound blaster
11: 20868 + aic7xxx
13: 1 math error
14: 247 + ide0
15: 170 + ide1
這個申請中斷資源的過程發生在驅動程式初始化的時候。系統中有一些中斷號的使
用是固定的,這是由於IBM PC體系結構的習慣遺留(Legacy)而來。例如,軟碟控制
器將一直使用中斷6。其他中斷,如PCI設備的中斷是在系統啟動時動態分配的
(譯者注:請注意ISA設備與PCI設備在中斷號占用方面的區別)。這種情況下,設備
驅動程式在登記/申請系統中一個中斷號之前,將首先探測它所管理的設備所將占用
的IRQ。對PCI中斷,Linux支援標準的PCI BIOS回調函數,以用來決定系統中設備的
信息,包括其中斷號。
一個中斷如何被傳遞到CPU中,不同的硬體體系結構有不同的方法。但大多數系統中,
中斷的傳遞是通過一種特殊的模式,在這種模式下,系統其他的中斷不會發生(譯者
注:這與處理中斷時,屏蔽掉同等級的中斷不是一回事,這裡講的是“傳遞”中斷
)。一個設備驅動程式的中斷處理例程要盡可能地簡單快速,從而Linux核心可以能
夠很快地撤銷(Dismiss)這個中斷並回到被中斷之前的現場(譯者注:系統被中斷時,
有可能一個程序正在使用者模式下運行)。需要為接收/處理中斷作很多工作的設備驅動
程式可以使用核心的bottom half handlers 或任務隊列。該任務隊列存放著那些將
被待後調用的函數例程。
8.2 直接記憶體存取-DMA(Direct Memeory Access)
當資料量很小的情況下,使用中斷驅動的設備驅動程式來從/向硬體設備傳遞資料是
合理的,可以工作的很好。例如,一個9600波特率的Modem的傳輸速率近似於沒毫秒
(millisecond)一個字元。如果中斷的延遲,硬體設備發出中斷和設備驅動程式處理
該中斷的時間非常小(比如2毫秒),那麼資料傳輸的總體系統影響也非常小。這個9600波
特率的Modem資料傳輸只要占用0.002%的CPU處理時間。但是對於高速設備,比如硬
盤控制器或Ethernet設備,它們的的傳輸速率要高很多。 一個SCSI設備能達到40M字
節每秒。
直接記憶體存取,或DMA,被提出用來解決傳輸上述大批量資料的問題。一個DMA控制
器允許設備與記憶體之間發送或接收資料,但不影響處理器CPU。PC的ISA DMA控制器
有8個DMA通道。第7個通道被用來為設備驅動程式服務。每一個DMA通道與一個16位
的位址暫存器器和一個16位的計數暫存器相關聯。當想要發起一次資料交換時,設
備驅動程式設置相應DMA通道的位址,計數暫存器的大小,這次資料傳輸的方向(讀
或寫)。然後通知設備可以啟動DMA操作。當DMA結束時,設備才中斷系統。因此,在
資料傳輸的過程中,CPU可以作其他的事情。
在使用DMA時,設備驅動程式必須額外小心。首先,對於DMA控制器而言,沒有虛擬
記憶體的概念,它所面對的,存取的是系統中的實體記憶體。因此被DMA的記憶體必須是一
塊連續的實體記憶體塊。這意味著你不能通過DMA去“直接”存取程序的虛擬空間位址。
當然一個方法是在DMA期間,可以鎖住一個程序的一些實體頁面,防止作業系統將其
對換到swap空間上,從而保証DMA正確地完成。
DMA通道是“短缺”資源,只有7個通道。而且通道不能被設備驅動程式間共享。就
像中斷一樣,一個設備驅動程式必須能夠知道哪一個DMA通道它要使用。有些設備使
用固定的中斷號,就像有些設備使用固定的中斷號一樣。例如,軟碟設備使用的DMA通
道一直是通道2。有時一個設備的DMA通道可以由跳線來設置。許多以太(Ethernet)設
備使用這種技術。一些更靈活的設備可以通過其CSR得知目前系統中哪些DMA通道是
空著的。從而設備驅動程式可以隨便挑選一個DMA通道使用。
Linux通過一個向量資料結構dma_chan(每一個DMA通道對應一個這樣的資料結構)來
掌握DMA通道的使用情況。dma_chan結構中只包含兩個域:一個指向一個字元串的指
針,這個指標描述了這個DMA通道擁有者。另外一個域是一個標誌,用來顯示目前的
DMA通道是空著的還是已被占據。當你使用命令"cat /proc/dma"時,其實是核心中
的向量dma_chan被打印出來了。
8.3 記憶體
在使用記憶體時,設備驅動程式要小心,因為它們是核心的一部份,故不能使用虛擬
記憶體(譯者注:作者在上一節和這裡反復強調“虛擬記憶體”是因為運行在不同“虛擬
記憶體”空間中的使用者模式程序之間不會發生衝突。作業系統的記憶體管理機制將負責。
)。每一次設備驅動程式因為來了中斷,或者bottom half或任務隊列中的handler被調
度到而運行,目前的程序有可能被剝奪。所以設備驅動程式不能依賴於一個特殊的
運行的程序,雖然設備驅動程式運行在一個程序的上下文上。像核心中的其他部份
一樣,設備驅動程式使用資料結構來管理跟蹤它所控制的設備。這些資料結構可以
靜態地分配,作為設備驅動程式程式碼的(資料的)一部份,但這樣會使得核心變的太
大,造成資源的浪費。大多數設備驅動程式採用從核心中動態分配非頁面的記憶體用
來存儲資料。
Linux提供核心記憶體分配和釋放的例程以供設備驅動程式使用。核心記憶體的分配是以
2的冪次方為單位的。例如,128位元或512位元即使設備驅動程式需要的記憶體量少於
這些值。設備驅動程式申請的(被分配的)位元數被“湊”到下一個塊的邊界處。這
種方法使得記憶體的釋放回收更容易因為系統可以將這些小的空閒塊合並成更大的內
存塊。(譯者注:以2的冪次方為單位進行記憶體分配可以減少系統中記憶體被弄的零碎。
)
當核心記憶體被申請時,Linux有可能要作許多額外的工作。如果剩餘的記憶體太下的話,
一些實體頁面需要被丟棄或寫進對換磁碟空間。通常地,Linux將這個處理暫停並放
到一個等待隊列中直到系統中有足夠的實體記憶體。當然不是所有的設備驅動程式(至
少Linux核心程式碼)都希望這樣被處理。所以當不能立刻分配記憶體時,核心記憶體分配
例程可以直接返回一個“失敗”。如果設備驅動程式希望用DMA與被分配的記憶體來交
換資料,它可以指定這片記憶體是DMA'able的。這種情況下Linux核心需要了解系統中
什麼地方構成了DMA'able的記憶體。
8.4 設備驅動程式與核心的介面
Linux核心必須能夠通過一些標準的方法來和設備驅動程式介面。每一類設備驅動程
序,(字元,區塊和網路)都提供一個一致的,共同的介面給核心以用來核心向它們申
請服務。這些共同介面(common interfaces)意味著核心可以將這些不同的設備和其
驅動程式一樣來對待。例如,SCSI和IDE磁碟的行為是不同的。但Linux核心對它們
使用一個同樣的介面進行操作。
Linux是非常動態的,可重構的。每次一個Linux核心啟動時,可能遇到不同的實體
設備,因此需要不同的相應的設備驅動程式。在核心重新構建(Build)的時候,Linux允
許通過配置檔案將設備驅動程式帶進核心。當這些驅動程式在機器啟動時初始化的
時候,有可能系統中並沒不存在相應的實體設備。有些驅動程式可以在需要時被裝
載進入核心。為了處理設備驅動程式的這種動態特性,系統要求設備驅動程式在初
始化時向系統進行登記。Linux核心負責維護一些含有登記了的設備驅動程式的表。
這些表中包含了一些例程(rountines)的指標和其他一些信息以用來支援核心與那些
設備的介面。
8.4.1 字元設備
圖8.1 字元設備
字元設備,Linux中最簡單的設備,是通過”檔案“的形式被存取。應用程式使用標
准的系統調用“打開”,“讀”,“寫”,和“關閉”字元設備就像它是一個檔案
一樣,即使這個設備是一個被PPP監控程式(Daemon)用來將Linux系統連接上網的Modem。
當一個字元設備初始化時,它的設備驅動程式在Linux核心中登記,通過添加一個入
口項(Entry)在含有device_struct資料結構的chrdevs向量中。這個設備的主設備號
(例如,4對於tty設備)被用來作為其在這個向量的索引。一個設備的主索引號是固
定的。
chrdevs向量的每一個入口項是一個device_struct資料結構,含有兩個元素。一個
指向那個登記”在這個入口處“的設備驅動程式名字的指標﹔一個指向一系列檔案
操作函數位址的指標。這些檔案操作函數位於這個字元設備的驅動程式裡並負責處
理相應的具體的檔案操作如:打開,讀,寫和關閉。檔案/proc/devices中對於字元
設備的內容是從chrdevs向量獲取的。
當一個代表一個字元設備的字元特殊檔案被打開時(例如/dev/cua0),系統必須正確
地工作保証相應的字元設備驅動程式的檔案操作例程被調用。就像一個普通檔案或
目錄一樣,每一個設備特殊檔案對應一個VFS inode。這個VFS inode(資料結構)中
含有這個設備的主和次設備號。VFS inode是當一個特殊設備檔案名被查詢時,由文
件系統所創建。
每一個VFS inode與一套檔案操作相聯繫。每當一個代表字元特殊檔案的VFS inode被
創建時,對應這個 VFS inode的檔案操作被設置成預設的字元設備操作。
當一個字元特殊檔案被一個應用程式打開時,這個“open"操作將使用這個設備的主
設備號作為chrdevs向量的索引來尋找對應這個設備的檔案操作集的(例程的)位址。
並且還要設置一個描述這個字元特殊檔案的資料結構--file,使得file結構中關於
檔案操作的指標指向設備驅動程式中相應的部份。經過這些之後,所有用戶層的文
件操作將被映射到對於這個字元設備的設備驅動程式提供的檔案操作。
8.4.2 區塊設備
區塊設備同樣支援以檔案的形式被存取。當遇到打開區塊設備操作時,用來提供一套對
應的檔案操作的機制與字元設備基本上是一樣的。Linux在blkdevs向量中維護登記
了的區塊設備。與chrdevs向量一樣,blkdevs向量使用設備的主設備號作為其索引。
向量的每一個入口仍是一個device_struct資料結構。與字元設備不同的是,這些數
據結構是屬於區塊設備的。SCSI設備和IDE設備是其中兩個例子。這些設備資料結構在
核心中登記並為核心提供對應於其設備的檔案操作。對應於某類設備的設備驅動程
序提供實現這些介面的細節。例如,一個SCSI設備驅動程式必須為SCSI子系統提供
介面。SCSI子系統利用這些介面,提供給核心一個一致的檔案介面。
除了檔案操作介面,每個區塊設備還必須提供緩衝區介面。每一個區塊設備驅動程式在
一個blk_dev向量中添加其入口。blk_dev向量的每個元素是一個blk_dev_struct數
據結構。向量的索引仍然是設備的主設備號。blk_dev_struct資料結構中含有一個
請求例程的位址和一個指向“request"資料結構的指標。每一個“request"資料結
構代表了一個從緩衝區到驅動程式的讀或寫資料塊的請求。
圖8.2 區塊設備的緩衝
每次一個緩衝區想要讀或寫一塊資料從/到一個登記了的設備,它將插入一個"request"數
據結構在blk_dev_struct中。由圖8.2所示,每一個申請含
有一個指向一個或多個”buffer_head"的資料結構。每一個buffer_head是讀或寫一個塊數
據的請求(譯者注:請參閱Linux資料結構章節)。Buffer_head結構是被緩衝區鎖住的。因此
有可能存在一個程序正在等待對這個緩衝區操作的完成。每一個“request"資料結構是從一
個靜態的鏈結串列中(all_requests)分配而來。如果一個請求(request)被加在一個空的請求隊列
上,設備驅動程式的請求函數(譯者注:blk_dev_struc結構中函數
指標所指向的函數)將被立即調用來處理這個請求隊列。否則,驅動程式將順序地處
理請求隊列中的所有請求。
一旦設備驅動程式完成一個請求,它必須從這個請求中移去每一個buffer_head結構,
將它們標誌成為更新並釋放對其的鎖。對一個buffer_head鎖的釋放將喚醒所有在睡
眠中等待這個塊操作完成的程序。一個例子是:當要解釋一個檔案名時,EXT2檔案
系統必須從區塊設備中讀取下一個EXT2目錄項。這個程序將睡眠在那個含有目錄項的
buffer_head上直到被設備驅動程式喚醒。這個request資料結構將被回收從而可以
被其他的塊請求使用。
8.5 硬碟
磁碟將資料保存在磁碟片上,提供一種持久的存儲方式。為了寫資料,一個很小的
磁頭在磁碟片的表面上磁化小微粒(minute particles)。資料也通過磁頭來讀寫。
磁頭能檢測一個小微粒是否被磁化。
一個磁碟有一個或多個磁碟片(platters)組成。每個磁碟片的表面分成一些小的同
心圓---磁軌(track)。磁軌0是最外層的磁軌,最大編號的磁軌最靠近圓心。一個柱
面(cylinder)是指一個有同樣編號的磁軌集合。因此所有磁片上的所有磁軌5構成了
柱面5。因為柱面的數目等於磁軌德數目,我們經常看見人們使用柱面來描述磁碟。
每一個磁軌分為一些sector(sectors)。一個sector是一個硬碟讀或寫的最小單位。一個
sector的大小就是一個區塊的大小(譯者注:換句話說,磁碟的讀寫是以區塊為單位的)。
通常一個sector的大小是512位元。一個sector的大小通常是在磁碟格式化的時候就被確
定了。
一個磁碟通常用其幾何參數來描述,柱面的數目,磁頭的數目和sector的數目。例如,
在啟動時,Linux描述一個IDE磁碟:
hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63
上述意味著這個磁碟含有1050個柱面,16個磁頭(8個磁片)和63個sector/每個磁軌。
如果每個sector(或每個塊)大小是512位元,這個磁碟的大小是529200位元。這個大小
與系統聲稱的516M大小不一致。這是因為磁碟的一些sector已被用來存放磁碟分區信
息。例外,一些磁碟可以自動地發現壞sector並重心索引磁碟以繞過這些壞sector。
硬碟可以更深一步地分為一些分區(partitions)。一個分區是一個用來作某個特殊
用途的sector的集合。將一個磁碟分區允許這個磁碟被幾個作業系統使用,或允許用
來作不同的用途。許多Linux系統只有一個磁碟,但分為3個分區。一個含有DOS檔案
系統﹔一個含有EXT2檔案系統﹔第3個是對換分區(譯者注:用於虛擬記憶體管理系統
)。一個硬碟的分區由一個分區表來描述。分區表中的每一個條目(entry)通過磁頭,
sector和柱面,描述了這個分區的起始和結束位址。fdisk支援3類分區類型。主分區,
擴展分區和邏輯分區。擴展分區不是一個真正的分區,可以含有任意數目的邏輯分
區。擴展和邏輯分區的發明是用來繞過系統中只允許4個主分區的限制。下面是用fdisk對
一個含有2個主分區的磁碟分區的信息:
Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Units = cylinders of 2048 * 512 bytes
Device Boot Begin Start End Blocks Id System
/dev/sda1 1 1 478 489456 83 Linux native
/dev/sda2 479 479 510 32768 82 Linux swap
Expert command (m for help): p
Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID
1 00 1 1 0 63 32 477 32 978912 83
2 00 0 1 478 63 32 509 978944 65536 82
3 00 0 0 0 0 0 0 0 0 00
4 00 0 0 0 0 0 0 0 0 00
上面所示,第一個分區起始於柱面或磁軌0,磁頭1和sector1,共延伸到477柱面,扇
區32和磁頭63。因為一個磁軌有32個sector,整個磁碟有64個讀/寫磁頭,所以這個分
區占據的柱面是完整的。確省地,fdisk自動將分區依照柱面的邊界對齊。它起始於
最外層的柱面(0)並向裡面延伸478個柱面。第二個分區是對換分區,起始於下一個
柱面(478)並一直延伸到最裡面的那個柱面。
圖8.3 磁碟的鏈接表
在系統初始化期間,Linux將映射系統中所有硬碟的拓樸。它發現有多少硬碟和其類
型。另外,Linux發現這些磁碟是如何分區的。這一切將體現在由指標gendisk_head
指向的,一個元素為gendisk資料結構的鏈結串列中。當每個磁碟子系統初始化時,例如
IDE,它負責產生代表它所發現的磁碟的gendisk資料結構。這個行為發生在與登記
它的檔案操作介面,插入一個入口項在blk_dev資料結構中的同一時間。每一個gendisk數
據結構有一個唯一的主設備號。這個主設備號與該區塊設備的主設備號一致。例如,
SCSI磁碟子系統產生一個單一的gendisk記錄”sd“。記錄中,其主設備號是8。系
統中所有的SCSI磁碟設備都擁有這個同樣的主設備號。圖8.3所示是兩個gendisk記錄,
第一個是為SCSI磁碟子系統,第二個是為IDE磁碟控制器,ide0, 主磁碟控制器。
雖然磁碟子系統在初始化時創建gendisk記錄,這些記錄只在Linux進行分區檢測時
使用。然而,每個磁碟子系統維護一個自己的資料結構,以用來映射設備的主設備
號和次設備號到實體磁碟的分區中。任何時刻當一個區塊設備被讀或寫,不管是來自
緩衝區還是檔案操作,核心將使用在區塊設備特殊檔案中發現的主設備號(例如,/dev/sda2)
,引導讀或寫操作指向正確的設備。值得注意的是各個設備驅動程式負責映射次設
備號到具體的實體設備中。
8.5.1 IDE 磁碟
在Linux系統中,最常見的是IDE(Integrated Disk Electronic)磁碟。IDE是一個磁
碟介面,而不是一個I/O匯流排,例如像SCSI。每個IDE控制器可以支援多達2個磁碟。
一個主(master)磁碟﹔一個副(slave)磁碟。主和副磁碟是通過設置磁碟的跳線來完
成的。系統中的第一個IDE控制器叫做主IDE控制器。依此類推,下一個叫做第二IDE控
制器。IDE介面可以達到3.3M的傳輸速率。IDE磁碟容量最大是538M位元。擴展IDE(EIDE)
可以達到8.6G位元和16.6M位元的傳輸速率每秒。IDE和EIDE磁碟比SCSI磁碟要便宜。
大多數PC機都有一個或多個IDE控制器。
Linux依據發現IDE控制器的順序對其上的IDE磁碟命名。在主控制器上的主磁碟是/dev/hda,
副磁碟是/dev/hdb。/dev/hdc是第二個IDE控制器 上的主磁碟。IDE子系統在核心中
登記IDE主控制器,而不是磁碟。主控制器的主設備號是3﹔副IDE控制器
的主設備號是22。這意味著如果系統有兩個IDE控制器,在向量blk_dev和blkdevs中
將插入兩個IDE子系統記錄在向量索引3和22的地方。從設備特殊檔案名中可以體現
這點。在主IDE控制器上的磁碟/dev/hda 和/dev/hdb的主設備號是3。任何對這兩個
設備特殊檔案的操作都會被核心根據被存取的主設備號傳到其對應的IDE子系統中。
IDE子系統將負責是哪一個IDE磁碟被申請,通過設備特殊檔案的次設備號。次設備
號裡含有信息關於哪一個分區和哪一個磁碟。/dev/hdb的設備標識是(3,64)。該磁
盤上的第一個分區(/dev/hdb1)的設備標識是(3,65)。
8.5.2 IDE子系統的初始化
IDE磁碟一直貫穿在IBM PC機的歷史。在這個期間,IDE介面發生了許多變化。這使
得IDE子系統的初始化變得越來越復雜。
Linux最多可以支援4個IDE控制器。每個控制器將體現在向量ide_hwifs的ide_hwif_t數
據結構中。每個ide_hwif_t中含有兩個ide_drive_t資料結構,對應於可能的主和副
IDE磁碟機。IDE子系統初始化期間,Linux首先通過系統的CMOS中信息查看是否有磁
盤。CMOS的位置由系統的BIOS設定並可以告訴Linux什麼IDE控制器和磁碟磁碟機在
系統中。Linux從BIOS中得到磁碟的幾何描述信息並為這些磁碟機設立ide_hwif_t
資料結構。目前越來越多的PC機使用包含了PCI EIDE控制器的PCI晶片集(chipsets)(例
如,Intel's 82430 VX )。IDE子系統使用PCI BIOS的回調(callback)來定位系統中
的PCI E(IDE)控制器,然後調用PCI專門的為這些控制器準備的例程(來初始化核心
的向量結構)。
一旦每個IDE介面和控制器被發現,相應的ide_hwif_t資料結構將被建立以反映這些
控制器和其上的磁碟。在運行期間,IDE驅動程式向I/O空間的IDE命令暫存器寫入命
令。主IDE控制器的控制和狀態暫存器的預設I/O位址在0x1F0 - 0x1F7。IDE驅動程
序在Linux的塊緩衝資料結構中登記每個控制器,在blk_dev和blkdevs分別加入記錄。
IDE磁碟機還將申請占有某個中斷。對於主IDE控制器,約定的中斷號是14﹔第二個
IDE控制器是15。然而,上述都可以別核心的命令選項所覆蓋。IDE驅動
程式還要為每個IDE控制器在gendisk鏈中加入一個gendisk記錄。這個鏈結串列用來尋找
所有在啟動時發現的磁碟的分區表信息。
8.5.3 SCSI磁碟
SCSI(Small Computer System Interface)匯流排是一個快速的端到端的資料匯流排。每
個SCSI匯流排上支援8個設備,其中包含一或多個hosts。每個SCSI設備必須有一個唯
一的標識名(一般通過磁碟的跳線來設置)。匯流排上的兩個設備可以同步地或非同步地
32位地交換資料。速率可達40M位元。SCSI匯流排可在設備之間資料和狀態信息,並且
在發起者(initiator)和目標(target )之間,一個單一的事務(transaction)可以包
含多達8個不同狀態的信息。我們可以從來自匯流排上的5個信號來辨別SCSI匯流排上目
前的狀態(phase )。
BUS FREE (匯流排空)
目前沒有設備正占據匯流排。沒有活躍的事務處理。
ARBITRATION (仲裁)
一個SCSI設備試圖占據匯流排。它通過將其SCSI標識資料“插入”位址線。(如有競
爭,)SCSI標識最大的獲得匯流排。
SELECTION (選擇)
當一個設備通過仲裁,成功地獲得匯流排後,它必須向目標(Target)發出信號表示它
想要對目標發出命令。這是通過將目標的SCSI標識放入位址線而達到的。
RESELECTION
SCSI設備有可能斷開連接在一個請求處理過程中。目標有可能等會兒重新選擇發起
者(initiator)。不是所有的SCSI設備都支援這個狀態。
COMMAND
在此狀態下,可以從發起者傳到目標端傳送6,10 或 12 位元的命令。
DATA IN, DATA OUT
在這些狀態下,資料在發起者和目標端之間傳送。
STATUS
當所有命令都完成時,可以進入這個狀態。允許目標發出一個狀態位元以向發起者
表明成功或失敗。
MESSAGE IN, MESSAGE OUT
附加的信息將在發起端和目標端傳送。
Linux的SCSI子系統有兩個基本元素組成。每一個都通過一些資料結構在核心中得到
體現。
host
一個SCSI host是一個實體硬體,即一個SCSI控制器。NCR810 PCI SCSI控制器就是
一個SCSI host的例子。如果一個Linux系統存在多於一個的,同樣類型的SCSI控制
器,每一個將對應於不同的SCSI host。這意味著一個SCSI設備驅動程式可能控制多
於一個的SCSI控制器。SCSI host一般都是SCSI命令的發起者。
device(設備)
最常見的SCSI設備就是SCSI磁碟。但SCSI標準還支援其他的幾類設備,如磁帶,CD-ROM和
通用(generic)SCSI設備。SCSI設備一般都是SCSI命令的目標。這些設備必須不同的
對待。例如,可移動的介質CD-ROMs或磁帶,Linux需要檢測介質是否被移動了。不同
的設備類型有不同的主設備號,Linux可以依此引導不同的對區塊設備的請求到其相應
的SCSI設備類型上。
SCSI子系統的初始化
由於SCSI匯流排和設備的動態特性,SCSI子系統的初始化比較復雜。Linux在系統啟動
時初始化SCSI子系統。它首先發現系統中的SCSI控制器(即SCSI hosts)然後探測在
所有SCSI匯流排上的所有的設備。然後初始化這些設備。通過向核心提供一套規範的
檔案操作和緩衝區操作例程集,使得對於Linux核心系統來說“可見”。這個初始化
的過程分為4個階段:
首先,Linux檢測在核心構建時已加入核心的那些SCSI hosts或控制器上是否有設備
需要控制。上面每個SCSI hosts在builtin_scsi_hosts 向量中有一個入口記錄相對
應。每個記錄是一個Scsi_Host_Template 資料結構。Scsi_Host_Template 中含有
一些函數指標。這些函數用來執行SCSI hosts的一些特定功能,如檢測什麼設備正
掛在SCSI hosts上。這些例程SCSI子系統所調用,屬於這種hosts設備類型的設備驅
動程式的一部份。每個在其上存在SCSI設備的SCSI host將Scsi_Host_Template 數
據結構加入一個Scsi_Host結構到一個scsi_hostslist列表 鏈中。例如,如果一個
系統有兩個NCR810 PCI SCSI 控制器,系統資料結構中將有兩個Scsi_Host記錄在
scsi_hostslist列表鏈中。每一個Scsi_Host指向代表起設備驅動程式的
Scsi_Host_Template。
圖8.4 SCSI 資料結構
到現在,系統中所有的SCSI host都已備發現,SCSI子系統必須知道在每個host上是
些什麼SCSI設備。SCSI設備是按照0-7來標號的。每個SCSI設備的標號在其所安裝的
host上是唯一的。SCSI標號通常是由設備上的跳線來設置的。SCSI初始化程式碼通過
發出TEST_UNIT_READY命令來探測一個SCSI匯流排上的SCSI設備。當一個設備存在並回
答時,它的SCSI標識信息(包括廠商,設備型號和版本號)被讀取,通過一個ENQUIRY
命令。SCSI命令含在一個Scsi_Cmnd資料結構中。這些Scsi_Cmnd資料被傳遞到屬於
這個SCSI host的設備驅動程式的相關函數中。這些函數的指標在先前已被登記在
Scsi_Host_Template結構中。每個已發現的SCSI設備將對應一個Scsi_Device資料結構.
每個 Scsi_Device資料結構指向其host的資料結構Scsi_Host。所有的Scsi_Device結構
鏈在一個叫做scsi_devices的鏈結串列上(譯者注:Scsi_Device:SCSI設備﹔Scsi_Host:
SCSI host或控制器﹔Scsi_Host_Template:SCSI host的設備驅動程式入口)。圖8.4所
示是上述資料結構的關系。
SCSI設備有四種:磁碟,磁帶,CD和generic。每一種都分別在核心中以不同的主塊
設備號進行登記。當然,只有在發現系統中存在相關的SCSI設備時才會進行登記。
每個SCSI類型,例如SCSI磁碟,維護一套其自己的表資料結構。這些表用來將來自
核心的塊操作請求映射到相應的設備驅動程式上或相應的SCSI host上。每一個SCSI類
型在核心中對應於一個Scsi_Device_Template 資料結構。這個結構中含有這種設備
的信息和各種針對這種設備的例程位址。SCSI子系統使用這些模板來調用對應於每
種SCSI設備的SCSI類型函數。換句話說,如果SCSI子系統想要”attach“(添加)一
個SCSI磁碟,它將調用SCSI磁碟類型的”attach“函數。Scsi_Device_Template 數
據結構全部掛在scsi_devicelist鏈結串列上。(譯者注:請注意 Scsi_Device_Template與
Scsi_Host_Template 的關系和區別)
SCSI子系統初始化的最後一步是對應於每一個登記了的Scsi_Device_Template調用”
完成“(finish)函數。對應於SCSI磁碟類型,這意味著旋轉機器上所有的SCSI磁碟
然後讀取它們的幾何參數。(根據獲得的幾何參數),核心填寫為每個SCSI磁碟填寫
gendisk資料結構在gendisk鏈中。
傳送區塊設備請求
一旦Linux 完成SCSI子系統的初始化,SCSI設備就可以被使用了。每個存在相應設
備的設備類型在核心進行了登記,從而Linux可以正確地將區塊設備請求定位/傳送到
正確的設備上。這些請求可以是來自blk_dev 的緩衝區操作或來自blkdevs的檔案操
作。舉一個例子,如果有一個SCSI磁碟含有一個或兩個EXT2檔案系統分區,當其中
一個EXT2檔案系統已被安裝(mounted),核心的緩衝區申請如何被定位到正確的磁碟
上?
每個讀或寫一塊SCSI磁碟資料的請求都導致在blk_dev向量中的current_request鏈
表中加入一個新的request 結構。如果這個申請隊列正在被處理,緩衝區不需要做
其他的事情。否則,必須提醒SCSI磁碟子系統去處理request 隊列。系統中每個SCSI磁
盤對應於一個Scsi_Disk 資料結構在rscsi_disks 向量中。rscsi_disks的索引使用
了部份SCSI磁碟分區的次設備號信息。例如,/dev/sdb1的主設備號是8,次設備號
是17,其在rscsi_disks中的索引號是1。 每個Scsi_Disk結構中含有一個指標指向
代表這個設備的Scsi_Device 結構。然後通過Scsi_Device 指向對應的Scsi_Host結
構(譯者注:對應於這個SCSI磁碟所屬的SCSI磁碟控制器)。 從緩衝區來的request結
構被轉換成描述SCSI命令的Scsi_Cmd 結構並將其放入這個對應的Scsi_Host的隊列
中。當一旦要求的塊資料被讀或寫完成之後,SCSI設備驅動程式將會處理這些Scsi_Cmd
結構。
8.6 網路設備
從Linux的網路子系統的角度而言,一個網路設備是用來發送和接收資料的一個”實
體“或一個”東西“,比如一個ethernet網路卡。每個網路設備在核心中對應於一個
device 資料結構。核心啟動時,網路設備驅動程式初始化並登記其控制的設備。這
個device 結構中包含了關於這個設備的信息和一些函數的位址。這些函數被用來對
各種高層的網路通訊協定提供底層支援。它們大多數是關於在實體網路設備上傳輸資料。
網路設備使用標準的網路機制將接收到的資料向高層網路通訊協定傳送。所有傳送的和
接收的資料報(packets)都對應於sk_buff資料結構。sk_buff是非常靈活的資料結構,
允許網路通訊協定頭(network protocol headers)很輕松地被加入和移去。網路通訊協定層
如何使用網路設備,如何使用sk_buff來回傳遞資料,請參閱第10章網路。本章關於
網路方面的重點是網路設備資料結構和網路設備如何被檢錯與初始化。
device 資料結構含有網路設備的如下信息:
Name
與用mknod 命令來創建設備特殊檔案的塊和字元設備不同的是,網路設備特殊檔案
是當系統網路設備被發現並初始化時出現的。它們的名字是標準的。每一個名字代表
了它是哪一種網路設備類型。屬於同一類型的設備的名字從數字0開始往上走。因此
ethernet設備名是/dev/eth0,/dev/eth1,/dev/eth2等等 。下面是一些通用的網路
設備名:
/dev/ethN Ethernet設備
/dev/slN SLIP設備
/dev/pppN PPP設備
/dev/lo Loopback設備
Bus Information
這個信息被設備驅動程式用來控制設備。irq 資料是這個設備使用的中斷。
base address 是設備的控制和狀態暫存器在I/O空間的位址。DMA channel 是這個網
絡設備用的DMA通道。所有的上述信息在設備初始化時被設置。
Interface Flags
用來描述網路設備的特性和能力:
IFF_UP (網路)介面在運行,
IFF_BORADCAST device中的廣播位址是有效的,
IFF_DEBUG 設備的調試功能已被打開,
IFF_LOOPBACK 目前設備是一個loopback設備,
IFF_POINTTOPOINT 這是個點到點的連接(SLP和PPP),
IFF_NOTRAILERS 沒有網路跟蹤(No network trailers),
IFF_RUNNING 分配的資源,
IFF_NOARY 不支援ARP通訊協定,
IFF_PROMISC 設備處在混雜接收模式,將接收任何網上資料包,
IFF_ALLMULTI 接收所有的IP多點廣播(multicast)資料frame,
IFF_MULTICAST 能夠接收IP多點廣播(multicast)資料frame。
Protocol Information
通過這些信息,設備描述自己將如何被網路通訊協定層所使用。
mtu 該設備能傳輸的最大封包大小(不包括所需要的封包頭)。這個最大值
通訊協定層被用來,例如IP,選擇適當的發送封包的大小。
Family 顯示該設備可以支援的通訊協定族。所有Linux網路設備支援的通訊協定族是
AF_INET, Internet 位址族。
Type 這個硬體介面類型描述該網路設備正與什麼介質相連。在Linux網路
設備中,可以支援很多種不同的設備,包括Ethernet, X.25, Token Ring,
Slip, PPP 和 Apple Localtalk。
Address
device資料結構含有許多與該設備相關的位址,例如:IP位址。
Packet Queue
sk_buff 封包的隊列。等待在這個網路設備上傳輸。
Support Functions
每個設備提供一套標準的例程作為該設備連接層介面一部份。
從而通訊協定層可以進行調用。這些例程包括:設置和frame傳輸例程﹔
添加標準封包frame頭和收集統計信息的例程。這些統計信息可以通過
ifconfig命令來查看。
8.6.1 網路設備的初始化
與其他Linux設備驅動程式一樣,網路設備驅動程式也可以被預先構造在核心中。
每一個潛在的網路設備都對應於一個device資料結構。這些結構組成一個由dev_base
指向的鏈結串列。如果網路層需要網路設備完成一個特定的任務,它調用 一個位址已在
device結構中的網路設備服務例程。在最開始,device結構中只含有初始化(initialization)
和探測(probe)函數的位址。
網路設備驅動程式需要解決兩個問題。首先,不是所有的已構建在核心中的網路設
備驅動程式都有相應的週邊存在。第二,不管什麼樣的設備驅動程式,系統中的ethernet設
備名始終是/dev/eth0, /dev/eth1等等。網路設備不存在的問題很容易解決。因為
當為每個網路設備初始化的例程被調用時,其函數返回值將顯示實體設備是否存在。
如果不存在,這個驅動程式對應的device資料結構將從被dev_base指向的鏈結串列中被
移去。如果驅動程式確實發現一個設備,device結構的剩餘部份將被填充。這些包
括設備信息和設備驅動程式中的支援函數的位址。
第二個問題是關於如何動態地將標準的/dev/ethN設備特殊檔案名賦值給系統中的ethernet
設備。 在device鏈中,有8個標準記錄。從eth0, eth1 到 eth7。它們的初始化都是一
樣的:依次檢測是否每個ethernet設備驅動程式有相應的實體設備存在直到發現一個。
當找到一個ethernet設備時,驅動程式填充其目前占據的ethN device資料結構。與
此同時,網路設備驅動程式初始化剛剛找到的實體設備,找出該實體設備想要占據
的IRQ號,DMA通道等等。一個驅動程式有可能檢測到幾個它所控制的網路設備 ,在
這種情況下,驅動程式將占據幾個/dev/ethN device 資料結構。當所有的8個標準
的/dev/ethN都被分配光之後,系統將不檢測其他的ethernet 設備。