1. 程式人生 > 其它 >Linux中的SCSI模型及iSCSI裝置發現示例

Linux中的SCSI模型及iSCSI裝置發現示例

在Linux核心中SCSI驅動應該是最為複雜的驅動,沒有之一。因為對於整個SCSI系統來說,不只包含一種型別的裝置,而是一類裝置。前面文章我們簡單介紹了Linux作業系統核心中SCSI子系統的整體架構,我們這裡簡單回憶一下。

在Linux核心中抽象了一個稱謂SCSI匯流排的虛擬匯流排。而在SCSI總線上又包含SCSI的驅動和裝置。

圖1 SCSI體系結構

Linux作業系統中的SCSI整個架構分為3層,各層的具體作用如下:

中間層,用於實現SCSI的公共功能,比如錯誤處理等。

高層或上層,它代表各種scsi裝置型別的驅動,如scsi磁碟驅動,scsi磁帶驅動,高層驅動認領低層驅動發現的scsi裝置,為這些裝置分配名稱,將對裝置的IO轉換為scsi命令,交由低層驅動處理。

底層或下層,它代表與SCSI的物理介面的實際驅動器,主要為各個廠商為其特定的主機介面卡(Host Bus Adapter, HBA)驅動,例如: FC卡驅動、SAS卡驅動和iSCSI(iSCSI可以使硬體HBA卡或者基於普通網絡卡的軟體實現)等。

SCSI裝置的發現

SCSI裝置通常是支援熱插拔(即插即用)的,也就是可以直接連線目標裝置,並在Linux作業系統看到並使用。比如我們常見優盤可以插上之後馬上使用,而不需要重啟電腦。而在企業級伺服器中的SAN儲存也是通過光纖連線後在Linux服務可以看到磁碟裝置(當然需要在儲存端有相關的配置)。為了後續內容的理解,本節將介紹在裝置物理連線就緒的情況下,在Linux伺服器中如何呈現裝置(專業術語叫掃描發現)。

我們日常連線儲存裝置的方式有很多種,除了最常見的優盤、光碟外,還有企業上常用的FC-SAN、IP-SAN和SAS磁碟陣列等。我們今天主要介紹一下FC-SAN和IP-SAN這兩種連線儲存裝置的方式。

對於FC-SAN儲存,當建立的Linux伺服器與儲存裝置的物理連線,且儲存裝置做過必須的配置之後,可以通過如下幾種方式在Linux服務程式磁碟裝置。

  1. 重啟主機
  2. 解除安裝並重新載入主機介面卡驅動
  3. 通過echo /sys檔案系統(僅適用於2.6核心版本)重新掃描匯流排
  4. 通過echo /proc或/sys檔案系統手動新增和刪除SCSI磁碟

這幾種方式的本質是一樣的,其實就是對總線上的裝置進行重新掃描,發現新加入的裝置,並在核心中建立必要的核心資料結構,然後呈現給使用者。前者自不必說,我們看一下後2者是如何進行操作的。

以下命令可掃描所有通道、target、LUN和主機。

echo “- - -” > /sys/class/scsi_host/hostH/scan

其中hostH中H是變化的,需要根據具體的適配卡(HBA)靈活調整。

可通過以下命令刪除或對SCSI磁碟取消配置:

echo "scsi remove-single-device H B T L" > /proc/scsi/scsi

命令示例中,H, B, T, L代表裝置的主機,匯流排,target,和LUN ID。

對於IP-SAN的操作要簡單的多,前面我們介紹過啟動器端的配置管理的命令。該命令中有一個登陸操作,在進行登陸的時候其實就相當於進行裝置的掃描,此時會建立新的裝置,並呈現給使用者。關於具體操作本文不再贅述,具體可以參考之前的文章。

上面命令是關於裝置掃描的基本操作,這裡作為一個簡單的入門,後續針對程式碼做詳細的介紹。

Linux核心發現裝置的原理

前文我們介紹過,SCSI的上層中sd的驅動其實就是一個SCSI磁碟驅動,它呈現給我們的是一個磁碟裝置。而底層的FC Driver則是FC-HBA卡的驅動。兩者結合就形成了我們在作業系統層面看到的基於FC-SAN的磁碟。因此,Linux作業系統發現裝置的過程其實就是上述兩個驅動建立例項(資料結構)的過程

SCSI裝置複雜的地方在於FC-HBA卡還可能包含多個通道,而儲存裝置又可能包含多個目標器,目標器中又可能有多個裝置(LUN)。因此,對應關係變得非常複雜。在Linux作業系統中通過一個四元組(host,channel,target,lun)來標識這種關係。這種組織關係在核心中也有對應的資料結構。如圖2是SCSI四元組的邏輯關係。

圖2 SCSI四元組

上述概念在Linux核心中有3個對應的資料結構,分別是Scsi_Host、scsi_target和scsi_device。

圖3 核心資料結構關係

上面3個核心資料結構中,Scsi_Host是介面卡(HBA)對應的資料結構,位於SCSI匯流排下面。scsi_target對應著一個目標器裝置,目標器裝置未必是物理的,也可能是虛擬的。而scsi_device則為目標器中的裝置,可以理解為儲存中的LUN在客戶端的體現。

觀察一下圖3,可以看出在Scsi_Host下面通過連結串列的方式儲存著多個scsi_target,而scsi_target下面又通過連結串列儲存著多個scsi_device。這種關係只是我們在圖2中描述的物理對應關係。

