Linux Kernel 核心中文手冊(8)--設備驅動程序

Device Drivers (設備驅動程序)
    操作系統其中一個目的就是向用戶掩蓋系統硬件設備的特殊性。例如,虛擬文件系
統呈現了安裝的文件系統的一個統一的試圖,而和底層的物理設備無關。本章描述 Lin
ux 核心是如何管理系統中的物理設備的。
    CPU 不是系統中唯一的智能設備,每一個物理設備都由它自己的硬件控制器。鍵盤
、鼠標和串行口由 SuperIO 芯片控制, IDE 磁盤由 IDE 控制器控制, SCSI 磁盤由
SCSI 控制器控制,等等。每一個硬件控制器都由自己的控制和狀態控制器( CSR ),
不同的設備之間是不同的。一個 Adaptec 2940 SCSI 控制器的 CSR 和 NCR 810 SCSI
控制器的完全不同。 CSR 用於啓動和停止設備,初始化設備和診斷它的問題。管理這些
硬件控制器的代碼不是放在每一個應用程序裏邊,而是放在 Linux 核心。這些處理或者
管理硬件控制器的軟件腳做設備驅動程序。 Linux 核心的設備驅動程序本質上是特權的
、駐留內存的低級的硬件控制例程的共享庫。是 Linux 的設備驅動程序在處理它們管理


發信人: sujm (小白蛇), 信區: KernelTech
標  題: Linux Kernel 核心中文手冊(8)--設備驅動程序
發信站: BBS 水木清華站 (Fri Oct  5 16:32:14 2001)
 
 
太平洋軟件諮詢, 在這裏你將找到你想要的東西。來看看吧,每天都有大量更新,不會
讓朋友們失望的。
Linux Kernel核心中文手冊
來自:藍森林自由軟件
Chapter 8
Device Drivers (設備驅動程序)
    操作系統其中一個目的就是向用戶掩蓋系統硬件設備的特殊性。例如,虛擬文件系
統呈現了安裝的文件系統的一個統一的試圖,而和底層的物理設備無關。本章描述 Lin
ux 核心是如何管理系統中的物理設備的。
    CPU 不是系統中唯一的智能設備,每一個物理設備都由它自己的硬件控制器。鍵盤
、鼠標和串行口由 SuperIO 芯片控制, IDE 磁盤由 IDE 控制器控制, SCSI 磁盤由
SCSI 控制器控制,等等。每一個硬件控制器都由自己的控制和狀態控制器( CSR ),
不同的設備之間是不同的。一個 Adaptec 2940 SCSI 控制器的 CSR 和 NCR 810 SCSI
控制器的完全不同。 CSR 用於啓動和停止設備,初始化設備和診斷它的問題。管理這些
硬件控制器的代碼不是放在每一個應用程序裏邊,而是放在 Linux 核心。這些處理或者
管理硬件控制器的軟件腳做設備驅動程序。 Linux 核心的設備驅動程序本質上是特權的
、駐留內存的低級的硬件控制例程的共享庫。是 Linux 的設備驅動程序在處理它們管理


的設備的特質。
    UNIX 的一個基本特點是它抽象了設備的處理。所有的硬件設備都象常規文件一樣看
待:它們可以使用和操作文件相同的、標準的系統調用來進行打開、關閉和讀寫。系統
中的每一個設備都用一個設備特殊文件代表。例如系統中第一個 IDE 硬盤用 /dev/had
 表示。對於塊(磁盤)和字符設備,這些設備特殊文件用 mknod 命令創建,並使用主
( major )和次( minor )設備編號來描述設備。網絡設備也用設備特殊文件表達,
但是它們由 Linux 在找到並初始化系統中的網絡控制器的時候創建。同一個設備驅動程
序控制的所有設備都由一個共同的 major 設備編號。次設備編號用於在不同的設備和它
們的控制器之間進行區分。例如,主 IDE 磁盤的不同分區都由一個不同的次設備編號。
所以, /dev/hda2 ,主 IDE 磁盤的第 2 個分區的主設備號是 3 ,而次設備號是 2 。
 Linux 使用主設備號表和一些系統表(例如字符設備表 chrdevs )把系統調用中傳遞
的設備特殊文件(比如在一個塊設備上安裝一個文件系統)映射到這個設備的設備驅動
程序中。
參見 fs/devices.c
    Linux 支持三類的硬件設備:字符、塊和網絡。字符設備直接讀寫,沒有緩衝區,
例如系統的串行端口 /dev/cua0 和 /dev/cua1 。塊設備只能按照一個塊(一般是 512
 字節或者 1024 字節)的倍數進行讀寫。塊設備通過 buffer cache 訪問,可以隨機存
取,就是說,任何塊都可以讀寫而不必考慮它在設備的什麼地方。塊設備可以通過它們
的設備特殊文件訪問,但是更常見的是通過文件系統進行訪問。只有一個塊設備可以支
持一個安裝的文件系統。網絡設備通過 BSD socket 接口訪問,網絡子系統在網絡章(
第 10 章)描述。
Linux 有許多不同的設備驅動程序(這也是 Linux 的力量之一)但是它們都具有一些一


般的屬性:
    Kernel code 設備驅動程序和核心中的其他代碼相似,是 kenel 的一部分,如果發
