LINUX KERNEL SPINLOCK使用不当的后果

spinlock(自旋锁)是内核中最常见的锁,它的特点是:等待锁的过程中不休眠,而是占着CPU空转,优点是避免了上下文切换的开销,缺点是该CPU空转属于浪费,spinlock适合用来保护快进快出的临界区。

spinlock有很多限制条件,其中最重要的是,持有spinlock的CPU不能被抢占,持有spinlock的代码不能休眠。如果违反,会发生死锁,后果很严重。持有spinlock的代码不能休眠,这一条是开发者编写内核程序使用spinlock的时候要人工保证的。而持有spinlock的CPU不能被抢占是由spinlock的API本身提供保证,出于效率的考虑,spinlock的API提供了多种选择,对抢占的防止程度也不一样,开发者在选用的时候需要谨慎,下文对此详细展开。

Linux内核提供了多种spinlock的API,其中最常用的是:

  1. spin_lock/spin_unlock — 禁止内核抢占
  2. spin_lock_irq/spin_unlock_irq — 禁止内核抢占并屏蔽中断
  3. spin_lock_irqsave/spin_unlock_irqrestore — 禁止内核抢占并屏蔽中断,事先保存中断屏蔽位并事后恢复原状

spin_lock()禁止了内核抢占,但是没有屏蔽中断,意味着持有该spinlock的CPU有可能被中断抢占。如果你的某段内核代码选用了spin_lock(),就必须保证这段代码不会被任何中断处理程序调用,否则就会发生死锁(参见后文的一个实际发生的案例)。如果某段内核代码有可能被中断处理程序调用,那就只能选择spin_lock_irqspin_lock_irqsave

下面是一个刚发生的实际案例,SLES11 SP4的系统失去响应,kdump生成了vmcore,分析过程中发现以下backtraces揭示了原因:

crash64> bt -c 2
PID: 47 TASK: ffff880230c78400 CPU: 2 COMMAND: "kswapd0"
#0 [ffff88023fa46e40] crash_nmi_callback at ffffffff81024bcf
#1 [ffff88023fa46e50] notifier_call_chain at ffffffff8146d6e7
#2 [ffff88023fa46e80] __atomic_notifier_call_chain at ffffffff8146d72d
#3 [ffff88023fa46e90] notify_die at ffffffff8146d77d
#4 [ffff88023fa46ec0] default_do_nmi at ffffffff8146ad13
#5 [ffff88023fa46ee0] do_nmi at ffffffff8146ae08
#6 [ffff88023fa46ef0] restart_nmi at ffffffff8146a295
[exception RIP: _raw_spin_lock+21]
RIP: ffffffff81469795 RSP: ffff88023fa43738 RFLAGS: 00000283
RAX: 0000000000002b41 RBX: ffff88023337fc00 RCX: 00000000000000d0
RDX: 0000000000002b3f RSI: ffff88023fa437d4 RDI: ffffffff81a02700
RBP: 0000000000000001 R8: 0000000000000000 R9: ffff88023fa43740
R10: ffff88023ffd95b8 R11: ffff88023ffd9520 R12: ffff88023337fc00
R13: 0000000000000001 R14: ffff88023337fce0 R15: 0000000000000008
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
--- <NMI exception stack> ---
#7 [ffff88023fa43738] _raw_spin_lock at ffffffff81469795
#8 [ffff88023fa43738] __shrink_dcache_sb at ffffffff81176b36
#9 [ffff88023fa437b8] prune_dcache at ffffffff81176d82
#10 [ffff88023fa43808] shrink_dcache_memory at ffffffff81176e88
#11 [ffff88023fa43818] shrink_slab at ffffffff81110874
#12 [ffff88023fa438b8] do_try_to_free_pages at ffffffff81111c43
#13 [ffff88023fa43928] try_to_free_pages at ffffffff81112072
#14 [ffff88023fa439c8] __alloc_pages_slowpath at ffffffff81104a6f
#15 [ffff88023fa43af8] __alloc_pages_nodemask at ffffffff81105079
#16 [ffff88023fa43b98] alloc_pages_current at ffffffff8113da6e
#17 [ffff88023fa43bd8] bnx2x_alloc_rx_sge at ffffffffa055d484 [bnx2x]
#18 [ffff88023fa43c18] bnx2x_fill_frag_skb at ffffffffa055d75e [bnx2x]
#19 [ffff88023fa43cb8] bnx2x_tpa_stop at ffffffffa055da86 [bnx2x]
#20 [ffff88023fa43d18] bnx2x_rx_int at ffffffffa056084b [bnx2x]
#21 [ffff88023fa43e48] bnx2x_poll at ffffffffa05613b4 [bnx2x]
#22 [ffff88023fa43e88] net_rx_action at ffffffff813adada
#23 [ffff88023fa43ed8] __do_softirq at ffffffff8106925f
#24 [ffff88023fa43f48] call_softirq at ffffffff81472a5c
#25 [ffff88023fa43f60] do_softirq at ffffffff81004695
#26 [ffff88023fa43f90] smp_apic_timer_interrupt at ffffffff81026fd8
#27 [ffff88023fa43fb0] apic_timer_interrupt at ffffffff814721f3
--- <IRQ stack> ---
#28 [ffff880230c7bad8] apic_timer_interrupt at ffffffff814721f3
[exception RIP: _raw_spin_trylock]
RIP: ffffffff81469750 RSP: ffff880230c7bb88 RFLAGS: 00000246
RAX: ffff8802c63aac40 RBX: ffffffff81469c0e RCX: ffff8802c63aad00
RDX: ffff880230c7bfd8 RSI: ffff880230c78400 RDI: ffff8802c63aac18
RBP: ffff8802c63aac18 R8: ffff880230c7a000 R9: 0000000000000000
R10: ffff88023fa509a0 R11: ffffffff81051970 R12: ffffffff814721ee
R13: ffffffff81051970 R14: ffffffff81469c0e R15: ffff880230c7bb80
ORIG_RAX: ffffffffffffff10 CS: 0010 SS: 0018
#29 [ffff880230c7bb88] __shrink_dcache_sb at ffffffff81176bec
#30 [ffff880230c7bc08] prune_dcache at ffffffff81176d82
#31 [ffff880230c7bc58] shrink_dcache_memory at ffffffff81176e88
#32 [ffff880230c7bc68] shrink_slab at ffffffff81110874
#33 [ffff880230c7bd08] kswapd_shrink_zone at ffffffff81111086
#34 [ffff880230c7bd68] balance_pgdat at ffffffff811115de
#35 [ffff880230c7be78] kswapd at ffffffff81111980
#36 [ffff880230c7bee8] kthread at ffffffff81084946
#37 [ffff880230c7bf48] kernel_thread_helper at ffffffff81472964

