2021-05-12 14:32:11
RAID1原始碼分析
正確寫流程的總體步驟是,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
相關文章