为什么在netfilter挂钩中清空临界区,发生`BUG:调度原子错误`?

时间:2013-11-30 09:19:23

标签: c linux-kernel linux-device-driver netfilter mutual-exclusion

我写过这个钩子:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/skbuff.h>
#include <linux/mutex.h>

static struct nf_hook_ops nfho;
static struct mutex critical_section;

unsigned int hook_func(unsigned int hooknum,
   struct sk_buff **skb,
   const struct net_device *in,
   const struct net_device *out,
   int (*okfn)(struct sk_buff *)) {

  mutex_lock(&critical_section);

  mutex_unlock(&critical_section);

  return NF_ACCEPT;
}

int init_module() {

  nfho.hook = hook_func;
  nfho.hooknum = NF_INET_PRE_ROUTING;
  nfho.pf = PF_INET;
  nfho.priority = NF_IP_PRI_FIRST;

  mutex_init(&critical_section);

  nf_register_hook(&nfho);

  return 0;
}

void cleanup_module() {
  nf_unregister_hook(&nfho);
}

init部分:

  mutex_init(&queue_critical_section);
  mutex_init(&ioctl_critical_section);

我已经定义了静态变量:

static struct mutex queue_critical_section;

由于lockunlock之间没有代码,我希望没有错误,但是当我运行这个模块时,内核会产生这些错误:

更新错误:

root@khajavi: # pppd call 80-2
[  519.722190] PPP generic driver version 2.4.2
root@khajavi:# [  519.917390] BUG: scheduling while atomic: swapper/0/0/0x10000100
[  519.940933] Modules linked in: ppp_async crc_ccitt ppp_generic slhc netfilter_mutex(P) nls_utf8 isofs udf crc_itu_t bnep    rfcomm bluetooth rfkill vboxsf(O) vboxvideo(O) drm]
[  520.022203] CPU 0 
[  520.023270] Modules linked in: ppp_async crc_ccitt ppp_generic slhc netfilter_mutex(P) nls_utf8 isofs udf crc_itu_t bnep rfcomm bluetooth rfkill vboxsf(O) vboxvideo(O) drm]
[  520.087002] 
[  520.088001] Pid: 0, comm: swapper/0 Tainted: P           O 3.2.51 #3 innotek GmbH VirtualBox/VirtualBox
[  520.130047] RIP: 0010:[<ffffffff8102d17d>]  [<ffffffff8102d17d>] native_safe_halt+0x6/0x8
[  520.135010] RSP: 0018:ffffffff81601ee8  EFLAGS: 00000246
[  520.140999] RAX: 0000000000000000 RBX: ffffffff810a4cfa RCX: ffffffffffffffbe
[  520.145972] RDX: 0000000000000001 RSI: 0000000000000000 RDI: 0000000000000001
[  520.158759] RBP: ffffffff81601ee8 R08: 0000000000000000 R09: 0000000000000000
[  520.163392] R10: 0000000000000400 R11: ffff88003fc13680 R12: 0000000000014040
[  520.172784] R13: ffff88003fc14040 R14: ffffffff81067fd2 R15: ffffffff81601e58
[  520.177767] FS:  0000000000000000(0000) GS:ffff88003fc00000(0000) knlGS:0000000000000000
[  520.188208] CS:  0010 DS: 0000 ES: 0000 CR0: 000000008005003b
[  520.196486] CR2: 00007fff961a3f40 CR3: 0000000001605000 CR4: 00000000000006f0
[  520.201437] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[  520.212332] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[  520.217155] Process swapper/0 (pid: 0, threadinfo ffffffff81600000, task ffffffff8160d020)
[  520.228706] Stack:
[  520.234394]  ffffffff81601ef8
Message from syslogd@khajavi at Dec 22 17:45:46 ...
 kernel:[  520.228706] Stack:
 ffffffff81014857 ffffffff81601f28 ffffffff8100d2a3
[  520.255069]  ffffffffffffffff 0d64eb669fae50fc ffffffff81601f28 0000000000000000
[  520.269238]  ffffffff81601f38 ffffffff81358c39 ffffffff81601f78 ffffffff816acb8a
[  520.274148] Call Trace:
[  520.275573]  [<ffffffff81014857>] default_idle+0x49/0x81
[  520.278985]  [<ffffffff8100d2a3>] cpu_idle+0xbc/0xcf
[  520.291491]  [<ffffffff81358c39>] rest_init+0x6d/0x6f

