Linux深入学习内核 - 中断与异常(下)

 软中断,TaskletWork Queue

由内核执行的几个任务之间有一些不是紧急的,他们可以被延缓一段时间!把可延迟的中断从中断处理程序中抽出来,有利于使得内核保持较短的响应时间,所以我们现在使用以下面的这些结构,来把这样的非紧急的中断处理函数抽象出来!下面列出还在使用三个的机制:

软中断(softirq):内核2.3引入,是最基本、最优先的软中断处理形式,为了避免名字冲突,本文中将这种子类型的软中断叫softirq。

tasklet:其底层使用softirq机制实现,提供了一种用户方便使用的软中方式,为软中断提供了很好的扩展性。(封装了soft_irq)

work queue:前两种软中断执行时是禁止抢占的(softirq的ksoftirq除外),对于用户进程不友好。如果在softirq执行时间过长,会继续推后到work queue中执行,work queue执行处于进程上下文,其可被抢占,也可以被调度,如果软中断需要执行睡眠、阻塞,直接选择work queue。

软中断

前已注册的软中断有10种,定义为一个全局数组:

static struct softirq_action softirq_vec[NR_SOFTIRQS];enum {HI_SOFTIRQ = 0, /* 优先级高的tasklets */TIMER_SOFTIRQ, /* 定时器的下半部 */NET_TX_SOFTIRQ, /* 发送网络数据包 */NET_RX_SOFTIRQ, /* 接收网络数据包 */BLOCK_SOFTIRQ, /* BLOCK装置 */BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ, /* 正常优先级的tasklets */SCHED_SOFTIRQ, /* 调度程序 */HRTIMER_SOFTIRQ, /* 高分辨率定时器 */RCU_SOFTIRQ, /* RCU锁定 */NR_SOFTIRQS /* 10 */
};

(2)注册软中断处理函数

/*** @nr: 软中断的索引号* @action: 软中断的处理函数*/
void open_softirq(int nr, void (*action) (struct softirq_action *))
{softirq_vec[nr].action = action;
}

例如:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

(3)触发软中断

调用raise_softirq()来触发软中断。

void raise_softirq(unsigned int nr)
{unsigned long flags;local_irq_save(flags);raise_softirq_irqoff(nr);local_irq_restore(flags);
}/* This function must run with irqs disabled */
inline void rasie_softirq_irqsoff(unsigned int nr)
{__raise_softirq_irqoff(nr);/* If we're in an interrupt or softirq, we're done* (this also catches softirq-disabled code). We will* actually run the softirq once we return from the irq* or softirq.* Otherwise we wake up ksoftirqd to make sure we* schedule the softirq soon.*/if (! in_interrupt()) /* 如果不处于硬中断或软中断 */wakeup_softirqd(void); /* 唤醒ksoftirqd/n进程 */
}

Percpu变量irq_cpustat_t中的__softirq_pending是等待处理的软中断的位图,通过设置此变量即可告诉内核该执行哪些软中断。

static inline void __rasie_softirq_irqoff(unsigned int nr)
{trace_softirq_raise(nr);or_softirq_pending(1UL << nr);
}typedef struct {unsigned int __softirq_pending;unsigned int __nmi_count; /* arch dependent */
} irq_cpustat_t;irq_cpustat_t irq_stat[];
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define or_softirq_pending(x) percpu_or(irq_stat.__softirq_pending, (x))
#define local_softirq_pending() percpu_read(irq_stat.__softirq_pending)

唤醒ksoftirqd内核线程处理软中断。

static void wakeup_softirqd(void)
{/* Interrupts are disabled: no need to stop preemption */struct task_struct *tsk = __get_cpu_var(ksoftirqd);if (tsk && tsk->state != TASK_RUNNING)wake_up_process(tsk);
}

在下列地方,待处理的软中断会被检查和执行:

a. 从一个硬件中断代码处返回时

b. 在ksoftirqd内核线程中

c. 在那些显示检查和执行待处理的软中断的代码中,如网络子系统中

而不管是用什么方法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,do_softirq()会循环遍历每一个,调用它们的相应的处理程序。

在中断处理程序中触发软中断是最常见的形式。中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用do_softirq(),于是软中断开始执行中断处理程序完成剩余的任务。

下面来看下do_softirq()的具体实现。

