版權宣告:本文為本文為博主原創文章,轉載請註明出處。如有問題,歡迎指正。部落格地址: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上兩個核心之間的時間漂移。如下:
回到問題本身,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測試原理為:
-
找一個時間點作為測試起始點,此時xenomai時間表示為
first_clock
,Linux絕對時鐘時間表示為first_tod
,它們均為一個數,單位納秒ns。 -
讓測量任務睡眠,睡眠時間是一個範圍的隨機數,睡眠範圍為:[1000000, 200000)納秒(這裡的睡眠時間至少1000000是因為讀取linux時鐘的函式(
SYS_gettimeofday
)精度只能讀取到us級,而xenomai讀取到的時間為ns級,為了使差距與us對齊,所以至少經過1ms,簡而言之計算週期1ms單位的漂移)。 -
讀取睡眠後的各自時鐘的計數值,讀取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} \]
三、 讀linux時鐘時間
在clock工具中讀取Linux參考時鐘時間使用系統呼叫syscall(SYS_gettimeofday, &tv, NULL)
;
系統函式do_gettimeofday()
讀取全域性時鐘timekeeper的值xtime_sec
,然後加上系統上一個tick到此時的納秒數nsece
,nsece
是直接呼叫timekeeper使用的clocksouse對應的讀函式讀取clocksouse counter計算得到的,也可看到底層讀取到的精度是納秒級的,只不過在上一個函式將精度丟棄了。
四、 讀xenomai時鐘時間
在clock工具中讀取xenomai時鐘時間的函式是static inline uint64_t read_clock(clockid_t clock_id)
;
read_clock()
函式呼叫系統呼叫函式clock_gettime()
,這是一個POSIX標準函式,在xenomai中kernel\xenomai\posix\clock.c
實現如下:
clock_gettime()
繼續呼叫__cobalt_clock_gettime()
來獲取時鐘,在colocktest中傳入的引數是:CLOCK_REALTIME
。進而呼叫核心函式xnclock_read_realtime (struct xnclock *clock)
讀取時間,再通過ns2ts函式將讀取的到的納秒轉換為需要的timespec結構體中的tv_sec
和tv_nsec
:
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()
函式:
xnclock_core_read_monotonic()
函式中由xnclock_core_ticks_to_ns()
函式將xnclock_core_read_raw()
函式返回的TSC CPU tick數轉換為納秒ns返回,這就是讀取的xenomai時鐘時間。怎樣獲取CPU的TSC值呢?在X86處理器中有一條指令rdtsc
用於讀取TSC值 。
到這,整個xenomai時間讀取流程完了,就是讀取TSC的值,沒其他的了,看似沒有什麼問題。難道真的x86中的TSC不準?注意到讀取的TSC數值還需要轉換才能得到時間,轉換函式xnarch_llmulshft()
,涉及到這兩個變數tsc_scale,tsc_shift
,懷疑是這兩個值有問題,繼續分析,那這兩個值是幹嘛用的?
當已知頻率F,要將A個cycles數轉換成納秒,具體公式如下:
這樣的轉換公式需要除法,絕大部分的CPU都有乘法器,但是有些處理器是不支援除法,雖然我們無法將除法操作的程式碼編譯成一條除法的彙編指令,但是也可以用程式碼庫中的其他運算來取代除法。這樣做的壞處就是效能會受影響。把1/F變成浮點數,這樣就可以去掉除法了,但是又引入了浮點運算,kernel是不建議使用浮點運算的。解決方案很簡單,使用移位操作,具體可以參考clocksource_cyc2ns
的操作:
通過TSC的xnclock_core_read_raw()
函式獲取了tick數目,乘以mult
這個因子然後右移shift
個bit就可以得到納秒數。這樣的操作雖然效能比較好,但是損失了精度(通過另外驗證下面程式碼算出的值,以這臺機器的2700M算出的值,帶入2700Mcycle得1000000230ns,有200納秒左右的偏移),還是那句話,設計是平衡的藝術,看你自己的取捨。
那tsc_scale,tsc_shift
在哪裡計算的呢?
具體計算在xnarch_init_llmulshft()
函式中計算:
如何獲取最佳的tsc_scale
和tsc_shift
組合?當一個公式中有兩個可變數的時候,最好的辦法就是固定其中一個,求出另外一個,然後帶入約束條件進行檢驗。我們首先固定shift這個引數。mult
這個因子一定是越大越好,mult
越大也就是意味著shift
越大。當然shift
總有一個起始值,我們設定為32bit,因此tsc_shift
從31開始搜尋,看看是否滿足最大時間範圍的要求。如果滿足,那麼就找到最佳的mult
和shift
組合,否則要tsc_shift
遞減,進行下一輪搜尋。
先考慮如何計算mult
值。根據公式(cycles * mult) >> shift
可以得到ns數,由此可以得到計算mult
值的公式:
如果我們設定ns數是10^9納秒(也就是1秒)的話,cycles數目就是頻率值。因此上面的公式可以修改為:
看看上面的公式,再對照程式碼,一切就很清晰了。
那到這自然也就想到,這個freq值就是TSC對應的CPU的頻率值了。那上面的函式是由誰呼叫計算的?CPU的頻率值freq在哪獲取的?這些值在xenomai核初始化時計算。
五、xenomai xnclock初始化
在xenomai核啟動函式xenomai_init()
中,由mach_setup()
函式完成xenomai域相關定時器、中斷、時鐘設定。在mach_setup()
函式中首先呼叫ipipe_select_timers()
從全域性timers連結串列中為每一個CPU選擇一個具有最高評級的clock_event_device
作為該cpu的percpu_timer
。而timers
是每一個clock_event_device
在register的時候, 由 ipipe_host_timer_register()
將該clock_event_device
新增到連結串列timers
上的。
當一個CPU找到一個合適的clock_event_device
的時候,就回撥用install_pcpu_timer()
設定該clock_event_device
為該CPU的percpu_timer
,並配置該ipipe_timer
頻率與CPU頻率的轉換因子(引數c2t_integ
,c2t_frac
, ipipe_timer_set
中用到,而ipipe_timer_set
常被__xnsched_run
呼叫,推測與任務時間片計算有關,有時間再來分析),同時設定CPU的ipipe_percpu.hrtimer_irq
為該timer
的中斷號。而這裡使用的 CPU頻率值為__ipipe_hrclock_freq
,他的定義如下;
接著mach_setup()
中呼叫ipipe_get_sysinfo(&sysinfo)
獲取系統的資訊,系統online的cpu數,cpu的頻率,這個頻率也是使用上面cpu_khz
的值。另外將0號cpu的hrtimer_irq
作為系統hrtimer的中斷號,並且設定sys_hrtimer_freq
的頻率(這個頻率是具體percpu_timer
的頻率lapic-timer或者HPET),同樣sys_hrclock_freq
也是cpu_khz
的值。這些資訊在下面的初始化中要用到。
接下來將cobalt_pipeline.timer_freq
設定為timerfreq_arg
也就是sysinfo
中的sys_hrtimer_freq
。
cobalt_pipeline.clock_freq
為sysinfo中的那個cpu_khz
得來的sys_hrclock_freq
。
下面註冊兩個虛擬中斷到ipipe,一個為cobalt_pipeline.apc_virq
,連結到root_domain
,處理linux掛起恢復。另一個為cobalt_pipeline.escalate_virq
,註冊域為xnsched_realtime_domain
,handler為__xnsched_run_handler
,一看這就是與xenomai排程相關的。
下面就是這裡主要的初始化xnclock的xnclock_init()
函式了。
需要注意的是xnclock_init()
函式傳的引數是cobalt_pipeline.clock_freq
,也就是那個cpu_khz
得來cobalt_pipeline.clock_freq
。
首先第一步做的就是求轉換因子tsc_scale
, tsc_shift
,執行xnclock_update_freq()
,到這就是熟悉的xnarch_init_llmulshft(1000000000, freq, &tsc_scale, &tsc_shift)
,在上面的分析中提到過,下面去看cpu_khz
在哪裡獲取到的。其他的不在這裡不分析。
![image-20200702191733579](D:\文件\原始碼筆記\xenomai blogs\實時核心與linux核心時鐘漂移過大原因.assets\image-20200702191733579.png)
六、 TSC init
\arch\x86\kernel\tsc.c
![image-20200702191841659](D:\文件\原始碼筆記\xenomai blogs\實時核心與linux核心時鐘漂移過大原因.assets\image-20200702191841659.png)
這裡很明瞭了,cpu_kh
和tsc_khz
是兩個值,而xenomai使用cpu_khz
去算tsc與納秒的轉換因子,如果cpu_kh
和tsc_khz
相等那沒有問題,但通過新增除錯輸出,這兩個值是不等的。
![image-20200702191953017](D:\文件\原始碼筆記\xenomai blogs\實時核心與linux核心時鐘漂移過大原因.assets\image-20200702191953017.png)
![image-20200702191959548](D:\文件\原始碼筆記\xenomai blogs\實時核心與linux核心時鐘漂移過大原因.assets\image-20200702191959548.png)
剛好下面的條件判斷沒有對tsc_khz
與cpu_khz
不相等做處理。
![image-20200702192026940](D:\文件\原始碼筆記\xenomai blogs\實時核心與linux核心時鐘漂移過大原因.assets\image-20200702192026940.png)
這臺機器上導致計算tsc_scale
, tsc_shift
時使用的是2700Mhz,而TSC的頻率是2712MHZ,用2700MHZ得來的tsc_scale
, tsc_shift
去轉換2712MHZ產生的cycles當然不對,每秒就會有12M的漂移,也就是每週期漂移\(\frac{12MHZ}{2700MHZ}=0.004444444\)秒,轉換為(微秒/秒)就是\(4444.4444(us/s)\).與機器上實際測試相符:
解決辦法:
當tsc_khz
不為0時,直接 cpu_khz=tsc_khz
修改後clocktest測試如下:
附:xenomai核心的一些時鐘資訊:
$cat /proc/xenomai/clock/coreclok
gravity: irq=100 kernel=1341 user=1341
devices: timer=lapic-deadline, clock=tsc
status: on
setup: 100
ticks: 443638843357 (0067 4aef87dd)
修改後:
$cat /proc/xenomai/clock/coreclok
gravity: irq=99 kernel=1334 user=1334
devices: timer=lapic-deadline, clock=tsc
status: on
setup: 99
ticks: 376931548560 (0057 c2defd90)
lapic-deadline 是上面解析的CPU0 的percpu_timer,deadline表示lapic-timer支援deadline事件觸發;
關於xenomai的時間子系統後續會有整理分析文章,敬請關注!!!。
後面一直沒有使用4.14及以上的核心,不知道現在還有沒這個問題。大家可以看看,再提個issue或者啥的.....