生錯誤,可能嚴重損害系統。一個寫錯的驅動程序甚至可能摧毀系統,可能破壞文件系
統,丟失數據。
Kenel interfaces 設備驅動程序必須向 Linux 核心或者它所在的子系統提供一個標準
的接口。例如,終端驅動程序向 Linux 核心提供了一個文件 I/O 接口,而 SCSI 設備
驅動程序向 SCSI 子系統提供了 SCSI 設備接口,接着,向核心提供了文件 I/O 和 bu
ffer cache 的接口。
Kernel mechanisms and services 設備驅動程序使用標準的核心服務例如內存分配、中
斷轉發和等待隊列來完成工作
Loadable Linux 大多數的設備驅動程序可以在需要的時候作爲核心模塊加載,在不再需
要的時候卸載。這使得核心對於系統資源非常具有適應性和效率。
Configurable Linux 設備驅動程序可以建立在覈心。哪些設備建立到核心在覈心編譯的
時候是可以配置的。
Dynamic 在系統啓動,每一個設備啓動程序初始化的時候它查找它管理的硬件設備。如
果一個設備驅動程序所控制的設備不存在並沒有關係。這時這個設備驅動程序只是多餘
的,佔用很少的系統內存,而不會產生危害。
8.1 Poling and Interrupts (輪詢和中斷)
    每一次給設備命令的時候,例如“把讀磁頭移到軟盤的第 42 扇區“,設備驅動程
序可以選擇它如何判斷命令是否執行結束。設備驅動程序可以輪詢設備或者使用中斷。
 
    輪詢設備通常意味着不斷讀取它的狀態寄存器,直到設備的狀態改變指示它已經完


成了請求。因爲設備驅動程序是核心的一部分,如果驅動程序一直在輪詢,核心在設備
完成請求之前不能運行其他任何東西,會是損失慘重的。所以輪詢的設備驅動程序使用
一個系統計時器,讓系統在晚些時候調用設備驅動程序中的一個例程。這個定時器例程
會檢查命令的狀態, Linux 的軟盤驅動程序就是這樣工作的。使用計時器進行輪詢是一
種最好的接近,而更加有效的方法是使用中斷。
中斷設備驅動程序在它控制的硬件設備需要服務的時候會發出一個硬件中斷。例如:一
個以太網設備驅動程序會在設備在網絡上接收到一個以太網報文的時候被中斷。 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 體系結構的遺留物。例如軟驅磁盤控制器總是用中斷 6 。其他中斷,例如
 PCI 設備的中斷,在啓動的時候動態分配。這時設備驅動程序必須首先找出它所控制的
設備的中斷號,然後才能請求擁有這個中斷(的處理權)。對於 PCI 中斷, Linux 支
持標準的 PCI BIOS 回調( callback )來確定系統中設備的信息,包括它們的 IRQ 。
 
    一箇中斷本身是如何轉發到 CPU 依賴於體系結構。但是在大多數的體系上,中斷都
用一種特殊的模式傳遞,而停止系統中發生其他中斷。設備驅動程序在它的中斷處理例
程中應該做儘可能少的工作,使得 Linux 核心可以結束中斷並返回到它中斷之前的地方
。收到中斷後需要做大量工作的設備驅動程序可以使用核心的 bottom half handler 或
者任務隊列把例程排在後面,以便在以後調用。
8.2 Direct Memory Access ( DMA )
    當數據量比較少的時候用中斷驅動的設備驅動程序向設備或者通過設備傳輸數據工
作地相當好。例如,一個 9600 波特率的 modem 每一毫秒( 1/1000 秒)大約可以傳輸
一個字符。如果中斷延遲,就是從硬件設備發出中斷到開始調用設備驅動程序中的中斷
處理程序所花的時間比較少(比如 2 毫秒),那麼數據傳輸對系統整體的映像就非常小
。 9600 波特率的 modem 數據傳出只會佔用 0.002% 的 CPU 處理時間。但是對於高速
的設備,比如硬盤控制器或者以太網設備,數據傳輸速率相當高。一個 SCSI 設備每秒
可以傳輸高達 40M 字節的信息。
直接內存存取,或者說 DMA ,就是發明來解決這個問題的。一個 DMA 控制器允許設備
不需要處理器的干預而和系統內存創樹數據。 PC 的 ISA DMA 控制器由 8 個 DMA 通道
,其中 7 個可用於設備驅動程序。每一個 DMA 通道都關聯一個 16 位的地址寄存器和
一個 16 位的計數寄存器( count register )。爲了初始化一次數據傳輸,設備驅動


程序需要建立 DMA 通道的地址和計數寄存器,加上數據傳輸的方向,讀或寫。當傳輸結
束的時候,設備中斷 PC 。這樣,傳輸發生的時候, CPU 可以作其他事情。
    使用 DMA 的時候設備驅動程序必須小心。首先,所有的 DMA 控制器都不瞭解虛擬
內存,它只能訪問系統中的物理內存。因此,需要進行 DMA 傳輸的內存必須是物理內存
中連續的塊。這意味着你不能對於一個進程的虛擬地址空間進行 DMA 訪問。但是你可以
在執行 DMA 操作的時候把進程的物理也鎖定到內存中。第二: DMA 控制器無法訪問全
部的物理內存。 DMA 通道的地址寄存器表示 DMA 地址的首 16 位,跟着的 8 位來自於
頁寄存器( page register )。這意味着 DMA 請求限制在底部的 16M 內存中。
    DMA 通道是稀少的資源,只有 7 個,又不能在設備驅動程序之間共享。象中斷一樣
,設備驅動程序必須有能力發現它可以使用哪一個 DMA 通道。象中斷一樣,一些設備有
固定的 DMA 通道。比如軟驅設備,總是用 DMA 通道 2 。有時,設備的 DMA 通道可以
用跳線設置:一些以太網設備用這種技術。一些更靈活的設備可以告訴它(通過它們的
 CSR )使用哪一個 DMA 通道,這時,設備驅動程序可以簡單地找出一個可用的 DMA 通