asmlinkage void do_softirq(void)
{__u32 pending;unsigned long flags;/* 如果当前已处于硬中断或软中断中,直接返回 */if (in_interrupt()) return;local_irq_save(flags);pending = local_softirq_pending();if (pending) /* 如果有激活的软中断 */__do_softirq(); /* 处理函数 */local_irq_restore(flags);
}
/* We restart softirq processing MAX_SOFTIRQ_RESTART times,* and we fall back to softirqd after that.* This number has been established via experimentation.* The two things to balance is latency against fairness - we want* to handle softirqs as soon as possible, but they should not be* able to lock up the box.*/
asmlinkage void __do_softirq(void)
{struct softirq_action *h;__u32 pending;/* 本函数能重复触发执行的次数,防止占用过多的cpu时间 */int max_restart = MAX_SOFTIRQ_RESTART;int cpu;pending = local_softirq_pending(); /* 激活的软中断位图 */account_system_vtime(current);/* 本地禁止当前的软中断 */__local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);lockdep_softirq_enter(); /* current->softirq_context++ */cpu = smp_processor_id(); /* 当前cpu编号 */restart:/* Reset the pending bitmask before enabling irqs */set_softirq_pending(0); /* 重置位图 */local_irq_enable();h = softirq_vec;do {if (pending & 1) {unsigned int vec_nr = h - softirq_vec; /* 软中断索引 */int prev_count = preempt_count();kstat_incr_softirqs_this_cpu(vec_nr);trace_softirq_entry(vec_nr);h->action(h); /* 调用软中断的处理函数 */trace_softirq_exit(vec_nr);if (unlikely(prev_count != preempt_count())) {printk(KERN_ERR "huh, entered softirq %u %s %p" "with preempt_count %08x,""exited with %08x?\n", vec_nr, softirq_to_name[vec_nr], h->action, prev_count,preempt_count());}rcu_bh_qs(cpu);}h++;pending >>= 1;} while(pending);local_irq_disable();pending = local_softirq_pending();if (pending & --max_restart) /* 重复触发 */goto restart;/* 如果重复触发了10次了,接下来唤醒ksoftirqd/n内核线程来处理 */if (pending)wakeup_softirqd(); lockdep_softirq_exit();account_system_vtime(current);__local_bh_enable(SOFTIRQ_OFFSET);
}

(4)ksoftirqd内核线程

内核不会立即处理重新触发的软中断。当大量软中断出现的时候,内核会唤醒一组内核线程来处理。这些线程的优先级最低(nice值为19),这能避免它们跟其它重要的任务抢夺资源。但它们最终肯定会被执行,所以这个折中的方案能够保证在软中断很多时用户程序不会因为得不到处理时间而处于饥饿状态,同时也保证过量的软中断最终会得到处理。

每个处理器都有一个这样的线程,名字为ksoftirqd/n,n为处理器的编号。

static int run_ksoftirqd(void *__bind_cpu)
{set_current_state(TASK_INTERRUPTIBLE);current->flags |= PF_KSOFTIRQD; /* I am ksoftirqd */while(! kthread_should_stop()) {preempt_disable();if (! local_softirq_pending()) { /* 如果没有要处理的软中断 */preempt_enable_no_resched();schedule();preempt_disable():}__set_current_state(TASK_RUNNING);while(local_softirq_pending()) {/* Preempt disable stops cpu going offline.* If already offline, we'll be on wrong CPU: don't process.*/if (cpu_is_offline(long)__bind_cpu))/* 被要求释放cpu */goto wait_to_die;do_softirq(); /* 软中断的统一处理函数 */preempt_enable_no_resched();cond_resched();preempt_disable();rcu_note_context_switch((long)__bind_cpu);}preempt_enable();set_current_state(TASK_INTERRUPTIBLE);}__set_current_state(TASK_RUNNING);return 0;wait_to_die:preempt_enable();/* Wait for kthread_stop */set_current_state(TASK_INTERRUPTIBLE);while(! kthread_should_stop()) {schedule();set_current_state(TASK_INTERRUPTIBLE);}__set_current_state(TASK_RUNNING);return 0;
}

Tasklet API

动态初始化函数:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
  • t: struct tasklet_struct 结构指针

  • func:小任务函数

  • data:传递给工作函数的实际参数

静态初始化:静态初始化DECLARE_TASKLET(name, func, data),定义一个名字为 name 的结构变量 ,并且使用 func,data对结构进行初始化,这个宏定义的 tasklet 是可调度的。静态初始化DECLARE_TASKLET_DISABLED(name, func, data)和DECLARE_TASKLET(name, func, data),不同是它开始不能被调度。必须先把 count 设置为0,才可以调度

