热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux内核task的5种状态,Linux内核调试技术——进程D状态死锁检测

Linux的进程存在多种状态,如TASK_RUNNING的运行态、EXIT_DEAD的停止态和TASK_INTERRUPTIBLE的接收信号的等待状态等等(可在inc

Linux的进程存在多种状态,如TASK_RUNNING的运行态、EXIT_DEAD的停止态和TASK_INTERRUPTIBLE的接收信号的等待状态等等(可在include/linux/sched.h中查看)。其中有一种状态等待为TASK_UNINTERRUPTIBLE,称为D状态,该种状态下进程不接收信号,只能通过wake_up唤醒。处于这种状态的情况有很多,例如mutex锁就可能会设置进程于该状态,有时候进程在等待某种IO资源就绪时(wait_event机制)会设置进程进入该状态。一般情况下,进程处于该状态的时间不会太久,但若IO设备出现故障或者出现进程死锁等情况,进程就可能长期处于该状态而无法再返回到TASK_RUNNING态。因此,内核为了便于发现这类情况设计出了hung task机制专门用于检测长期处于D状态的进程并发出告警。本文分析内核hung task机制的源码并给出一个示例演示。

一、hung task机制分析

内核在很早的版本中就已经引入了hung task机制,本文以较新的Linux 4.1.15版本源码为例进行分析,代码量并不多,源代码文件为kernel/hung_task.c。

首先给出整体流程框图和设计思想:

dc2144ef446a8a288c76c3b5d34a6ac2.png

图 D状态死锁流程图

其核心思想为创建一个内核监测进程循环监测处于D状态的每一个进程(任务),统计它们在两次检测之间的调度次数,如果发现有任务在两次监测之间没有发生任何的调度则可判断该进程一直处于D状态,很有可能已经死锁,因此触发报警日志打印,输出进程的基本信息,栈回溯以及寄存器保存信息以供内核开发人员定位。

下面详细分析实现方式:

staticint__init hung_task_init(void)

{

atomic_notifier_chain_register(&panic_notifier_list, &panic_block);

watchdog_task = kthread_run(watchdog, NULL, "khungtaskd");

return0;

}

subsys_initcall(hung_task_init);

首先,若在内核配置中启用了该机制,在内核的subsys初始化阶段就会调用hung_task_init()函数启用功能,首先向内核的panic_notifier_list通知链注册回调:

staticstructnotifier_block panic_block = {

.notifier_call = hung_task_panic,

};

在内核触发panic时就会调用该hung_task_panic()函数,这个函数的作用稍后再看。继续往下初始化,调用kthread_run()函数创建了一个名为khungtaskd的线程,执行watchdog()函数,立即尝试调度执行。该线程就是专用于检测D状态死锁进程的后台内核线程。

/*

* kthread which checks for tasks stuck in D state

*/

staticintwatchdog(void*dummy)

{

set_user_nice(current, 0);

for( ; ; ) {

unsigned longtimeout = sysctl_hung_task_timeout_secs;

while(schedule_timeout_interruptible(timeout_jiffies(timeout)))

timeout = sysctl_hung_task_timeout_secs;

if(atomic_xchg(&reset_hung_task, 0))

continue;

check_hung_uninterruptible_tasks(timeout);

}

return0;

}

本进程首先设置优先级为0,即一般优先级,不影响其他进程。然后进入主循环(每隔timeout时间执行一次),首先让进程睡眠,设置的睡眠时间为

CONFIG_DEFAULT_HUNG_TASK_TIMEOUT,可以通过内核配置选项修改,默认值为120s,睡眠结束被唤醒后判断原子变量标识reset_hung_task,若被置位则跳过本轮监测,同时会清除该标识。该标识通过reset_hung_task_detector()函数设置(目前内核中尚无其他程序使用该接口):

voidreset_hung_task_detector(void)

{

atomic_set(&reset_hung_task, 1);

}

EXPORT_SYMBOL_GPL(reset_hung_task_detector);

接下来循环的最后即为监测函数check_hung_uninterruptible_tasks(),函数入参为监测超时时间。

/*

* Check whether a TASK_UNINTERRUPTIBLE does not get woken up for

* a really long time (120 seconds). If that happens, print out

* a warning.

*/

staticvoidcheck_hung_uninterruptible_tasks(unsignedlongtimeout)

{

intmax_count = sysctl_hung_task_check_count;

intbatch_count = HUNG_TASK_BATCHING;

structtask_struct *g, *t;

/*

* If the system crashed already then all bets are off,

* do not report extra hung tasks:

*/

if(test_taint(TAINT_DIE) || did_panic)

return;

rcu_read_lock();

for_each_process_thread(g, t) {

if(!max_count--)

gotounlock;

if(!--batch_count) {

batch_count = HUNG_TASK_BATCHING;

if(!rcu_lock_break(g, t))

gotounlock;

}

/* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */

if(t->state == TASK_UNINTERRUPTIBLE)

check_hung_task(t, timeout);

}

unlock:

rcu_read_unlock();

}

首先检测内核是否已经DIE了或者已经panic了,如果是则表明内核已经crash了,无需再进行监测了,直接返回即可。注意这里的did_panic标识在前文中的panic通知链回调函数中hung_task_panic()置位:

staticint

hung_task_panic(structnotifier_block *this, unsignedlongevent,void*ptr)

{

did_panic = 1;

returnNOTIFY_DONE;

}

接下去若尚无触发内核crash,则进入监测流程并逐一检测内核中的所有进程(任务task),该过程在RCU加锁的状态下进行,因此为了避免在进程较多的情况下加锁时间过长,这里设置了一个batch_count,一次最多检测HUNG_TASK_BATCHING个进程。于此同时用户也可以设定最大的检测个数max_count=sysctl_hung_task_check_count,默认值为最大PID个数PID_MAX_LIMIT(通过sysctl命令设置)。

函数调用for_each_process_thread()函数轮询内核中的所有进程(任务task),仅对状态处于TASK_UNINTERRUPTIBLE状态的进程进行超时判断,调用check_hung_task()函数,入参为task_struct结构和超时时间(120s):

staticvoidcheck_hung_task(structtask_struct *t, unsignedlongtimeout)

{

unsigned longswitch_count = t->nvcsw + t->nivcsw;

/*

* Ensure the task is not frozen.

* Also, skip vfork and any other user process that freezer should skip.

*/

if(unlikely(t->flags & (PF_FROZEN | PF_FREEZER_SKIP)))

return;

/*

* When a freshly created task is scheduled once, changes its state to

* TASK_UNINTERRUPTIBLE without having ever been switched out once, it

* musn't be checked.

*/

if(unlikely(!switch_count))

return;

if(switch_count != t->last_switch_count) {

t->last_switch_count = switch_count;

return;

}

trace_sched_process_hang(t);

if(!sysctl_hung_task_warnings)

return;

if(sysctl_hung_task_warnings > 0)

sysctl_hung_task_warnings--;

首先通过t->nvcsw和t->nivcsw的计数累加表示进程从创建开始至今的调度次数总和,其中t->nvcsw表示进程主动放弃CPU的次数,t->nivcsw表示被强制抢占的次数。随后函数判断几个标识:(1)如果进程被frozen了那就跳过检测;(2)调度次数为0的不检测。

接下来判断从上一次检测时保存的进程调度次数和本次是否相同,若不相同则表明这轮timeout(120s)时间内进程发生了调度,则更新该调度值返回,否则则表明该进程已经有timeout(120s)时间没有得到调度了,一直处于D状态。接下来的trace_sched_process_hang()暂不清楚作用,然后判断sysctl_hung_task_warnings标识,它表示需要触发报警的次数,用户也可以通过sysctl命令配置,默认值为10,即若当前检测的进程一直处于D状态,默认情况下此处每2分钟发出一次告警,一共发出10次,之后不再发出告警。下面来看告警代码:

/*

* Ok, the task did not get scheduled for more than 2 minutes,

* complain:

*/

pr_err("INFO: task %s:%d blocked for more than %ld seconds.\n",

t->comm, t->pid, timeout);

pr_err("      %s %s %.*s\n",

print_tainted(), init_utsname()->release,

(int)strcspn(init_utsname()->version," "),

init_utsname()->version);

pr_err("\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\""

" disables this message.\n");

sched_show_task(t);

debug_show_held_locks(t);

touch_nmi_watchdog();

这里会在控制台和日志中打印死锁任务的名称、PID号、超时时间、内核tainted信息、sysinfo、内核栈barktrace以及寄存器信息等。如果开启了debug lock则打印锁占用的情况,并touch nmi_watchdog以防止nmi_watchdog超时(对于我的ARM环境无需考虑nmi_watchdog)。

if(sysctl_hung_task_panic) {

trigger_all_cpu_backtrace();

panic("hung_task: blocked tasks");

}

最后如果设置了sysctl_hung_task_panic标识则直接触发panic(该值可通过内核配置文件配置也可以通过sysctl设置)。

二、示例演示

演示环境:树莓派b(Linux 4.1.15)

1、首先确认内核配置选项以确认开启hung stak机制

Kernel hacking  --->

Debug Lockups and Hangs  --->

[*] Detect Hung Tasks

(120) Default timeout for hung task detection (in seconds)

2、编写测试程序

#include

#include

#include

#include

DEFINE_MUTEX(dlock);

staticint__init dlock_init(void)

{

mutex_lock(&dlock);

mutex_lock(&dlock);

return0;

}

staticvoid__exit dlock_exit(void)

{

return;

}

module_init(dlock_init);

module_exit(dlock_exit);

MODULE_LICENSE("GPL");

本示例程序定义了一个mutex锁,然后在模块的init函数中重复加锁,人为造成死锁现象(mutex_lock()函数会调用__mutex_lock_slowpath()将进程设置为TASK_UNINTERRUPTIBLE状态),进程进入D状态后是无法退出的。可以通过ps命令来查看:

root@apple:~# busybox ps

PID   USER     TIME   COMMAND

......

521 root       0:00 insmod dlock.ko

......

然后查看该进程的状态,可见已经进入了D状态

root@apple:~# cat /proc/521/status

Name:   insmod

State:  D (disk sleep)

Tgid:   521

Ngid:   0

Pid:    521

至此在等待两分钟后调试串口就会输出以下信息,可见每两分钟就会输出一次:

[  360.625466]INFO: task insmod:521 blocked for more than 120 seconds.

[  360.631878]       Tainted: G           O    4.1.15 #5

[  360.637042] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.

[  360.644986] [] (__schedule) from [] (schedule+0x40/0xa4)

[  360.652129] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)

[  360.660570] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)