这是完整的系统日志错误:http://paste.ubuntu.com/6617614/

4 个答案:

答案 0 :(得分:7)

这是来自内核的一个钩子。不允许睡觉,锁定信号量(pend)或任何阻止操作;你正在锁定内核!

如果您想要同步对象,可以尝试使用自旋锁。

this answer to similar question所述,mutex_lock将触发调度程序;但内核将会感到困惑,因为你正在尝试安排另一个任务,而你处于关键部分(回调本身就是一个很重要的部分)。

检查此帖子Understanding execution context of netfilter hooks是否有类似情况。

答案 1 :(得分:6)

即使mutex_lock()在这种情况下可能无法休眠,但仍然可能睡眠。 由于这是在原子上下文中调用的,因此会引发错误。

具体而言,这是由mutex_lock()调用might_sleep()引起的,__schedule()可能会调用{{1}}

如果确实需要同步,请使用适当的调用,例如。自旋锁和rcu。

答案 2 :(得分:5)

如果您的任务在同步时进行了预定,则会看到此消息,很可能是自旋锁。 当你锁定一个螺旋锁时,它会增加preempt_count;当调度程序使用增加的preempt_count控制调度的情况时,它会打印出确切的消息:

/ *  *原子bug时打印调度:

 */
static noinline void __schedule_bug(struct task_struct *prev)
{
        if (oops_in_progress)
                return;

        printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",
                prev->comm, prev->pid, preempt_count());

        debug_show_held_locks(prev);
        print_modules();
        if (irqs_disabled())
                print_irqtrace_events(prev);
        dump_stack();
}

所以,可能你拿着锁,或者你必须解锁一些锁。

PS。从Linux文档中的互斥锁描述:

  
      
  • 'struct mutex'语义是明确定义的,如果打开CONFIG_DEBUG_MUTEXES则强制执行。另一方面,信号量有   几乎没有调试代码或工具。互斥子系统
      检查并执行以下规则:

         
        
      •   
      • 一次只能有一个任务可以保存互斥锁* - 只有   所有者可以解锁互斥锁* - 不允许多次解锁
      •   
    •   
      •   
      • 不允许递归锁定* - 必须通过API *初始化互斥对象 - 不能初始化互斥对象   通过memset或复制* - 任务可能无法退出持有互斥锁* -   保持锁定所在的内存区域不得被释放* - 持有   不能重新初始化互斥锁* - 不能使用互斥锁   硬件或软件中断*上下文,如tasklet和   定时器
      •   
    •   
  •   

在您的设计中,可以同时使用相同的互斥锁:

  1. 致电1 - &gt;你的代码 - &gt; mutex_lock
  2. 调度程序会中断您的代码。
  3. 致电2 - &gt;你的代码 - &gt; mutex_lock(已锁定) - &gt; BUG
  4. 祝你好运。

答案 3 :(得分:4)

这显然是执行上下文问题。要在内核代码中使用适当的锁定,需要知道代码被调用的执行上下文(hard_irq | bottom_half | process_context)。

mutex_lock | mutex_unlock仅用于保护process_context代码。

根据http://www.gossamer-threads.com/lists/iptables/devel/56853

讨论,可以在hook_funcsot_irq中调用您的函数process_context。 因此,您需要使用适合在这两个上下文之间保护的锁定机制。

我建议您浏览内核锁定指南(https://www.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/)。该指南还解释了当系统是SMP(非常常见)和抢占时所涉及的特性。

要进行快速测试,您可以在spin_lock_irqsave中使用hook_func锁定。 spin_lock_irqsave始终是安全的,它会禁用当前cpu上的中断,而spinlock将保证SMP系统上的原子操作。

现在,关于崩溃的消息:

mutex_lock | mutex_unlock只能在process_context代码中使用。当您从hook_func调用soft_irq时,mutex_lock会导致当前进程进入休眠状态,然后调用调度程序。在原子上下文中不允许在内核代码中睡眠(这里是soft_irq)。