二逼了吧,你竟然在中断里面休眠

如果要看下面的文章之前,建议之前的文章也瞄一眼

为什么不能在中断上半部休眠?

扒一扒中断为什么不能调printf


大家好,我是老吴「我只是老吴的朋友」。

今天是周一「今天不是周一」,大家工作顺利吗?

这篇文章给大家分享一点小知识:为什么中断里不能睡眠?

网上很多文章尝试解释这个问题,看后我觉得头皮发麻。

下面,我试着总结一下原因。

明确问题

首先,让我们明确一下问题。

对于这个问题,稍微准确一点的问法是:为什么在 Linux 的中断里,不能 sleep?

但是这个问法仍然不准确。

中断 (interrupt) 和中断服务程序 (interrupt service routine, ISR,或者是 interrupt handler),是 2 个不同的概念。

前者是硬件相关的概念,后者是软件相关的概念。

所以,对于这个问题,最准确的问法是:为什么在 Linux 的 ISR 里,不能 sleep?

由于 sleep 意味着 call scheduler,所以更直白一点的问法是

为什么在 Linux 的 ISR 里,不能 call scheduler?

最后,再加点限制条件会更准确:为什么在 Linux 的 ISR 里,即便 ISR 没有 hold 住任何 lock 的时候,都不能 call scheduler?

一种常见的解释

不能在 ISR 里睡眠的原因是:ISR 与任何 process context (进程上下文) 无关。

process context 是进程的状态信息,包括:

  • kernelspace and userspace stack pointers;

  • register set,或者称为 hardware context;

  • page table;

对于每一个进程,在内核都会有一个 pcb (process control, block,即 Linux 里的 task_struct 结构体) 来管理这些信息。

scheduler 可以访问所有这些信息,以抢占一个进程并运行另一个进程。

与此相反,取决于内核和迎接架构的版本,ISR 使用单独的中断栈或被中断的进程的内核栈,并且在中断中会有自己的 hardware context.

因此,由于在 ISR 里没有 process context,所以不能进行调度。

但是,这个说法描述的其实是当下设计的状况,而不是当初这样设计的原因。

在 Linux 的早期版本中,ISR 总是借用当前进程的栈。

所以如果内核想设计成允许在 ISR 里睡眠,是可以很自然地实现进程上下文切换的。

但是,Linux 采用的设计是:在 ISR 里禁止睡眠。

现在,我们的问题变成了

为什么在 Linux 里,ISR 被设计成不能睡眠?

将 ISR 设计成不可睡眠的原因

sleep 会导致 call scheduler 以选择另一个进程来运行。

内核代码里有大量的 critical p (临界区)。

critical p 本质上是一段会访问或操作共享资源的代码,例如:

static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{struct fs_struct *fs = current->fs;if (clone_flags & CLONE_FS) {/* tsk->fs is already what we want */spin_lock(&fs->lock);if (fs->in_exec) {spin_unlock(&fs->lock);return -EAGAIN;}fs->users++;spin_unlock(&fs->lock);return 0;}tsk->fs = copy_fs_struct(fs);if (!tsk->fs)return -ENOMEM;return 0;
}

在 critical p 里,是不能 call scheduler 的。

因为已经有一个进程持有锁了,如果这时切换到另一个进程,最好的情况下是等待一段无法预测的时间后前一个进程会将锁释放出来,最坏的情况是死锁。

硬件中断是随时可能发生的,即便内核执行的路径正处于 critical p 中。

如果想在 ISR 里支持 sleep,也就是支持 call scheduler 的话,那么所有的 critical p 都必须得禁用中断,否则硬件中断一旦来临系统就会出现 race condition,接下来大概率是死锁。

Sleep 和 ISR

我查阅了一下 Linux 4.9 的代码,当你在一个不能调度的地方 call scheduler (例如 ISR 里 sleep) 的话,内核可以提示你写的代码有 BUG:

static inline void schedule_debug(struct task_struct *prev)
{
#ifdef CONFIG_SCHED_STACK_END_CHECKif (task_stack_end_corrupted(prev))panic("corrupted stack end detected inside scheduler\n");
#endif// 错误的时机 call sheduler ?if (unlikely(in_atomic_preempt_off())) {__schedule_bug(prev);preempt_count_set(PREEMPT_DISABLED);}[...]
}

我在某个设备驱动的中断处理函数 XXX_ISR() 里加了 msleep(10) 之后:

