阅读GIC-500 Technical Reference Manual笔记

不羁岁月 提交于 2019-12-06 10:26:38

转自:https://www.cnblogs.com/arnoldlu/p/7406441.html

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,可以看出细节部分。

 
 
 
 
 
 
 
 3.2 耗时统计信息
下面是每个case的平均值、最大值、最小值、均方差的统计信息。

 

 

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       |
+------------------------+-------+--------+-------+-------+--------+------------------+
复制代码

 下面是这些中断在时间轴上的分不情况,长度表示耗时。可以看出他们的频率,以及相互之间的关系。

 

 

 

联系方式:arnoldlu@qq.com

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,可以看出细节部分。

 
 
 
 
 
 
 
 3.2 耗时统计信息
下面是每个case的平均值、最大值、最小值、均方差的统计信息。

 

 

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       |
+------------------------+-------+--------+-------+-------+--------+------------------+
复制代码

 下面是这些中断在时间轴上的分不情况,长度表示耗时。可以看出他们的频率,以及相互之间的关系。

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!