1. 程式人生 > >【原創】解BUG-xenomai核心與linux核心時間子系統之間存在漂移

【原創】解BUG-xenomai核心與linux核心時間子系統之間存在漂移

版權宣告:本文為本文為博主原創文章,轉載請註明出處。如有問題,歡迎指正。部落格地址:https://www.cnblogs.com/wsg1100/ ## 一、問題起源 >何為漂移?舉個例子兩顆32.768kHz晶振$C_1$和$C_2$,由於製造工藝原因或者使用時溫度、輔助元件引數等影響,與他們的實際頻率一定不是相同的,與32.768kHz有不同的偏差,假如$C_1$實際使用時頻率32.766kHz,$C_2$實際頻率32.770kHz。 > >假如有那麼兩個電子手錶,使用32.768kHz晶振,每來一個脈衝暫存器計數加1,我們通過這個電路來獲取時間,這樣計算1s的時間暫存器裡應該是32.768kHz(我們認為我們的晶振是沒問題的嘛)。好現在用$C_1$來計數就會使我們得到的時間比真實1S長$\frac{1}{2000}=0.0005$秒,這樣下來這個手錶會越走越快,即與真實時間的偏移越來越大。同樣$C_2$得到的時間比真實1S短$0.0005$秒,越走越慢。 > >兩個手錶在它們的計時週期(這裡舉例1秒)存在的偏差就是漂移。 X86平臺上,linux 4.4.xx之後的版本構建的xenomai,出現**linux核心**與**xenomai核心**兩者時鐘存在漂移,打xenomai補丁之後,有兩個核心分別有各自的時間子系統,只不過xenomai掌管著底層的硬體`timer-event`的中斷觸發設定和處理,linux時間子系統的觸發源就退化為xenomai時間子系統管理的軟體timer了(linux是xenomai的idle任務嘛,當然要xenomai來提供時鐘),本質上它們還是使用同一個硬體timer源。 > 此問題解決時本人還未閱讀xenomai的時間子系統相關原始碼,所以其中有些解釋現在看起來·只見樹木不見森林·,懶得改了,關於xenomai的時間子系統後續會有分析文章,敬請關注!!!。 構建xenomai系統後,linux核心與xenomai核心兩個時間子系統之間的時間漂移可通過xenomai庫編譯出的工具`clocktest`來檢視,其中的`dirft`列就是表示該cpu上兩個核心之間的時間漂移。如下: ![image-20200702192057651](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702192057651.png) 回到問題本身,xenomai來常用來執行ethercat主站,主站DC模式下同步執行時,出現的現象是主站本地時間永遠無法與參考時鐘同步,導致每週期主站都需要讀回參考時鐘進行調整; 下面分析問題:主站由xenomai實時排程,ethercat主站工作過程中使用的是xenomai時間子系統根據底層硬體timer計算得到的時間,ethercat主站在這個時間上去同步參考時鐘,增加或減少偏移量。先不管硬體timer與真實時間的偏移,非常小先忽略,這不是重點,兩個核心都使用這個硬體timer,現在出現的問題是兩個核心對同一硬體timer的度量不一致,才會存在漂移。 由此可以推斷出xenomai時間子系統對硬體timer的度量計算有問題,下面開始從一步步挖掘分析。 ## 二、 clocktest工具分析 `clocktest`工具主要用於測試xenomai 時鐘(`CLOCK_REALTIME`、`CLOCK_MONOTONIC`、`CLOCK_MONOTONIC_RAW`、`CLOCK_HOST_REALTIME`、coreclk預設`CLOCK_REALTIME`),相對於Linux絕對時鐘`CLOCK_MONOTONIC`之間的**漂移**,clocktest首先為每個CPU建立一個執行緒cpu_thread,並固定到相應CPU上執行,cpu_thread測試原理為: 1. 找一個時間點作為測試起始點,此時xenomai時間表示為`first_clock`,Linux絕對時鐘時間表示為`first_tod`,它們均為一個數,單位納秒ns。 2. **讓測量任務睡眠,睡眠時間是一個範圍的隨機數,睡眠範圍為:[1000000, 200000)納秒(這裡的睡眠時間至少1000000是因為讀取linux時鐘的函式(`SYS_gettimeofday`)精度只能讀取到us級,而xenomai讀取到的時間為ns級,為了使差距與us對齊,所以至少經過1ms,簡而言之計算週期1ms單位的漂移)。** 3. 讀取睡眠後的各自時鐘的計數值,讀取xenomai時鐘讀取到的值為`clock_val`,Linux時間計數值為`first_tod`同樣的時間段,xenomai時鐘計數為`clock_val`-`first_clock`,Linux時間計數值`tod_val`-`first_tod`;這個時間段的偏移率為: $$\frac{clock\_val-first\_clock}{tod_val-first_tod}$$ ![image-20200702185626887](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185626887.png) ## 三、 讀linux時鐘時間 在clock工具中讀取Linux參考時鐘時間使用系統呼叫`syscall(SYS_gettimeofday, &tv, NULL)`; ![image-20200702185737947](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185737947.png) ![image-20200702185743615](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185743615.png) 系統函式`do_gettimeofday()`讀取全域性時鐘timekeeper的值`xtime_sec`,然後加上系統上一個tick到此時的納秒數`nsece`,`nsece`是直接呼叫timekeeper使用的clocksouse對應的讀函式讀取clocksouse counter計算得到的,也可看到底層讀取到的精度是納秒級的,只不過在上一個函式將精度丟棄了。 ![image-20200702185819000](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185819000.png) ![image-20200702185825121](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185825121.png) ## 四、 讀xenomai時鐘時間 在clock工具中讀取xenomai時鐘時間的函式是`static inline uint64_t read_clock(clockid_t clock_id)`; ![image-20200702185859839](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185859839.png) `read_clock()`函式呼叫系統呼叫函式`clock_gettime()`,這是一個POSIX標準函式,在xenomai中`kernel\xenomai\posix\clock.c`實現如下: ![image-20200702185924171](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185924171.png) ![image-20200702185930882](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702185930882.png) `clock_gettime()`繼續呼叫`__cobalt_clock_gettime()`來獲取時鐘,在colocktest中傳入的引數是:`CLOCK_REALTIME`。進而呼叫核心函式`xnclock_read_realtime (struct xnclock *clock)`讀取時間,再通過ns2ts函式將讀取的到的納秒轉換為需要的timespec結構體中的`tv_sec`和`tv_nsec`: ![image-20200702190018198](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190018198.png) `xnclock_read_realtime`返回 nkclock的時間加上一個與wallclock的偏移(clock->wallclock_offset), (nkclock是xenomai的時鐘源,型別為`struct xnclock`,當沒有使用外部時鐘時,時鐘使用**X86處理器中的TSC時鐘(早期X86CPU中TSC與CPU的頻率有關,現在的CPU TSC頻率一般是固定的)**,當使用外部時鐘作為xenomai的時鐘時是另外一回事)。 `xnclock_read_monotonic()`最終呼叫`xnclock_core_read_monotonic()`函式: ![image-20200702190059340](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190059340.png) `xnclock_core_read_monotonic()`函式中由`xnclock_core_ticks_to_ns()`函式將`xnclock_core_read_raw()`函式返回的TSC CPU tick數轉換為納秒ns返回,這就是讀取的xenomai時鐘時間。怎樣獲取CPU的TSC值呢?在X86處理器中有一條指令`rdtsc`用於讀取TSC值 。 ![image-20200702190133392](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190133392.png) ![image-20200702190139364](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190139364.png) 到這,整個xenomai時間讀取流程完了,就是讀取TSC的值,沒其他的了,看似沒有什麼問題。難道真的x86中的TSC不準?注意到讀取的TSC數值還需要轉換才能得到時間,轉換函式`xnarch_llmulshft()`,涉及到這兩個變數`tsc_scale,tsc_shift`,懷疑是這兩個值有問題,繼續分析,那這兩個值是幹嘛用的? ![image-20200702190207700](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190207700.png) 當已知頻率F,要將A個cycles數轉換成納秒,具體公式如下: $$轉換後的納秒數 =\frac{A}{F}*1000000000$$ 這樣的轉換公式需要除法,絕大部分的CPU都有乘法器,但是有些處理器是不支援除法,雖然我們無法將除法操作的程式碼編譯成一條除法的彙編指令,但是也可以用程式碼庫中的其他運算來取代除法。這樣做的壞處就是效能會受影響。把1/F變成浮點數,這樣就可以去掉除法了,但是又引入了浮點運算,kernel是不建議使用浮點運算的。解決方案很簡單,使用移位操作,具體可以參考`clocksource_cyc2ns`的操作: ![image-20200702190518788](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190518788.png) 通過TSC的`xnclock_core_read_raw()`函式獲取了tick數目,乘以`mult`這個因子然後右移`shift`個bit就可以得到納秒數。這樣的操作雖然效能比較好,但是損失了精度(通過另外驗證下面程式碼算出的值,以這臺機器的2700M算出的值,帶入2700Mcycle得1000000230ns,有200納秒左右的偏移),還是那句話,設計是平衡的藝術,看你自己的取捨。 那`tsc_scale,tsc_shift`在哪裡計算的呢? 具體計算在`xnarch_init_llmulshft()`函式中計算: ![image-20200702190602567](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200702190602567.png) 如何獲取最佳的`tsc_scale`和`tsc_shift`組合?當一個公式中有兩個可變數的時候,最好的辦法就是固定其中一個,求出另外一個,然後帶入約束條件進行檢驗。我們首先固定shift這個引數。`mult`這個因子一定是越大越好,`mult`越大也就是意味著`shift`越大。當然`shift`總有一個起始值,我們設定為32bit,因此`tsc_shift`從31開始搜尋,看看是否滿足最大時間範圍的要求。如果滿足,那麼就找到最佳的`mult`和`shift`組合,否則要`tsc_shift`遞減,進行下一輪搜尋。 先考慮如何計算`mult`值。根據公式`(cycles * mult) >> shift`可以得到ns數,由此可以得到計算`mult`值的公式: $$mult=\frac{ns<