[  360.670142] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)

[  360.678432] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])

[  360.686480] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)

[  360.694976] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)

[  360.703170] [] (do_init_module) from [] (load_module+0x1690/0x1d34)

[  360.711284] [] (load_module) from [] (SyS_init_module+0xdc/0x130)

[  360.719239] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54)

[  480.725351]INFO: task insmod:521 blocked for more than 120 seconds.

[  480.731759]       Tainted: G           O    4.1.15 #5

[  480.736917] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.

[  480.744842] [] (__schedule) from [] (schedule+0x40/0xa4)

[  480.752029] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)

[  480.760479] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)

[  480.770066] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)

[  480.778363] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])

[  480.786402] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)

[  480.794897] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)

[  480.803085] [] (do_init_module) from [] (load_module+0x1690/0x1d34)

[  480.811188] [] (load_module) from [] (SyS_init_module+0xdc/0x130)

[  480.819113] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54)

[  600.825353]INFO: task insmod:521 blocked for more than 120 seconds.

[  600.831759]       Tainted: G           O    4.1.15 #5

[  600.836916] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.

[  600.844865] [] (__schedule) from [] (schedule+0x40/0xa4)

[  600.852005] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)

[  600.860445] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)

[  600.870014] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)

[  600.878303] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])

[  600.886339] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)