name:struct tasklet_struct的名字
func:tasklet函数指针
data:传递给func函数的参数

激活/取消激活 tasklet

void tasklet_disable(struct tasklet_struct *t)   // 把 count 设置为1
void tasklet_enable (struct tasklet_struct *t)   // 把count 设置为0

调度函数

void  tasklet_schedule (struct tasklet_struct *t)

调度某个指定的tasklet小任务,调用后tasklet关联的函数会执行.一旦执行,则会在适当时候去执行 tasklet_struct 绑定的函数。对同一个 struct tasklet_struct 连续调度多次,效果等同一次(前提条件:当前一次调用,绑定函数还没有执行)。

5)kill掉函数(取消任务)

tasklet_kill(struct tasklet_struct *t);

6) tasklet和普通工作队列区别:

它所绑定的函数不能休眠

它的响应速度高于普通工作队列。


tasklet 微线程的编程步骤:

taskle 内核机制实现过程是非常复杂的,但是对于驱动开发者来说,重点是掌握如果使用内核已经给我们实现好的tasklet机制。tasklet编程其实只有简单的几步,下面我们总结一下tasklet机制的编程步骤。

1. 定义tasklet 工作函数

2. 定义tasklet 结构变量

定义分有静态定义和动态定义两种方式:

// 动态定义:
struct tasklet_struct my_tasklet;
// 静态定义:
DECLARE_TASKLET(my_tasklet, my_tasklet_function, data);

3. 初始化tasklet结构,绑定工作函数

如果上一步是采用静态定义,则这一步不用再做,跳过。如果是采用动态定义tasklet,则使用tasklet_init()函数进行初始化以及绑定。

tasklet_init(&my_tasklet, my_tasklet_function, data)

4. 在适当的地方调度工作函数

tasklet一般是用于处理中断的下半部的,所以一般在中断的上半部调度tasklet工作函数。

tasklet_schedule(&my_tasklet);

5. 销毁tasklet工作任务

在确定不再使用tasklet时,应该在适当的地方调用tasklet_kill()函数销毁tasklet任务,释放资源,这个适当的地方一般的tasklet初始化地方是相反的,比如,如果是在模块初始化函数初始化了tasklet,则相应地是在模块卸载函数调用tasklet_kill函数来销毁tasklet任务。

tasklet_kill(&my_tasklet);

从中断和异常返回

我们用《深入理解Linux内核》的一张大图来收尾。

我们的ret_from_intr和ret_from_exception本质上等价于:

入口点

ret_from_exception:cli    // 只有从异常返回时才使用 cli,禁用本地中断
ret_from_intr:movl $-8192, %ebp  // 将当前 thread_info 描述符的地址装载到 ebp 寄存器andl %esp, %ebpmovl 0x30(%esp), %eaxmovb 0x2c(%esp), %al
​// 根据发生中断或异常压入栈中的 cs 和 eflags 寄存器的值,// 确定中断的程序在中断时是否运行在用户态testl $0x0002003, %eax  jnz resume_userspacejpm resume_kernel

恢复内核控制路径

rusume_kernel:clicmpl $0, 0x14(%ebp)  // 如果 thread_info 描述符的 preempt_count 字段为0(运行内核抢占)jz need_resched      // 跳到 need_resched
restore_all:       // 否则,被中断的程序重新开始执行popl %ebxpopl %ecxpopl %edxpopl %esipopl %edipopl %ebppopl %eaxpopl %dspopl %esaddl $4, %espiret   // 结束控制

检查内核抢占

need_resched:movl 0x8(%ebp), %ecxtestb $(1<<TIF_NEED_RESCHED), %cl  // 如果 current->thread_info 的 flags 字段中的 TIF_NEED_RESCHED == 0,没有需要切换的进程jz restore_all                     // 因此跳到 restore_alltestl $0x00000200, 0x30(%ebp)      // 如果正在被恢复的内核控制路径是在禁用本地 CPU 的情况下运行jz restore_all                     // 也跳到 restore_all,否则进程切换可能回破坏内核数据结构call preempt_schedule_irq          // 进程切换,设置 preempt_count 字段的 PREEMPT_ACTIVE 标志,大内核锁计数器暂时设置为 -1,调用 schedule()jmp need_resched 

恢复用户态程序

resume_userspace:cli  // 禁用本地中断movl 0x8(%ebp), %ecx
​// 检测 current->thread_info 的 flags 字段,// 如果只设置了 TIF_SYSCALL_TRACE,TIF_SYSCALL_AUDIT 或 TIF_SINGLESTEP 标志,// 跳到 restore_allandl $0x0000ff6e, %ecxje restore_alljmp work_pending

