Linux性能分析与实时编程全解析
1. Linux性能分析工具概述
在Linux系统中,有众多用于性能分析和跟踪的工具。当系统性能不如预期时,可按以下步骤进行分析:
- 首先使用 top 命令初步识别问题。若问题出在单个应用程序上,可用 perf record/report 进行性能分析。不过,使用该工具前需配置内核以启用 perf ,并为二进制文件和内核准备调试符号。
- 若问题不局限于某个应用,可使用 perf 或BCC工具获取系统全局视图。
此外,还有其他一些实用工具:
- Ftrace :适用于深入了解内核行为。其函数和函数图跟踪器能详细展示函数调用的关系和顺序,事件跟踪器可提取函数的更多信息,如参数和返回值。
- LTTng :利用事件跟踪机制,添加高速环形缓冲区,可从内核提取大量数据。
- Valgrind :在沙盒中运行代码,能报告难以追踪的错误。使用 Callgrind 工具可生成调用图并报告处理器缓存使用情况,使用 Helgrind 可报告线程相关问题。
- strace :可用于查找程序正在进行的系统调用,如跟踪文件打开调用、查找文件路径名以及检查系统唤醒和传入信号。
在使用这些工具时,要注意避免观察者效应,确保测量结果对生产系统有效。
2. 实时编程基础
实时编程在计算机系统与现实世界的交互中至关重要,尤其对于嵌入式系统开发者而言。
2.1 实时的定义
实时任务是指必须在特定时间点(即截止时间)之前完成的任务。以在计算机上播放音频流和编译Linux内核为例,播放音频流是实时任务,因为音频驱动不断接收数据,音频样本块必须以播放速率写入音频接口;而编译内核不是实时任务,因为没有明确的截止时间,只希望尽快完成。
错过截止时间的后果各不相同,可分为以下几类:
|任务类型|截止时间|错过截止时间的后果|实时类型|
| ---- | ---- | ---- | ---- |
|播放音频流|几十毫秒|听到咔哒声,令人烦恼但可接受|软实时|
|移动和点击鼠标|几十毫秒|鼠标移动不稳定,丢失点击,系统可能无法使用|软实时|
|打印纸张|毫秒级|打印机可能卡纸,需人工修复|软实时|
|生产线上打印保质期|无明确提及|生产线需停止、移除未打印瓶子并重新启动,成本高|硬实时(任务关键)|
|烤蛋糕|约30分钟|蛋糕可能烤坏,严重时房子可能着火|硬实时(安全关键)|
|电源浪涌检测系统|2毫秒|设备损坏,可能造成人员伤亡|硬实时(安全关键)|
一般来说,配置良好的主线内核Linux系统适用于截止时间为几十毫秒的软实时任务,而应用了 PREEMPT_RT 补丁的内核适用于截止时间为几百微秒的软实时和硬实时任务关键系统。创建实时系统的关键是减少响应时间的可变性,使系统更具确定性,不过这通常会牺牲一定的性能。
2.2 非确定性来源识别
实时编程的核心是确保控制实时输出的线程在需要时能被调度,以在截止时间前完成任务。以下是可能影响这一过程的问题区域:
- 调度 :实时线程必须具有实时调度策略( SCHED_FIFO 或 SCHED_RR ),并根据速率单调分析理论按截止时间从短到长分配优先级。
- 调度延迟 :内核应能在中断或定时器等事件发生时尽快重新调度,避免无界延迟。
- 优先级反转 :高优先级线程因低优先级线程持有的互斥锁而阻塞,导致无界延迟。用户空间有优先级继承和优先级上限互斥锁,内核空间有实现优先级继承的RT互斥锁。
- 精确定时器 :若要管理低毫秒或微秒级的截止时间,需要高精度定时器,这几乎是所有内核的配置选项。
- 页面错误 :在执行关键代码段时发生页面错误会打乱所有时间估计,可通过锁定内存来避免。
- 中断 :中断发生时间不可预测,大量中断可能导致意外的处理开销。可将中断作为内核线程运行,或在多核设备上屏蔽一个或多个CPU的中断处理。
- 处理器缓存 :是系统非确定性的来源之一,尤其是在多核设备上,但本文暂不深入讨论。
- 内存总线争用 :属于硬件问题,也不在本文讨论范围内。
3. 调度延迟理解
实时线程在有任务时需尽快被调度,但即使没有相同或更高优先级的其他线程,从唤醒事件(如中断或系统定时器)发生到线程开始运行仍存在延迟,即调度延迟。它可分为以下几个部分:
graph LR
A[硬件中断延迟] --> B[中断延迟]
B --> C[抢占延迟]
- 硬件中断延迟 :从中断被触发到中断服务例程(ISR)开始运行的时间。部分延迟源于中断硬件本身,但主要问题是软件禁用中断,因此最小化IRQ关闭时间很重要。
- 中断延迟 :ISR处理中断并唤醒等待该事件的线程所需的时间,主要取决于ISR的编写方式,通常应在微秒级。
- 抢占延迟 :从内核得知线程准备运行到调度器实际运行该线程的时间,取决于内核是否可被抢占。
4. 内核抢占
主线Linux有三种内核抢占设置,可通过 Kernel Features | Preemption Model 菜单选择:
- CONFIG_PREEMPT_NONE :无抢占。内核代码将继续运行,直到通过系统调用返回用户空间或遇到睡眠等待停止当前线程。此选项可减少内核与用户空间之间的转换次数和上下文切换总数,实现最高吞吐量,但会导致较大的抢占延迟,适用于对吞吐量要求高于响应性的服务器和部分桌面内核。
- CONFIG_PREEMPT_VOLUNTARY :启用额外的抢占请求检查。当 need_resched 标志设置时调用调度器,可减少最坏情况下的抢占延迟,但会略微降低吞吐量,部分桌面发行版采用此选项。
- CONFIG_PREEMPT :允许内核被抢占。只要内核不在原子上下文中执行,中断可导致立即重新调度,将最坏情况下的抢占延迟和整体调度延迟降低到几毫秒左右,常用于嵌入式内核,虽会略微降低整体吞吐量,但能使调度更具确定性。
5. 实时Linux内核(PREEMPT_RT)
PREEMPT_RT 项目旨在进一步降低延迟,由Ingo Molnar、Thomas Gleixner和Steven Rostedt发起,多年来有众多开发者参与。内核补丁可在 https://www.kernel.org/pub/linux/kernel/projects/rt 获取,相关维基页面为 https://wiki.linuxfoundation.org/realtime/start ,还有一个虽已过时但仍有参考价值的FAQ页面 https://rt.wiki.kernel.org/index.php/Frequently_Asked_Questions 。
该项目的核心是减少内核在原子上下文中运行的时间,原子上下文通常包括以下几种情况:
- 运行中断或陷阱处理程序。
- 持有自旋锁或处于RCU关键部分。
- 在 preempt_disable() 和 preempt_enable() 调用之间。
- 硬件中断被禁用(IRQs关闭)。
PREEMPT_RT 的更改主要集中在两个方面:
- 将中断处理程序转换为内核线程,以减少其影响。
- 使锁可被抢占,允许线程在持有锁时睡眠。这些更改虽会增加开销,使平均中断处理速度变慢,但能使系统更具确定性。
6. 线程化中断处理程序
并非所有中断都是实时任务的触发器,但所有中断都会占用实时任务的处理周期。线程化中断处理程序允许为中断分配优先级,并在适当时间进行调度。
在主线Linux 2.6.30及以后版本中支持线程化中断处理程序。可使用 request_threaded_irq() 代替 request_irq() 请求单个中断处理程序线程化,也可通过配置内核 CONFIG_IRQ_FORCED_THREADING=y 使所有处理程序成为线程,除非设置了 IRQF_NO_THREAD 标志。应用 PREEMPT_RT 补丁后,中断默认配置为线程。
例如,在运行 linux-yocto-rt 的BeagleBone上,以下是部分中断线程的信息:
# ps -Leo pid,tid,class,rtprio,stat,comm,wchan | grep FF
PID TID CLS RTPRIO STAT COMMAND WCHAN
3 3 FF 1 S ksoftirqd/0 smpboot_th
7 7 FF 99 S posixcputmr/0 posix_cpu_
19 19 FF 50 S irq/28-edma irq_thread
20 20 FF 50 S irq/30-edma_err irq_thread
42 42 FF 50 S irq/91-rtc0 irq_thread
43 43 FF 50 S irq/92-rtc0 irq_thread
44 44 FF 50 S irq/80-mmc0 irq_thread
45 45 FF 50 S irq/150-mmc0 irq_thread
47 47 FF 50 S irq/44-mmc1 irq_thread
52 52 FF 50 S irq/86-44e0b000 irq_thread
59 59 FF 50 S irq/52-tilcdc irq_thread
65 65 FF 50 S irq/56-4a100000 irq_thread
66 66 FF 50 S irq/57-4a100000 irq_thread
67 67 FF 50 S irq/58-4a100000 irq_thread
68 68 FF 50 S irq/59-4a100000 irq_thread
76 76 FF 50 S irq/88-OMAP UAR irq_thread
在这个例子中,只有 gp_timer 中断未线程化,定时器中断处理程序通常以内联方式运行。
需要注意的是,中断线程默认采用 SCHED_FIFO 策略和优先级50,可根据中断与实时用户空间线程的重要性分配优先级。建议的线程优先级降序顺序如下:
1. POSIX定时器线程 posixcputmr 应始终具有最高优先级。
2. 与最高优先级实时线程关联的硬件中断。
3. 最高优先级实时线程。
4. 优先级逐渐降低的实时线程的硬件中断,随后是线程本身。
5. 次高优先级实时线程。
6. 非实时接口的硬件中断。
7. 软IRQ守护进程 ksoftirqd ,在RT内核中负责运行延迟中断例程,在Linux 3.6之前还负责运行网络栈、块I/O层等,可能需要试验不同的优先级级别以达到平衡。
可在启动脚本中使用 chrt 命令更改优先级,例如:
# chrt -f -p 90 `pgrep irq/28-edma`
其中 pgrep 命令是 procps 包的一部分。
Linux性能分析与实时编程全解析
7. 高分辨率定时器
在实时编程中,精确的时间控制至关重要,尤其是对于那些截止时间在低毫秒或微秒级别的任务。高分辨率定时器(High-resolution timers)在这种场景下就显得尤为关键。
高分辨率定时器是几乎所有内核都具备的一个配置选项。如果想要管理低毫秒或微秒级别的截止时间,就需要与之匹配的定时器。这些定时器能够提供更精确的时间测量和事件调度,确保实时任务能够按照预期的时间点执行。
例如,在一些对时间精度要求极高的嵌入式系统中,如工业自动化控制系统、航空航天设备等,高分辨率定时器可以帮助系统更准确地响应外部事件,避免因时间误差导致的任务失败或系统故障。
8. 避免页面错误
在实时编程中,页面错误(Page faults)是一个需要特别关注的问题。当执行关键代码段时,如果发生页面错误,会严重打乱所有的时间估计,影响实时任务的按时完成。
为了避免页面错误,可以通过锁定内存(Memory locking)的方式来实现。锁定内存可以确保在执行关键代码时,所需的数据和代码都已经加载到物理内存中,不会因为页面错误而导致额外的延迟。
具体操作步骤如下:
1. 在代码中使用相关的系统调用(如 mlock 或 mlockall )来锁定所需的内存区域。
- mlock 函数用于锁定指定的内存区域,其原型如下:
#include <sys/mman.h>
int mlock(const void *addr, size_t len);
- `mlockall`函数用于锁定整个进程的地址空间,其原型如下:
#include <sys/mman.h>
int mlockall(int flags);
- 在使用完锁定的内存后,记得使用
munlock或munlockall函数来解锁内存,以释放系统资源。-
munlock函数用于解锁指定的内存区域,其原型如下:
-
#include <sys/mman.h>
int munlock(const void *addr, size_t len);
- `munlockall`函数用于解锁整个进程的地址空间,其原型如下:
#include <sys/mman.h>
int munlockall(void);
9. 中断屏蔽
中断(Interrupts)在实时系统中是一个不可忽视的因素。它们的发生时间是不可预测的,而且如果突然出现大量中断,可能会导致系统出现意外的处理开销,影响实时任务的执行。
为了避免中断带来的问题,有两种常见的方法:
9.1 运行中断作为内核线程
将中断处理程序转换为内核线程,可以使中断的处理更加灵活。这样,中断处理就不会像传统方式那样立即打断正在执行的任务,而是可以根据系统的调度策略,在合适的时间进行处理。
在主线Linux 2.6.30及以后版本中,支持将中断处理程序线程化。可以使用 request_threaded_irq() 函数来请求将单个中断处理程序线程化,示例代码如下:
#include <linux/interrupt.h>
request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname, void *dev_id);
其中, irq 是中断号, handler 是中断处理函数, thread_fn 是线程处理函数, irqflags 是中断标志, devname 是设备名称, dev_id 是设备ID。
9.2 多核设备上屏蔽CPU的中断处理
在多核设备上,可以选择屏蔽一个或多个CPU的中断处理,从而为实时任务提供一个相对稳定的执行环境。这样,被屏蔽的CPU可以专注于执行实时任务,不受中断的干扰。
具体操作步骤如下:
1. 确定需要屏蔽中断的CPU核心。
2. 使用相关的系统调用或内核配置选项来屏蔽指定CPU核心的中断处理。例如,可以通过修改内核的中断亲和性(Interrupt affinity)设置来实现。
10. 测量调度延迟
为了确保实时系统的性能符合预期,需要对调度延迟进行测量。以下介绍两种常用的工具:
10.1 cyclictest
cyclictest 是一个专门用于测量系统调度延迟的工具。它通过周期性地执行任务,并记录任务的实际执行时间与预期执行时间之间的差异,来评估系统的调度性能。
使用 cyclictest 的步骤如下:
1. 安装 cyclictest 工具。可以通过系统的包管理器进行安装,例如在Ubuntu系统中,可以使用以下命令:
sudo apt-get install rt-tests
- 运行
cyclictest命令进行测试。以下是一个简单的示例:
sudo cyclictest -p 99 -i 1000 -l 10000
其中, -p 参数指定任务的优先级, -i 参数指定任务的周期(单位为微秒), -l 参数指定测试的循环次数。
- 分析测试结果。
cyclictest会输出任务的最大延迟、最小延迟和平均延迟等信息,通过分析这些数据可以了解系统的调度性能。
10.2 Ftrace
Ftrace是Linux内核提供的一个强大的跟踪工具,它可以用于跟踪内核的各种事件和函数调用,包括调度相关的事件。
使用Ftrace测量调度延迟的步骤如下:
1. 启用Ftrace功能。可以通过修改内核的配置文件或使用相关的命令来启用Ftrace。
2. 配置Ftrace跟踪的事件。例如,可以配置跟踪调度器的事件,以记录任务的调度信息。
echo sched_switch > /sys/kernel/debug/tracing/set_event
- 开始跟踪。可以使用以下命令开始记录调度事件:
echo 1 > /sys/kernel/debug/tracing/tracing_on
- 执行需要测试的实时任务。
- 停止跟踪并查看结果。使用以下命令停止跟踪,并查看记录的调度事件:
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
11. 总结
通过对Linux系统的性能分析工具和实时编程相关知识的介绍,我们了解到在Linux环境下进行实时编程需要综合考虑多个方面的因素。
从性能分析工具来看, top 、 perf 、 Ftrace 、 LTTng 、 Valgrind 和 strace 等工具可以帮助我们定位和解决系统性能问题。在实时编程方面,需要明确实时任务的定义和分类,识别非确定性来源,如调度、优先级反转、页面错误、中断等,并采取相应的措施来减少延迟,提高系统的确定性。
例如,通过使用高分辨率定时器、避免页面错误、进行中断屏蔽和测量调度延迟等方法,可以优化实时系统的性能,确保实时任务能够按时完成。同时,对于线程化中断处理程序,要合理分配优先级,以达到系统性能的平衡。
总之,在Linux系统中实现实时编程需要深入理解系统的工作原理和相关技术,通过合理的配置和优化,才能构建出高效、稳定的实时系统。
graph LR
A[实时编程] --> B[性能分析工具]
A --> C[实时任务特性]
C --> D[截止时间]
C --> E[非确定性来源]
E --> F[调度]
E --> G[优先级反转]
E --> H[页面错误]
E --> I[中断]
A --> J[优化措施]
J --> K[高分辨率定时器]
J --> L[避免页面错误]
J --> M[中断屏蔽]
J --> N[测量调度延迟]
以上流程图展示了实时编程的主要方面,包括性能分析工具、实时任务特性、非确定性来源以及相应的优化措施,它们相互关联,共同构成了Linux实时编程的整体框架。
超级会员免费看
4167

被折叠的 条评论
为什么被折叠?



