解析中断引起的调度延迟问题

news2025/1/17 1:16:21

解析软中断引起的调度延迟问题

  • 一、导言
  • 二、线程调度的原理
  • 三、如何定位中断导致的调度延迟
    • 方法一:使用内核 ftrace工具
    • 方法二:使用开源ko工具
    • 方法三:修改内核源码添加打印

一、导言

  硬件中断和软件中断都有可能导致调度延迟,但两者的影响方式略有不同。

  硬件中断:当硬件设备发送中断请求时,CPU 会立即响应中断并执行对应的中断处理程序。在处理硬件中断时,CPU 会暂时中断当前任务的执行,切换到中断处理程序,处理完中断后再切换回原任务。硬件中断有可能打断正在执行的任务,引起调度延迟。

  软件中断:软件中断是由软件程序触发的中断,通常是通过系统调用或软中断指令来实现。软件中断不像硬件中断那样突然而来,一般在优先级比较低,不会立即打断正在执行的任务。但是,软件中断也需要处理,其处理过程可能会影响调度和任务切换。

  在处理硬件中断或软件中断时,操作系统需要适时地调度和管理中断处理程序的执行,以及恢复被打断的任务的执行。如果中断处理程序执行时间过长或调度机制不够高效,就有可能导致调度延迟,影响系统的响应性能和实时性能。

二、线程调度的原理

  在 Linux 中,线程调度的原理是通过内核的调度器(Scheduler)来实现的。Linux 内核中的调度器负责决定在多个就绪状态的任务中选择哪个任务来运行,并在何时进行任务切换。Linux 的调度器采用抢占式调度(Preemptive Scheduling),即操作系统会根据一定的调度策略主动地进行进程(包括线程)的切换,以确保系统资源的合理利用和响应性能。

  Linux 线程调度器通常会在以下情况下进行线程调度:

  1. 当一个线程主动放弃 CPU(例如调用了 sleep()、yield()、sched_yield() 等),使当前线程从运行状态切换到就绪状态时,调度器会根据调度策略选择就绪队列中的下一个任务来运行。

  2. 当一个线程的时间片用尽,需要被调度器挂起以便切换到另一个任务时,调度器会将当前线程放到就绪队列的末尾,然后选择另一个任务来执行。

  3. 当一个线程因为等待某个事件而被阻塞时(如等待 I/O 完成),调度器会从就绪队列中选择另一个可运行的任务来执行。

  4. 当一个高优先级的进程或线程需要执行时,调度器会挂起当前运行的低优先级任务,以确保高优先级任务得到及时响应。

  5. 当发生硬件中断时,调度器可能会对当前运行的任务进行抢占,以便执行中断处理程序。

  总的来说,Linux 线程调度器会根据调度策略和就绪队列中的任务情况来动态地进行任务切换,以提高系统的资源利用率和响应速度。Linux 的调度策略可以通过设置调度器的参数和调度类别来进行调整,以满足不同场景下的需求。

  在 Linux 中,需要周期性地检查各个线程状态并进行调度操作的触发条件通常是与时钟中断(Timer Interrupt)相关联的。时钟中断是操作系统中的一个重要机制,用于定时触发中断并告知内核当前时间已经过去,以便操作系统进行一些必要的处理,比如更新系统时间、检查线程状态、执行调度等。涉及到时钟中断处理和调度操作的函数包括:

  1. tick_handle_periodic:这个函数用于处理定时器子系统生成的周期性时钟中断,并负责更新系统时间、执行调度器的调度操作等。这个函数位于kernel/time/tick-common.c 文件中。
  2. scheduler_tick:这个函数是调度器模块中用于响应时钟中断事件的函数,负责在时钟中断发生时执行调度操作,包括检查各个线程的状态、进行线程调度等。这个函数根据不同的调度器(例如CFS 调度器)可能会有所不同,需要查看具体的调度器实现。
  3. 各个调度器特定的调度函数:不同的调度器模块(如 CFS、RT等)会有自己的调度函数,用于在时钟中断发生时进行相应的调度操作。这些函数通常在 kernel/sched/ 目录下。

三、如何定位中断导致的调度延迟

方法一:使用内核 ftrace工具

