首頁 > 軟體

RAID1原始碼分析

2020-06-16 17:56:56

  正確寫流程的總體步驟是,raid1接收上層的寫bio,申請一個r1_bio結構,將其中的所有bios[]指向該bio。假設盤陣中有N塊盤。然後克隆N份上層的bio結構,並分別將每個bios[]指向克隆出來一個bio結構,然後進行相應設定。

  對於沒有Write Behind模式而言,之後將所有這些bios[](共用頁結構)放入佇列pending_list中,對記憶體bitmap置位。接著由守護行程摘取pending_list鏈中的bio,然後將記憶體bitmap同步下刷到磁碟,緊接著立即一次性下發bio,寫成功返回,同時更新bitmap狀態,然後非同步刷磁碟。如圖4所示。

  對於設定了Write Behind模式而言,還需要將接收到的上層bio的頁結構拷貝到WriteMostly盤對應的bios[]中(每個WriteMostly盤對應一份拷貝),之後將所有這些bios[]放入佇列pending_list中,對記憶體bitmap置位。接著由守護行程摘取pending_list鏈中的bio,然後將記憶體bitmap同步下刷到磁碟,緊接著立即一次性下發bio。當只剩下WriteMostly盤未完全寫成功後(即非WriteMostly盤都寫成功了),則認為已經寫成功,返回。等到所有WriteMostly盤真正全部寫完之後才釋放拷貝的頁結構和r1_bio。同時更新bitmap狀態,然後非同步刷磁碟。如圖1、2所示。

  整體的函數呼叫關係、進程切換關係和大體流程,如圖3所示。

圖1 無Write Behind模式的寫流程

圖2 有Write Behind模式的寫流程

圖3  raid1讀流程整體框架圖

  寫流程主要涉及以下函數:

        請求函數make_request

        寫請求下raid1d

        回撥函數raid1_end_write_request

        寫出錯處理raid1d

  下面具體分析寫流程。

1)請求函數make_request

  寫請求封裝成bio後,由md裝置的md_make_request下發請求,md又發給具體的裝置raid1,對應raid1的make_request函數,下面將從raid1的make_request開始理解該部分的流程。總體流程如圖4所示。

圖4  make request函數寫流程整體框架圖

  程式碼的具體分析如下:

  1. 呼叫md_write_start,等待盤陣的超級快更新完成之後繼續下面的步驟。

    1.1    如果不為寫則直接返回。

    1.2    如果陣列為臨時唯讀狀態,則設定為讀寫狀態,設定陣列mddev的MD_RECOVERY_NEEDED位,並喚醒守護行程和同步守護執行緒。

    註:

set_bit(MD_RECOVERY_NEEDED, &mddev->recovery);表示可能需要resync或recovery;