道。
    Linux 使用 dma_chan 數據結構向量表(每一個 DMA 通道一個)跟蹤 DMA 通道的
使用。 Dma_chan 數據結構只有兩個玉:一個字符指針,描述這個 DMA 通道的屬主,一
個標誌顯示這個 DMA 通道是否被分配。當你 cat /proc/dma 的時候顯示的就是 dma_c
han 向量表。
8.3 Memory (內存)
    設備驅動程序必須小心使用內存。因爲它們是 Linux 核心的一部分,它們不能使用
虛擬內存。每一次設備驅動程序運行的時候,可能是接收到了中斷或者調度了一個 but
tom half handler 或任務隊列,當前的進程都可能改變。設備驅動程序不能依賴於一個


正在運行的特殊進程。象核心中其他部分一樣,設備驅動程序使用數據結構跟蹤它控制
的設備。這些數據結構可以在設備驅動程序的代碼部分靜態分配,但是這會讓核心不必
要地增大而浪費。多數設備驅動程序分配核心的、不分頁的內存存放它們的數據。
    Linux 核心提供了核心的內存分配和釋放例程,設備驅動程序正是使用了這些例程
。核心內存按照 2 的冪數的塊進行分配。例如 128 或 512 字節,即使設備驅動程序請
求的數量沒有這麼多。設備驅動程序請求的字節數按照下一個塊的大小取整。這使得核
心的內存回收更容易,因爲較小的空閒塊可以組合成更大的塊。
    請求核心內存的時候 Linux 還需要做更多的附加工作。如果空閒內存的總數太少,
物理頁需要廢棄或者寫到交換設備。通常, Linux 會掛起請求者,把這個進程放到一個
等待隊列,直到有了足夠的物理內存。不是所有的設備驅動程序(或者實際是 Linux 的
核心代碼)希望發生這樣的事情,核心內存分配例程可以請求如果不能立刻分配內存就
失敗。如果設備驅動程序希望爲 DMA 訪問分配內存,它也需要指出這塊內存是可以進行
 DMA 的。因爲需要讓 Linux 核心明白系統中哪些是連續的可以進行 DMA 的內存,而不
是讓設備驅動程序決定。
8.4 Interfacing Device Drivers with the Kernel (設備驅動程序和核心接口)
    Linux 核心必須能夠用標準的方式和它們作用。每一類的設備驅動程序:字符、塊
和網絡,都提供了通用的接口供核心在需要請求它們的服務的時候使用。這些通用的接
口意味着核心可以完全相同地看待通常是非常不同的設備和它們的設備驅動程序。例如
, SCSI 和 IDE 磁盤的行爲非常不同,但是 Linux 核心對它們使用相同的接口。
    Linux 非常地動態,每一次 Linux 核心啓動,它都可能遇到不同的物理設備從而需
要不同的設備驅動程序。 Linux 允許你在覈心建立的時間通過配置腳本包含設備驅動程
序。當啓動的時候這些設備驅動程序初始化,它們可能沒發現它們可以控制的任何硬件


。其他驅動程序可以在需要的時候作爲核心模塊加載。爲了處理設備驅動程序的這種動
態的特質,設備驅動程序在它們初始化的時候向核心登記。 Linux 維護已經登記的設備
驅動程序列表,作爲和它們接口的一部分。這些列表包括了例程的指針和支持這一類設
備的接口的信息。
8.4.1 Character Devices (字符設備)
    字符設備, Linux 最簡單的設備,象文件一樣訪問。應用程序使用標準系統調用打
開、讀取、寫和關閉,完全好像這個設備是一個普通文件一樣。甚至連接一個 Linux 系
統上網的 PPP 守護進程使用的 modem ,也是這樣的。當字符設備初始化的時候,它的
設備驅動程序向 Linux 核心登記,在 chrdevs 向量表增加一個 device_struct 數據結
構條目。這個設備的主設備標識符(例如對於 tty 設備是 4 ),用作這個向量表的索
引。一個設備的主設備標識符是固定的。 Chrdevs 向量表中的每一個條目,一個 devi
ce_struct 數據結構,包括兩個元素:一個登記的設備驅動程序的名稱的指針和一個指
向一組文件操作的指針。這塊文件操作本身位於這個設備的字符設備驅動程序中,每一
個都處理特定的文件操作比如打開、讀、寫和關閉。 /proc/devices 中字符設備的內容
來自 chrdevs 向量表
參見 include/linux/major.h
    當代表一個字符設備(例如 /dev/cua0 )的字符特殊文件打開,核心必須做一些事
情,從而去掉用正確的字符設備驅動程序的文件操作例程。和普通文件或目錄一樣,每
一個設備特殊文件都用 VFS I 節點表達。這個字符特殊文件的 VFS inode (實際上所
有的設備特殊文件)都包括設備的 major 和 minor 標識符。這個 VFS I 節點由底層的
文件系統(例如 EXT2 ),在查找這個設備特殊文件的時候根據實際的文件系統創建。
 


參見 fs/ext2/inode.c ext2_read_inode()
    每一個 VFS I 節點都聯繫着一組文件操作,依賴於 I 節點所代表的文件系統對象
