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了,当系统正常起来之后就处于这个状态。。

最新文章

  1. python os.path.dirname 是什么目录
  2. 小菜鸟学 MQ(一)
  3. LightOJ1382 The Queue(树形DP)
  4. Paxos算法(转)
  5. android SurfaceView绘制 重新学习--切图clipRect详解
  6. C++中L和_T()之区别(转)
  7. MongoDB获得短暂的
  8. Android_declare-styleable_自己定义控件的属性
  9. ssh爆破篇
  10. [Python Study Notes]字典操作
  11. pt工具主从一致性检查并修复以及版本3.0.4的版本缺点
  12. 【Shader拓展】Illustrative Rendering in Team Fortress 2
  13. VC工程的.gitignore模板
  14. python获取日期加减之后的日期
  15. linux防火墙设置常用命令
  16. vc++基础班[24]---系统各种路径信息的获取
  17. BZOJ2653 middle(二分答案+主席树)
  18. spring boot 服务 正确关闭方式
  19. 自定义动画animate()
  20. C# 获取textbox行数

热门文章

  1. Mysql阶段性项目——QQ数据库管理
  2. Flink SQL 子图复用逻辑分析
  3. jenkins流水线部署springboot应用到k8s集群(k3s+jenkins+gitee+maven+docker)(2)
  4. uniapp scroll-view组件隐藏滚动条
  5. 云原生虚拟网络 tun/tap & veth-pair
  6. P7114 [NOIP2020] 字符串匹配 (字符串hash+树状数组)
  7. 【博学谷学习记录】超强总结,用心分享|Linux修改文件权限方法总结
  8. IPv4 与 IPv6的区别
  9. Docker | 容器数据卷详解
  10. Linux自动切换用户