在这里插入图片描述

  ftrace 是 Linux 内核提供的一个功能丰富的跟踪工具,用于帮助开发人员和系统管理员跟踪和调试内核和用户空间程序的性能和行为。ftrace 可以跟踪内核函数的调用关系、事件发生时序、中断处理等,以帮助诊断和优化系统性能。

  以下是 ftrace 工具的一些主要特点和用途:

  1. 函数跟踪(Function Tracing):ftrace 可以跟踪内核函数的调用关系,包括函数的调用次数、执行时间等信息,帮助开发人员了解内核函数的执行情况。

  2. 事件跟踪(Event Tracing):ftrace 支持跟踪系统各种事件的发生时序,比如中断发生、进程切换等,帮助分析系统的行为和性能瓶颈。

  3. 深度跟踪(Deep Tracing):ftrace 可以跟踪更底层的操作,比如跟踪硬件事件、内核函数的参数等,帮助深入了解系统内部运行机制。

  4. 动态追踪(Dynamic Tracing):ftrace 允许用户在运行时配置跟踪参数和事件,实现动态地调整跟踪范围和精度。

  5. 性能分析和优化:通过使用 ftrace,开发人员可以快速诊断系统性能问题、分析系统瓶颈,并进行优化。

  6. 轻量级和低开销:ftrace 在设计上尽可能减小对系统性能的影响,可以在生产环境中使用。

  ftrace 工具利用了 Linux 内核的功能,通过在内核中插入跟踪事件和钩子,来实现对系统行为的跟踪和分析。使用 ftrace 可以帮助开发人员更好地了解系统内部的运行情况,快速解决问题并进行性能优化。

  关于ftrace的配置和使用方法,如何利用其进行问题排查,我找了几篇博客学习了下可以解决我们的需求,分享给大家(不重复制造垃圾,没太大意义):
  linux内核:ftrace——追踪内核行为
  【一文秒懂】Ftrace系统调试工具使用终极指南
  irqs跟踪器

方法二:使用开源ko工具

  Linux系统中,在执行硬件中断以及软件中断前都先会关闭中断,避免中断执行过程中被打断,所以某种程度而言,可以认为中断关闭操作伴随着中断的处理过程,所以我们可以借助这个原理来追溯中断;

  下面链接是字节跳动的一个开源项目ko,我自己使用过,很不错,分享给大家;源码我自己看过加了一些注释,分享一个注释版本;
  Kernel trace tools(一):中断和软中断关闭时间过长问题追踪

// SPDX-License-Identifier: GPL-2.0
/*
 * Trace Irqsoff
 *
 * Copyright (C) 2020 Bytedance, Inc., Muchun Song
 *
 * The main authors of the trace irqsoff code are:
 *
 * Muchun Song <songmuchun@bytedance.com>
 */
#define pr_fmt(fmt) "trace-irqoff: " fmt

#include <linux/hrtimer.h>
#include <linux/irqflags.h>
#include <linux/kernel.h>
#include <linux/kallsyms.h>
#include <linux/module.h>
#include <linux/percpu.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/sizes.h>
#include <linux/stacktrace.h>
#include <linux/timer.h>
#include <linux/uaccess.h>
#include <linux/kprobes.h>
#include <linux/version.h>
#include <asm/irq_regs.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#include <linux/sched.h>
#else
#include <linux/sched/clock.h>
#endif

#define MAX_TRACE_ENTRIES		(SZ_1K / sizeof(unsigned long))
#define PER_TRACE_ENTRIES_AVERAGE	(8 + 8)

#define MAX_STACE_TRACE_ENTRIES		\
	(MAX_TRACE_ENTRIES / PER_TRACE_ENTRIES_AVERAGE)

#define MAX_LATENCY_RECORD		10

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
#ifndef DEFINE_SHOW_ATTRIBUTE
#define DEFINE_SHOW_ATTRIBUTE(__name)					\
static int __name ## _open(struct inode *inode, struct file *file)	\
{
     									\
	return single_open(file, __name ## _show, inode->i_private);	\
}									\
									\
static const struct file_operations __name ## _fops = {
     			\
	.owner		= THIS_MODULE,					\
	.open		= __name ## _open,				\
	.read		= seq_read,					\
	.llseek		= seq_lseek,					\
	.release	= single_release,				\
}
#endif /* DEFINE_SHOW_ATTRIBUTE */
#define IRQ_OFF_DEFINE_SHOW_ATTRIBUTE DEFINE_SHOW_ATTRIBUTE

