博客專欄

        EEPW首頁 > 博客 > 關于linux SCSI 子系統

        關于linux SCSI 子系統

        發(fā)布人:電子禪石 時間:2019-12-27 來源:工程師 發(fā)布文章

        Small Computer Systems Interface (SCSI)  是一組標準集,它定義了與大量設備(主要是與存儲相關的設備)通信所需的接口和協議。 Linux? 提供了一種 SCSI  子系統,用于與這些設備通信。Linux 是分層架構的一個很好的例子,它將高層的驅動器(比如磁盤驅動器或光驅)連接到物理接口,比如 Fibre  Channel 或 Serial Attached SCSI(SAS).

        scsi設備:機器外設總線是計算機內部與外設進行通訊的總線,分為IDE總線,SCSI總線和USB總線.IDE總線是PC機上用得最多的總線,其造價比較便 宜.SCSI總線的速度比IDE總線要快得多,不過造價比較高.IDE總線和SCSI總線一般只于硬盤,光驅和掃描儀等,而USB總線則可以用于更多的外 設,且速度更快.一般來說,這三種外設總線是不可以混合使用的,但如果有總線轉換器則可以在一定程度上混合使用,如SCSI總線就可以有向IDE總線進行 轉換的轉換器.

        SCSI-3 的開發(fā)開始于 1993 年,現已成為了一組標準集,可以定義協議、命令集和信令方法。在 SCSI-3 中,包含一組命名為 Ultra 的并行 SCSI 標準和基于串行 SCSI 的協議,比如 IEEE 1394 (FireWire)、Fibre Channel, 、Internet SCSI (iSCSI) 和新興的 SAS。這些標準通過引入存儲網絡技術(比如 FC-AL 或  iSCSI)改變了傳統的存儲理念,將數據速率擴展到了 1 Gbit/s,將最大的可尋址設備數增加到了 100 以上,并將最大的電纜長度擴展到了  25 米。圖 1 展示了從 1986 至 2007 年 SCSI 的數據速率的變化 .

        SCSI 傳輸所采用的協議已經時過境遷,SCSI 命令卻保持了最初的元素。SCSI 命令是在 Command Descriptor Block (CDB) 中定義的。CDB 包含了用來定義要執(zhí)行的特定操作的操作代碼,以及大量特定于操作的參數。

        SCSI 命令支持讀寫數據(各有四個變量)以及很多非數據命令,比如  test-unit-ready(設備是否已就緒)、inquiry(檢索有關目標設備的基本信息)、read-capacity(檢索目標設備的存儲容 量)等等。目標設備支持何種命令取決于設備的類型。發(fā)起者通過 inquiry 命令識別設備類型。表 1 列出了最常用的 SCSI 命令。

        表 1. 常見 SCSI 命令

        命令用途Test unit readyInquiryRequest senseRead capacityReadWriteMode senseMode select

        查詢設備是否已經準備好進行傳輸
        請求設備基本信息
        請求之前命令的錯誤信息
        請求存儲容量信息
        從設備讀取數據
        向設備寫入數據
        請求模式頁面(設備參數)
        在模式頁面配置設備參數


                   借助大約 60 種可用命令,SCSI 可適用于許多設備(包括隨機存取設備,比如磁盤和像磁帶這樣的順序存儲設備)。SCSI 也提供了專門的命令以訪問箱體服務(比如存儲箱體內部當前的傳感和溫度)。

        Linux 內核中的 SCSI 架構

        圖 2 顯示了 SCSI 子系統在 Linux  內核中的位置。內核的頂部是系統調用接口,處理用戶空間調用到內核中合適的目的地的路由(例如 open、read 或  write)。而虛擬文件系統(VFS)  是內核中支持的大多數文件系統的抽象層。它負責將請求路由到合適的文件系統。大多數文件系統都通過緩沖區(qū)緩存來相互通信,這種緩存通過緩存最近使用的數據 來優(yōu)化對物理設備的訪問。接下來是塊設備驅動器層,它包括針對底層設備的各種塊驅動器。SCSI 子系統是這種塊設備驅動器之一。


        與 Linux 內核中的其他主流子系統不同,SCSI 子系統是一種分層的架構,共分為三層。頂部的那層叫做較高層,代表的是內核針對 SCSI  和主要設備類型的驅動器的最高接口。接下來的是中間層,也稱為公共層或統一層。在這一層包含 SCSI  堆棧的較高層和較低層的一些公共服務。最后是較低層,代表的是適用于 SCSI 的物理接口的實際驅動器(參見圖 3)

        圖 3. Linux SCSI 子系統的分層架構


        SCSI 較高層

        SCSI 子系統的較高層代表的是內核(設備級)最高級別的接口。它由一組驅動器組成,比如塊設備(SCSI 磁盤和  SCSI CD-ROM)和字符設備(SCSI 磁帶和 SCSI generic)。較高層接受來自上層(比如 VFS)的請求并將其轉換成 SCSI 請求。較高層負責完成 SCSI 命令并將狀態(tài)信息通知上層。

        SCSI 磁盤驅動器在 ./linux/drivers/scsi/sd.c 內實現。SCSI 磁盤驅動器通過調用        register_blkdev(作為塊驅動器)進行自初始化并通過 scsi_register_driver 提供一組函數以表示所有 SCSI 設備。其中 sd_probe 和 sd_init_command 這兩個函數很重要。只要有新的 SCSI 設備附加到系統, SCSI 中間層就會調用 sd_probe 函數。sd_probe 函數可決定此設備是否由 SCSI 磁盤驅動器管理,如果是,就創(chuàng)建新的 scsi_disk 結構來表示它。sd_init_command 函數將來自文件系統層的請求轉變成 SCSI 讀或寫命令(為完成這個 I/O 請求,sd_rw_intr 會被調用)。

        SCSI 磁帶驅動器在 ./linux/drivers/scsi/st.c 內實現。磁帶驅動器是順序存取設備,會通過        register_chrdev_region 將自身注冊為字符設備。SCSI 磁帶驅動器還提供了一個 probe 函數,稱為 st_probe。該函數會創(chuàng)建一種新磁帶設備并將其添加到稱為        scsi_tapes 的向量。SCSI 磁帶驅動器的獨特之處在于,如果可能,它可以直接從用戶空間執(zhí)行 I/O 傳輸。否則,數據會通過驅動器緩沖被分段。

        SCSI CD-ROM 驅動器在 ./linux/drivers/scsi/sr.c 內實現。CD-ROM        驅動器是另一種塊設備并為 SCSI       磁盤驅動器提供類似的函數集。sr_probe 函數可用來創(chuàng)建 scsi_sd 結構以表示 CD-ROM 設備,并用 register_cdrom 注冊此 CD-ROM。SCSI        磁帶驅動器還會導出 sr_init_command,以將請求轉換成 SCSI CD-ROM 讀或寫請求。

        SCSI generic 驅動器在 ./linux/drivers/scsi/sg.c 內實現。該驅動器允許用戶應用程序向設備發(fā)送 SCSI 命令(比如格式化、模式感知或診斷命令)。通過 sg3utils 包還可以從用戶空間利用 SCSI        generic 驅動器。這個用戶空間包包括多種實用工具,可用來發(fā)送 SCSI 命令和解析這些命令的響應。

        SCSI 中間層

        SCSI 中間層是 SCSI 較高層和較低層的公共服務層(可以在  ./linux/drivers/scsi/scsi.c  內部分地實現)。它提供了很多可供較高層和較低層驅動器使用的函數,因而可以充當這兩層間的連接層。中間層很重要,原因是它抽象化了較低層驅動器 (LLD)的實現,可以在 ./linux/drivers/scsi/hosts.c 中部分地實現。這意味著可以以同樣的方式使用帶不同接口的  Fibre        Channel 主機總線適配器(HBA)。

        低層驅動器注冊和錯誤處理都由 SCSI 中間層提供。中間層還提供了較高層和較低層間的 SCSI 命令排隊。SCSI 中間層的一個重要功能是將來自較高層的命令請求轉換成 SCSI 請求。它也負責管理特定于 SCSI 的錯誤恢復。

        中間層可以連接 SCSI 子系統的較高層和較低層。它接受對 SCSI 事務的請求并對這些請求進行排隊以便處理            (如 ./linux/drivers/scsi/scsi_lib.c 中所示)。當這些命令完成后,它接受來自 LLD 的 SCSI 響應并通知較較高層此請求已經完成。

        中間層最重要的職責之一是錯誤和超時處理。如果 SCSI 命令沒有在合理的時間內完成或者 SCSI  請求返回錯誤,中間層就會管理錯誤或重新發(fā)送此請求。中間層還可管理較高層恢復,比如請求 HBA (LLD) 或 SCSI 設備重置。SCSI  錯誤和超時處理程序在 ./linux/drivers/scsi/scsi_error.c 內實現。

        SCSI 較低層

        在最低層的是一組驅動器,稱為 SCSI 低層驅動器。它們是一些可與物理設備(比如 HBA)鏈接的特定驅動器。LLD 提供了自公共中間層到特定于設備的 HBA 的一種抽象。每個 LLD 都提供了到特定底層硬件的接口,但所使用的到中間層的接口卻是一組標準接口。

        較低層包含大量代碼,原因是它要負責處理各種不同的 SCSI 適配器類型。例如,Fibre Channel 協議包含了針對 Emulex 和 QLogic 的各種適配器的 LLD。面向 Adaptec 和 LSI 的 SAS 適配器的 LLD 也包括在內。


        SCSI 客戶機/服務器模型

        在主機和存儲介質進行通信期間,主機通常充當 SCSI 啟動程序。在計算機存儲中,SCSI 啟動程序是啟動 SCSI 會話的端點,這意味著它會發(fā)送 SCSI 命令。存儲介質通常充當 SCSI 目標,它接收和處理 SCSI 命令。SCSI 目標等待啟動程序的命令,然后提供請求的輸入/輸出數據轉換。

        SCSI 目標通常為啟動程序提供一個或多個邏輯單元號(LUN)。在計算機存儲介質上,LUN 僅是分配給邏輯單元的號碼。邏輯單元是一個 SCSI 協議實體,實際的 I/O 操作只處理這種實體。每個 SCSI 目標可以提供一個或多個邏輯單元;它本身不執(zhí)行 I/O,但代替特定的邏輯單元執(zhí)行。

        在存儲區(qū)域中,LUN 通常表示一個主機能夠執(zhí)行讀寫操作的 SCSI 磁盤。圖 1 顯示 SCSI 客戶機/服務器模型是如何工作的。

        啟動程序首先向目標發(fā)送命令,然后目標解碼命令并向啟動程序請求數據,或將數據發(fā)送給啟動程序。在這之后,目標將狀態(tài)發(fā)送給啟動程序。如果狀態(tài)損壞,啟動程序將向目標發(fā)送一個請求檢測(sense)指令。目標將返回檢測數據,告知啟動程序哪里出錯。

        現在我們研究與存儲相關的 SCSI 命令。

        Linux 通用 SCSI 驅動器

        Linux 中的 SCSI 設備的命名方式能夠幫助用戶識別設備。例如,第一個 SCSI CD-ROM 是  /dev/scd0。SCSI 磁盤的標簽為 /dev/sda、/dev/sdb 和 /dev/sdc 等。當設備初始化完成時,Linux  SCSI 磁盤驅動器接口僅發(fā)送 SCSI READ 和 WRITE 命令。

        這些 SCSI 設備可能具有通用的名稱和接口,比如 /dev/sg0、/dev/sg1 或 /dev/sga、/dev/sgb  等。通過這些通用的 驅動器接口,您就可以將 SCSI 命令直接發(fā)送到 SCSI 設備,而不需要經過在 SCSI  磁盤上創(chuàng)建(并裝載到某個目錄)的文件系統。在圖 2 中,您可以看到不同的應用程序如何與 SCSI 設備通信。 

        圖 2. 與 SCSI 設備通信的各種方式

        通過 Linux 通用驅動器接口,您可以構建能夠向 SCSI 設備發(fā)送更多 SCSI 命令的應用程序。也就是說您又多了一種選擇。要確定哪個 SCSI 設備表示某個 sg 接口,您可以使用 sg_map 命令列出所有映射:

        [root@taomaoy ~]# sg_map -i /dev/sg0  /dev/sda  ATA       ST3160812AS       3.AA /dev/sg1  /dev/scd0  HL-DT-ST  RW/DVD GCC-4244N  1.02


        如何使用 Red Hat 或 Fedora,則要安裝 sg3_utils。現在我們看看如何執(zhí)行典型的 SCSI 系統調用命令。

        典型的 SCSI 通用驅動器命令

        對于字符設備,SCSI 通用驅動器支持許多典型的系統調用,比如 open()、close()、read()、write、poll() 和 ioctl()。向特定的 SCSI 設備發(fā)送 SCSI 命令的步驟也非常簡單:

        1. 打開 SCSI 通用設備文件(比如 sg1)獲取 SCSI 設備的文件描述符。

        2. 準備好 SCSI 命令。

        3. 設置相關的內存緩沖區(qū)。

        4. 調用 ioctl() 函數執(zhí)行 SCSI 命令。

        5. 關閉設備文件。

        典型的 ioctl() 函數類似于:ioctl(fd,SG_IO,p_io_hdr);。

        這里的 ioctl() 函數必須具有 3 個參數:

        1. fd 是設備文件的文件描述符。通過調用 open() 成功打開設備文件之后,將需要獲取這個參數。

        2. SG_IO 表明將 sg_io_hdr 對象作為 ioctl() 函數的第三個參數提交,并且在 SCSI 命令結束時返回。

        3. p_io_hdr 是指向 sg_io_hdr 對象的指針,該對象包含 SCSI 命令和其他設置。

        SCSI 通用驅動器的最重要數據結構是 struct sg_io_hdr,它在 scsi/sg.h 中定義,并且包含如何使用 SCSI 命令的信息。清單 1 給出了這個結構的定義。


        清單 1. sg_io_hdr 結構的定義

        1. typedef struct sg_io_hdr

        2. {

        3.     int interface_id;               /* [i] 'S' (required) */

        4.     int dxfer_direction;            /* [i] */

        5.     unsigned char cmd_len;          /* [i] */

        6.     unsigned char mx_sb_len;        /* [i] */

        7.     unsigned short iovec_count;     /* [i] */

        8.     unsigned int dxfer_len;         /* [i] */

        9.     void * dxferp;                  /* [i], [*io] */

        10.     unsigned char * cmdp;           /* [i], [*i]  */

        11.     unsigned char * sbp;            /* [i], [*o]  */

        12.     unsigned int timeout;           /* [i] unit: millisecs */

        13.     unsigned int flags;             /* [i] */

        14.     int pack_id;                    /* [i->o] */

        15.     void * usr_ptr;                 /* [i->o] */

        16.     unsigned char status;           /* [o] */

        17.     unsigned char masked_status;    /* [o] */

        18.     unsigned char msg_status;       /* [o] */

        19.     unsigned char sb_len_wr;        /* [o] */

        20.     unsigned short host_status;     /* [o] */

        21.     unsigned short driver_status;   /* [o] */

        22.     int resid;                      /* [o] */

        23.     unsigned int duration;          /* [o] */

        24.     unsigned int info;              /* [o] */

        25. } sg_io_hdr_t;  /* 64 bytes long (on i386) */



        不需要用到這個結構中的所有字段,因此這?僅列出最常用的字段:

        • interface_id:一般應該設置為 S。

        • dxfer_direction:用于確定數據傳輸的方向;常常使用以下值之一:

          • SG_DXFER_NONE:不需要傳輸數據。比如 SCSI Test Unit Ready 命令。

          • SG_DXFER_TO_DEV:將數據傳輸到設備。使用 SCSI WRITE 命令。

          • SG_DXFER_FROM_DEV:從設備輸出數據。使用 SCSI READ 命令。

          • SG_DXFER_TO_FROM_DEV:雙向傳輸數據。

          • SG_DXFER_UNKNOWN:數據的傳輸方向未知。

        • cmd_len:指向 SCSI 命令的  cmdp 的字節(jié)長度。

        • mx_sb_len:當 sense_buffer 為輸出時,可以寫回到 sbp 的最大大小。

        • dxfer_len:數據傳輸的用戶內存的長度。

        • dxferp:指向數據傳輸時長度至少為 dxfer_len 字節(jié)的用戶內存的指針。

        • cmdp:指向將要執(zhí)行的 SCSI 命令的指針。

        • sbp:緩沖檢測指針。

        • timeout:用于使特定命令超時。

        • status:由 SCSI 標準定義的 SCSI 狀態(tài)字節(jié)。

        總而言之,當用這種方法傳輸數據時,cmdp 必須指向其長度存儲在 cmd_len 中的 SCSI CDB;sbp 指向最大長度為 mx_sb_len 的用戶內存。如果出現錯誤,將把檢測數據寫回到這個位置。dxferp 指向內存;數據將根據 dxfer_direction 傳輸到 SCSI 設備或從中傳輸出來。

        最后,我們看看 inquiry 命令,以及如何使用通用驅動器執(zhí)行它。

        例子:執(zhí)行一個 inquiry 命令

        inquiry 命令是所有 SCSI 設備實現的最常用的 SCSI 命令。這個命令用于請求 SCSI 設備的基本信息,并且常常用作 ping 操作,以測試 SCSI 設備是否在線。表 2 顯示如何定義 SCSI 標準。

        表 2. inquiry 命令格式定義


        位 7位 6位 5位 4位 3位 2位 1位 0

        字節(jié) 0Operation code = 12h
        字節(jié) 1LUNReservedEVPD
        字節(jié) 2Page code
        字節(jié) 3Reserved
        字節(jié) 4Allocation length
        字節(jié) 5Control

        如果 EVPD 參數位(用于啟用關鍵產品數據)為 0 并且 Page Code 參數字節(jié)為 0,那么目標將返回標準 inquiry 數據。如果 EVPD 參數為 1,那么目標將返回對應 page code 字段的特定于供應商的數據。

        清單 2 顯示了使用 SCSI 通用 API 的源代碼片段。我們先看看設置 sg_io_hdr 的示例。


        清單 2. 設置 sg_io_hdr

        1. struct  sg_io_hdr * init_io_hdr() {

        2.   struct sg_io_hdr * p_scsi_hdr = (struct sg_io_hdr *)malloc(sizeof(struct sg_io_hdr));

        3.   memset(p_scsi_hdr, 0, sizeof(struct sg_io_hdr));

        4.   if (p_scsi_hdr) {

        5.    p_scsi_hdr->interface_id = 'S'; /* this is the only choice we  */

        6.     /* this would put the LUN to 2nd byte of cdb*/

        7.     p_scsi_hdr->flags = SG_FLAG_LUN_INHIBIT;

        8.   }

        9.   return p_scsi_hdr;

        10. }


        11. void destroy_io_hdr(struct sg_io_hdr * p_hdr) {

        12.     if (p_hdr) {

        13.         free(p_hdr);

        14.     }

        15. }


        16. void set_xfer_data(struct sg_io_hdr * p_hdr, void * data, unsigned int length) {

        17.     if (p_hdr) {

        18.         p_hdr->dxferp = data;

        19.         p_hdr->dxfer_len = length;

        20.     }

        21. }


        22. void set_sense_data(struct sg_io_hdr * p_hdr, unsigned char * data,

        23.         unsigned int length) {

        24.     if (p_hdr) {

        25.         p_hdr->sbp = data;

        26.         p_hdr->mx_sb_len = length;

        27.     }

        28. }


        這些函數還用于設置 sg_io_hdr 對象。其中的一些字段指向用戶空間內存;當執(zhí)行完畢時,來自 SCSI 命令的 inquiry 輸出數據將復制到 dxferp 指向的內存。如果出現錯誤并且需要檢測數據,檢測數據將復制到 sbp 指向的位置。清單 3 顯示了一個向 SCSI 目標發(fā)送 inquiry 命令的示例。


        清單 3. 向 SCSI 目標發(fā)送 inquiry 命令

        1. int execute_Inquiry(int fd, int page_code, int evpd, struct sg_io_hdr * p_hdr) {

        2.     unsigned char cdb[6];

        3.     /* set the cdb format */

        4.     cdb[0] = 0x12; /*This is for Inquery*/

        5.     cdb[1] = evpd & 1;

        6.     cdb[2] = page_code & 0xff;

        7.     cdb[3] = 0;

        8.     cdb[4] = 0xff;

        9.     cdb[5] = 0; /*For control filed, just use 0 */

        10.     

        11.     p_hdr->dxfer_direction = SG_DXFER_FROM_DEV;

        12.     p_hdr->cmdp = cdb;

        13.     p_hdr->cmd_len = 6;


        14.     int ret = ioctl(fd, SG_IO, p_hdr);

        15.     if (ret<0) {

        16.         printf("Sending SCSI Command failed.\n");

        17.         close(fd);

        18.         exit(1);

        19.     }

        20.     return p_hdr->status;

        21. }


        因此,這個函數首先根據 inquiry 標準格式準備 CDB,然后調用 ioctl() 函數,提交文件描述符 SG_IO 和 sg_io_hdr 對象;返回的狀態(tài)存儲在 sg_io_hdr 對象的 status 字段中。

        現在我們看看應用程序如何使用這個函數執(zhí)行 inquiry 命令,如清單 4 所示:


        清單 4. 應用程序執(zhí)行 inquiry 命令

        1. unsigned char sense_buffer[SENSE_LEN];

        2. unsigned char data_buffer[BLOCK_LEN*256];

        3. void test_execute_Inquiry(char * path, int evpd, int page_code) {

        4.     struct sg_io_hdr * p_hdr = init_io_hdr();

        5.     set_xfer_data(p_hdr, data_buffer, BLOCK_LEN*256);

        6.     set_sense_data(p_hdr, sense_buffer, SENSE_LEN);

        7.     int status = 0;

        8.     int fd = open(path, O_RDWR);

        9.     if (fd>0) {

        10.         status = execute_Inquiry(fd, page_code, evpd, p_hdr);

        11.         printf("the return status is %d\n", status);

        12.         if (status!=0) {

        13.             show_sense_buffer(p_hdr);

        14.         } else{

        15.             show_vendor(p_hdr);

        16.             show_product(p_hdr);

        17.             show_product_rev(p_hdr);

        18.         }

        19.     } else {

        20.         printf("failed to open sg file %s\n", path);

        21.     }

        22.     close(fd);

        23.     destroy_io_hdr(p_hdr);

        24. }


        發(fā)送 SCSI 命令的步驟非常簡單。首先必須分配用戶空間數據緩沖區(qū)和檢測緩沖區(qū),并將它們指向 sg_io_hdr 對象。然后打開設備驅動器并獲取文件描述符。有了這些參數之后,就可以將 SCSI 命令發(fā)送到目標設備。當這個命令完成時,SCSI 目標的輸出將被復制到用戶空間緩沖區(qū)。


        清單 5. 使用參數將 SCSI 命令發(fā)送到目標設備

        1. void show_vendor(struct sg_io_hdr * hdr) {

        2.     unsigned char * buffer = hdr->dxferp;

        3.     int i;

        4.     printf("vendor id:");

        5.     for (i=8; i<16; ++i) {

        6.         putchar(buffer[i]);

        7.     }

        8.     putchar('\n');

        9. }


        10. void show_product(struct sg_io_hdr * hdr) {

        11.     unsigned char * buffer = hdr->dxferp;

        12.     int i;

        13.     printf("product id:");

        14.     for (i=16; i<32; ++i) {

        15.         putchar(buffer[i]);

        16.     }

        17.     putchar('\n');

        18. }


        19. void show_product_rev(struct sg_io_hdr * hdr) {

        20.     unsigned char * buffer = hdr->dxferp;

        21.     int i;

        22.     printf("product ver:");

        23.     for (i=32; i<36; ++i) {

        24.         putchar(buffer[i]);

        25.     }

        26.     putchar('\n');

        27. }

        28. int main(int argc, char * argv[]) {

        29.     test_execute_Inquiry(argv[1], 0, 0);

        30.     return EXIT_SUCCESS;

        31. }


        SCSI Inquiry Command(Page Code 和 EVPD 字段皆設置為 0)的標準響應很復雜。根據標準,供應商  ID 從第 8 字節(jié)擴展到第 15 字節(jié),產品 ID 從第 16 字節(jié)擴展到第 31 字節(jié),產品版本從第 32 字節(jié)擴展到第 35  字節(jié)。必須獲取這些信息,以檢查命令是否成功執(zhí)行。


        SCSI command 的所有指令   指令含義

        文章內容來自
        Linux SCSI 子系統剖析 
        探索 Linux 通用 SCSI 驅動器


        *博客內容為網友個人發(fā)布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



        關鍵詞: 驅動

        相關推薦

        技術專區(qū)

        關閉
        主站蜘蛛池模板: 台州市| 兴化市| 鄱阳县| 宜春市| 宜兴市| 承德县| 黎平县| 常山县| 綦江县| 贵定县| 会宁县| 武城县| 英吉沙县| 克山县| 边坝县| 尉犁县| 清水河县| 维西| 英吉沙县| 建水县| 昆明市| 宜春市| 文成县| 论坛| 定安县| 三明市| 皮山县| 新田县| 锡林郭勒盟| 苏尼特右旗| 大荔县| 海林市| 昌宁县| 崇文区| 鹤山市| 辰溪县| 甘泉县| 阿瓦提县| 左云县| 湖北省| 卫辉市|