[   27.221560] BUG: scheduling while atomic: swapper/0/0x00010002
[   27.221609] Modules linked in: 8021q garp stp mrp llc usb_f_eem g_ether usb_f_rndis u_ether exfat(O)
[   27.221712] CPU: 0 PID: 0 Comm: swapper Tainted: G           O    4.9.203 #640
[   27.224736] Hardware name: Samsung Device
[   27.230575] [<c010d3b4>] (unwind_backtrace) from [<c010afc8>] (show_stack+0x10/0x14)
[   27.238267] [<c010afc8>] (show_stack) from [<c014848c>] (__schedule_bug+0x64/0x84)
[   27.245802] [<c014848c>] (__schedule_bug) from [<c084a2b0>] (__schedule+0x3fc/0x550)
[   27.253512] [<c084a2b0>] (__schedule) from [<c084a454>] (schedule+0x50/0xb4)
[   27.260533] [<c084a454>] (schedule) from [<c084ccb0>] (schedule_timeout+0x114/0x1e8)
[   27.268246] [<c084ccb0>] (schedule_timeout) from [<c016dd04>] (msleep+0x2c/0x38)
[   27.275612] [<c016dd04>] (msleep) from [<c057ebf8>] (XXX_ISR+0x34/0x8c)
[   27.282982] [<c057ebf8>] (XXX_ISR) from [<c015f928>] (__handle_irq_event_percpu+0x88/0x124)
[   27.292075] [<c015f928>] (__handle_irq_event_percpu) from [<c015f9e0>] (handle_irq_event_percpu+0x1c/0x58)
[   27.301693] [<c015f9e0>] (handle_irq_event_percpu) from [<c015fa54>] (handle_irq_event+0x38/0x5c)
[   27.310532] [<c015fa54>] (handle_irq_event) from [<c0162808>] (handle_edge_irq+0xe0/0x1a4)
[   27.318764] [<c0162808>] (handle_edge_irq) from [<c015ed64>] (generic_handle_irq+0x24/0x34)
[   27.327091] [<c015ed64>] (generic_handle_irq) from [<c0430ed8>] (exynos_irq_eint0_15+0x44/0x98)
[   27.335751] [<c0430ed8>] (exynos_irq_eint0_15) from [<c015ed64>] (generic_handle_irq+0x24/0x34)
[   27.344415] [<c015ed64>] (generic_handle_irq) from [<c015f20c>] (__handle_domain_irq+0x54/0xa8)
[   27.353080] [<c015f20c>] (__handle_domain_irq) from [<c010146c>] (vic_handle_irq+0x58/0x94)
[   27.361398] [<c010146c>] (vic_handle_irq) from [<c010ba4c>] (__irq_svc+0x6c/0xa8)
[   27.368847] Exception stack(0xc0d01f58 to 0xc0d01fa0)

总结一下

硬件中断是超级宝贵的资源,想在中断里睡眠的话就得在大量的 critical p 中关闭中断才能避免 race condition,而关闭硬件中断将会大大地增加中断响应的延迟,降低系统的反应速度,这是操作系统的用户所无法接受的, 因此内核开发者采用的设计是在中断里不允许睡眠,并且 ISR 应尽快执行并返回以便系统里的进程继续运行。

那么,那些很耗时的工作该怎么处理呢?

ISR 里如何处理耗时的工作

由于硬件中断可能随时发生,ISR 随时会执行。因此,它必须快速运行并退出,以便尽快恢复被中断代码的执行。在操作系统看来,无论是硬件中断还是被中断的代码,两者都是很重要的,因此,ISR 应在尽可能短的时间内执行完毕。

但是,现实情况是,许多 ISR 有大量工作要执行。例如网络设备的 ISR 除了响应硬件之外,还需要 将网络数据包从硬件复制到内存中,处理它们,并将数据包向下分发到适当的协议栈或应用程序。

Linux 如何解决这种活多钱少的问题?

答:将 ISR 分为 top half 和 bottom half。

top half 在收到中断后立即运行,仅执行时间紧迫的工作,例如确认收到中断或重置硬件,执行完 top half 后,如果进入 ISR 前是处于 critical p 且内核抢占是被关闭 ( 例如 spinlock ) 的话,就会返回到 critical p 里继续运行,不会产生 race condition 的问题。

void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLEDlocal_irq_disable();
#elseWARN_ON_ONCE(!irqs_disabled());
#endifaccount_irq_exit_time(current);preempt_count_sub(HARDIRQ_OFFSET);// 内核抢占没被关闭、已经没有其他 hardirq 了、有 softirq 在 pending 等条件都被满足时,才会处理 softirqif (!in_interrupt() && local_softirq_pending())invoke_softirq();[...]
}

