Kernel Exploring
  • 前言
  • 支持
  • 老司机带你探索内核编译系统
    • 编译出你的第一个内核
    • 内核编译中的小目标
    • 可能是kbuild中最直接的小目标 – help
    • 使用了一个kbuild函数的目标 – cscope
    • 内核中单个.o文件的编译过程
    • 根目录vmlinux的编译过程
    • 启动镜像bzImage的前世今生
    • setup.bin的诞生记
    • 真假vmlinux–由vmlinux.bin揭开的秘密
    • bzImage的全貌
    • kbuild系统浅析
  • 启动时的小秘密
    • INIT_CALLS的秘密
    • 内核参数
  • 内核加载全流程
    • bootloader如何加载bzImage
    • 内核压缩与解压
    • 内核加载的几个阶段
    • 保护模式内核代码赏析
  • 内存管理
    • 内核页表成长记
      • 未解压时的内核页表
      • 内核早期的页表
      • cleanup_highmap之后的页表
      • 映射完整物理地址
      • 启用init_level4_pgt
    • 自底而上话内存
      • e820从硬件获取内存分布
      • 原始内存分配器--memblock
      • 页分配器
        • 寻找页结构体的位置
        • 眼花的页结构体
        • Node-Zone-Page
        • 传说的伙伴系统
        • Compound Page
        • GFP的功效
        • 页分配器的用户们
      • slub分配器
        • slub的理念
        • 图解slub
      • 内存管理的不同粒度
      • 挑战和进化
        • 扩展性的设计和实现
        • 减少竞争 per_cpu_pageset
        • 海量内存
        • 延迟初始化
        • 内存热插拔
        • 连续内存分配器
    • 虚拟内存空间
      • 页表和缺页中断
      • 虚拟地址空间的管家--vma
      • 匿名反向映射的前世今生
      • 图解匿名反向映射
      • THP和mapcount之间的恩恩怨怨
      • 透明大页的玄机
      • NUMA策略
      • numa balance
      • 老版vma
    • 内存的回收再利用
      • 水线
      • Big Picture
      • 手动触发回收
      • Page Fram Reclaim Algorithm
      • swapfile原理使用和演进
    • 内存隔离
      • memcg初始化
      • 限制memcg大小
      • 对memcg记账
    • 通用
      • 常用全局变量
      • 常用转换
    • 测试
      • 功能测试
      • 性能测试
  • 中断和异常
    • 从IDT开始
    • 中断?异常?有什么区别
    • 系统调用的实现
    • 异常向量表的设置
    • 中断向量和中断函数
    • APIC
    • 时钟中断
    • 软中断
    • 中断、软中断、抢占和多处理器
  • 设备模型
    • 总线
    • 驱动
    • 设备
    • 绑定
  • nvdimm初探
    • 使用手册
    • 上帝视角
    • nvdimm_bus
    • nvdimm
    • nd_region
    • nd_namespace_X
    • nd_dax
      • dev_dax
  • KVM
    • 内存虚拟化
      • Qemu内存模型
      • KVM内存管理
  • cgroup
    • 使用cgroup控制进程cpu和内存
    • cgroup文件系统
    • cgroup层次结构
    • cgroup和进程的关联
    • cgroup数据统计
  • 同步机制
    • 内存屏障
    • RCU
  • Trace/Profie/Debug
    • ftrace的使用
    • 探秘ftrace
    • 内核热补丁的黑科技
    • eBPF初探
    • TraceEvent
    • Drgn
  • 内核中的数据结构
    • 双链表
    • 优先级队列
    • 哈希表
    • xarray
    • B树
    • Maple Tree
    • Interval Tree
  • Tools
  • Good To Read
    • 内核自带文档
    • 内存相关
    • 下载社区邮件
Powered by GitBook
On this page
  • 初始化
  • 硬塞的__preemt_count
  • 何时调用软中断?
  • 标记软中断请求
  • 在适当的时机执行软中断
  • 响应软中断

Was this helpful?

  1. 中断和异常

软中断

软中断,softirq,经常听说,但是究竟是什么,怎么用其实我并不清楚。这不,最近看到有代码使用了软中断,为了能够进一步了解只好硬着头皮来看看源代码。

软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。

那究竟是怎么做的呢?让我们一探究竟。

初始化

一切的一切总有个起头的,既然是抓瞎的状态,那就先抓住开始的部分。

start_kernel()
    softirq_init(void)
        open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    	  open_softirq(HI_SOFTIRQ, tasklet_hi_action);
            softirq_vec[nr].action = action;

瞧,这其实啥都没干,就填写了一个数组的成员。那这个数组长什么样子呢?

      softirq_vec[NR_SOFTIRQS]
      +------------------------------------+
      |action                              |
      |  void (*)(struct softirq_action *) |
      +------------------------------------+
      |action                              |
      |  void (*)(struct softirq_action *) |
      +------------------------------------+
      |action                              |
      |  void (*)(struct softirq_action *) |
      +------------------------------------+