不同而不同。不管代表一個字符特殊文件的 VFS I 節點什麼時候創建,它的文件操作被
設置成字符設備的缺省操作。這隻有一種文件操作: open 操作。當一個應用程序打開
這個字符特殊文件的時候,通用的 open 文件操作使用設備的主設備標識符作爲 chrde
vs 向量表中的索引,取出這種特殊設備的文件操作塊。它也建立描述這個字符特殊文件
的 file 數據結構,讓它的文件操作指向設備驅動程序中的操作。然後應用程序所有的
文件系統操作都被映射到字符設備的文件操作。
參見 fs/devices.c chrdev_open() def_chr_fops
8.4.2 Block Devices (塊設備)
    塊設備也支持象文件一樣被訪問。這種爲打開的塊特殊文件提供正確的文件操作組
的機制和字符設備的十分相似。 Linux 用 blkdevs 向量表維護已經登記的塊設備文件
。它象 chrdevs 向量表一樣,使用設備的主設備號作爲索引。它的條目也是 device_s
truct 數據結構。和字符設備不同,塊設備進行分類。 SCSI 是其中一類,而 IDE 是另
一類。類向 Linux 核心登記並向核心提供文件操作。一種塊設備類的設備驅動程序向這
種類提供和類相關的接口。例如, SCSI 設備驅動程序必須向 SCSI 子系統提供接口,
讓 SCSI 子系統用來對核心提供這種設備的文件操作
參見 fs/devices.c
    每一個塊設備驅動程序必須提供普通的文件操作接口和對於 buffer cache 的接口
。每一個塊設備驅動程序填充 blk_dev 向量表中它的 blk_dev_struct 數據結構。這個
向量表的索引還是設備的主設備號。這個 blk_dev_struct 數據結構包括一個請求例程
的地址和一個指針,指向一個 request 數據結構的列表,每一個都表達 buffer cache


 向設備讀寫一塊數據的一個請求。
參見 drivers/block/ll_rw_blk.c include/linux/blkdev.h
    每一次 buffer cache 希望讀寫一塊數據到或從一個登記的設備的時候它就在它的
 blk_dev_struc 中增加一個 request 數據結構。圖 8.2 顯示了每一個 request 都有
一個指針指向一個或多個 buffer_head 數據結構,每一個都是一個讀寫一塊數據的請求
。這個 buffer_head 數據結構被鎖定( buffer cache ),可能會有一個進程在等待這
個緩衝區的阻塞進程完成。每一個 request 結構都是從一個靜態表, all_request 表
中分配的。如果這個 request 增加到一個空的 request 列表,就調用驅動程序的 req
uest 函數處理這個 request 隊列。否則,驅動程序只是簡單地處理 request 隊列中的
每一個請求。
    一旦設備驅動程序完成了一個請求,它必須把每一個 buffer_head 結構從 reques
t 結構中刪除,標記它們爲最新的,然後解鎖。對於 buffer_head 的解鎖會喚醒任何正
在等待這個阻塞操作完成的進程。這樣的例子包括文件解析的時候:必須等待 EXT2 文
件系統從包括這個文件系統的塊設備上讀取包括下一個 EXT2 目錄條目的數據塊,這個
進程將會在將要包括目錄條目的 buff_head 隊列中睡眠,直到設備驅動程序喚醒它。這
個 request 數據結構會被標記爲空閒,可以被另一個塊請求使用。
8.5 Hard Disks (硬盤)
    硬盤把數據存放在轉動的磁碟上,提供了一個更永久存儲數據的方式。爲了寫入數
據,微小的磁頭把磁碟表面的一個微小的點磁化。通過磁頭可以探測指定的微粒是否被
磁化,從而可以讀出數據。
    一個磁盤驅動器由一個或多個磁碟組成,每一個都用相當光滑的玻璃或者陶瓷製成
,並覆蓋上一層精細的金屬氧化物。磁碟放在一箇中心軸上面,並按照穩定的速度轉動


。轉動速度根據型號不同從 3000 到 1000RPM (轉 / 每分鐘)。磁盤的讀 / 寫磁頭負
責讀寫數據,每一個磁碟有一對,每一面一個。讀 / 寫磁頭和磁碟表面並沒有物理的接
觸,而是在一個很薄的空氣墊(十萬分之一英寸)上面漂浮。讀寫磁頭通過一個驅動器
在磁碟表面移動。所有的磁頭都粘在一起,一起在磁碟表面移動。
    每一個磁碟的表面都分成多個狹窄的同心環,叫做磁道( track )。磁道 0 是最
外面的磁道,最高編號的磁道是最接近中心軸的磁道。一個柱面( cylinder )是相同
編號磁道的組合。所以每一個磁碟的每一面的所有的第 5 磁道就是第 5 柱面。因爲柱
面數和磁道數相同,所以磁盤的尺寸常用柱面來描述。每一個磁道分成扇區。一個扇區
是可以從硬盤讀寫的最小數據單元,也就是磁盤的塊大小。通常扇區大小是 512 字節,
扇區大小通常是在製造磁盤的時候進行格式化的時候設定的。
    磁盤通常用它的尺寸( geometry )描述:柱面數、磁頭數和扇區數。例如,啓動
的時候 Linux 這樣描述我的 IDE 磁盤:
hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63
    這意味着它由 1050 柱面(磁道), 16 頭( 8 個磁碟)和 63 個扇區 / 磁道。
對於 512 字節的扇區或塊大小,磁盤的容量是 529200K 字節。這和磁盤聲明的 516M
的存儲能力不符合,因爲一些扇區用作存儲磁盤的分區信息。一些磁盤可以自動找出壞
的扇區,對其進行重新索引。
    硬盤可以再分爲分區。一個分區是分配用於特定目的的一大組扇區。對磁盤分區允