而晚一点执行也没问题的工作将推迟到 bottom half。bottom half 将在某个未来更方便的时间运行,并且是在使能所有中断、使能内核抢占的情况下进行,那时我们想怎么折腾就怎么折腾吧。

Linux 提供了许多 bottom half 的机制,例如 softirqs、tasklets、workqueues。

点击查看大图

所以,有了 bottom half 之后,在 ISR 里睡眠这种需求,其实是完全没有必要的。

到此,这个问题就解释完毕了,感谢大家的阅读。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

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

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

相关文章

康纳的表情包(思维)

UMR 现在手里有 n 张康纳的表情&#xff0c;最上面一张是玛吉呀巴库乃。现在 UMR 如果每次把最上面的 m 张牌移到最下面而不改变他们的顺序及朝向&#xff0c;那么至少经过多少次移动玛吉呀巴库乃才会又出现在最上面呢&#xff1f; Input 多组输入。 对于每组数据&#xff0c;输…

用gdb搞清楚一道union相关的面试题

题目并不是特别新鲜&#xff0c;不过这个题目在面试上肯定能筛选一大波人&#xff0c;特别是&#xff0c;有的题目大家看到很多次&#xff0c;但是每次都是简单看看&#xff0c;没有深入分析&#xff0c;结果笔试遇到差不多一样的题目时&#xff0c;自己又傻逼了。搞C语言&…

噪声控制简史,以及几个简单的声学概念

文 | 子鱼编辑 | 贰沐 子鱼前言前段时间无意中发现了一个非常棒的声学教育平台&#xff08;acoucou.org&#xff09;&#xff0c;里边内容非常丰富&#xff0c;涉猎面很广&#xff0c;同时又有很多基础知识。不仅可以给不了解声学的人领路&#xff0c;也可以给声学从业人员带来…

魔戒(思维+bfs)

Description 蓝色空间号和万有引力号进入了四维水洼&#xff0c;发现了四维物体--魔戒。 这里我们把飞船和魔戒都抽象为四维空间中的一个点&#xff0c;分别标为 "S" 和 "E"。空间中可能存在障碍物&#xff0c;标为 "#"&#xff0c;其他为可以通…

熬夜给这个C语言游戏项目找了几个bug

晚上看到一个非常有意思的C语言游戏项目&#xff0c;这个项目完全都是用C语言写的&#xff0c;而且资料也比较齐全&#xff0c;有github资料&#xff0c;也有QQ群。它的项目介绍是这样的&#xff1a;哦&#xff0c;还有它的官网http://painterengine.com/index.html还有它的git…

怎么算掌握了mysql_MySQL你必须掌握了解的锁知识!

一、前言MySQL 的锁按照范围可以分为全局锁、表锁、行锁&#xff0c;其中行锁是由数据库引擎实现的&#xff0c;并不是所有的引擎都提供行锁&#xff0c;MyISAM 就不支持行锁&#xff0c;所以文章介绍行锁会以InnoDB引擎为例来介绍。二、全局锁MySQL 提供全局锁来对整个数据库实…

我学机械的可以转嵌入式吗?

▼点击下方名片&#xff0c;关注公众号▼编排 | strongerHuang微信公众号 | 嵌入式专栏前几天&#xff0c;有读者在后台问&#xff0c;他是一个机械专业的学生&#xff0c;想转到嵌入式方向&#xff0c;问我有没有必要转&#xff1f;如果转嵌入式该怎么学&#xff1f;今天我们特…

入门物联网还得靠嵌入式

小米在十一周年的发布会上&#xff0c;展示了一个新产品&#xff1a;CyberDog仿生四足机器人“铁蛋”&#xff0c;继腾讯X实验室的四足机器人MAX后&#xff0c;小米也开始跨界入局尝试研制机器狗。2020年国家会议召开&#xff0c;加快推动新基建建设&#xff0c;各产业进行数字…

mysql中定时任务_mysql中定时任务的用法

1.什么是事件一组SQL集&#xff0c;用来执行定时任务&#xff0c;跟触发器很像&#xff0c;都是被动执行的&#xff0c;事件是因为时间到了触发执行&#xff0c;而触发器是因为某件事件(增删改)触发执行&#xff1b;mqsql的事件类似于linux的定时任务&#xff0c;不过是完全在m…

一文看懂 | 内存交换机制

