内核常用的might_sleep函数
1. 前言
内核版本:linux 4.9.225。内核版本:linux 4.9.225。对于内核常用的might_sleep函数,如果没有调试的需要(没有定义CONFIG_DEBUG_ATOMIC_SLEEP),这个宏/函数什么事情都不,might_sleep就是一个空函数,所以平常看code的时候可以忽略。内核只是用它来提醒开发人员,调用该函数的函数可能会sleep。
2. might_sleep的定义
# include/linux/kernel.
#ifdef CONFIG_PREEMPT_VOLUNTARY
extern int _cond_resched(void);
# define might_resched() _cond_resched()
#else
# define might_resched() do { } while (0)
#endif
#ifdef CONFIG_DEBUG_ATOMIC_SLEEP
void ___might_sleep(const char *file, int line, int preempt_offset);
void __might_sleep(const char *file, int line, int preempt_offset);
/**
* might_sleep - annotation for functions that can sleep
*
* this macro will print a stack trace if it is executed in an atomic
* context (spinlock, irq-handler, ...).
*
* This is a useful debugging help to be able to catch problems early and not
* be bitten later when the calling function happens to sleep when it is not
* supposed to.
*/
# define might_sleep() \
do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)
#else
# define might_sleep() do { might_resched(); } while (0)
#endif
2.1 未开启CONFIG_DEBUG_ATOMIC_SLEEP选项
在没有调试需求,即在选项 CONFIG_DEBUG_ATOMIC_SLEEP和 CONFIG_PREEMPT_VOLUNTARY(自愿抢占,代码中增加抢占点,在中断退出后遇到抢占点时进行抢占切换)未打开的情况下:
# define might_resched() do { } while (0)
# define might_sleep() do { might_resched(); } while (0)
可以看到,这里什么事情都没做。其中内核源码对此也有明确的注释:might_sleep - annotation for functions that can sleep。所以对于release版的kernel 而言,might_sleep函数仅仅是一个annotation,用来提醒开发人员,一个使用might_sleep的函数在其后的代码执行中可能会sleep。
2.2 开启CONFIG_DEBUG_ATOMIC_SLEEP选项
如果有调试需求的话,就必须打开内核的 CONFIG_DEBUG_ATOMIC_SLEEP选项。 此时might_sleep定义如下:
# define might_resched() _cond_resched()
# define might_sleep() \
do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)
CONFIG_DEBUG_ATOMIC_SLEEP选项主要用来排查是否在一个ATOMIC操作的上下文中有函数发生sleep行为,关于什么是 ATOMIC操作,内核源码在might_sleep函数前也有一段注释:this macro will print a stack trace if it is executed in an atomic context (spinlock, irq-handler, ...)
所以,一个进程获得了spinlock之后(进入所谓的atomic context),或者是在一个irq-handle中(也就是一个中断上下文中)。这两种情况下理论上不应该让当前的execution path进入sleep状态(虽然不是强制规定,换句话说,一个拥有spinlock的进程进入sleep并不必然意味着系统就一定会deadlock 等,但是对内核编程而言,还是应该尽力避开这个雷区)。
在CONFIG_DEBUG_ATOMIC_SLEEP选项打开的情形下,might_sleep具体实现的功能分析如下:
# kernel/sched/core.c
void ___might_sleep(const char *file, int line, int preempt_offset)
{
static unsigned long prev_jiffy; /* ratelimiting */
unsigned long preempt_disable_ip;
rcu_sleep_check(); /* WARN_ON_ONCE() by default, no rate limit reqd. */
if ((preempt_count_equals(preempt_offset) && !irqs_disabled() &&!is_idle_task(current)) ||
system_state != SYSTEM_RUNNING || oops_in_progress) /* 核心判断 */
return;
if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
return;
prev_jiffy = jiffies;
/* Save this before calling printk(), since that will clobber it */
preempt_disable_ip = get_preempt_disable_ip(current);
printk(KERN_ERR
"BUG: sleeping function called from invalid context at %s:%d\n",
file, line);
printk(KERN_ERR
"in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
in_atomic(), irqs_disabled(),
current->pid, current->comm);
if (task_stack_end_corrupted(current))
printk(KERN_EMERG "Thread overran stack, or stack corrupted\n");
debug_show_held_locks(current);
if (irqs_disabled())
print_irqtrace_events(current);
if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)
&& !preempt_count_equals(preempt_offset)) {
pr_err("Preemption disabled at:");
print_ip_sym(preempt_disable_ip);
pr_cont("\n");
}
dump_stack();
add_taint(TAINT_WARN, LOCKDEP_STILL_OK);
}
EXPORT_SYMBOL(___might_sleep);
在当前CONFIG_DEBUG_ATOMIC_SLEEP选项使能的前提下, 可以看到__might_sleep还是干了不少事情的,最主要的工作是在第一个if语句那里,尤其是preempt_count_equals和 irqs_disabled,都是用来判断当前的上下文是否是一个atomic context,因为我们知道,只要进程获得了spin_lock的任一个变种形式的lock,那么无论是单处理器系统还是多处理器系统,都会导致 preempt_count发生变化,而irq_disabled则是用来判断当前中断是否开启。__might_sleep正是根据这些信息来判断当前正在执行的代码上下文是否是个atomic,如果不是,那么函数就直接返回了,因为一切正常。如果是,那么代码下行。
所以让CONFIG_DEBUG_ATOMIC_SLEEP选项打开,可以捕捉到在一个atomic context中是否发生了sleep,如果你的代码不小心在某处的确出现了这种情形,那么might_sleep会通过后续的printk以及dump_stack来协助你发现这种情形。
至于__might_sleep函数中的system_state,它是一个全局性的enum型变量,在 /init/main.c中声明,主要用来记录当前系统的状态:
# init/main.c
enum system_states system_state __read_mostly;
EXPORT_SYMBOL(system_state);
注意system_state已经被export出来,所以内核模块可以直接读该值来判断当前系统的运行状态,具体定义及常见的状态如下:
# include\linux\kernel.h
/* Values used for system_state */
extern enum system_states {
SYSTEM_BOOTING,
SYSTEM_RUNNING,
SYSTEM_HALT,
SYSTEM_POWER_OFF,
SYSTEM_RESTART,
} system_state;
最常见的状态是SYSTEM_RUNNING了,当系统正常起来之后就处于这个状态。。
本文来自博客园,作者:BSP-路人甲,转载请注明原文链接:https://www.cnblogs.com/jianhua1992/p/16852785.html,并保留此段声明,否则保留追究法律责任的权利。