天天看點

Linux中的SCSI模型及iSCSI示例

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

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

Linux中的SCSI模型及iSCSI示例

圖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伺服器中如何呈現裝置(專業術語叫掃描發現)。

我們日常連接配接儲存設備的方式有很多種,除了最常見的優盤、CD光牒外,還有企業上常用的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四元組的邏輯關系。

Linux中的SCSI模型及iSCSI示例

圖2 SCSI四元組

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

Linux中的SCSI模型及iSCSI示例

圖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子系統結合起來形成了整個功能。

Linux中的SCSI模型及iSCSI示例

圖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中,在該函數中完成了所有功能。但該函數是調用其它函數實作的,具體如圖所示。

Linux中的SCSI模型及iSCSI示例

圖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。

Linux中的SCSI模型及iSCSI示例

圖6 啟動連接配接主要流程

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

Linux中的SCSI模型及iSCSI示例

圖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)發送到存儲端。

繼續閱讀