核心中除了這三個主要的資料結構外,還有一些輔助的資料結構,比如scsi_host_template和scsi_transport_template等資料結構,這些資料結構主要是完成一些公共的功能。比如對於SCSI裝置,有磁碟、光碟機和磁帶等等,但這些裝置有一些公共的功能,因此核心中將這些公共的功能提取成模板。這樣,在初始化裝置的時候可以藉助模板減少初始化的複雜度。

裝置發現的iSCSI例項分析

本節結合iSCSI分析一下在SCSI裝置中的裝置發現流程,通過這個流程及關鍵資料結構的瞭解,對整個SCSI和iSCSI就可以有一個概括的瞭解

在介紹裝置發現之前,我們先看一下在Linux作業系統中iSCSI的整體軟體架構。如圖4所示,整個iSCSI啟動器(客戶端)的軟體架構圖包括使用者態和核心態兩部分的內容。其中使用者態主要負責管理,包括iscsiadm命令列工具和iscsid守護程序。而核心態包括如圖中綠色的4個核心模組,這些核心模組與SCSI子系統結合起來形成了整個功能。

圖4 iSCSI軟體架構

對於一個管理命令,通常是iscsiadm命令列通過本地套接字傳送給iscsid守護程序,而該守護程序經過處理後通過netlink傳送到核心模組進行處理。關於整個流程還是比較複雜的,本文暫時不做介紹,後續文章再進行詳細的分析。

本文我們需要知道的是在iSCSI中啟動器和目標器之間是通過session維護兩者之間的關係,而每個session中可能不止有一條連線。但只要建立連線之後,在Linux作業系統中就可以看到磁碟裝置(當然,前提是儲存端已經做過相關配置)。對於session中的連線數量,設計為可以在同一session中有多條連線的目的是為了可以實現二手出售地圖物理鏈路的冗餘,這樣可以保證某條鏈路故障的情況下可以通過其它鏈路繼續提供服務。

廢話說了半天,我們當前只需要知道在啟動器和目標器之間建立session之後,也就是目標器登入之後,就可以在啟動器端看到磁碟,這個也就是裝置發現的過程。因此,這裡核心就是分析iSCSI中建立session的過程

這裡所說的建立session的過程其實是個寬泛的概念,其實在Linux最終實現時是分2步進行的,第一個是建立session,此時是建立必要的資料結構和進行處理函式的初始化等工作;而第2步則是建立session中的一個連線,這個連線建立成功後,session才能真正起作用。

1) 建立session

我們這裡忽略不必要的細節,從核心中建立session流程講起。其入口函式是iscsi_if_create_session,該函式在檔案scsi_transport_iscsi.c中,在該函式中完成了所有功能。但該函式是呼叫其它函式實現的,具體如圖所示。

圖5 核心建立session流程

對於純軟體的iSCSI其實是呼叫了iscsi_sw_tcp_session_create函式,而該函式則通過iscsi_host_alloc分配一個Scsi_Host例項,並通過iscsi_session_setup完成session的設定。

我們前面提到過host其實對應著硬體的適配卡,適配卡是Linux客戶端與儲存端(服務端)通訊的通道,即使是純軟體的情況下核心也會建立這樣一個數據結構,只不過通訊的方式是通過TCP協議。而在函式iscsi_host_alloc中正是完成了該結構體的分配和部分初始化工作。

函式iscsi_session_setup則是進行session的建立和初始化,核心是將iscsi_sw_tcp_transport函式集初始化到session當中,這樣在後續的操作中就可以使用該函式集中的函式。最終,該函式會將建立的session新增到全域性session連結串列sesslist中。

2) 建立連線

前面建立了一個session,這裡就是要建立連線了。建立連線也是由iscsiadm命令列發起的。最終觸發到核心態的函式介面。該函式介面為iscsi_conn_start

圖6 啟動連線主要流程

通過圖6的流程可以看出來,啟動連線的本質其實是掃描目標器。掃描目標器也就是看看是否有新的目標器,並且目標器上是否有新的LUN。如果有新的裝置加入,則需要建立對應的資料結構。

圖7 掃描目標器主要流程

掃描目標器的函式為__scsi_scan_target,該函式一個SCSI子系統的公共函式。不僅僅iSCSI要用到該函式,基於FC和iSCSI HBA卡的驅動也要用到該函式。

繼續分析該函式內部的實現,這裡面有幾個比較核心的函式需要介紹一下。scsi_alloc_target函式用於分配一個目標器結構體,並進行必要的初始化。scsi_alloc_sdev函式是建立scsi_device結構體,並進行必要的初始化,這個裝置就是與具體磁碟對應的裝置。這裡面比較重要的是初始化了請求佇列。這個佇列用於處理讀寫資料,我們在後面介紹讀寫資料的流程的時候會介紹到。

	if (shost_use_blk_mq(shost))
		sdev->request_queue = scsi_mq_alloc_queue(sdev);
	else
		sdev->request_queue = scsi_alloc_queue(sdev);

最後一個比較重要的函式是scsi_add_lun,這個函式是進行裝置掃描的重頭戲。它最終會掉到sd驅動(也就是SCSI磁碟驅動)的sd_probe函式,該函式完成建立磁碟和與裝置關聯的相關操作。簡單的說,這個函式完成後在使用者層面就可以看到磁碟,並且該磁碟與底層的驅動建立了關聯。這樣,當用戶對磁碟進行讀寫操作的時候,請求就可以經過底層的驅動(乙太網卡,iSCSI-HBA或者FC-HBA)傳送到儲存端。

本文並沒有介紹每個函式的細節,只是介紹了大體的流程。主要是考慮到貼程式碼程式碼會導致文章太亂,也不利於表達。後面本號在寫一篇關於IO的文章,這樣就比較清楚的瞭解了資料是如何從使用者的應用到儲存裝置了。