好吧,就这么一个光杆司令,每个数组就是一个毁掉函数的成员。真的是不知所云。

硬塞的__preemt_count

按照正常逻辑,现在我应该来讲什么时候softirq被调用的。但是呢,我发先有一个非常重要的概念(变量)对理解何时调用很关键,所以就硬插进来,模拟一个“软中断”。

这个变量叫__preemt_count。名字很简单,但是定义却藏着玄机。

DEFINE_PER_CPU(int, __preempt_count) = INIT_PREEMPT_COUNT;

乍一看就是一个32位的整型,但是你再往里看其实这个整型被切几个小块,没块有自己的含义。

            PREEMPT_MASK:	0x000000ff
            SOFTIRQ_MASK:	0x0000ff00
            HARDIRQ_MASK:	0x000f0000
                NMI_MASK:	0x00100000
    PREEMPT_NEED_RESCHED:	0x80000000

然后我用一张简易图来展示一下上面的定义:

    |<    8bits     >|<    8bits     >|<    8bits     >|<     8bits    >|
    +-+--------------+-----+-+--------+--------------+-+----------------+
    | |              |     | |hard irq| softirq cnt  | | preempt cnt    |
    +-+--------------+-----+-+--------+--------------+-+----------------+
     ^                      ^                         ^
     |                      |                         |
     |                      |                         |
     |                      |                         |
     |                      |                         +--- Bit8:  in_serving_softirq()
     |                      +----------------------------- Bit20: NMI_MASK
     +---------------------------------------------------- Bit31: PREEMPT_NEED_RESCHED

与此同时,就要引出几个和当前cpu运行状态相关的函数了。

  • in_irq() - We're in (hard) IRQ context

  • in_softirq() - We have BH disabled, or are processing softirqs

  • in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled

  • in_serving_softirq() - We're in softirq context

  • in_nmi() - We're in NMI context

  • in_task() - We're in task context

原来我们通常判断cpu状态的函数,就是根据cpu上变量__preempt_count来确定的。顿时有种找到根的感觉。

那为什么会插播这么一个变量呢?是因为在softirq中,以及其他很多地方都会判断这个值来确认当前cpu运行状态,以此来判断是否应该执行什么操作。

好了,这个“软中断”结束了,让我们回到上文。

何时调用软中断?

对软中断的调用,还得分成两步:

  • 标记有软中断的请求

  • 在适当的时机执行

毕竟软中断不像硬中断,可以来了就执行。软中断没有硬件的这种权利打断别人的运行,只好先标记好自己的到来,等待时机的出现。

标记软中断请求

标记请求由函数raise_softirq(nr)来完成。

    raise_softirq(nr), explicit raise
        local_irq_save(flags);
        raise_softirq_irqoff(nr);
            __raise_softirq_irqoff(nr);
                or_softirq_pending(1UL << nr)
                    __this_cpu_or(local_softirq_pending_ref, (x))
            wakeup_softirqd(), if !in_interrupt()
                tsk = __this_cpu_read(ksoftirqd);
                wake_up_process(tsk); wakeup ksoftirqd
        local_irq_restore(flags);

实际上做了什么呢?打开看一看。

  • 关中断

  • 在变量local_softirq_pending_ref上,标记nr

  • 如果不在in_interrupt(),唤醒softirq线程

在这里我们着重要讲的是第二部,标记变量local_softirq_pending_ref。

在适当的时机执行软中断

之前我们也提到,软中断不像硬中断能够要求硬件及时响应。所以只好等到某个时间,再由cpu来处理。那么都有哪些时机,cpu回来处理软中断呢?

  • irq_exit(): 中断处理函数返回时

  • local_bh_enable(): 允许软中断时

  • raise_softirq(): 标记软中断时(通过唤醒线程)

在没有线程化irq时,前两者是立即执行的,只有第三者是通过wakeup来唤醒软中断线程。

这里我们结合一下上一小节硬插进来的概念,看看raise_softirq()中的处理。

    if(!in_interrupt())
        wakeup_softirq()

而这个in_interrupt()的含义是: We're in NMI,IRQ,SoftIRQ context or have BH disabled。这就是当我们cpu运行在这几个状态下时,我们就不去唤醒软中断了。 为什么呢?因为当我们从中断/软中断返回时,我们会去处理新的这个软中断的。

这就是上一小节引入变量的作用。

响应软中断

终于是时候来看软中断的响应流程了。在上面三个响应软中的地方看下来,最终都会走到函数__do_softirq()。

这个函数就是根据变量local_softirq_pending_ref上标记的软中断号,来依次处理事先注册好的软中断函数。

当然里面有几个点值得关注:

  • 函数__local_bh_disable_ip(RET_IP, SOFTIRQ_OFFSET)和__local_bh_enable(SOFTIRQ_OFFSET) 来表示in_serving_softirq()。

  • 保存好现场后才开中断

好了,大致框架梳理完了。有机会再来细扣其中的细节。

Previous时钟中断Next中断、软中断、抢占和多处理器

Last updated 3 years ago

Was this helpful?