自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux內(nèi)核調(diào)試技術(shù)——進(jìn)程D狀態(tài)死鎖檢測

系統(tǒng) Linux 系統(tǒng)運維
所謂的D狀態(tài)就是Linux的進(jìn)程存在多種狀態(tài),如TASK_RUNNING的運行態(tài)、EXIT_DEAD的停止態(tài)和TASK_INTERRUPTIBLE的接收信號的等待狀態(tài)等等(可在include/linux/sched.h中查看)。其中有一種狀態(tài)等待為TASK_UNINTERRUPTIBLE,稱為D狀態(tài)。

Linux的進(jìn)程存在多種狀態(tài),如TASK_RUNNING的運行態(tài)、EXIT_DEAD的停止態(tài)和TASK_INTERRUPTIBLE的接收信號的等待狀態(tài)等等(可在include/linux/sched.h中查看)。其中有一種狀態(tài)等待為TASK_UNINTERRUPTIBLE,稱為D狀態(tài),該種狀態(tài)下進(jìn)程不接收信號,只能通過wake_up喚醒。處于這種狀態(tài)的情況有很多,例如mutex鎖就可能會設(shè)置進(jìn)程于該狀態(tài),有時候進(jìn)程在等待某種IO資源就緒時(wait_event機制)會設(shè)置進(jìn)程進(jìn)入該狀態(tài)。一般情況下,進(jìn)程處于該狀態(tài)的時間不會太久,但若IO設(shè)備出現(xiàn)故障或者出現(xiàn)進(jìn)程死鎖等情況,進(jìn)程就可能長期處于該狀態(tài)而無法再返回到TASK_RUNNING態(tài)。因此,內(nèi)核為了便于發(fā)現(xiàn)這類情況設(shè)計出了hung task機制專門用于檢測長期處于D狀態(tài)的進(jìn)程并發(fā)出告警。本文分析內(nèi)核hung task機制的源碼并給出一個示例演示。

一、hung task機制分析

內(nèi)核在很早的版本中就已經(jīng)引入了hung task機制,本文以較新的Linux 4.1.15版本源碼為例進(jìn)行分析,代碼量并不多,源代碼文件為kernel/hung_task.c。

首先給出整體流程框圖和設(shè)計思想:

圖 D狀態(tài)死鎖流程圖

其核心思想為創(chuàng)建一個內(nèi)核監(jiān)測進(jìn)程循環(huán)監(jiān)測處于D狀態(tài)的每一個進(jìn)程(任務(wù)),統(tǒng)計它們在兩次檢測之間的調(diào)度次數(shù),如果發(fā)現(xiàn)有任務(wù)在兩次監(jiān)測之間沒有發(fā)生任何的調(diào)度則可判斷該進(jìn)程一直處于D狀態(tài),很有可能已經(jīng)死鎖,因此觸發(fā)報警日志打印,輸出進(jìn)程的基本信息,?;厮菀约凹拇嫫鞅4嫘畔⒁怨﹥?nèi)核開發(fā)人員定位。

下面詳細(xì)分析實現(xiàn)方式:

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. static int __init hung_task_init(void)   
  3. {   
  4.     atomic_notifier_chain_register(&panic_notifier_list, &panic_block);   
  5.     watchdog_task = kthread_run(watchdog, NULL"khungtaskd");   
  6.    
  7.     return 0;   
  8. }   
  9. subsys_initcall(hung_task_init);   

首先,若在內(nèi)核配置中啟用了該機制,在內(nèi)核的subsys初始化階段就會調(diào)用hung_task_init()函數(shù)啟用功能,首先向內(nèi)核的panic_notifier_list通知鏈注冊回調(diào):

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. static struct notifier_block panic_block = {   
  3.     .notifier_call = hung_task_panic,   
  4. };   