許磁盤用於幾個操作系統或多個目的。大多數單個磁盤的 Linux 系統都由 3 個分區:
一個包含 DOS 文件系統,另一個是 EXT2 文件系統,第三個是交換分區。硬盤的分區用
分區表描述,每一個條目用磁頭、扇區和柱面號描述分區的起止位置。對於用 fdisk 格
式化的 DOS 磁盤,可以有 4 個主磁盤分區。不是分區表所有的 4 個條目都必須用到。


 Fdisk 支持三種類型的分區:主分區、擴展分區和邏輯分區。擴展分區不是真正的分區
,它可以包括任意數目的邏輯分區。發明擴展分區和邏輯分區是爲了突破 4 個主分區的
限制。下面是一個包括 2 個主分區的磁盤的 fdisk 的輸出:
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 和扇區 1 ,直到柱面 477 ,
扇區 32 和磁頭 63 。因爲一個磁道由 32 個扇區和 64 個讀寫磁頭,這個分區的柱面
都是完全包括的。 Fdisk 缺省把分區對齊在柱面的邊界。它從最外面的柱面( 0 )開
始向內,朝向中心軸,擴展 478 個柱面。第 2 個分區,交換分區,開始於下一個柱面
( 478 )並擴展到磁盤最裏面的柱面。
    在初始化的時候 Linux 映射系統中的硬盤的拓撲結構。它找出系統中有多少個硬盤
以及硬盤的類型。 Linux 還找出每一個磁盤如何分區。這些都是由 gendisk_head 指針


列表指向的一組 gendisk 數據結構的列表表達。對於每一個磁盤子系統,例如 IDE ,
初始化的時候生成 gendisk 數據結構表示它找到的磁盤。這個過程和它登記它的文件操
作和在 blk_dev 數據結構中增加它的條目發生在同一時間。每一個 gendisk 數據結構
都由一個唯一的主設備號,和塊特殊設備的相同。例如, SCSI 磁盤子系統會創建一個
獨立的 gendisk 條目(“ sd ”),主設備號是 8 (所有 SCSI 磁盤設備的主設備號
)。圖 8.3 顯示了兩個 gendisk 條目,第一個是 SCSI 磁盤子系統,第二個是 IDE 磁
盤控制器。這裏是 ide0 ,主 IDE 控制器。
    雖然磁盤子系統在初始化的時候會建立相應的 gendisk 條目, Linux 只是在進行
分區檢查的時候纔用到。每一個磁盤子系統必須維護自己的數據結構,讓它自己可以把
設備的主設備號和次設備號映射到物理磁盤的分區上。不管什麼時候讀寫塊設備,不管
是通過 buffer cache 或者文件操作,核心都根據它在塊特殊設備文件(例如 /dev/sd
a2 )中找到的主設備號和次設備號把操作定向到合適的設備。是每一個設備驅動程序或
子系統把次設備號映射到真正的物理設備上。
8.5.1 IDE Disks ( IDE 磁盤)
    今天 Linux 系統中最常用的磁盤是 IDE 磁盤( Integrated Disk Electronic )
。 IDE 和 SCSI 一樣是一個磁盤接口而不是一個 I/O 總線。每一個 IDE 控制器可以支
持最多 2 個磁盤,一個是 master ,另一個是 slave 。 Master 和 slave 通常用磁盤
上的跳線設置。系統中的第一個 IDE 控制器叫做主 IDE 控制器,下一個叫從屬控制器
等等。 IDE 可以從 / 向磁盤進行 3.3M/ 秒的傳輸, IDE 磁盤的最大尺寸是 538M 字
節。擴展 IDE 或 EIDE 把最大磁盤尺寸增加到 8.6G 字節,數據傳輸速率高達 16.6M/
 秒。 IDE 和 EIDE 磁盤比 SCSI 磁盤便宜,大多數現代 PC 都有一個或更多的主板上
的 IDE 控制器。


    Linux 按照它發現的控制器的順序命名 IDE 磁盤。主控制器上的主磁盤是 /dev/h
ad , slave 磁盤是 /dev/hdb 。 /dev/hdc 是次 IDE 控制器上的 master 磁盤。 ID
E 子系統向 Linux 登記 IDE 控制器而不是磁盤。主 IDE 控制器的主標識符是 3 ,次
 IDE 控制器的標識符是 22 。這意味着如果一個系統有兩個 IDE 控制器,那麼在 blk
_dev 和 blkdevs 向量表中在索引 3 和 22 會有 IDE 子系統的條目。 IDE 磁盤的塊特
殊文件反映了這種編號:磁盤 /dev/had 和 /dev/hdb ,都連接在主 IDE 控制器上,主
設備號都是 3 。核心使用主設備標識符作爲索引,對於這些塊特殊文件的 IDE 子系統
進行的所有的文件或者 buffer cache 操作都被定向到相應的 IDE 子系統。當執行一個
請求的時候, IDE 子系統負責判斷這個請求是針對哪一個 IDE 磁盤。爲此, IDE 子系
統使用設備特殊文件中的次設備號,這些信息允許它把請求定向到正確的磁盤的正確的
分區。 /dev/hdb ,主 IDE 控制器上的 slave IDE 磁盤的設備標識符是( 3 , 64 )
。它的第一個分區( /dev/hdb1 )的設備標識符是( 3 , 65 )。
8.5.2 Initializing the IDE Subsystem (初始化 IDE 子系統)
IBM PC 的大部分歷史中都有 IDE 磁盤。這期間這些設備的接口發生了變化。這讓 IDE
 子系統的初始化過程比它第一次出現的時候更加複雜。
    Linux 可以支持的最大 IDE 控制器數目是 4 。每一個控制器都用一個 ide_hwifs
 向量表中的一個 ide_hwif_t 數據結構表示。每一個 ide_hwif_t 數據結構包含兩個
