1.前言
了解Linux中斷子系統,同時也需要了解ARM體系結構中斷處理流程;在熟悉整個軟硬件架構和流程基礎上,才能對流程進行細化,然后找出問題的瓶頸。《2. 梳理中斷處理子系統》
但是所有的優化都離不開一個量化的過程,有個可靠、高效、可讀性強的度量必不可少。《3. 一種測量中斷性能手段》
最后基於此,進行中斷性能的優化。《4.中斷性能優化》
2. 梳理中斷處理子系統
中斷系統涉及到軟硬件兩部分,具體到ARM系統和Linux涉及到很多相關點。
硬件以Cortex-A53為基礎,整個GIC架構包括兩部分:CPU內部的GIC CPU Interface(Cortex-A53 Chapter 9)和CPU外部的GIC external distributor component。
《ARM Cortex-A53 MPCore Processor Technical Reference Manual》簡單介紹了A53核內部的GIC CPU Interface。
《ARM Generic Interrupt Controller Architecture Specification v3/v4》詳細介紹了整個GIC架構的方方面面,具體實現比如GIC-600在《GIC-600 Generic Interrupt ControllerTechnical Reference Manual》。
相關閱讀記錄在《閱讀GIC-500 Technical Reference Manual筆記》。
軟件方面可以參考蝸窩科技關於中斷子系統的一系列文章《Linux中斷子系統》,一共9篇文章,講述了Linux中斷的方方面面。
《綜述》是一個導論性質文檔,從更高層次介紹了中斷相關軟硬件架構;
《IRQ number和中斷描述符》重點介紹了中斷描述符相關數據結構以及API;
在一個中斷出發之后,從CPU架構相模塊進行現場保護《ARM中斷處理過程》-->machine相關中斷處理handler將HW Interrupt ID翻譯成IRQ number《IRQ Domain介紹》-->IRQ number對應中斷例程《High level irq event handler》,以及最終現場恢復流程《ARM中斷處理過程》;
《驅動申請中斷API》是從中斷使用者角度介紹如何使用中斷;中斷處理的下半部包括《softirq》和《tasklet》,以及workqueue 1 2 3 4。
《GIC代碼分析》重點介紹了ARM架構下中斷控制器的方方面面。
3. 一種測量中斷性能手段
3.1 明確評估標的
評估一個系統的中斷性能,首先要明確評估那一段處理的性能。這里評估的是從中斷觸發開始,到執行中斷例程(ISR)這段處理。
這一段從外部設備觸發中斷,到中斷控制器,再到CPU處理,直到ISR的調用執行,涉及到軟硬件的方方面面。
3.2 如何對標的進行量化
從硬件觸發開始到軟件ISR執行時間度量,跨軟硬件。測量起來難免會有誤差,尤其是兩者的時間軸問題不易同步。
好在Linux有周期性Timer,周期性Timer設置一個Load值,從Load開始倒計數,計數到達0的時候觸發中斷。
然后重新計數,並且可以隨時讀取當前計數值。
在ISR中讀取計數,就可以知道從上次0計數觸發中斷到當前消耗的Cycle數目,進而得到標的耗時。
3.3 內核實現
內核中主要注冊中斷、提供修改Load接口、創建proc節點。
中斷相關初始化,注冊中斷處理函數。在ISR中進行Timer Cycle的讀取。
提供修改Load接口,供動態修改Load,以達到在不同頻率下測試標的的目的。
選取不同頻率的load: echo n > /proc/interrupt_stats 讀取Timer中斷統計信息: cat /proc/interrupt_stats > interrupt_xxx.txt
proc文件提供設置Load和讀取標的結果的接口。
//=================================================== #include <asm/uaccess.h> extern unsigned int interrupt_statiscs[1024][2]; extern unsigned int interrupt_period_count; extern void interrupt_set_period(unsigned int cycles); static int interrupts_proc_show(struct seq_file *m, void *v) { int i; for(i = 0; i < sizeof(interrupt_statiscs)/sizeof(interrupt_statiscs[0]); i++) seq_printf(m, "%u, %u, %u\n", interrupt_period_count, interrupt_statiscs[i][0], interrupt_statiscs[i][1]); return 0; } static ssize_t interrupts_proc_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { unsigned int period_cycles; char buf[1]; if (copy_from_user(buf, user_buf, 1)) return -EFAULT; sscanf(buf, "%u", &period_cycles); printk("%s period_cycles %u\n", __func__, period_cycles); if (period_cycles > 0 && period_cycles < 5) { switch(period_cycles) { case 0: period_cycles = 2600000; break; case 1: period_cycles = 260000; break; case 2: period_cycles = 26000; break; case 3: period_cycles = 2600; break; case 4: period_cycles = 1300; break; default: period_cycles = 260000; } interrupt_set_period(period_cycles); printk("%s set interrupt period to %u\n", __func__, period_cycles); } return 1; } static int interrupts_proc_open(struct inode *inode, struct file *file) { return single_open(file, interrupts_proc_show, NULL); } static const struct file_operations interrupt_stats_proc_fops = { .open = interrupts_proc_open, .write = interrupts_proc_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; //=================================================== static int __init proc_uptime_init(void) { proc_create("uptime", 0, NULL, &uptime_proc_fops); proc_create("interrupt_stats", 0, NULL, &interrupt_stats_proc_fops); return 0; } module_init(proc_uptime_init);
3.4 分析結果
當前使用測試Timer是26MHz,輔助衡量Load准確行的是32768時鍾計數。
26MHz的時鍾,一個Cycle=38.46納秒,這個精度已經很高了。用於衡量中斷性能應該夠用。
1. 將測試結果從設備中導出。第1列是當前Load數,第2列是標的耗時,第3列是輔助時鍾計數。第1、2列是26MHz時鍾,第3列是32K時鍾。
將這些結果按照Load不同存儲到interrupts_1300.txt/...文件中。
2600000, 321, 3277 2600000, 334, 3277 2600000, 315, 3277 2600000, 321, 3277 2600000, 324, 3277 2600000, 335, 3276 2600000, 355, 3277 2600000, 341, 3277 2600000, 345, 3277 2600000, 346, 3277 ...
2. 編寫分析腳本
import pandas as pd import numpy as np import os import re import matplotlib.pyplot as plt import matplotlib matplotlib.style.use('ggplot') cnames = ['index', 'count', 'duration'] output = [] output_cycles = [] timer_freq = 26000000 p = re.compile('^interrupts_([0-9]*).txt$') filenames = os.listdir('.') for file in filenames: if p.match(file): data = [] interrupt_data = [] interrupt_stats = [] duration_data = [] interrupt_stats = pd.read_csv(file, names = cnames)--------------------讀取數據源 #Show the plotting of interrupts time consumption ts = pd.Series(interrupt_stats['count'], index=range(len(interrupt_stats['count']))) ts.plot(title='%s'%file) fig = plt.gcf() fig.set_size_inches(25, 4) plt.ylabel('Cycles from trigger to ISR.') plt.show() #Convert to time consumption for i in interrupt_stats['count'].tolist(): data.append(float(i)*1000000/timer_freq)------------------------轉換成時間單位us #Calc the timer duration for i in interrupt_stats['duration'].tolist(): duration_data.append(i) #Statistics of interrupts interrupt_data = np.array(data) output.append([interrupt_data.mean(), interrupt_data.max(), interrupt_data.min(), interrupt_data.std()])----每個case的統計信息 output_cycles.append([interrupt_stats['count'].mean(), interrupt_stats['count'].max(), interrupt_stats['count'].min(), interrupt_stats['count'].std()])------------------------------------------------------cycles形式的統計信息 df = pd.DataFrame(output, columns=['mean(us)','max(us)','min(us)', 'std(us)'], index=['1300', '2600', '26000', '260000', '2600000']) df.to_csv('interrupt.csv') pd.pivot_table(df, index='mean(us)')----------pivot table形式展示統計信息
f2 = pd.DataFrame(output_cycles, columns=['mean(us)','max(us)','min(us)', 'std(us)'], index=['1300', '2600', '26000', '260000', '2600000'])
pd.pivot_table(df2, index='mean(us)')--------pivot table形式展示統計信息
3. 結果分析
3.1 耗時圖表分析
從下面5張圖中可以看出標的耗時分布情況,總體來講數據比較穩定。
- 26000/260000/2600000會有個別特別長的延時;
- 所有case的最低值比較接近在90-120左右個Cycles;
- 隨着Load增加,平均耗時呈遞增趨勢;
- 對26000/260000/2600000修改了ylim到500,可以看出細節部分。
4.中斷性能優化
中斷性能優化可以分為兩個階段:中斷公用部分和每個中斷例程包括下半部。
4.1 中斷共用部分
中斷共用部分包括:架構相關代碼、中斷控制器驅動等。
- 提高cache命中率?
- 將相關處理代碼放入cache中?
- 中斷和CPU綁定?
- 級聯對中斷性能的影響?
- ......
4.2 每中斷例程及下半部
每中斷例程及下半部:首先針對中斷例程,盡量短小快速、不睡眠;對下半部,采取合適的方法softirq/tasklet/workqueue。
中斷例程的優化,可以通過Tracepoint中中斷相關trace進行統計。
/sys/kernel/debug/tracing/events/irq/irq_handler_entry /sys/kernel/debug/tracing/events/irq/irq_handler_exit /sys/kernel/debug/tracing/events/irq/softirq_entry /sys/kernel/debug/tracing/events/irq/softirq_exit /sys/kernel/debug/tracing/events/irq/softirq_raise
下面是一個中斷例程執行耗時統計信息。
平均值大說明例程需要優化,因為在中斷例程執行期間是屏蔽中斷的,屏蔽時間太長容易丟中斷。
如果均方差大,說明中斷例程內流程差異較大,可能存在隱患。
+------------------------+-------+--------+-------+-------+--------+------------------+ | name | mean | max | min | count | sum | std | +------------------------+-------+--------+-------+-------+--------+------------------+ | dwc_otg_pcd | 0.457 | 32.196 | 0.0 | 104 | 47.516 | 3.26191450776 | | xxxxxxx | 0.004 | 0.031 | 0.0 | 675 | 2.903 | 0.0106282606939 | | dwc_otg_powerdown_up | 7.644 | 7.66 | 7.629 | 2 | 15.289 | 0.0155 | | icp_ps | 0.006 | 0.031 | 0.0 | 5 | 0.031 | 0.0124 | | xxxx_i2c.0 | 0.010 | 0.031 | 0.0 | 48 | 0.459 | 0.0141861232812 | | xxxx_timer | 0.003 | 0.092 | 0.0 | 1378 | 4.305 | 0.00947335198132 | | dwc_otg_powerdown down | 5.264 | 6.5 | 4.028 | 2 | 10.528 | 1.236 | +------------------------+-------+--------+-------+-------+--------+------------------+
下面是這些中斷在時間軸上的分不情況,長度表示耗時。可以看出他們的頻率,以及相互之間的關系。