在內(nèi)核觸發(fā)panic時就會調(diào)用該hung_task_panic()函數(shù),這個函數(shù)的作用稍后再看。繼續(xù)往下初始化,調(diào)用kthread_run()函數(shù)創(chuàng)建了一個名為khungtaskd的線程,執(zhí)行watchdog()函數(shù),立即嘗試調(diào)度執(zhí)行。該線程就是專用于檢測D狀態(tài)死鎖進(jìn)程的后臺內(nèi)核線程。

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. /*  
  3.  * kthread which checks for tasks stuck in D state  
  4.  */   
  5. static int watchdog(void *dummy)   
  6. {   
  7.     set_user_nice(current, 0);   
  8.    
  9.     for ( ; ; ) {   
  10.         unsigned long timeout = sysctl_hung_task_timeout_secs;   
  11.    
  12.         while (schedule_timeout_interruptible(timeout_jiffies(timeout)))   
  13.             timeout = sysctl_hung_task_timeout_secs;   
  14.    
  15.         if (atomic_xchg(&reset_hung_task, 0))   
  16.             continue;   
  17.    
  18.         check_hung_uninterruptible_tasks(timeout);   
  19.     }   
  20.    
  21.     return 0;   
  22. }   

本進(jìn)程首先設(shè)置優(yōu)先級為0,即一般優(yōu)先級,不影響其他進(jìn)程。然后進(jìn)入主循環(huán)(每隔timeout時間執(zhí)行一次),首先讓進(jìn)程睡眠,設(shè)置的睡眠時間為

CONFIG_DEFAULT_HUNG_TASK_TIMEOUT,可以通過內(nèi)核配置選項修改,默認(rèn)值為120s,睡眠結(jié)束被喚醒后判斷原子變量標(biāo)識reset_hung_task,若被置位則跳過本輪監(jiān)測,同時會清除該標(biāo)識。該標(biāo)識通過reset_hung_task_detector()函數(shù)設(shè)置(目前內(nèi)核中尚無其他程序使用該接口):

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. void reset_hung_task_detector(void)   
  3. {   
  4.     atomic_set(&reset_hung_task, 1);   
  5. }   
  6. EXPORT_SYMBOL_GPL(reset_hung_task_detector);   

接下來循環(huán)的***即為監(jiān)測函數(shù)check_hung_uninterruptible_tasks(),函數(shù)入?yún)楸O(jiān)測超時時間。

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. /*  
  3.  * Check whether a TASK_UNINTERRUPTIBLE does not get woken up for  
  4.  * a really long time (120 seconds). If that happens, print out  
  5.  * a warning.  
  6.  */   
  7. static void check_hung_uninterruptible_tasks(unsigned long timeout)   
  8. {   
  9.     int max_count = sysctl_hung_task_check_count;   
  10.     int batch_count = HUNG_TASK_BATCHING;   
  11.     struct task_struct *g, *t;   
  12.    
  13.     /*  
  14.      * If the system crashed already then all bets are off,  
  15.      * do not report extra hung tasks:  
  16.      */   
  17.     if (test_taint(TAINT_DIE) || did_panic)   
  18.         return;   
  19.    
  20.     rcu_read_lock();   
  21.     for_each_process_thread(g, t) {   
  22.         if (!max_count--)   
  23.             goto unlock;   
  24.         if (!--batch_count) {   
  25.             batch_count = HUNG_TASK_BATCHING;   
  26.             if (!rcu_lock_break(g, t))   
  27.                 goto unlock;   
  28.         }   
  29.         /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */   
  30.         if (t->state == TASK_UNINTERRUPTIBLE)   
  31.             check_hung_task(t, timeout);   
  32.     }   
  33.  unlock:   
  34.     rcu_read_unlock();   
  35. }   

首先檢測內(nèi)核是否已經(jīng)DIE了或者已經(jīng)panic了,如果是則表明內(nèi)核已經(jīng)crash了,無需再進(jìn)行監(jiān)測了,直接返回即可。注意這里的did_panic標(biāo)識在前文中的panic通知鏈回調(diào)函數(shù)中hung_task_panic()置位:

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. static int   
  3. hung_task_panic(struct notifier_block *this, unsigned long event, void *ptr)   
  4. {   
  5.     did_panic = 1;   
  6.    
  7.     return NOTIFY_DONE;   
  8. }   

