面试官:为什么在中断里不能sleep?

TsinghuaJoking 2021-12-15 08:56

大家好,我是老吴。

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

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

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

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


明确问题

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

对于这个问题,稍微准确一点的问法是:为什么在 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 section (临界区)。

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

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 section 里,是不能 call scheduler 的。

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

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

如果想在 ISR 里支持 sleep,也就是支持 call scheduler 的话,那么所有的 critical section 都必须得禁用中断,否则硬件中断一旦来临系统就会出现 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_CHECK
 if (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] [] (unwind_backtrace) from [] (show_stack+0x10/0x14)
[   27.238267] [] (show_stack) from [] (__schedule_bug+0x64/0x84)
[   27.245802] [] (__schedule_bug) from [] (__schedule+0x3fc/0x550)
[   27.253512] [] (__schedule) from [] (schedule+0x50/0xb4)
[   27.260533] [] (schedule) from [] (schedule_timeout+0x114/0x1e8)
[   27.268246] [] (schedule_timeout) from [] (msleep+0x2c/0x38)
[   27.275612] [] (msleep) from [] (XXX_ISR+0x34/0x8c)
[   27.282982] [] (XXX_ISR) from [] (__handle_irq_event_percpu+0x88/0x124)
[   27.292075] [] (__handle_irq_event_percpu) from [] (handle_irq_event_percpu+0x1c/0x58)
[   27.301693] [] (handle_irq_event_percpu) from [] (handle_irq_event+0x38/0x5c)
[   27.310532] [] (handle_irq_event) from [] (handle_edge_irq+0xe0/0x1a4)
[   27.318764] [] (handle_edge_irq) from [] (generic_handle_irq+0x24/0x34)
[   27.327091] [] (generic_handle_irq) from [] (exynos_irq_eint0_15+0x44/0x98)
[   27.335751] [] (exynos_irq_eint0_15) from [] (generic_handle_irq+0x24/0x34)
[   27.344415] [] (generic_handle_irq) from [] (__handle_domain_irq+0x54/0xa8)
[   27.353080] [] (__handle_domain_irq) from [] (vic_handle_irq+0x58/0x94)
[   27.361398] [] (vic_handle_irq) from [] (__irq_svc+0x6c/0xa8)
[   27.368847] Exception stack(0xc0d01f58 to 0xc0d01fa0)

总结一下

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

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

ISR 里如何处理耗时的工作

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

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

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

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

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

void irq_exit(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
 local_irq_disable();
#else
 WARN_ON_ONCE(!irqs_disabled());
#endif

 account_irq_exit_time(current);
 preempt_count_sub(HARDIRQ_OFFSET);

 // 内核抢占没被关闭、已经没有其他 hardirq 了、有 softirq 在 pending 等条件都被满足时,才会处理 softirq
 if (!in_interrupt() && local_softirq_pending())
  invoke_softirq();

 [...]
}

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

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

点击查看大图

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

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

—— The End —

推荐阅读  点击蓝色字体即可跳转
☞ 使用FreeRTOS要好好理解任务状态机
☞ 手把手教你在STM32F4上跑freeRTOS
 图解FreeRTOS原理系列之任务管理器基本框架
 傅里叶变换、拉普拉斯变换、Z 变换的联系是什么?为什么要变换

欢迎转发、留言、点赞、分享给您的朋友,感谢您的支持!

TsinghuaJoking 这是一个公众号,它不端、不装,与你同游在课下、课上。 卓晴博士,清华大学中央主楼 626A。010-62773349, 13501115467,zhuoqing@tsinghua.edu.cn
评论
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 116浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 125浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
我要评论
2
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