MD(四)進入syncd內部

2021-05-24 05:18:55 字數 3536 閱讀 3800

這些文章已經寫了好幾年了,可能已經過時了。在msn space和qqzone幾經輾轉之後,我想也許這些技術文章還是放在搞技術的部落格中更能幫助人。於是做了乙個艱難的決定,把這些文章一篇篇搬過來!絕對是原創的。

看md_check_recovery中註冊syncd的**,就能知道syncd執行的動作就是函式md_do_sync。這個函式也不是省油的燈!

首先來看這個函式中最前面的乙個迴圈。這個迴圈的作用避免resync/recovery衝突。這裡所謂「衝突」,我的理解就是同乙個裝置處於不同的md中,這些md同時做resync就會發生衝突。用一般的ide硬碟舉個例子來說,假如我們有三個硬碟hda,hdb和hdc,將他們分別建立兩個分割槽,用hd[abc]1三個裝置組建乙個raid-1,同時用hd[abc]2組建乙個raid-5,於是他們都需要做resync,他們各自的syncd都註冊啟動,但是只有乙個能進行resync,另乙個只能被delay。要避免這種衝突應該是出於resync效率的考慮,因為同乙個hd上兩個分離的區域同時做i/o並不是高效的做法。關於這個迴圈是如何完成這項避免衝突的任務的,我也不想多討論,因為對於curr_resync的重用我也還沒有完全理解,而且對於那兩個指標來比較大小的做法我總覺得很奇怪,如果有誰花時間理解了這些內容,希望能跟我講解講解。在這個迴圈中最重要的無非就是match_mddev_units這個函式的呼叫,它能發現衝突的md。

在正式進行resync/recovery之前,先要確定有多少扇區要做resync/recovery。resync與recovery需要的扇區數可能會不同,但通常來講都是裝置的物理大小。接著,就是對resync/recovery做初始化。首先,如果是resync,我們會將recovery_cp作為resync的起始扇區。接著,初始化一些時間戳變數,用作resync/recovery計算速度的資料。在resync/recovery中會用到乙個變數window,它的值被設為32*(page_size/512),也就是256個扇區。window的作用就是將sync的請求分組,每次處理都是一組一組進行,每組之間的間隔就來檢視是否需要釋放cpu一段時間給正常的請求讓讓路,畢竟正常i/o能保證才是我們的目標。

md_do_sync函式裡最重要的就是那個while迴圈。它的結束條件很簡單,那就是將需要sync的扇區都sync完了,所以在這個迴圈之前就已經把要sync的總扇區數都算好了。於是這個迴圈就按從小到大的扇區順序進行同步。同步的動作各級raid各不相同,所以需要同步的raid都會提供sync_request函式來給md_do_sync呼叫。如果是raid-5,我們已經知道它是怎麼做resync/recovery的了。呼叫sync_request需要將要sync的起始扇區號傳給它,這個函式會返回它做了多少扇區的sync。如果返回0,那只能是發生了錯誤。sync_request也從skipped這個引數返回資訊,如果skipped為1,說明這次同步被跳過,那麼關於這次同步的統計資訊就不用進行了。通過觀察raid-5的**,我們很容易會發現,skipped被置1通常就是缺盤的時候,這個時候當然不用sync。

現在應該是來看看標籤repeat這裡的**了。這段**在while迴圈中形成了乙個子迴圈,它的作用就是前面提到的檢查及給正常i/o讓路。它是怎麼做的?首先,需要統計當前sync的速度,sync的速度就是我們要監測和控制的物件。sync速度的演算法是這樣,我們使用乙個迴圈陣列,每乙個陣列元素記錄一次當前sync的位置和時間,那麼

sync速度=(陣列尾位置-陣列頭位置)/(陣列尾的時間-陣列頭的時間)

有了速度我們就可以判斷是否需要給正常i/o讓路了。在支援resync/recovery的raid級中,都可以設定resync的最大和最小速度,當然如果沒有設定這些值,那就會使用md的預設值作為最大和最小速度。於是我們就有了速度要滿足的乙個條件:要保證在最大和最小速度之間。相關**如下:

if (currspeed > speed_min(mddev)) }