ide_drive_t 數據結構,分別表示可能支持的 master 和 slave IDE 驅動器。在 IDE
子系統初始化期間, Linux 首先查看在系統的 CMOS 內存中記錄的磁盤的信息。這種用
電池做後備的內存在 PC 關機的時候不會丟失它的內容。這個 CMOS 內存實際上在系統
的實時時鐘設備裏面,不管你的 PC 開或者關,它都在運行。 CMOS 內存的位置由系統
的 BIOS 設置,同時告訴 Linux 系統中找到了什麼 IDE 控制器和驅動器。 Linux 從


BIOS 中獲取找到的磁盤的尺寸( geometry ),用這些信息設置這個驅動器的 ide_hw
if_t 的數據結構。大多數現代 PC 使用 PCI 芯片組例如 Intel 的 82430 VX 芯片組,
包括了一個 PCI EIDE 控制器。 IDE 子系統使用 PCI BIOS 回調( callback )定位系
統中的 PCI ( E ) IDE 控制器。然後調用這些芯片組的詢問例程。
    一旦發現一個 IDE 接口或者控制器,就設置它的 ide_hwif_t 來反映這個控制器和
上面的磁盤。操作過程中 IDE 驅動程序向 I/O 內存空間的 IDE 命令寄存器寫命令。主
 IDE 控制器的控制和狀態寄存器的缺省的 I/O 地址是 0x1F0-0x1F7 。這些地址是早期
的 IBM PC 約定下來的。 IDE 驅動程序向 Linux 的 buffer cache 和 VFS 登記每一個
控制器,分別把它加到 blk_dev 和 blkdevs 向量表中。 IDE 驅動程序也請求控制適當
的中斷。同樣,這些中斷也有約定,主 IDE 控制器是 14 ,次 IDE 控制器是 15 。但
是,象所有的 IDE 細節一樣,這些都可以用核心的命令行選項改變。 IDE 驅動程序在
啓動的時候也爲每一個找到的 IDE 控制器在 gendisk 列表中增加一個 gendisk 條目。
這個列表稍後用於查看啓動時找到的所有的硬盤的分區表。分區檢查代碼明白每一個 I
DE 控制器可以控制兩個 IDE 磁盤。
8.5.3 SCSI Disks ( SCSI 磁盤)
    SCSI ( Small Computer System Interface 小型計算機系統接口)總線是一種有
效的點對點的數據總線,每個總線支持多達 8 個設備,每個主機可以有一或者多個。每
一個設備都必須由一個唯一的標識符,通常用磁盤上的跳線設置。數據可以在總線上的
任意兩個設備之間同步或者異步傳輸,可以用 32 位寬的數據傳輸,速度可能高達 40M
/ 秒。 SCSI 總線可以在設備之間傳輸數據和狀態信息,發起者( initiator )和目標
( target )之間的事務會涉及多達 8 個不同的階段。你可以通過 SCSI 總線上的 5
種信號判斷出當前的階段。這 8 個階段是:


BUS FREE 沒有設備有總線的控制權,當前沒有發生任何事務。
ARBITRATION (仲裁)一個 SCSI 設備試圖得到 SCSI 總線的控制權,它在地址管腳上
聲明( assert )它的 SCSI 標識符。最高編號的 SCSI 標識符成功。
SELECTION 一個設備通過仲裁成功地得到了 SCSI 總線的控制權,現在它必須向它要發
送命令的 SCSI 目標發送信號。它在地址管腳上聲明目標的 SCSI 標識符。
RESELECTION SCSI 設備在處理請求的過程中可能斷線,目標會重新選擇發起者。並非所
有的 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 host 通常總是 SCSI 命令的發起者( initiato
r )。
    Device SCSI 設備通常是磁盤,但是 SCSI 標準支持多種類型:磁帶、 CD-ROM 和
通用( generic ) SCSI 設備。 SCSI 設備通常都是 SCSI 命令的目標。這些設備必須
不同地對待。例如可移動介質如 CD-ROM 或磁帶, Linux 需要探測介質是否取出。不同
的磁盤類型有不同的主設備編號,允許 Linux 把塊設備請求定向到合適的 SCSI 類型。


 
Initializing the SCSI Subsystem (初始化 SCSI 子系統)
    初始化 SCSI 子系統相當複雜,反映出 SCSI 總線和設備的動態的實質。 Linux 在
啓動的時候初始化 SCSI 子系統:它查找系統中的 SCSI 控制器( SCSI host ),並探
測每一個 SCSI 總線,查找每一個設備。然後初始化這些設備,讓 Linux 核心的其餘部
分可以通過普通的文件和 buffer cache 塊設備操作訪問它們。這個初始化過程有四個
階段:
首先, Linux 找出核心建立的時候建立到核心的哪一個 SCSI host 適配器或控制器有
可以控制的硬件。每一個內建的 SCSI host 在 buildin_scsi_hosts 向量表中都有一個
 Scsi_Host_Template 的條目。這個 Scsi_Host_Template 數據結構包括例程的指針,
這些例程可以執行和 SCSI host 相關的動作例如探測這個 SCSI host 上粘附了什麼 S
CSI 設備。這些例程在 SCSI 子系統配置期間被調用,是支持這種 host 類型的 SCSI
設備驅動程序的一部分。每一個查到的 SCSI 控制器(有真實的 SCSI 設備粘附),它
的 Scsi_Host_Template 數據結構都加到 scsi_hosts 列表中,表示有效的 SCSI host
 。每一個探測到的 host 類型的每一個實例都用 scsi_hostlist 列表中的一個 Scsi_