resync使各子裝置上的資料同步,recovery就是恢復資料的過程。

    1.3    如果陣列為安全模式,則設定為不安全模式。

    1.4    如果陣列mddev的in_sync=1,則設定in_sync=0,表示陣列要開始進行寫操作了。喚醒守護行程。

        set_bit(MD_CHANGE_CLEAN, &mddev->flags);也就是將superblock中的MD_SB_CLEAN標誌清掉。

    1.5    同步in_sync標誌到磁碟中陣列超級塊上。

  2.  如果存取要求設定barrier,而MD裝置(這裡是指raid1)不支援設定barrier,則結束bio,立即返回,將-EOPNOTSUPP資訊反饋給上層。

    註:這裡的barrier指的是bio帶有的barrier屬性。

  3.  等待裝置上的barrier消除。

    註:這裡是指raid1自己為同步做的一套barrier。

  4.  申請一個r1_bio結構(該結構主要用於管理raid1的bio),該結構中有一個陣列bios陣列指向對應各磁碟的bio。

  5.  遍歷盤陣中所有盤。

    5.1    如果盤存在,但是阻塞了(Blocked),那麼跳出迴圈等待阻塞消除,重新進入迴圈開頭。(通常由使用者發ioctl設定和清除)

    5.2    如果盤存在,並且盤沒有壞(!Faulty[fp6] ),增加該盤的下發IO計數。

      5.2.1        如果該盤壞了(Faulty),減少該盤的下發IO計數,r1_bio的bio[]陣列中的該盤的bio置NULL。

      5.2.2        將r1_bio的陣列中的該盤指向使用者bio。targets用來表示可用的盤。

    5.3    如果是其他情況(一定是出錯情況),r1_bio的陣列中的該盤的bio置NULL。

  6.  如果盤陣中的可用的盤數量targets小於conf->raid_disks,則說明有的盤壞掉了。那麼就將盤陣設定為降級(R1BIO_Degraded)狀態。

  7.  如果設定了延遲寫,需要將使用者bio的資料通過呼叫alloc_behind_pages函數拷貝一份儲存在behind_pages中。並將盤陣設定為R1BIO_BehindIO狀態。

  8.  設定r1_bio的未完成請求數和延遲寫的未完成請求數都置為0。

  9.  根據使用者bio中的BIO_RW_BARRIER標誌,確定是否設定r1_bio中的barrier標誌。也就是判斷是否要set_bit(R1BIO_Barrier[fp7] , &r1_bio->state)。

    註:根據使用者bio中的標誌,確定是否設定raid-bio中的barrie標誌;

      如果下掛的磁碟不支援barrier操作,則在raid1_end_write_request中加以處理,具體的處理就是在守護行程中重試。

  10. 初始化一個bio_list鏈b1。

  11. 遍歷盤陣中所有盤。

    11.1 對於每個磁碟,克隆一份使用者bio到r1_bio陣列對應元素bios中,並設定相關欄位以及回撥函數raid1_end_write_request。

    11.2 如果設定了延遲寫,則r1_bio中的陣列bios每個元素的bio_vec指向儲存的延遲寫拷貝behind_pages。如果設定了WriteMostly模式,則對盤陣增加一個延遲寫的未完成請求數。

    11.3 r1_bio->remaining記錄還未提交的請求數,這裡每到一個盤都會+1。

    11.4 將克隆的這份bio掛到bio_list 鏈b1中。

  12. 呼叫bitmap_startwrite,通知bitmap進行寫資料塊對應的設定。

  13. 將該克隆的得到的b1(多份相同的bio)加到raid1的pending_bio_list鏈中。

  14. 如果使用者IO為sync io,則喚醒守護行程raid1d,進程切換到raid1d,由守護行程通過操作pending_bio_list鏈,繼續處理r1_bio請求。

2)寫請求下發raid1d

  pending_bio_list所有bio項是一起提交的,retry_list中的r1_bio則是逐個處理。

  如果pending_bio_list佇列不為空(有等待的存取請求),則將這些請求逐一提交。在提交寫請求之前,需要將記憶體bitmap刷磁碟(為了避免掉電等情況下,記憶體中的資料丟失,出現錯誤),保證在資料寫入前完成bitmap的寫入。直到pending_bio_list連結串列的所有請求全部提交。

  正常流程走下來,在這裡就把寫請求下發了。如圖5所示。

圖5 守護行程下發寫請求

3)回撥函數raid1_end_write_request

  總體流程如圖6所示。

  首先我們不考慮出錯流程。假設有5塊盤,其中3塊為WriteMostly盤。當設定了Write Behind時,behind remaining = 3,remaining = 5。

  如果已經返回了1個WriteMostly盤,1個非WriteMostly盤。那麼還剩下2個WriteMostly盤,1個非WriteMostly盤,此時behind remaining = 2,remaining = 3。如果接下來非WriteMostly盤返回,不需要減behind remaining即到了判斷語句behind remaining >= remaining - 1,所以這時該條件成立。那麼設定R1BIO_Returned,endio,通知上層寫請求已經結束。此時只剩下WriteMostly盤,進而達到延遲寫的效果。但是此時r1_bio等相關結構體和behind pages還未釋放。等WriteMostly盤返回之後,save_put_page(), bitmap_endwrite(),釋放behind pages和r1_bio結構。

  如果所有WriteMostly盤都返回了,仍然有非WriteMostly盤未返回,那麼一直有behind remaining

  沒有設定Write Behind的情況比較簡單,參照流程圖和下面的程式碼走讀分析即可理解。