#else /* LINUX_VERSION_CODE */
#define IRQ_OFF_DEFINE_SHOW_ATTRIBUTE(__name)				\
static int __name ## _open(struct inode *inode, struct file *file)	\
{
     									\
	return single_open(file, __name ## _show, inode->i_private);	\
}									\
									\
static const struct proc_ops __name ## _fops = {
     			\
	.proc_open	= __name ## _open,				\
	.proc_read	= seq_read,					\
	.proc_lseek	= seq_lseek,					\
	.proc_release	= single_release,				\
}
#endif /* LINUX_VERSION_CODE */

static bool trace_enable;

/**
 * Default sampling period is 10000000ns. The minimum value is 1000000ns.
 */
static u64 sampling_period = 10 * 1000 * 1000UL;

/**
 * How many times should we record the stack trace.
 * Default is 50000000ns.
 */
static u64 trace_irqoff_latency = 50 * 1000 * 1000UL;

struct irqoff_trace {
   
	unsigned int nr_entries;
	unsigned long *entries;
};

struct stack_trace_metadata {
   
	u64 last_timestamp;
	unsigned long nr_irqoff_trace;
	struct irqoff_trace trace[MAX_STACE_TRACE_ENTRIES];
	unsigned long nr_entries;
	unsigned long entries[MAX_TRACE_ENTRIES];
	unsigned long latency_count[MAX_LATENCY_RECORD];

	/* Task command names*/
	char comms[MAX_STACE_TRACE_ENTRIES][TASK_COMM_LEN];

	/* Task pids*/
	pid_t pids[MAX_STACE_TRACE_ENTRIES];

	struct {
   
		u64 nsecs:63;
		u64 more:1;
	} latency[MAX_STACE_TRACE_ENTRIES];
};

struct per_cpu_stack_trace {
   
	struct timer_list timer;
	struct hrtimer hrtimer;
	struct stack_trace_metadata hardirq_trace;
	struct stack_trace_metadata softirq_trace;

	bool softirq_delayed;
};

static struct per_cpu_stack_trace __percpu *cpu_stack_trace;

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0)
static void (*save_stack_trace_skip_hardirq)(struct pt_regs *regs,
					     struct stack_trace *trace);

/**
 * stack_trace_skip_hardirq_init - 初始化用于跳过硬中断堆栈跟踪的符号地址
 * 
 * 本函数旨在查找并保存名为"save_stack_trace_regs"的内核符号的地址。
 * 这样做的目的是为了在后续的堆栈跟踪操作中,能够跳过硬中断处理程序的上下文,
 * 提供更准确的堆栈信息,特别是对于调试内核问题时非常有用。
 * 
 * 注意:此函数使用inline定义,意味着它应该在调用点被内联展开,
 * 以减少函数调用的开销。此外,函数声明为静态,意味着它只在当前文件中可见。
 */
static inline void stack_trace_skip_hardirq_init(void)
{
   
    /* 通过kallsyms_lookup_name函数查找并保存符号"save_stack_trace_regs"的地址 */
    save_stack_trace_skip_hardirq =
            (void *)kallsyms_lookup_name("save_stack_trace_regs");
}

/**
 * store_stack_trace - 获取并存储堆栈跟踪信息
 * @regs:         寄存器状态指针,用于获取调用点的上下文信息
 * @trace:        用于存储堆栈跟踪结果的结构体指针
 * @entries:      用于存储堆栈地址的数组指针
 * @max_entries:  entries数组的最大容量
 * @skip:         跳过的前几层堆栈帧,通常是为了避开特定的调用点
 *
 * 此函数用于在中断或异常发生时收集并存储堆栈跟踪信息。它首先初始化一个
 * stack_trace结构体,然后根据是否在中断上下文中调用,选择不同的方法来
 * 保存堆栈信息。最后,它将收集到的堆栈跟踪信息存储到提供的trace结构体中。
 * 
 * 注意:某些架构会在堆栈跟踪的末尾添加ULONG_MAX来表示这是一个完整的跟踪,
 * 但这种做法可能会导致一个完全填满的跟踪被错误地报告为不完整。
 */