Host 數據結構表示。例如一個系統有兩個 NCR810 PCI SCSI 控制器,在這個列表中會
有兩個 Scsi_Host 條目,每一個控制器一個。每一個 Scsi_Host 指向的 Scsi_Host_T
emplate 表示它的設備驅動程序。
 
現在每一個 SCSI host 都找到了, SCSI 子系統必須找到每一個 host 總線上的所有的
 SCSI 設備。 SCSI 設備編號從 0 到 7 ,每一個設備編號或者 SCSI 標識符在它所粘
附的 SCSI 總線上都是唯一的。 SCSI 標識符通常用設備上的跳線設置。 SCSI 初始化


代碼通過向每一個設備發送 TEST_UNIT_READY 命令來查找一個 SCSI 總線上的每一個
SCSI 設備。當一個設備迴應,再向它發送一個 ENQUIRY 命令來完成它的判別。這向 L
inux 給出 Vendor 的名稱和設備的型號和修訂號。 SCSI 命令用一個 Scsi_Cmnd 數據
結構來表示,這些命令通過調用這個 SCSI host 的 Scsi_Host_Template 數據結構中的
設備驅動程序例程傳遞給設備驅動程序。每一個找到的 SCSI 設備用一個 Scsi_Device
 數據結構表示,每一個都指向它的父 Scsi_Host 。所有的 Scsi_Device 數據結構都加
到 scsi_devices 列表中。圖 8.4 顯示了主要的數據結構和其他數據結構的關係。
 
有四種 SCSI 設備類型:磁盤、磁帶、 CD 和通用( generic )。每一種 SCSI 類型都
分別向核心登記,有不同的主塊設備類型。但是,它們只有在一個或多個給定的 SCSI
設備類型的設備找到的時候才登記自己。每一個 SCSI 類型,例如 SCSI 磁盤,維護它
自己的設備表。它用這些表把核心的塊操作(文件或 buffer cache )定向到正確的設
備驅動程序或 SCSI host 。每一個 SCSI 類型都用一個 Scsi_Type_Template 數據結構
表示。它包括這種類型的 SCSI 設備的信息和執行多種任務的例程的地址。 SCSI 子系
統使用這些模板調用每一種 SCSI 設備類型的 SCSI 類型例程。換句話說,如果 SCSI
子系統希望粘附一個 SCSI 磁盤設備,它會調用 SCSI 磁盤類型的例程。如果探測到某
類型的一個或多個 SCSI 設備,它的 Scsi_Type_Templates 的數據結構就加到了 scsi
_devicelist 列表中。
 
SCSI 子系統初始化的最後階段是調用每一個登記的 Scsi_Device_Template 的完成函數
。對於 SCSI 磁盤類型讓所有的 SCSI 磁盤轉動起來並記錄它們的磁盤尺寸。它也把表
示所有 SCSI 磁盤的 gendisk 數據結構增腳的磁盤的鏈接列表中,如圖 8.3 。


 
Delivering Block Device Requests (傳遞塊設備請求)
 
一旦 Linux 初始化了 SCSI 子系統,就可以使用 SCSI 設備了。每一個有效的 SCSI 設
備類型都在覈心中登記自己,所以 Linux 可以把塊設備請求定向到它那裏。這些請求可
能是通過 blk_dev 的 buffer cache 請求或者是通過 blkdevs 的文件操作。拿一個由
一個或多個 EXT2 文件系統分區的 SCSI 磁盤驅動器爲例,當它的 EXT2 分區安裝上的
時候核心的緩衝區請求是如何定向到正確的 SCSI 磁盤呢?
 
每一個向 / 從一個 SCSI 磁盤分區讀 / 寫一塊數據的請求都會在 blk_dev 向量表中這
個 SCSI 磁盤的 current_request 列表中加入一個新的 request 數據結構。如果這個
 request 列表正在處理,那麼 buffer cache 不需要做什麼。否則它必須讓 SCSI 磁盤
子系統處理它的請求隊列。系統中的每一個 SCSI 磁盤用一個 Scsi_Disk 數據結構表示
。它們保存在 rscsi_disks 向量表中,用 SCSI 磁盤分區的次設備號的一部分作爲索引
。例如, /dev/sdb1 主設備號 8 ,次設備號 17 ,它的所以是 1 。每一個 Scsi_Dis
k 的數據結構包括一個指向表示這個設備的 Scsi_Device 數據結構的指針。 Scsi_Dev
ice 又指向一個“擁有它”的 Scsi_Host 數據結構。 Buffer cache 中的 request 數
據結構轉換成爲描述需要發送到 SCSI 設備的 SCSI 命令的 Scsi_Cmd 數據結構中,並
在表示這個設備的 Scsi_Host 數據結構中排隊。一旦適當的數據塊讀 / 寫之後,會由
各自的 SCSI 設備驅動程序處理。
 
8.6 Network Devices (網絡設備)


 
一個網絡設備,只要關係到 Linux 的網絡子系統,是一個發送和接收數據包的實體。通
常是一個物理的設備,例如一個以太網卡。但是一些網絡設備是純軟件的,例如 loopb
ack 設備,用於向自己發送數據。每一個網絡設備用一個 device 數據結構表示。網絡
設備驅動程序在覈心啓動網絡初始化的時候向 Linux 登記它控制的設備。 Device 數據
結構包括這個設備的信息和允許大量支持的網絡協議使用這個設備的服務的函數的地址
。這些函數多數和使用這個網絡設備傳輸數據有關。設備使用標準的網絡支持機制,向
適當的協議層傳輸接收的數據。傳輸和接收的所有的網絡數據(包 packets )都用 sk
_buff 數據結構表示,這是靈活的數據結構,允許網絡協議頭很容易地增加和刪除。網
絡協議層如何使用網絡設備,它們如何使用 sk_buff 數據結構來回傳遞數據,在網絡章
(第 10 章)有詳細的描述。本章集中在 device 數據結構以及網絡設備如何被發現和
初始化。
參見 include/linux/netdevice.h
 