接下去若尚無觸發(fā)內(nèi)核crash,則進(jìn)入監(jiān)測流程并逐一檢測內(nèi)核中的所有進(jìn)程(任務(wù)task),該過程在RCU加鎖的狀態(tài)下進(jìn)行,因此為了避免在進(jìn)程較多的情況下加鎖時間過長,這里設(shè)置了一個batch_count,一次最多檢測HUNG_TASK_BATCHING個進(jìn)程。于此同時用戶也可以設(shè)定***的檢測個數(shù)max_count=sysctl_hung_task_check_count,默認(rèn)值為***PID個數(shù)PID_MAX_LIMIT(通過sysctl命令設(shè)置)。

函數(shù)調(diào)用for_each_process_thread()函數(shù)輪詢內(nèi)核中的所有進(jìn)程(任務(wù)task),僅對狀態(tài)處于TASK_UNINTERRUPTIBLE狀態(tài)的進(jìn)程進(jìn)行超時判斷,調(diào)用check_hung_task()函數(shù),入?yún)閠ask_struct結(jié)構(gòu)和超時時間(120s):

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. static void check_hung_task(struct task_struct *t, unsigned long timeout)   
  3. {   
  4.     unsigned long switch_count = t->nvcsw + t->nivcsw;   
  5.    
  6.     /*  
  7.      * Ensure the task is not frozen.  
  8.      * Also, skip vfork and any other user process that freezer should skip.  
  9.      */   
  10.     if (unlikely(t->flags & (PF_FROZEN | PF_FREEZER_SKIP)))   
  11.         return;   
  12.    
  13.     /*  
  14.      * When a freshly created task is scheduled once, changes its state to  
  15.      * TASK_UNINTERRUPTIBLE without having ever been switched out once, it  
  16.      * musn't be checked.  
  17.      */   
  18.     if (unlikely(!switch_count))   
  19.         return;   
  20.    
  21.     if (switch_count != t->last_switch_count) {   
  22.         t->last_switch_count = switch_count;   
  23.         return;   
  24.     }   
  25.    
  26.     trace_sched_process_hang(t);   
  27.    
  28.     if (!sysctl_hung_task_warnings)   
  29.         return;   
  30.    
  31.     if (sysctl_hung_task_warnings > 0)   
  32.         sysctl_hung_task_warnings--;   

首先通過t->nvcsw和t->nivcsw的計數(shù)累加表示進(jìn)程從創(chuàng)建開始至今的調(diào)度次數(shù)總和,其中t->nvcsw表示進(jìn)程主動放棄CPU的次數(shù),t->nivcsw表示被強制搶占的次數(shù)。隨后函數(shù)判斷幾個標(biāo)識:(1)如果進(jìn)程被frozen了那就跳過檢測;(2)調(diào)度次數(shù)為0的不檢測。

接下來判斷從上一次檢測時保存的進(jìn)程調(diào)度次數(shù)和本次是否相同,若不相同則表明這輪timeout(120s)時間內(nèi)進(jìn)程發(fā)生了調(diào)度,則更新該調(diào)度值返回,否則則表明該進(jìn)程已經(jīng)有timeout(120s)時間沒有得到調(diào)度了,一直處于D狀態(tài)。接下來的trace_sched_process_hang()暫不清楚作用,然后判斷sysctl_hung_task_warnings標(biāo)識,它表示需要觸發(fā)報警的次數(shù),用戶也可以通過sysctl命令配置,默認(rèn)值為10,即若當(dāng)前檢測的進(jìn)程一直處于D狀態(tài),默認(rèn)情況下此處每2分鐘發(fā)出一次告警,一共發(fā)出10次,之后不再發(fā)出告警。下面來看告警代碼:

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. /*  
  3.  * Ok, the task did not get scheduled for more than 2 minutes,  
  4.  * complain:  
  5.  */   
  6. pr_err("INFO: task %s:%d blocked for more than %ld seconds.\n",   
  7.     t->comm, t->pid, timeout);   
  8. pr_err("      %s %s %.*s\n",   
  9.     print_tainted(), init_utsname()->release,   
  10.     (int)strcspn(init_utsname()->version, " "),   
  11.     init_utsname()->version);   
  12. pr_err("\"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\""   
  13.     " disables this message.\n");   
  14. sched_show_task(t);   
  15. debug_show_held_locks(t);   
  16.    
  17. touch_nmi_watchdog();   

這里會在控制臺和日志中打印死鎖任務(wù)的名稱、PID號、超時時間、內(nèi)核tainted信息、sysinfo、內(nèi)核棧barktrace以及寄存器信息等。如果開啟了debug lock則打印鎖占用的情況,并touch nmi_watchdog以防止nmi_watchdog超時(對于我的ARM環(huán)境無需考慮nmi_watchdog)。

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. if (sysctl_hung_task_panic) {   
  3.     trigger_all_cpu_backtrace();   
  4.     panic("hung_task: blocked tasks");   
  5. }   

***如果設(shè)置了sysctl_hung_task_panic標(biāo)識則直接觸發(fā)panic(該值可通過內(nèi)核配置文件配置也可以通過sysctl設(shè)置)。

二、示例演示

演示環(huán)境:樹莓派b(Linux 4.1.15)

1、首先確認(rèn)內(nèi)核配置選項以確認(rèn)開啟hung stak機制

  1. [cpp] view plain copy  在CODE上查看代碼片派生到我的代碼片 
  2. #include      
  3. #include      
  4. #include      
  5. #include    
  6.    
  7. DEFINE_MUTEX(dlock);   
  8.    
  9. static int __init dlock_init(void)   
  10. {   
  11.     mutex_lock(&dlock);   
  12.     mutex_lock(&dlock);    
  13.        
  14.     return 0;   
  15. }   
  16.    
  17. static void __exit dlock_exit(void)    
  18. {   
  19.     return;   
  20. }   
  21.    
  22. module_init(dlock_init);     
  23. module_exit(dlock_exit);     
  24. MODULE_LICENSE("GPL");     

本示例程序定義了一個mutex鎖,然后在模塊的init函數(shù)中重復(fù)加鎖,人為造成死鎖現(xiàn)象(mutex_lock()函數(shù)會調(diào)用__mutex_lock_slowpath()將進(jìn)程設(shè)置為TASK_UNINTERRUPTIBLE狀態(tài)),進(jìn)程進(jìn)入D狀態(tài)后是無法退出的??梢酝ㄟ^ps命令來查看:

 

  1. root@apple:~# busybox ps  
  2. PID USER TIME COMMAND  
  3. ...... 
  4.  
  5. 521 root 0:00 insmod dlock.ko  
  6. ...... 

然后查看該進(jìn)程的狀態(tài),可見已經(jīng)進(jìn)入了D狀態(tài)。

 

  1. root@apple:~# cat /proc/521/status  
  2. Name: insmod  
  3. State: D (disk sleep)  
  4. Tgid: 521  
  5. Ngid: 0  
  6. Pid: 521 

至此在等待兩分鐘后調(diào)試串口就會輸出以下信息,可見每兩分鐘就會輸出一次:

 

  1. [ 360.625466] INFO: task insmod:521 blocked for more than 120 seconds.  
  2. [ 360.631878] Tainted: G O 4.1.15 #5  
  3. [ 360.637042] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.  
  4. [ 360.644986] [] (__schedule) from [] (schedule+0x40/0xa4)  
  5. [ 360.652129] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)  
  6. [ 360.660570] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)  
  7. [ 360.670142] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)  
  8. [ 360.678432] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])  
  9. [ 360.686480] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)  
  10. [ 360.694976] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)  
  11. [ 360.703170] [] (do_init_module) from [] (load_module+0x1690/0x1d34)  
  12. [ 360.711284] [] (load_module) from [] (SyS_init_module+0xdc/0x130)  
  13. [ 360.719239] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54)  
  14. [ 480.725351] INFO: task insmod:521 blocked for more than 120 seconds.  
  15. [ 480.731759] Tainted: G O 4.1.15 #5  
  16. [ 480.736917] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.  
  17. [ 480.744842] [] (__schedule) from [] (schedule+0x40/0xa4)  
  18. [ 480.752029] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)  
  19. [ 480.760479] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)  
  20. [ 480.770066] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)  
  21. [ 480.778363] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])  
  22. [ 480.786402] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)  
  23. [ 480.794897] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)  
  24. [ 480.803085] [] (do_init_module) from [] (load_module+0x1690/0x1d34)  
  25. [ 480.811188] [] (load_module) from [] (SyS_init_module+0xdc/0x130)  
  26. [ 480.819113] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54)  
  27. [ 600.825353] INFO: task insmod:521 blocked for more than 120 seconds.  
  28. [ 600.831759] Tainted: G O 4.1.15 #5  
  29. [ 600.836916] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.  
  30. [ 600.844865] [] (__schedule) from [] (schedule+0x40/0xa4)  
  31. [ 600.852005] [] (schedule) from [] (schedule_preempt_disabled+0x18/0x1c)  
  32. [ 600.860445] [] (schedule_preempt_disabled) from [] (__mutex_lock_slowpath+0x6c/0xe4)  
  33. [ 600.870014] [] (__mutex_lock_slowpath) from [] (mutex_lock+0x44/0x48)  
  34. [ 600.878303] [] (mutex_lock) from [] (dlock_init+0x20/0x2c [dlock])  
  35. [ 600.886339] [] (dlock_init [dlock]) from [] (do_one_initcall+0x90/0x1e8)  
  36. [ 600.894835] [] (do_one_initcall) from [] (do_init_module+0x6c/0x1c0)  
  37. [ 600.903023] [] (do_init_module) from [] (load_module+0x1690/0x1d34)  
  38. [ 600.911133] [] (load_module) from [] (SyS_init_module+0xdc/0x130)  
  39. [ 600.919059] [] (SyS_init_module) from [] (ret_fast_syscall+0x0/0x54) 