static inline void store_stack_trace(struct pt_regs *regs,
				     struct irqoff_trace *trace,
				     unsigned long *entries,
				     unsigned int max_entries, int skip)
{
   
	struct stack_trace stack_trace;

	/* 初始化堆栈跟踪结构体 */
	stack_trace.nr_entries = 0;
	stack_trace.max_entries = max_entries;
	stack_trace.entries = entries;
	stack_trace.skip = skip;

	/* 根据是否在中断上下文中,选择合适的函数来保存堆栈跟踪 */
	if (regs && save_stack_trace_skip_hardirq)
		save_stack_trace_skip_hardirq(regs, &stack_trace);
	else
		save_stack_trace(&stack_trace);

	/* 将收集到的堆栈跟踪信息存储到trace结构体中 */
	trace->entries = entries;
	trace->nr_entries = stack_trace.nr_entries;

	/*
	 * 如果堆栈跟踪的最后一个条目是ULONG_MAX,则认为它是一个填充项,
	 * 并将其从跟踪长度中移除。这是为了处理某些架构的特殊表示方法。
	 */
	/*
	 * Some daft arches put -1 at the end to indicate its a full trace.
	 *
	 * <rant> this is buggy anyway, since it takes a whole extra entry so a
	 * complete trace that maxes out the entries provided will be reported
	 * as incomplete, friggin useless </rant>.
	 */
	if (trace->nr_entries != 0 &&
	    trace->entries[trace->nr_entries - 1] == ULONG_MAX)
		trace->nr_entries--;
}
#else
static unsigned int (*stack_trace_save_skip_hardirq)(struct pt_regs *regs,
						     unsigned long *store,
						     unsigned int size,
						     unsigned int skipnr);

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
static inline void stack_trace_skip_hardirq_init(void)
{
   
	stack_trace_save_skip_hardirq =
			(void *)kallsyms_lookup_name("stack_trace_save_regs");
}
#else 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1810260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stm32MP135裸机编程:启动流程分析

0 参考资料 轻松使用STM32MP13x - 如MCU般在cortex A核上裸跑应用程序.pdf STM32MP135AD数据手册.pdf1 stm32MP135裸机启动流程分析 1.1 启动方式 stm32MP135支持8种启动方式&#xff1a; 注&#xff1a; UART和USB启动并不是指通过UART/USB加载程序&#xff0c;而是通过UA…

Spring Boot 项目启动时在 prepareContext 阶段做了哪些事?

概览 如果你对Spring Boot 启动流程还不甚了解&#xff0c;可阅读《Spring Boot 启动流程详解》这篇文章。如果你已了解&#xff0c;那就让我们直接看看prepareContext() 源码。 private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironme…

2024.6.7

思维导图 代码 #include <iostream>using namespace std;//创建一个RMB类 class RMB {friend const RMB operator(const RMB &p1, const RMB &p2);friend const RMB operator-(const RMB &p1, const RMB &p2);friend bool operator>(const RMB &…

《Windows API每日一练》3.1 绘制文本

本节我们将讲述如何在窗口客户区绘制文本。如果在客户区绘制文本&#xff0c;需要将整个客户区或指定文本所在的矩形区域设置为无效区域&#xff0c;然后产生WM_PANIT消息&#xff0c;调用GDI函数绘制文本。此外&#xff0c;如果要绘制文本还需要使用设备环境上下文句柄&#x…

阿里发布最强开源大模型通义千问Qwen2,国产最好用的LLM

前言 近年来&#xff0c;大模型技术发展迅速&#xff0c;开源模型的出现为AI研究和应用带来了新的活力。在这一背景下&#xff0c;阿里云通义千问团队发布了全新升级的Qwen2系列开源模型&#xff0c;为国内外开发者提供了更强大的工具和更丰富的选择。 Huggingface模型下载&am…

springboot3 数据访问

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 数据访问 一、准备数据库表二、项目创建2.1、使用spring initializer 创建2.2、添加数据库配置2.3 mapper2.4 编写controller2.5 总结 三、其他数据源 一、准备数据库表 CRE…

追觅科技2025校园招聘测评已发(真题)

&#x1f4e3;追觅科技 2025校园招聘测评已发&#xff0c;正在申请的小伙伴看过来哦&#x1f440; ㊙本次校招面向全球于2023年7月 - 2025年12月期间毕业的同学&#xff0c;开放了四大类岗位&#xff1a;营销类、研发类、制作供应类、职能类~ ✅测评解析 &#x1f449; 测评自…

Kimichat使用案例012:用Kimichat拆解雷军在小米汽车SU7发布会上的演讲技巧

文章目录 一、介绍二、输入内容三、输出内容四、继续追问五、继续回答六、讲解对比七、对比回答相似之处:不同之处:八、职场人士如何借鉴九、借鉴内容一、介绍 小米SU7发布会可以说是非常成功。雷军的演讲技巧是发布会成功的重要因素之一,很值得借鉴学习。 可以借助Kimichat…

