Scsi總線在掃描磁盤設備後生成的盤符與設備通道之間的關係是不固定的,其最主要的緣由是設計者考慮到scsi總線在系統中不會靜態、惟一存在,會動態生成,而盤符空間在全局只有一個,所以,盤符與設備通道之間很難實現綁定,至少這種綁定關係會隨着系統中scsi總線的增長而遭到破壞。因此,設計者採用了動態映射的方法維護盤符與設備通道之間的關係。linux
盤符與設備通道之間的動態映射會影響到存儲設備的管理。例如,一個存儲設備因爲某種緣由拆除了一個磁盤,從新啓動以後,全部的盤符將會從新生成,從而會致使磁盤上層的存儲軟件沒法正常啓動,除非這些存儲軟件可以自動識別磁盤設備,而後進行重構。顯而易見,盤符與設備通道之間的動態映射增長了存儲設備管理的難度。算法
爲了下降存儲設備的管理難度,須要固定盤符與磁盤通道之間的映射關係。考慮到存儲設備的硬件資源相對固定,因此,這種映射關係在理論上是能夠固定的。爲此,本文從Linux SCSI層磁盤掃描的角度對這個問題進行分析、總結。數組
一般SCSI總線適配器做爲PCI設備的形式存在,其在計算機體系結構中的位置描述以下圖所示:數據結構
圖1 scis host及device在計算機體系結構中的位置ide
在系統初始化時會掃描系統PCI總線,因爲scsi host adapter掛接在pci總線上,所以會被pci掃描軟件掃描獲得,而且生成一個pci device(PDO)。而後掃描軟件須要爲該pci device加載相應的驅動程序。在linux系統中,掃描軟件會遍歷pci bus上存在的全部驅動程序,檢查是否有符合要求的驅動程序存在。這裏假設scsi host是marwell的設備,那麼,若是存在marwell提供的scsi host driver,就會被成功調用。加載scsi host驅動時,pci掃描程序會調用scsi host driver提供的probe函數,該probe函數是scsi host driver在初始化驅動時註冊到pci-driver上的(Linux的總線驅動都是採用的這種思路)。在scsi host具體的probe函數中會初始化scsi host,註冊中斷處理函數,而且調用scsi_host_alloc函數生成一個scsi host,而後添加到scsi middle level,最後調用scsi_scan_host函數掃描scsi host adapter所管理的全部scsi總線。函數
一個scsi host adapter可能擁有多個channel,每一個channel擁有一條scsi總線。傳統scsi總線是並行共享總線,現有的SATA、SAS等P2P接口在邏輯上能夠理解成總線的一種特例,因此scsi middle level驅動程序是通用的。因爲一個scsi host可能存在多個channel,所以依次掃描每一個channel。按照spec,傳統scsi bus上最多能夠鏈接16個scsi target,所以,scsi掃描程序會依次探測target。一個scsi target能夠存在多種功能,每種功能稱之爲LUN,對於單功能設備(例如磁盤),其LUN一般爲0。性能
Scsi host的掃描過程能夠簡單採用以下僞碼進行描述:spa
For (channel = 0; channel < max_channel; channel++) { /* 對一個適配器的每一個通道中的設備進行識別 */ … For (id=0; id<max_id; id++) { /* 對一個通道中的每一個ID對應設備進行識別 */ ... For (lun=1; lun<max_dev_lun; lun++) { /* 對一個ID對應設備的每一個LUN進行識別 */ ... } } }
經過上述掃描過程能夠知道,在系統中能夠採用以下方法對一個scsi device進行描述:host_id : channel_id : target_id : lun_id設計
其中,host_id是系統動態分配的,這與PCI總線的掃描順序相關,對於固定硬件的系統host_id掃描獲得的結果不會改變,可是,若是動態添加一個scsi host(PCI device),系統的host_id可能會發生變化,這一點須要注意。指針
在SCSI Disk probe的過程當中,內核會爲scsi disk分配盤符名稱。因爲scsi disk的盤符空間是全局的,而系統中的scsi host、scsi target以及scsi lun都會動態變化,因此,Linux系統沒有辦法爲每一個scsi disk分配一個固定的、簡單易用的盤符名稱。所以,Linux對scsi disk的盤符采用動態分配的策略。Linux在內核中維護了一棵管理設備盤符名稱的樹(idr_tree),當系統每次probe到一個新設備以後,都會從idr_tree中查找一個空閒節點,而且獲取該空閒節點的id號,做爲盤符的索引名稱。
例如,當系統掃描到一個新的scsi disk以後,會從idr_tree中查找而且獲取一個空閒節點的id號,而後系統再將id號映射成26個英文字母,最後與」sd」一塊兒組合成一個新的字符串,該字符串就是scsi disk的盤符。
該機制的優點在於盤符管理簡單,缺點在於對於一個動態系統,盤符的名稱可能發生變化,影響到上層應用系統。
Idr_tree是內核提供的一種算法,其實現了整型id號與一個內核指針之間的映射。Idr_tree算法的實現採用了radix_tree的思想,其數據結構定義與radix tree有着相似之處。Idr_tree在2003年的時候引入至內核,目前在scsi盤符空間的管理、i2c總線設備號與設備結構之間的映射方面都有應用。
在整型id與內存指針之間的映射能夠採用數組的方式,可是隨着存儲規模的增大,數組的方式顯然不能知足應用的需求;另外一種方式能夠採用鏈表進行映射信息的管理,一樣不能管理較大的映射資源。因此,在Linux中採用樹進行映射資源的管理。這種樹就是idr_tree。
採用idr_tree來管理整型id與內存指針之間的映射關係具備以下兩方面對優點:
一、具備很好的映射關係查找效率。
二、可以很好的擴展存儲規模,動態增大樹的規模對查找性能影響不是很大。
Idr_tree的結構示意圖描述以下圖所示:
圖 idr_tree結構示意圖
Idr_tree的葉子節點用於存儲用戶定義的數據,一般用於存儲用戶須要查找的內存指針。樹中的每一個節點定義爲idr_layer,該數據結構中包含了一個bitmap位圖,用於描述下層節點的屬性。當下層節點不存在空穴(空閒節點)時,上層節點bitmap位圖中的對應位置位。所以在查找過程當中,從頂層節點開始,判斷每一個節點的bitmap信息,而後遍歷第一個bit位爲0的下層節點,直到遍歷到葉子節點爲止。因而可知,Idr_tree的算法複雜度與樹的層次數量相關。Idr_tree是動態生成的,隨着葉子節點的增多,樹的層次將會不斷增大。