如何理解宏定义中的do{} while(0)语法?

7

虽然这个话题在本论坛和其他论坛已经被讨论了很多次,但我仍然有疑问。请帮忙。

do{} while(0)宏在Linux内核中是如何工作的? 例如,

#define preempt_disable()    do { } while (0)

它如何禁用抢占?
#define might_resched()    do { } while (0)

它是如何重新安排计划的?

同样,我也看到了用于互斥锁和其他内容的宏。这有什么帮助呢?我理解下面的问题,但不理解上面的例子。

#define foo(x)    do { do something } while(0)

编辑:

以下代码对于rt_mutex_lock如何?

/**
 * rt_mutex_lock - lock a rt_mutex
 *
 * @lock: the rt_mutex to be locked
 */
void __sched rt_mutex_lock(struct rt_mutex *lock)
{
        might_sleep();
        rt_mutex_fastlock(lock, TASK_UNINTERRUPTIBLE, 0, rt_mutex_slowlock);
}
EXPORT_SYMBOL_GPL(rt_mutex_lock);


/*
 * debug aware fast / slowpath lock,trylock,unlock
 *
 * The atomic acquire/release ops are compiled away, when either the
 * architecture does not support cmpxchg or when debugging is enabled.
 */

static inline int rt_mutex_fastlock(struct rt_mutex *lock, 
    int state, int detect_deadlock, int (*slowfn)(struct rt_mutex *lock, 
    int state, struct hrtimer_sleeper *timeout, int detect_deadlock))
{
        if (!detect_deadlock && likely(rt_mutex_cmpxchg(lock, NULL, current))) {
                rt_mutex_deadlock_account_lock(lock, current);
                return 0;
        } else{
                return slowfn(lock, state, NULL, detect_deadlock);
        }
}

我感到困惑,因为rt_mutex_deadlock_account_lock在内核中的两个位置都有定义:

kernel/rtmutex-debug.c中:

void rt_mutex_deadlock_account_lock(struct rt_mutex *lock, 
    struct task_struct *task)
{
    //....
}

kernel/rtmutex.h中:
#define rt_mutex_deadlock_account_lock(m, t) do { } while (0)

在新的2.6.35.4内核中,i2c驱动程序中的mutex_lock()已被rt_mutex_lock(&adap->bus_lock);替换。那么这个锁是如何工作的呢?

看起来它重新定义了那个函数什么也不做。 - mpen
@Mark:听起来很有说服力。ravspratapsingh:我们理解得对,对于这两个上面的语句,括号之间实际上没有任何内容吗?还是你只是简化了代码呢? - chiccodoro
2
@paxdiablo:'possible duplicate'并不是非常接近;它讨论了do ... while (0),但是那里的代码在循环体内有动作,不像这里。所以这有些不同。 - Jonathan Leffler
3
对我来说很明显内核作者有一些理由希望宏展开不仅仅是一个完全为空的表达式,但是乍一看似乎使其展开为无内容或只是注释也同样有用。无论如何,这导致 OP 在阅读内核源码时对此提出了疑问,所以我同意 @Jonathan 的观点,重要的不是 do{}while(0) 的含义,而是缺少函数体。 - RBerteig
1
@chiccodoro:这是内核的原始代码片段,并未进行任何修改。我关心的是,当do-while(0)什么都不做时,它如何实际上实现锁定。 - iSegFault
显示剩余2条评论
3个回答

12

如果要更好地了解,可以参考此链接


2
+1 为了弹出一级间接性,第一句引文回答了这个问题:(来自 Dave Miller)空语句会从编译器产生警告,所以你会看到 #define FOO do { } while(0) - Jens Gustedt
@justin,链接失效了。如果你能找到镜像,请在回答中添加相关信息。 - dandan78

5

@Kragen已经回答了什么是do...while结构 - 它基本上使宏的使用更加安全。

然而,我不认为这回答了“这是如何工作的?”的问题:

#define preempt_disable()    do { } while (0)

宏定义为空,是为什么呢?
  • 有时候你想要使用一个宏作为占位符,以便后续进行一些操作。例如,你可能在一个系统上编写代码,其中“preempt”不是问题,但是你知道代码可能会被移植到需要特殊处理“preempt”的另一个系统。因此,你会在第二个系统所需的每个地方都使用宏(这样稍后启用处理就很容易),但对于第一个系统,你可以将该宏定义为空白。

  • 有时候你需要完成一个由不同部分组成的任务(例如:START_TABLE(); TABLE_ENTRY(1); TABLE_ENTRY(2); END_TABLE();)。这样可以很清晰明了地实现表格。但是,你可能发现实际上不需要END_TABLE()宏。为了保持客户端代码的整洁,你可以将该宏定义为空。这样,所有的表格都有一个END_TABLE,并且代码更易于阅读。

  • 两个状态(启用/禁用)可能出现类似的情况,其中一个状态需要宏执行某些操作,而另一个状态默认情况下没有操作,所以一个实现“空” - 你仍然使用宏,因为它使客户端代码更易于理解,因为它明确指出了启用或禁用事物的位置。


如果do {} while (0)什么也不做,那么编译器会生成垃圾指令吗?还是这个循环会被优化掉? - Zingam
1
现在的编译器都有非常好的优化器,因此你极不可能找到一个会浪费时间在这样一个明显的空情况上的编译器。 - Jason Williams

3
据我所知,在宏中使用do-while是为了使它们看起来更像普通函数调用;在未加括号的if语句等语法细节上存在一些微妙的问题。如果没有do-while,宏可能看起来像普通的函数调用,但工作方式不同。
我猜在这种情况下,这些宏被用于使某些函数调用编译为空;如果CONFIG_PREEMPT未设置,则看起来可能会出现这种情况,因此仅对抢占所必需的内核部分会消失。因此,这些循环不会禁用抢占或重新安排任何内容;在内核源代码的其他地方将有另一个定义(可能是真正的函数)。

谢谢,我理解了这个抢占的机制是如何工作的,但是对于互斥锁还是有些困惑。请看一下我的关于rt_mutex_lock的回复。 - iSegFault
1
我认为互斥锁是类似的 - 有一个“真正”的实现和一个什么都不做的“虚拟”实现。可能在某些情况下,如果内核未配置需要它,则使用虚拟实现 - 也许是因为未启用CONFIG_SMP? - Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接