检测重调度标志

work_pending:testb $(1<<TIF_NEED_RESCHED), %cljz work_notifysig
work_resched:call schedule  // 如果进程切换请求被挂起,选择另外 一个进程运行clijmp resume_userspace  // 当前面的进程要恢复时

处理挂起信号、虚拟 8086 模式和单步执行

work_notifysig:movl %esp, %eaxtestl $0x00020000, 0x30(%esp)je 1f
​
// 如果用户态程序 eflags 寄存器的 VM 控制标志被设置
work_notifysig_v86:pushl %ecxcall save_v86_state    // 在用户态地址空间建立虚拟8086模式的数据结构popl %ecxmovl %eax, %esp
1:xorl %edx, %edxcall do_notify_resume  // 处理挂起信号和单步执行jmp restore_all        // 恢复被中断的程序

Reference

80x86中断 - 知乎 (zhihu.com)

Linux内核19-中断描述符表IDT的初始化-腾讯云开发者社区-腾讯云 (tencent.com)

Linux 中断 —— GIC (数据结构 irq_domain/irq_desc/irq_data/irq_chip/irqaction)_irq_data、irq_chip、irq_domain和irq_desc-CSDN博客

Linux内核硬中断 / 软中断的原理和实现-腾讯云开发者社区-腾讯云 (tencent.com)

linux内核之tasklet使用_tasklet 改绑定-CSDN博客

深入理解 Linux 内核---中断和异常_ret_from_exception-CSDN博客

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

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

相关文章

通用漏洞评估系统CVSS4.0简介

文章目录 什么是CVSS&#xff1f;CVSS 漏洞等级分类历史版本的 CVSS 存在哪些问题&#xff1f;CVSS 4.0改进的“命名法”改进的“基本指标”考虑“OT/IOT”新增的“其他指标”CVSS 4.0存在的问题 Reference: 什么是CVSS&#xff1f; 在信息安全评估领域&#xff0c;CVSS为我们…

2024五一数学建模C题Python代码+结果表数据教学

2024五一数学建模竞赛&#xff08;五一赛&#xff09;C题保姆级分析完整思路代码数据教学 C题 煤矿深部开采冲击地压危险预测 第一问 导入数据 以下仅展示部分&#xff0c;完整版看文末的文章 import numpy as np import pandas as pd import matplotlib.pyplot as plt imp…

基于Springboot的音乐翻唱与分享平台

基于SpringbootVue的音乐翻唱与分享平台设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 音乐资讯 音乐翻唱 在线听歌 后台登录 后台首页 用户管理 音乐资讯管理…

labview强制转换的一个坑

32位整形强制转换成枚举的结果如何&#xff1f; 你以为的结果是 实际上的结果是 仔细看&#xff0c;枚举的数据类型是U16&#xff0c;"1"的数据类型是U32&#xff0c;所以转换产生了不可预期的结果。所以使用强制转换时一定要保证两个数据类型一致&#xff0c;否则…

CentOS7安装MySQL8.3(最新版)踩坑教程

安装环境说明 项值系统版本CentOS7 &#xff08;具体是7.9&#xff0c;其他7系列版本均可&#xff09;位数X86_64&#xff0c;64位操作系统MySQL版本mysql-8.3.0-1.el7.x86_64.rpm-bundle.tar 实际操作 官网下载安装包 具体操作不记录&#xff0c;相关教程很多。 mkdir /o…

锂电池SOH预测 | 基于BP神经网络的锂电池SOH预测(附matlab完整源码)

锂电池SOH预测 锂电池SOH预测完整代码锂电池SOH预测 锂电池的SOH(状态健康度)预测是一项重要的任务,它可以帮助确定电池的健康状况和剩余寿命,从而优化电池的使用和维护策略。 SOH预测可以通过多种方法实现,其中一些常用的方法包括: 容量衰减法:通过监测电池的容量衰减…

QT5制做两个独立窗口

目录 增加第二个窗口 主窗口文件添加一个私有成员为子窗口 定义两个槽函数和 关联按钮和子窗口和主窗口 添加子窗口成员 子窗口处理函数 补充回顾 增加第二个窗口 1、 2、 3 主窗口文件添加一个私有成员为子窗口 在mainwidget.h文件 同时添加两个槽&#xff1b;来处理…

