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,一经查实,立即删除!

相关文章

【C++11】多线程创建/互斥详解

目录 1. 背景2. 线程创建2.1 使用std::thread类来创建线程2.2 使用std::async 函数来创建线程2.3 std::thread和std::async的区别 3. 线程互斥3.1 互斥体std::mutex3.2 互斥体包装器std::lock_guard3.3 条件变量std::condition_variable3.4 原子类型std::atomic 4. 线程控制自己…

JAVA每日面试题(二)

Java 高级面试问题及答案 问题1: 什么是Java内存模型(JMM)&#xff0c;它在多线程编程中扮演什么角色&#xff1f; 答案&#xff1a; Java内存模型&#xff08;JMM&#xff09;是一个抽象的概念&#xff0c;它定义了一组规则&#xff0c;这些规则决定了程序中的变量&#xff…

通用漏洞评估系统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;否则…

Python绝对路径及命令行执行路径的写法收录

Python绝对路径及命令行执行路径的写法 在Python中&#xff0c;以使用字符串来表示绝对路径。为了避免转义字符的问题&#xff0c;可以使用原始字符串&#xff08;raw string&#xff09;来表示路径。 直接r 后面路径是单或反斜杠均可&#xff0c;r让转义字符不起作用&#x…

【JavaEE网络】TCP套接字编程详解:从概念到实现

目录 TCP流套接字编程ServerSocket APISocket APITCP回显客户端服务器 TCP流套接字编程 TCP用的协议比UDP更多&#xff0c;可靠性 提供的api主要有两个类ServerSocket&#xff08;给服务器使用的socket&#xff09;&#xff0c;Socket&#xff08;既会给服务器使用也会给客户…

SQL server 使用教程

1.安装和配置SQL Server&#xff1a; 下载安装包&#xff1a;首先&#xff0c;你需要下载SQL Server的安装包。可以从Microsoft官方网站获取最新版的安装包链接&#xff0c;然后下载适用于你的操作系统的版本。 安装SQL Server&#xff1a;双击运行下载的安装包&#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;来处理…

Linux 系统上安装 NVIDIA 驱动程序失败(X server问题)

报错信息&#xff1a; ERROR: You appear to be running an X server; please exit X before installing. For further details, please see the section INSTALLING THE NVIDIA DRIVER in the README available on the Linux driver download page at www.nvidia.com. ERROR: …

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

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

vue2实现面包屑功能

目录 1. store/index.js 2. router/index.js 3. Header.vue 在Vue 2中实现面包屑导航是一种常见的前端实践,它可以帮助用户了解当前页面在网站结构中的位置,并快速导航到上一级或根目录。以下是使用Vue 2实现面包屑导航的基本步骤: 1. store/index.js state中定义一个面…

python 关键字(await)

2、await 在Python的异步编程中,await关键字扮演着至关重要的角色。对于初学者来说,理解await的使用和背后的概念可能有些困难,但对于有经验的开发者来说,掌握它则是编写高效、响应性强的代码的关键。下面我将从基础到高级,逐步解析await关键字。 基础知识:await是什么?…

NLP(11)--词向量

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 one-hot 编码 i love u [1,2,3] 词向量训练目标&#xff1a; 如果两个词在文本出现&#xff0c;它的前后出现的词相似&#xff0c;则这两个词语义相似 cbow(基于窗口预测词)缺点 :输出层是vocab_size 会很大 收敛速度会很慢…

【综述】多核处理器芯片

文章目录 前言 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;其中钢板切割的路径规划是钢板切割过程的一个关键环节。 钢板切割就是使用特殊…