歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> linux內核md源代碼解讀 七 陣列同步一 :介紹陣列同步

linux內核md源代碼解讀 七 陣列同步一 :介紹陣列同步

日期:2017/3/3 16:17:33   编辑:Linux內核

陣列同步在md_do_sync,那麼入口在哪裡呢?就是說陣列同步觸發點在哪裡呢?聽說過md_check_recovery吧,但這還不是同步的入口點。那raid5d函數是入口點吧?如果要認真分析起來還不算是。

真正的同步入口點在do_md_run函數,就是在運行陣列run函數之後,有這麼一行:

5171 md_wakeup_thread(mddev->thread);

是這一行把raid5d喚醒的,raid5d函數如下:

4823 static void raid5d(struct md_thread *thread)  
4824 {  
4825         struct mddev *mddev = thread->mddev;  
4826         struct r5conf *conf = mddev->private;  
4827         int handled;  
4828         struct blk_plug plug;  
4829   
4830         pr_debug("+++ raid5d active\n");  
4831   
4832         md_check_recovery(mddev);

4832行,顧名思義就是檢查同步,說明這裡只是同步的檢查點,不是真正處理同步的地方。

raid5d剩余部分是處理數據流的地方先不看,跟進md_check_recovery,先看注釋:

7672 /* 
7673  * This routine is regularly called by all per-raid-array threads to 
7674  * deal with generic issues like resync and super-block update. 
7675  * Raid personalities that don't have a thread (linear/raid0) do not 
7676  * need this as they never do any recovery or update the superblock. 
7677  * 
7678  * It does not do any resync itself, but rather "forks" off other threads 
7679  * to do that as needed. 
7680  * When it is determined that resync is needed, we set MD_RECOVERY_RUNNING in 
7681  * "->recovery" and create a thread at ->sync_thread. 
7682  * When the thread finishes it sets MD_RECOVERY_DONE 
7683  * and wakeups up this thread which will reap the thread and finish up. 
7684  * This thread also removes any faulty devices (with nr_pending == 0). 
7685  * 
7686  * The overall approach is: 
7687  *  1/ if the superblock needs updating, update it. 
7688  *  2/ If a recovery thread is running, don't do anything else. 
7689  *  3/ If recovery has finished, clean up, possibly marking spares active. 
7690  *  4/ If there are any faulty devices, remove them. 
7691  *  5/ If array is degraded, try to add spares devices 
7692  *  6/ If array has spares or is not in-sync, start a resync thread. 
7693  */

這個函數通常由陣列主線程調用,用於處理同步和超級塊更新等事件。沒有主線程的陣列不需要調用(如線性/raid0),因為這些陣列不需要重建或更新超級塊。

這個函數並不做具體事宜,只是按需啟動陣列同步線程。

當陣列需要同步時,設置MD_RECOVERY_RUNNING標志,並創建同步線程。

當同步線程結束時設置MD_RECOVERY_DONE標志,並喚醒主線程回收同步線程並結束同步。

這個線程還用於移除壞盤(當nr_pending==0時)。

這個函數處理過程如下:

1、需要時更新超級塊

2、同步線程運行時就返回

3、同步線程結束時,回收資源,如果是重建完成則激活熱備盤

4、移除壞盤

5、對降級陣列添加熱備盤

6、有熱備盤但陣列還不是同步狀態,則啟動同步線程

看完了以上的注釋,我已經淚流滿面了,因為寫代碼的哥們太敬業了,把所有精華都已經說出來了,害得像我這種寫個博文已經沒有什麼可寫的了。還好我寫博文只是自娛自樂,如果是拿這個當飯碗還不早就喝西北風了。

說歸說,還是得一行行閱讀代碼:

7694 void md_check_recovery(struct mddev *mddev)  
7695 {  
7696         if (mddev->suspended)  
7697                 return;  
7698   
7699         if (mddev->bitmap)  
7700                 bitmap_daemon_work(mddev);  
7701   
7702         if (signal_pending(current)) {  
7703                 if (mddev->pers->sync_request && !mddev->external) {  
7704                         printk(KERN_INFO "md: %s in immediate safe mode\n",  
7705                                mdname(mddev));  
7706                         mddev->safemode = 2;  
7707                 }  
7708                 flush_signals(current);  
7709         }  
7710   
7711         if (mddev->ro && !test_bit(MD_RECOVERY_NEEDED, &mddev->recovery))  
7712                 return;  
7713         if ( ! (  
7714                 (mddev->flags & ~ (1<<MD_CHANGE_PENDING)) ||  
7715                 test_bit(MD_RECOVERY_NEEDED, &mddev->recovery) ||  
7716                 test_bit(MD_RECOVERY_DONE, &mddev->recovery) ||  
7717                 (mddev->external == 0 && mddev->safemode == 1) ||  
7718                 (mddev->safemode == 2 && ! atomic_read(&mddev->writes_pending)  
7719                  && !mddev->in_sync && mddev->recovery_cp == MaxSector)  
7720                 ))  
7721                 return;

7696行,檢查陣列是否掛起。陣列掛起是一個管理命令,掛起時可以將IO流hold住。

7699行,bitmap清理操作,等bitmap小節再講。

7702行,接收到信號,進入safemode。

7711行,只讀陣列並且未設置檢查標志則返回。

7713行,只要一個條件滿足,就繼續檢查,否則返回。

7714行,陣列狀態發生改變,則繼續檢查。

7715行,設置了需要檢查標志,則繼續檢查。

7716行,同步完成,則繼續檢查。

7717行,安全模式且非external,則繼續檢查。

7718行,safemode為2有兩種情況,一是系統重啟時,二是7768行接收到信號時,第一種情況時in_sync為1,第二種情況可以觸發更新超級塊,根據in_sync標志寫回磁盤resync_offset等等。

7723         if (mddev_trylock(mddev)) {  
7724                 int spares = 0;  
...  
7746                 if (!mddev->external) {  
7747                         int did_change = 0;  
7748                         spin_lock_irq(&mddev->write_lock);  
7749                         if (mddev->safemode &&  
7750                             !atomic_read(&mddev->writes_pending) &&  
7751                             !mddev->in_sync &&  
7752                             mddev->recovery_cp == MaxSector) {  
7753                                 mddev->in_sync = 1;  
7754                                 did_change = 1;  
7755                                 set_bit(MD_CHANGE_CLEAN, &mddev->flags);  
7756                         }  
7757                         if (mddev->safemode == 1)  
7758                                 mddev->safemode = 0;  
7759                         spin_unlock_irq(&mddev->write_lock);  
7760                         if (did_change)  
7761                                 sysfs_notify_dirent_safe(mddev->sysfs_state);  
7762                 }  
7763   
7764                 if (mddev->flags)  
7765                         md_update_sb(mddev, 0);  
7766   
7767                 if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) &&  
7768                     !test_bit(MD_RECOVERY_DONE, &mddev->recovery)) {  
7769                         /* resync/recovery still happening */
7770                         clear_bit(MD_RECOVERY_NEEDED, &mddev->recovery);  
7771                         goto unlock;  
7772                 }  
7773                 if (mddev->sync_thread) {  
7774                         reap_sync_thread(mddev);  
7775                         goto unlock;  
7776                 }

7723行,對mddev加鎖,這裡用trylock是因為這裡是raid5主線程調用,如果直接用lock會導致主線程休眠,IO流阻塞。

7746行,external表示超級塊存儲位置,為0表示存儲在陣列所在磁盤上。默認值為0,也許好奇的你會問那可不可以既存儲在外面又存儲在陣列所在磁盤上呢?那只能怪你想像力太豐富了,不過我就是這麼干的,原代碼是不支持的需要自己修改代碼支持這個特性。這個特性的重要性跟數據的重要性成正比。試想為什麼市面上那麼多數據恢復軟件,為什麼會丟數據?根本原因就是metadata丟了,metadata就是寶藏的藏寶圖,再回頭想想就很有必要啦。

7749行,這個判斷就是剛剛7718行的判斷,分支裡代碼也就驗證了之前的說明,這個判斷主要目的是用來更新超級塊的。至於這裡更新超級塊的重要性理解了in_sync的功能就知道了。

7753行,設置in_sync標志。

7754行,設置陣列改變標識。

7755行,設置mddev改變標志。

7757行,設置safemode為0。盡管前面已經講解了安全模式了,這裡再從另外一個角度說一下,safemode標志就像軟件看門狗,在陣列寫數據時設置為0,然後需要在寫完成時喂狗,如果不喂狗那麼陣列為髒需要重新啟動同步,喂狗程序就是safemode_timer定時器。

7760行,更新mddev的sysfs下狀態。

7764行,更新陣列超級塊。

7767行,同步線程正在工作,就沒有必要再去湊熱鬧了。

7773行,同步已完成。

7774行,回收同步線程。

繼續md_check_recovery函數:

7777                 /* Set RUNNING before clearing NEEDED to avoid 
7778                  * any transients in the value of "sync_action". 
7779                  */
7780                 set_bit(MD_RECOVERY_RUNNING, &mddev->recovery);  
7781                 /* Clear some bits that don't mean anything, but 
7782                  * might be left set 
7783                  */
7784                 clear_bit(MD_RECOVERY_INTR, &mddev->recovery);  
7785                 clear_bit(MD_RECOVERY_DONE, &mddev->recovery);  
7786   
7787                 if (!test_and_clear_bit(MD_RECOVERY_NEEDED, &mddev->recovery) ||  
7788                     test_bit(MD_RECOVERY_FROZEN, &mddev->recovery))  
7789                         goto unlock;  
7790                 /* no recovery is running. 
7791                  * remove any failed drives, then 
7792                  * add spares if possible. 
7793                  * Spare are also removed and re-added, to allow 
7794                  * the personality to fail the re-add. 
7795                  */
7796   
7797                 if (mddev->reshape_position != MaxSector) {  
7798                         if (mddev->pers->check_reshape == NULL ||  
7799                             mddev->pers->check_reshape(mddev) != 0)  
7800                                 /* Cannot proceed */
7801                                 goto unlock;  
7802                         set_bit(MD_RECOVERY_RESHAPE, &mddev->recovery);  
7803                         clear_bit(MD_RECOVERY_RECOVER, &mddev->recovery);  
7804                 } else if ((spares = remove_and_add_spares(mddev))) {  
7805                         clear_bit(MD_RECOVERY_SYNC, &mddev->recovery);  
7806                         clear_bit(MD_RECOVERY_CHECK, &mddev->recovery);  
7807                         clear_bit(MD_RECOVERY_REQUESTED, &mddev->recovery);  
7808                         set_bit(MD_RECOVERY_RECOVER, &mddev->recovery);  
7809                 } else if (mddev->recovery_cp < MaxSector) {  
7810                         set_bit(MD_RECOVERY_SYNC, &mddev->recovery);  
7811                         clear_bit(MD_RECOVERY_RECOVER, &mddev->recovery);  
7812                 } else if (!test_bit(MD_RECOVERY_SYNC, &mddev->recovery))  
7813                         /* nothing to be done ... */
7814                         goto unlock;

7780行,設置同步運行狀態。同步狀態有如下:

     /* recovery/resync flags  
     * NEEDED:   we might need to start a resync/recover 
     * RUNNING:  a thread is running, or about to be started 
     * SYNC:     actually doing a resync, not a recovery 
     * RECOVER:  doing recovery, or need to try it. 
     * INTR:     resync needs to be aborted for some reason 
     * DONE:     thread is done and is waiting to be reaped 
     * REQUEST:  user-space has requested a sync (used with SYNC) 
     * CHECK:    user-space request for check-only, no repair 
     * RESHAPE:  A reshape is happening 
     * 
     * If neither SYNC or RESHAPE are set, then it is a recovery. 
     */
#define     MD_RECOVERY_RUNNING     0  
#define     MD_RECOVERY_SYNC     1  
#define     MD_RECOVERY_RECOVER     2  
#define     MD_RECOVERY_INTR     3  
#define     MD_RECOVERY_DONE     4  
#define     MD_RECOVERY_NEEDED     5  
#define     MD_RECOVERY_REQUESTED     6  
#define     MD_RECOVERY_CHECK     7  
#define     MD_RECOVERY_RESHAPE     8  
#define     MD_RECOVERY_FROZEN     9

* NEEDED: 需要啟動同步線程

* RUNNING: 准備啟動或已經有同步線程有運行

* SYNC: 做同步操作

* RECOVER: 嘗試或已經在重建操作

* INTR: 同步中斷

* DONE: 同步完成

* REQUEST: 用戶請求同步

* CHECK: 用戶請求檢查

* RESHAPE: reshape操作

 

這裡有必要解釋一下同步線程的意思,這裡的同步是指廣義的sync,包括狹義的同步和重建,因為同步和重建實際上做的是同一件事情就是把數據從一個地方拷貝到另一個地方。sync是包括syncing和recovery,所以不要一看到同步線程就以為是做同步操作。

正是這些標志指定同步線程下一步該怎麼做,而我們一看到這些標志的時候心裡必須要明白此時線程在做什麼或者應該做什麼。

7804行,這個if分支用於啟動重建操作。

7809行,這個分支用於啟動同步操作。

7812行,既不同步也不重建那就沒有什麼可以做的。

7816                 if (mddev->pers->sync_request) {  
7817                         if (spares) {  
...  
7823                         }  
7824                         mddev->sync_thread = md_register_thread(md_do_sync,  
7825                                                                 mddev,  
7826                                                                 "resync");  
7827                         if (!mddev->sync_thread) {  
...  
7837                         } else
7838                                 md_wakeup_thread(mddev->sync_thread);  
7839                         sysfs_notify_dirent_safe(mddev->sysfs_action);  
7840                         md_new_event(mddev);  
7841                 }  
...  
7850                 mddev_unlock(mddev);  
7851         }

7816行,對於沒有同步線程的陣列來說,就沒什麼事情了。7824行,啟動同步線程。7838行,喚醒同步線程。7839行,更新同步狀態。這時主線程調用md_check_recovery函數就已經結束了,啟動的同步線程來做同步的具體事宜。那麼為什麼主線程自己不做同步而另起一個線程做同步呢?不妨想想現在事情都放在主線程裡做有什麼負面影響?當主線程在同步的時候是不是就不能很及時響應正常數據流了。而且同步和數據流本來就是兩件差異很大的事情。本小節只是介紹到同步入口,同步過程在下一小節開始閱讀。

出處:http://blog.csdn.net/liumangxiong

Copyright © Linux教程網 All Rights Reserved