圖6 raid1_end_write_request函數流程

  下面對具體程式碼流程進行分析:

  1.  選出要回撥結束bio的盤號mirror。

  2.  如果請求要求設定barrier,但是下掛的裝置不支援barrier,則設定該盤陣為R1BIO_BarrierRetry狀態。跳到步驟8。

  註:這種情況是RAID1裝置支援barrier bio,但是下層裝置不支援;這裡的barrier和make request中剛開始的時候的barrier的不同,這裡的-EOPNOTSUPP值,是下發之後,下層回撥傳上來的值。而make_request中bio_endio傳入的-EOPNOTSUPP,是將-EOPNOTSUPP回撥給raid1的上層。一個是給接收到的下層裝置的返回資訊,一個是反饋給上層的返回資訊。

  3.  r1_bio->bios[mirror]指標置為NULL。(所指原區域還未釋放,用to_put指標來找)

  4.  如果狀態不是"有效"的(不是uptodate),就將該盤置為出錯。並將盤陣降級處理。

  5.  如果狀態是”有效”的,將盤陣設定為R1BIO_Uptodate。

  6.  記錄這次操作結束的在磁碟上的位置。

  7.  如果有延遲寫。

    7.1    如果該盤是WriteMostly,延遲寫的未完成請求數-1。

    7.2    如果只剩下WriteMostly盤的請求,並且r1_bio的狀態是R1BIO_Uptodate,那麼就認為寫操作成功,endio返回。

    7.3    減少該盤的io下發計數。 

  8.  減少一個remaining,並且檢查是否全部請求都完成了(remaining為0)。如果r1_bio中所有請求都完成了,那麼進入下面流程。表示該請求真的完全完成,可以釋放了相關的結構了。

    8.1    如果R1BIO_BarrierRetry狀態(前面設定過),那麼將這個r1_bio加入retry佇列。跳到retry流程。

    8.2    釋放延遲寫的頁。

    8.3    設定bitmap attr屬性為CLEAN。

    8.4    關於安全模式。

    8.5    end io。

  9.  如果計數為0,把to_put這個bio釋放掉。

  當下發磁碟的寫請求完成後,需要將bitmap記憶體頁中相應的bit清零,然後把bitmap檔案下刷。這些通過守護行程來做,而這個過程不需要等待寫bitmap磁碟檔案完成,因此是非同步的。(由bitmap_daemon_work完成)這裡bitmap不需要同步來做,因為可以保證資料??正確性。即使寫失敗,最多帶來額外的同步,不帶來資料的危害。

4)寫出錯處理raid1d

  如果接收到的上層bio是因為設定了barrier屬性,而子裝置又不支援barrier而失敗的(這個情況只發生在寫操作),則清除r1_bio的barrier屬性,重新提交這個r1_bio。

  守護行程處理這種寫出錯的具體流程如圖7所示。

圖7 守護行程處理barrier bio造成的寫出錯流程

  具體程式碼流程如下:

  1.  清除r1_bio的R1BIO_BarrierRetry和R1BIO_Barrier狀態位。

  2.  增加盤陣中r1_bio->remaining請求數,增加個數為盤陣中盤的個數。

  3.  對於盤陣中的每一個磁碟,克隆master_bio給它,並進行初始化。(其中原failed bio的每個page要逐一複製給新的bio,因為可能存在write behind裝置)。

  4.  下發這個新的bio。

更多詳情見請繼續閱讀下一頁的精彩內容http://www.linuxidc.com/Linux/2015-07/120593p2.htm


IT145.com E-mail:sddin#qq.com