device 數據結構包括網絡設備的信息:
 
Name 不象塊和字符設備,它們的設備特殊文件用 mknod 命令創建,網絡設備特殊文件
在系統的網絡設備發現並初始化的時候自然出現。它們的名字是標準的,每一個名字都
表示了它的設備類型。同種類型的多個設備從 0 向上依次編號。因此以太網設備編號爲
 /dev/eth0 、 /dev/eth1 、 /dev/eth2 等等。一些常見的網絡設備是:
 
/dev/ethN 以太網設備


/dev/slN SLIP 設備
/dev/pppN PPP 設備
/dev/lo loopback 設備
 
Bus Information 這是設備驅動程序控制設備需要的信息。 Irq 是設備使用的中斷。
Base address 是設備的控制和狀態寄存器在 I/O 內存種的地址。 DMA 通道是這個網絡
設備使用的 DMA 通道號。所有這些信息在啓動時設備初始化的時候設置。
 
Interface Flags 這些描述了這個網絡設備的特性和能力。
IFF_UP 接口 up ,正在運行
IFF_BROADCAST 設備的廣播地址有效
IFF_DEBUG 設備的 debug 選項打開
IFF_LOOPBACK 這是一個 loopback 設備
IFF_POINTTOPOINT 這是點對點的連接( SLIP and PPP )
IFF_NOTRAILERS No network trailers
IFF_RUNNING 分配了資源
IFF_NOARP 不支持 ARP 協議
IF_PROMISC 設備在混合( promiscuous )接收模式,它會接收所有的包,不管它們的
地址是誰。
IFF_ALLMULTI 接收所有的 IP Multicast 幀
IFF_MULTICAST 可以接收 IP multicast 幀
 


Protocal Information 每一個設備都描述它可以被網絡協議層如何使用:
Mtu 不包括需要增加的鏈路層的頭這個網絡能夠傳輸的最大尺寸的包。這個最大值用於
協議層例如 IP ,來選擇一個合適的包大小進行發送。
Family family 顯示了設備可以支持的協議族。所有 Linux 網絡設備都支持的 family
 是 AF_INET , Internet 地址 family 。
Type 硬件接口類型描述了這個網絡設備連接的介質。 Linux 網絡設備支持多種介質類
型。包括 Ethernet 、 X.25 , Token Ring 、 Slip 、 PPP 和 Apple Localtalk 。
 
Addresses device 數據結構保存一些和這個網絡設備相關的地址,包括 IP 地址
 
Packet Queue 這是一個 sk_buff 的包隊列,等待網絡設備進行傳輸
 
Support Functions 每一個設備都提供了一組標準的例程,讓協議層調用,作爲對於設
備鏈路層的接口的一部分。包括設置和幀傳輸例程,以及增加標準幀頭和收集統計信息
的例程。這些統計信息可以用 ifcnfig 看到
 
8.6.1 Initializing Network Devices (初始化網絡設備)
 
網絡設備驅動程序象其他 Linux 設備驅動程序一樣,可以建立到 Linux 核心中。每一
個可能的網絡設備都用 dev_base 列表指針指向的網絡設備列表中的一個 device 數據
結構表示。如果需要設備相關的操作,網絡層調用網絡設備服務例程(放在 device 數
據結構中)其中的一個。但是,初始的時候,每一個 device 數據結構只是放了初始化


或者探測例程的地址。
 
網絡驅動程序必須解決兩個問題。首先,不是所有建立在 Linux 核心的網絡設備驅動程
序都會有控制的設備;第二,系統中的以太網設備總是叫做 /dev/eth0 、 /dev/eth1
等等,而不管底層的設備驅動程序是什麼。“丟失“網絡設備的問題容易解決。在調用
每一個網絡設備的初始化例程的時候,它返回一個狀態,顯示它是否定位到了它驅動的
控制器的一個實例。如果驅動程序沒有找到任何設備,它由 dev_base 指向的 device
列表中的條目就被刪除。如果驅動程序可以找到一個設備,它就用這個設備的信息和網
絡設備驅動程序中的支持函數的地址填充 device 數據結構其餘的部分。
 
第二個問題,就是動態地分配以太網設備到標準的 /dev/ethN 設備特殊文件上,用更優
雅的方式解決。 Device 列表中有 8 個標準的條目: eth0 、 eth1 到 eth7 。所有條
目的初始化例程都一樣。它順序嘗試建立在覈心的每一個以太網設備驅動程序,直到找
到一個設備。當驅動程序找到它的以太網設備,它就填充它現在擁有的 ethN 的 devic
e 數據結構。這時網絡驅動程序也要初始化它控制的物理硬件,並找出它使用的 IRQ 、
 DMA 等等。驅動程序可能找到它控制的網絡設備的幾個實例,在這種情況下,它就佔用
幾個 /dev/ethN 的 device 數據結構。一旦所有的 8 個標準的 /dev/ethN 都分配了,
就不會再探測更多的以太網設備。

發佈了0 篇原創文章 · 獲贊 0 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章