本文基于 Linux-2.4.16 内核版本由于计算机的物理内存是有限的, 而进程对内存的使用是不确定的, 所以物理内存总有用完的可能性. 那么当系统的物理内存不足时, Linux内核使用什么方案来避免申请不到物理内存这个问题呢?相对于内存来说, 磁盘的容量是非常大的, 所以Linux内核实…

无论是cisco还是华三的书上对于子网划分有个问题需要解释

无论是cisco还是华三的书上对于子网划分有个问题&#xff0c;例如&#xff1a;如果子网为有五位 &#xff0c;则可以划分为30个子网。在实际中却不是这样的 子网位五位&#xff0c;可以划分为32个子网。那为什么这么写&#xff0c;难道是出书的人写错了&#xff0c;其实不是。这…

mysql online ddl 5.6_MySQL 5.6的Online DDL功能测试

online DDL的前身是 innodb fast index creation(5.1和5.5), 5.6里对这个功能做了扩展&#xff1a;很多alter table的操作绕开了 table copying&#xff0c;支持DML并发操作。一、online ddl的支持测试&#xff1a;1、主键的增删主键添加&#xff1a;支持online ddl&#xff0c…

Stupid cat Doge (分形图)

【题目描述】 【题目链接】 http://noi.openjudge.cn/ch0204/8463/ 【算法】 为求等级N下的点的坐标可由几何关系找到其与等级N-1下对应点的关系&#xff0c;然后递归直至所有点的祖先&#xff08;等级0&#xff09;即可计算出坐标。 【代码】 1 #include <bits/stdc.h>2…

电赛时,如何快速搭建电路?

大家好&#xff0c;我是张巧龙&#xff0c;电赛只有四天三夜&#xff0c;电路方案可能需要多次验证&#xff0c;有的同学选择直接洞洞板焊接&#xff0c;自行跳线。有些同学可能会选择雕刻机雕刻。我带的学生一般会使用传统工艺-腐蚀法&#xff0c;这种方法的优点&#xff0c;成…

Javaweb经典三层架构的演变

1.Javaweb经历了三个时期 ①JSP Model1第一代 JSP Model1是JavaWeb早期的模型&#xff0c;它适合小型Web项目&#xff0c;开发成本低&#xff01;Model1第一代时期&#xff0c;服务器端只有JSP页面&#xff0c;所有的操作都在JSP页面中&#xff0c;连访问数据库的API也在JSP页面…

我决定去读研了

读书这个事情&#xff0c;我一直都是比较推荐大家去做的&#xff0c;今天有一位同学找到我&#xff0c;问我现在哪个行业比较赚钱&#xff0c;自己马上要毕业了&#xff0c;想马上大显身手一下。然后我问他&#xff0c;你有没有赚钱的压力&#xff0c;还有自己目前的学习成绩怎…

win2008 php mysql zend phpmyadmin_Windows2008 最新版Apache2.PHP5.MySQL6.PHPMyadmin.ZendOptimizer安装图解...

首先到PHPCHINA的网站www.phpchina.com下载最新的正式版本Apache 2.2.8地址&#xff1a;http://apache.mirror.phpchina.com/httpd/httpd-2.2.8-win32-src.zipPHP-5.2.5地址&#xff1a;http://cn.php.net/get/php-5.2.5-Win32.zip/from/this/mirrorMySQL-6.0.3-alpha-win32地址…

WPF 开源项目 【watcher】 守望者,一款监控,统计,分析你每天在自己电脑上究竟干了什么的软件...

时隔多年&#xff08;两年&#xff09;&#xff0c;天天沉迷写PHP的我在连续加薪了逐渐发现自己不怎么写代码了。 甚至有一天我发现我连IDE 都没有打开&#xff0c;实在是太堕落了 为了及时悬崖勒马&#xff0c;回头是岸。为了鼓励自己专心写代码&#xff0c;我决定写一款监控自…

Java iText PDF:用 iText 包生成简单的 pdf 文件

有点兴趣想要看看 Java 怎么生成 PDF 文件&#xff0c;搜索了一下&#xff0c;据说 iText 包比较好&#xff0c;到 这里下载 iText.jar 包&#xff0c;顺便把源代码、文档都一起下载了吧。但是&#xff0c;仅仅有这么一些材料想要写代码生成 pdf 文件还是比较困难的&#xff0c…

java互换_两个变量交换的四种方法(Java)

对于两种变量的交换,我发现四种方法,下面我用Java来演示一下。 1.利用第三个变量交换数值,简单的方法。 (代码演示一下) 1 class TestEV 2 //创建一个类 3 {4 public static void main(String[]args) 5 {6 int x =5,y=10; //定义两个变量 7 8 int temp = x;    //定义第…