Docker: 如何不新建容器 修改运行容器的端口

目录 一、修改容器的映射端口 二、解决方案 三、方案 一、修改容器的映射端口 项目需求修改容器的映射端口 二、解决方案 停止需要修改的容器 修改hostconfig.json文件 重启docker 服务 启动修改容器 三、方案 目前正在运行的容器 宿主机的3000 端口 映射 容器…

【综述】多核处理器芯片

文章目录 前言 Infineon处理器 AURIX™系列 TC399XX-256F300S 典型应用 开发工具 参考资料 前言 见《【综述】DSP处理器芯片》 Infineon处理器 AURIX™系列&#xff0c;基于TriCore内核&#xff0c;用于汽车和工业领域。 XMC™系列&#xff0c;基于ARM Cortex-M内核&…

test4282

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

2024五一杯数学建模A题思路分析-钢板最优切割路径问题

文章目录 1 赛题选题分析 2 解题思路3 最新思路更新 1 赛题 A题 钢板最优切割路径问题 提高钢板下料切割过程中的工作效率&#xff0c;是模具加工企业降低成本和增加经济效益的重要途径&#xff0c;其中钢板切割的路径规划是钢板切割过程的一个关键环节。 钢板切割就是使用特殊…

2024 五一杯高校数学建模邀请赛(C题)| 煤矿深部开采冲击地压危险预测 |建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量身打造创新解决方案。小秘籍团队&#xff0c;始终引领着建模问题求解的风潮。 抓紧小秘籍&#xff0c;我们出发吧~ 让我们看看五一杯的C题&#xff01; 完…

ChatGPT 网络安全秘籍(一)

原文&#xff1a;zh.annas-archive.org/md5/6b2705e0d6d24d8c113752f67b42d7d8 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 在不断发展的网络安全领域中&#xff0c;由 OpenAI 推出的 ChatGPT 所代表的生成式人工智能和大型语言模型&#xff08;LLMs&#xf…

【软件开发规范篇】JAVA后端开发编码格式规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

公共 IP 地址与私有 IP 地址区别有哪些?

​  IP 地址是分配给互联网上每个设备的唯一数字 ID。 IP 地址可以在 Internet 上公开使用&#xff0c;也可以在局域网 (LAN)上私有使用。本文&#xff0c;我们主要探讨公共 IP 地址和私有 IP 地址之间的区别。 公共IP地址&#xff1a;公共IP地址是用于访问Internet的向外的I…

头歌:SparkSQL简单使用

第1关&#xff1a;SparkSQL初识 任务描述 本关任务&#xff1a;编写一个sparksql基础程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1. 什么是SparkSQL 2. 什么是SparkSession。 什么是SparkSQL Spark SQL是用来操作结构化和半结构化数据的接口。…

AI家居设备的未来:智能家庭的下一个大步

&#x1f512;目录 ☂️智能家居设备的发展和AI技术的作用 ❤️AI技术实现智能家居设备的自动化控制和智能化交互的依赖 AI家居设备的未来应用场景 &#x1f4a3;智能家庭在未来的发展和应用前景 &#x1f4a5;智能家居设备的发展和AI技术的作用 智能家居设备的发展和AI技术的…

Liunx发布tomcat项目

Liunx在Tomcat发布JavaWeb项目 1.问题2.下载JDK3.下载Tomcat4.Tomcat本地JavaWeb项目打war包、解压、发布5.重启Tomcat,查看项目 1.问题 1.JDK 与 Tomcat 版本需匹配&#xff0c;否则页面不能正确显示 报错相关&#xff1a;Caused by: java.lang.ClassNotFoundException: java…

优化NGINX性能:使用NGINX_THREADS提高并发处理能力

目录标题 1. 什么是NGINX_THREADS&#xff1f;2. 配置NGINX_THREADS3. 使用NGINX_THREADS处理耗时操作4. 性能调优5. 结论 NGINX作为一个高性能的HTTP和反向代理服务器&#xff0c;在处理高并发请求时表现出色。但随着互联网应用对性能要求的不断提高&#xff0c;深入了解和优化…

DA14531如何配置SDK工程路径

1 前言 当我们获取一个DA14531的工成样列&#xff0c;有时是不包括SDK中的公共部分的代码&#xff0c;只有用户项目代码。而想拷贝到自己的SDK中&#xff0c;如&#xff1a;F:\A02_Work\A01_Dailog\DA145xx_SDK\6.0.18.1182.1\projects\User\ble_app_sensor&#xff0c;打开工…