我来解释一下,上面的backtraces意思是:CPU 2上正在运行的进程是”kswapd0″(kswapd0是负责swapping的内核线程),它正在压缩dcache以便腾出一些空闲内存,当它执行到__shrink_dcache_sb()的时候被一个中断抢占了CPU,(注意被中断抢占的进程不会离开当前CPU,不会有机会到其它CPU上运行,只能等中断处理结束之后把CPU交还给它),中断处理程序是bnx2x驱动模块(注意看[],表示的是内核模块),它发现内存不够,于是自动清理内存,最终也走到了压缩dcache这一步,也去调用__shrink_dcache_sb(),但是__shrink_dcache_sb()的临界区受到spinlock保护,见下面源代码第0823行,这个名为dcache_lru_lock的spinlock刚才已经被”kswapd0″进程持有了,所以中断处理程序不可能抢到,问题是持有dcache_lru_lock的”kswapd0″进程又被中断抢占了CPU,不可能继续运行,也就没机会释放掉dcache_lru_lock,这就陷入了死锁状态。

// SLES11 SP4: kernel 3.0.101-71, fs/dcache.c

0814 static void __shrink_dcache_sb(struct super_block *sb, int *count, int flags)
0815 {
0816 /* called from prune_dcache() and shrink_dcache_parent() */
0817 struct dentry *dentry;
0818 LIST_HEAD(referenced);
0819 LIST_HEAD(tmp);
0820 int cnt = *count;
0821
0822 relock:
0823 spin_lock(&dcache_lru_lock);
0824 while (!list_empty(&sb->s_dentry_lru)) {
0825 dentry = list_entry(sb->s_dentry_lru.prev,
0826 struct dentry, d_lru);
0827 BUG_ON(dentry->d_sb != sb);
0828
0829 if (!spin_trylock(&dentry->d_lock)) {
0830 spin_unlock(&dcache_lru_lock);
0831 cpu_relax();
0832 goto relock;
0833 }
...

根本原因在于,既然__shrink_dcache_sb()选用了spin_lock(),就意味着设计者认为它不会被中断处理程序调用,因为spin_lock()不屏蔽中断,是不能防止中断抢占的,只要中断处理程序不调用__shrink_dcache_sb(),死锁就不会发生;如果要让__shrink_dcache_sb()可以被中断处理程序调用,那就不能选用spin_lock(),而应该用spin_lock_irqspin_lock_irqsave。这个案例中的问题出在bnx2x驱动程序中,它在bnx2x_alloc_rx_sge() 中调用alloc_pages()时不恰当地使用了GFP_KERNEL标志,实际上应该使用GFP_ATOMIC标志,这样alloc_pages()就不会试图去主动回收内存、也就不会最终调用__shrink_dcache_sb()了。此bug记载在SUSE的bsc#975358中,在kernel 3.0.101-77中得以修复。

LINUX KERNEL SPINLOCK使用不当的后果的更多相关文章

  1. spinlock in linux kernel

    spinlock in linux kernel 作为一种锁机制, spinlock可以制造一段临界区, 同一时刻只有一个线程能进入这个临界区, 从而达到保护数据的目的. semaphore, mut ...

  2. Intel 80x86 Linux Kernel Interrupt(中断)、Interrupt Priority、Interrupt nesting、Prohibit Things Whthin CPU In The Interrupt Off State

    目录 . 引言 . Linux 中断的概念 . 中断处理流程 . Linux 中断相关的源代码分析 . Linux 硬件中断 . Linux 软中断 . 中断优先级 . CPU在关中断状态下编程要注意 ...

  3. Linux Kernel 排程機制介紹

    http://loda.hala01.com/2011/12/linux-kernel-%E6%8E%92%E7%A8%8B%E6%A9%9F%E5%88%B6%E4%BB%8B%E7%B4%B9/ ...

  4. Install Linux Kernel - AT91SAM9260EK

    两.AT91SAM9260EK 2.1下载 介绍页: http://www.at91.com/linux4sam/bin/view/Linux4SAM/LegacyLinuxKernel 下载页: a ...

  5. Linux kernel的中断子系统之(三):IRQ number和中断描述符

    返回目录:<ARM-Linux中断系统>. 总结: 二描述了中断处理示意图,以及关中断.开中断,和IRQ number重要概念. 三介绍了三个重要的结构体,irq_desc.irq_dat ...

  6. Linux kernel的中断子系统之(六):ARM中断处理过程

    返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...

  7. 从基本理解到深入探究 Linux kernel 通知链(notifier chain)【转】

    转自:https://blog.csdn.net/u014134180/article/details/86563754 版权声明:本文为博主原创文章,未经博主允许不得转载.——Wu_Being ht ...

  8. linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

    转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interr ...

  9. [轉]Exploit The Linux Kernel NULL Pointer Dereference

    Exploit The Linux Kernel NULL Pointer Dereference Author: wztHome: http://hi.baidu.com/wzt85date: 20 ...

随机推荐

  1. apache zookeeper的安装

    original article:http://zookeeper.praveendeshmane.co.in/zookeeper/zookeeper-3-4-6-single-server-setu ...

  2. [Android]Fragment源代码分析(三) 事务

    Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的很的出彩.我们先引入一个简单经常使用的Fragment事务管理代码片段: FragmentTransaction ft = thi ...

  3. ICM Technex 2017 and Codeforces Round #400 (Div. 1 + Div. 2, combined) C. Molly's Chemicals

    感觉自己做有关区间的题目方面的思维异常的差...有时简单题都搞半天还完全没思路,,然后别人提示下立马就明白了...=_= 题意:给一个含有n个元素的数组和k,问存在多少个区间的和值为k的次方数. 题解 ...

  4. jquery文件批量上传控件Uploadify3.2(java springMVC)

    人比較懒  有用为主 不怎么排版了 先放上Uploadify的官网链接:http://www.uploadify.com/  -->里面能够看到PHP的演示样例,属性说明,以及控件下载地址.分f ...

  5. BeanUtils使用案例

     1.BeanUtils框架/工具(APACHE开源组织开发)    (1)BeanUtils框架可以完毕内省的一切功能.并且优化    (2)BeanUtils框架可以对String<-> ...

  6. oc52--autorelease1

    // // main.m /* autorelease也是用于内存管理的,给对象发送autorelease消息就会把对象放入autoreleasepool这个池子中,当池子销毁的时候会对池子里面的所有 ...

  7. C# 正则表达式 和 JAVA表达式是想通的

    正则表达式语法 也许有人会说,现在需要正则表达式去验证什么的话,直接在网上找不久一大片吗?还需要学什么啊! 是的,现在在网上找确实是一找一大片,但是,有时候我们也遇到这样的情况,就是我们在网上找的复制 ...

  8. Genesis 多边形闭轮廓填充算法

    通过逐行扫描,计算得出直线与多边形相交点进行求解 原理图形如下所示: 相关函数: /// <summary> /// 求点P到线段L距离 /// </summary> /// ...

  9. HDU1043 Eight

    题目: 简单介绍一下八数码问题:        在一个3×3的九宫格上,填有1~8八个数字,空余一个位置,例如下图: 1 2 3 4 5 6 7 8           在上图中,由于右下角位置是空的 ...

  10. 工具分享1:文本编辑器EditPlus、汇编编译器masm、Dos盒子

    工具已打包好,需要即下载 链接 https://pan.baidu.com/s/1dvMyvW 密码 mic4