可以看到如果當前速度小於等於最小速度,則不用sleep,直接去做下一輪的resync。如果當前速度超過了最大速度,則需要睡上500毫秒,這樣速度就降下來了。這裡我們還能看到乙個有趣的函式is_mddev_idle(),它的作用就是判斷是否md處於空閒狀態,如果不是就需要給正常i/o讓路。

開啟is_mddev_idle()的函式體,可以看到這個函式很簡單,但是能說一說的東西還真不少,畢竟我在這個地方也走過彎路。用一句話來概括的話,這個函式就是用子裝置上正常讀寫的數量來判斷md是否空閒。這個函式會對每乙個子裝置做這麼幾件事:首先,用子裝置上已經記錄的io數量減去sync io數量(就是resync/recovery的io),得到curr_events。然後用curr_events與last_events(上一次的curr_events)做個比較,如果差值超過了乙個閾值,就認為該md不是空閒的。看函式中的這個條件語句: 

if ((curr_events - rdev->last_events + 4096) > 8192) (1)

我想大家應該都會象我一樣有個疑問,為什麼不寫成:

if ((curr_events - rdev->last_events) > 4096) (2)

其實很簡單,因為兩個events都是unsigned long,做減法的話如果被減數比減數小,差值將仍會是乙個正數,而且通常是個很大的正數。這裡加上乙個4096,就是為了將這個異常的正數調整回乙個正常正數。隨之而來的又有兩個問題:為什麼兩個events的差值會出現負數?4096又是怎麼得出來的?**中那段長長的注釋就是為了說明這兩個問題。第乙個問題的答案很簡單,就是因為sync io的數量並不是立即被計入到子裝置的io數中。像這樣的sync io作者稱之為in-flight sync io。至於4096的來歷就是通過對比raid5和raid1的最大可能的in-flight sync io的數量,因為raid5的預設stripe cache數是256,而stripe size是4k,因此它的in-flight sync io最大將只能是2048個扇區;而raid1的sync io它的深度(deepth巨集)是32,最大的bio是64k,算一算就得到了4096個扇區。

雖然上面花了不少的語言去解釋這個函式,但是這裡面卻是隱含著幾個bug。最大的乙個bug就是,4096太不靈活了。我們看raid5的**就知道,raid5的stripe cache的數量是可調的,而且如果記憶體充裕可以調到很大的值,那麼in-flight sync io的數量就有可能超過4096,那麼用4096作為調整值就不夠用了。非常高興的看到最近kernel中raid的patch已經fix了這個bug,更高興的是他的改法跟我的不謀而合,那就是將兩個events作為signed long而不是unsigned long。這樣語句(1)就能寫成(2)。但是這樣改我還是覺得在某些非常極端的條件下會出異常,但是因為非常極端,其實也許很難碰到。

回到md_do_sync(),while迴圈裡的東西好像都說完了,那就該說說md_do_sync結束之前,也就是resync停止之前的一些動作。首先是unplug md的queue,讓sync io盡快返回,然後就等待所有的sync io結束。然後呼叫最後一次raid的sync_request來做收尾。如果在resync/recovery過程中沒有出現錯誤那就分兩種情況:如果resync/recovery是被中途打斷,則將其中斷的位置計入recovery_cp中,否則如果resync/recovery正常完成則將recovery_cp設為maxsector。最後recovery的狀態被設為done,喚醒raid守護執行緒去處理。

docker進入容器內部

docker ps a 檢視狀態 進入容器方式 安裝nsenter wget configure without ncurses make nsenter sudo cp nsenter usr local bin 安裝好之後使用以下命令進入容器 nsenter target 容器pid mount...

imp庫,python進入import內部

warning 更新於2020年 imp庫在python3裡面已經不推薦使用了 imp模組提供了乙個可以實現import語句的介面。使用imp可以用來匯入模組和類。imp.py source 1 imp.py compiled 2 imp.c extension 3 imp.find module ...

shell進入內部命令執行命令

在linux操作過程中,經常會出現進入服務內部進行操作,比如oracle執行查詢,比如orace goden gate等。在使用指令碼程式設計過程中,如何使用指令碼進行內部操作有如下兩種方式 僅接觸到如下兩種,後續有其他再進行補充 以ogg的使用為例進行梳理 方式一 ggsci eod info a...