三、總結(jié)

D狀態(tài)死鎖一般在驅(qū)動開發(fā)的過程中比較常見,且不太容易定位,內(nèi)核提供這種hung task機制,開發(fā)人員只需要將這些輸出的定位信息抓取并保留下來就可以快速的進(jìn)行定位。

責(zé)任編輯:武曉燕 來源: chinaunix
相關(guān)推薦

2010-03-02 10:27:56

Linux進(jìn)程狀態(tài)

2017-01-12 19:15:03

Linux內(nèi)核調(diào)試自構(gòu)proc

2010-01-22 11:01:04

linux內(nèi)核模塊

2014-08-28 15:08:35

Linux內(nèi)核

2021-04-15 05:51:25

Linux

2010-07-06 10:08:57

SQL Server

2011-01-14 14:49:05

2019-04-10 13:43:19

Linux內(nèi)核進(jìn)程負(fù)載

2022-02-08 15:15:26

OpenHarmonlinux鴻蒙

2021-07-11 06:45:18

Linux內(nèi)核靜態(tài)

2009-12-25 11:22:13

Linux進(jìn)程技術(shù)

2017-09-29 10:49:30

2009-11-18 09:05:36

Oracle死鎖進(jìn)程

2012-05-14 14:09:53

Linux內(nèi)核調(diào)度系統(tǒng)

2010-06-02 09:31:43

Linux core

2023-04-10 09:44:22

內(nèi)核鼠標(biāo)調(diào)試鴻蒙

2010-07-07 13:58:25

SQL Server死

2023-02-28 09:47:42

2022-03-03 18:18:53

BPF解釋器系統(tǒng)

2021-07-26 07:47:36

數(shù)據(jù)庫
點贊
收藏

51CTO技術(shù)棧公眾號