攻防世界---misc---gif

1、题目描述 2、下载附件&#xff0c;是一堆黑白图片&#xff0c;看到这里我一头雾水 3、看别人写的wp&#xff0c;说是白色表示0&#xff0c;黑色表示1。按照顺序写出来后得到 4、解码的时候&#xff0c;把逗号去掉。二进制转字符串得到&#xff1a; 5、 flag{FuN_giF}

「OC」UI练习(一)—— 登陆界面

「OC」登陆界面 明确要求 一个登陆界面的组成&#xff0c;用户名提示以及输入框&#xff0c;密码提示提示以及输入框&#xff0c;登陆按钮&#xff0c;以及注册按钮&#xff0c;根据以上要求我们将我们的组件设置为成员变量。 //viewControl.h #import <UIKit/UIKit.h>…

Kimichat使用案例013:用kimichat批量识别出图片版PDF文件中的文字内容

文章目录 一、介绍二、具体操作三、信息识别一、介绍 图片版的PDF文件,怎么才能借助AI工具来提取其中全部的文字内容呢? 第一步:将PDF文件转换成图片格式 具体方法参见文章: Kimichat使用案例011:用kimichat将PDF自动批量分割成多个图片(零代码编程) 第二步:识别图片中…

Go模板页面浏览器显示HTML源码问题

<!--* Title: This is a file for ……* Author: JackieZheng* Date: 2024-06-09 17:00:01* LastEditTime: 2024-06-09 17:01:12* LastEditors: Please set LastEditors* Description:* FilePath: \\GoCode\\templates\\index.html --> <!DOCTYPE html> <html …

【安装笔记-20240610-Linux-免费域名服务之eu.org】

安装笔记-系列文章目录 安装笔记-20240610-Linux-免费域名服务之eu.org 文章目录 安装笔记-系列文章目录安装笔记-20240610-Linux-免费域名服务之eu.org 前言一、软件介绍名称&#xff1a;eu.org主页官方介绍 二、安装步骤测试版本&#xff1a;openwrt-23.05.3-x86-64注册填写…

Java基础——多线程(一)

概念 线程和进程 进程&#xff1a;进程是程序的基本执行实体 线程&#xff1a;线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程的实际运作单位 简单理解&#xff1a;应用软件中互相独立&#xff0c;可以同时运行的功能。多线程可以…

C++ | Leetcode C++题解之第144题二叉树的前序遍历

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> preorderTraversal(TreeNode *root) {vector<int> res;if (root nullptr) {return res;}TreeNode *p1 root, *p2 nullptr;while (p1 ! nullptr) {p2 p1->left;if (p2 ! nullptr) {…

Nginx配置详细解释:(4)高级配置

目录 1.网页的状态页 2.Nginx第三方模块(echo) 3.变量 4.自定义访问日志 5.Nginx压缩功能 6.https功能 7.自定义图标 Nginx除了一些基本配置外&#xff0c;还有一些高级配置&#xff0c;如网页的状态&#xff0c;第三方模块需要另外安装&#xff0c;支持变量&#xff0c…

SpringTask-Timer实现定时任务

1、Timer 实现定时任务 1.1、JDK1.3 开始推出定时任务实现工具。 1.2、API 执行代码 public static void main(String[] args) throws ParseException {Timer timer new Timer();String str"2024-06-10 23:24:00";Date date new SimpleDateFormat("yyyy-MM…

【docker】日志

ocker 日志相关的操作主要涉及查看、管理和理解容器的日志输出。以下是一些常用的 Docker 日志命令和选项&#xff1a; 查看日志 docker logs container_id_or_name&#xff1a;获取指定容器的日志。docker logs -f container_id_or_name&#xff1a;跟随&#xff08;实时输出…

idea开发java高校社团推广管理系统springboot框架web结构java编程计算机网页LayUI技术

一、源码特点 java 高校社团推广管理系统是一套完善的完整信息系统&#xff0c;结合java web开发springboot框架和LayUI框架完成本系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要…

使用Vue CLI在其他磁盘创建项目出现错误及解决

Vue CLI是Vue.js官方推出的脚手架工具&#xff0c;可以帮我们快速的创建Vue项目框架。 我们创建Vue项目时一般默认都是在C盘&#xff0c;但由于某些因素我们需要在其他磁盘上创建Vue项目。 通过“winr”打开终端时默认位置都是C盘&#xff0c;但是Vue CLI不接受绝对路径作为参…