본문 바로가기

[Debugging] Tips

[리눅스커널] 커널 동적 타이머를 사용해 실행 시각을 측정해보기

이번에는 조금 더 재미있는 패치 코드를 같이 볼까요? 다음은 패치 코드의 내용입니다.
 
diff --git a/kernel/workqueue.c b/kernel/workqueue.c
index cd8b61b..128c998 100644
--- a/kernel/workqueue.c
+++ b/kernel/workqueue.c
@@ -49,7 +49,7 @@
 #include <linux/uaccess.h>
 #include <linux/sched/isolation.h>
 #include <linux/nmi.h>
-
+#include <linux/sched_clock.h>
 #include "workqueue_internal.h"
 
 enum {
@@ -2029,6 +2029,18 @@ static bool manage_workers(struct worker *worker)
        return true;
 }
 
+static int work_timer_debug = 1;
+static unsigned long long max_work_handler_time_stamp = 0;
+
+static void work_handler_elapsed_timer_func(struct timer_list *unused)
+{
+       unsigned long long curr_time = sched_clock();
+
+       pr_err("work_handler max elapsed time : %lld, cur time: %lld\n",
+                       max_work_handler_time_stamp, curr_time);
+}
+static DEFINE_TIMER(work_handler_elapsed_timer, work_handler_elapsed_timer_func);
+
 /**
  * process_one_work - process single work
  * @worker: self
@@ -2052,6 +2064,7 @@ __acquires(&pool->lock)
        bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
        int work_color;
        struct worker *collision;
+       u64 start_time, end_time, elapse_time;
 #ifdef CONFIG_LOCKDEP
        /*
         * It is permissible to free the struct work_struct from
@@ -2149,6 +2162,12 @@ __acquires(&pool->lock)
         * workqueues), so hiding them isn't a problem.
         */
        lockdep_invariant_state(true);
+
+       if (work_timer_debug) {
+               start_time = sched_clock();
+               mod_timer(&work_handler_elapsed_timer, msecs_to_jiffies(500) + jiffies);
+       }
+
        trace_workqueue_execute_start(work);
        worker->current_func(work);
        /*
@@ -2156,6 +2175,19 @@ __acquires(&pool->lock)
         * point will only record its address.
         */
        trace_workqueue_execute_end(work);
+
+       if (work_timer_debug) {
+               end_time = sched_clock();
+               del_timer_sync(&work_handler_elapsed_timer);
+               elapse_time = end_time - start_time;
+
+               if (elapse_time > max_work_handler_time_stamp) {
+                       max_work_handler_time_stamp = elapse_time;
+                       pr_err("work_handler elapsed time : %lld, handler: %pS\n",
+                                       max_work_handler_time_stamp, worker->current_func);
+               }
+       }
+
        lock_map_release(&lockdep_map);
        lock_map_release(&pwq->wq->lockdep_map);
 
@@ -2235,6 +2267,9 @@ static void set_pf_worker(bool val)
  *
  * Return: 0
  */
 
 
위 패치 코드는 2가지 기능을 지원하는데 세부 동작은 두 가지 시나리오에서 생각해볼 수 있습니다.
 
< 첫 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이내이면 
   타이머 해제
4. 최대 실행 시각을 저장
 
< 두 번째 시나리오 >
1. 타이머 설정(500ms)
2.         worker->current_func(work); 함수 실행
3. 'worker->current_func(work)' 코드의 실행 시각이 500ms 이상이 걸리면  
   타이머 핸들러 함수가 호출됨
4. work_handler_elapsed_timer_func() 타이머 핸들러에서 에러 정보 출력
   : 관련 자료 구조
   : 프로세스의 콜스택
 
임베디드 리눅스 개발자분들은 이 포스팅에 올린 자료를 참고해서 더 빨리 퇴근하셨으면 좋겠습니다.