[  600.894835] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)

[  600.903023] [] (do_init_module) from [] (load_module+0x1690/0x1d34)

[  600.911133] [] (load_module) from [] (SyS_init_module+0xdc/0x130)

[  600.919059] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54)

三、总结

D状态死锁一般在驱动开发的过程中比较常见,且不太容易定位,内核提供这种hung task机制,开发人员只需要将这些输出的定位信息抓取并保留下来就可以快速的进行定位。



推荐阅读
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 在运行于MS SQL Server 2005的.NET 2.0 Web应用中,我偶尔会遇到令人头疼的SQL死锁问题。过去,我们主要通过调整查询来解决这些问题,但这既耗时又不可靠。我希望能找到一种确定性的查询模式,确保从设计上彻底避免SQL死锁。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 用C语言实现的科学计算器,支持2种常量,10种基本函数,Ans寄存器。相对来说拓展性应该是不错的,思路是首先化简复杂名称的函 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 在Java开发中,保护代码安全是一个重要的课题。由于Java字节码容易被反编译,因此使用代码混淆工具如ProGuard变得尤为重要。本文将详细介绍如何使用ProGuard进行代码混淆,以及其基本原理和常见问题。 ... [详细]
  • 本文介绍了如何在 MapReduce 作业中使用 SequenceFileOutputFormat 生成 SequenceFile 文件,并详细解释了 SequenceFile 的结构和用途。 ... [详细]
  • 关于进程的复习:#管道#数据的共享Managerdictlist#进程池#cpu个数1#retmap(func,iterable)#异步自带close和join#所有 ... [详细]
  • mybatis 详解(七)一对一、一对多、多对多
    mybatis详解(七)------一 ... [详细]
  • web页面报表js下载,web报表软件 ... [详细]
  • 在iOS开发中,多线程技术的应用非常广泛,能够高效地执行多个调度任务。本文将重点介绍GCD(Grand Central Dispatch)在多线程开发中的应用,包括其函数和队列的实现细节。 ... [详细]
  • 深入理解Java多线程与并发机制
    本文探讨了Java多线程和并发机制的核心概念,包括多线程类的分类、执行器框架、并发容器及控制工具。通过详细解析这些组件,帮助开发者更好地理解和应用多线程技术。 ... [详细]
  • Spring 中策略模式的应用:Resource 接口详解
    本文探讨了在 Spring 框架中如何利用 Resource 接口实现资源访问策略。Resource 接口作为资源访问策略的抽象,通过多种实现类支持不同类型的资源访问。 ... [详细]
  • 本文将深入探讨 iOS 中的 Grand Central Dispatch (GCD),并介绍如何利用 GCD 进行高效多线程编程。如果你对线程的基本概念还不熟悉,建议先阅读相关基础资料。 ... [详细]
author-avatar
阴森森林蛀
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有