Overview
ftrace로 다양한 디버깅 정보를 출력할 수 있다.
물론 function, function_graph 트레이서를 활용해 화려한 디버깅 정보를 볼 수 있는데, nop 트레이서로 다음과 같은 정보를 보면 유용할 것이다.
rcu_preempt-9 [000] ...1 5429.091523: sched_preemption_execute: delta=8890468(ns) Callers: (schedule<-rcu_nocb_kthread<-kthread<-ret_from_fork)
ftrace에서 sched_preemption_trigger 이벤트를 추가하는 방법에 대해서 알아보자.
추가할 ftrace 이벤트의 스팩은 다음과 같다.
■ 이름: sched_preemption_execute
■ 디버깅 정보
- delta: 실행 시간
- 콜스택
ftrace 이벤트 정의하기
sched_preemption_execute 이벤트를 아래와 같이 정의하자. (참고로 아래 패치 코드는 라즈베리 파이에서 추가했다.)
diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
index 9a4bdfa..e192f5b 100644
--- a/include/trace/events/sched.h
+++ b/include/trace/events/sched.h
@@ -587,6 +587,35 @@ TRACE_EVENT(sched_wake_idle_without_ipi,
TP_printk("cpu=%d", __entry->cpu)
);
+TRACE_EVENT(sched_preemption_execute,
+
+ TP_PROTO(u64 delta,
+ unsigned long caller_addr0, unsigned long caller_addr1,
+ unsigned long caller_addr2, unsigned long caller_addr3),
+
+ TP_ARGS(delta, caller_addr0, caller_addr1, caller_addr2, caller_addr3),
+
+ TP_STRUCT__entry(
+ __field(u64, delta)
+ __field(void*, caller_addr0)
+ __field(void*, caller_addr1)
+ __field(void*, caller_addr2)
+ __field(void*, caller_addr3)
+ ),
+
+ TP_fast_assign(
+ __entry->delta = delta;
+ __entry->caller_addr0 = (void *)caller_addr0;
+ __entry->caller_addr1 = (void *)caller_addr1;
+ __entry->caller_addr2 = (void *)caller_addr2;
+ __entry->caller_addr3 = (void *)caller_addr3;
+ ),
+
+ TP_printk("delta=%llu(ns) Callers:(%ps<-%ps<-%ps<-%ps)",
+ __entry->delta,
+ __entry->caller_addr0, __entry->caller_addr1,
+ __entry->caller_addr2, __entry->caller_addr3)
+);
#endif /* _TRACE_SCHED_H */
/* This part must be outside protection */
위 코드에서 핵심은 다음 코드이다.
+ TP_printk("delta=%llu(ns) Callers:(%ps<-%ps<-%ps<-%ps)",
+ __entry->delta,
+ __entry->caller_addr0, __entry->caller_addr1,
+ __entry->caller_addr2, __entry->caller_addr3)
함수의 시각과 콜스택의 심보를 출력한다.
ftrace 이벤트를 출력하는 코드
ftrace 이벤트를 출력하는 패치 코드는 아래와 같다. __schedule() 함수이다.
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 2befd2c..2b0e6f7 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3429,6 +3429,7 @@ static void __sched notrace __schedule(bool preempt)
struct rq_flags rf;
struct rq *rq;
int cpu;
+ u64 delta = 0;
cpu = smp_processor_id();
rq = cpu_rq(cpu);
@@ -3490,6 +3491,10 @@ static void __sched notrace __schedule(bool preempt)
clear_tsk_need_resched(prev);
clear_preempt_need_resched();
+ delta = sched_clock();
+ trace_sched_preemption_execute(delta, CALLER_ADDR0, CALLER_ADDR1,
+ CALLER_ADDR2, CALLER_ADDR3);
+
if (likely(prev != next)) {
rq->nr_switches++;
rq->curr = next;
위 패치에서 핵심 코드 조각은 아래와 같다.
+ trace_sched_preemption_execute(delta, CALLER_ADDR0, CALLER_ADDR1,
+ CALLER_ADDR2, CALLER_ADDR3);
함수가 실행되는 시간과 함수의 콜 스택을 출력하는 기능이다.
CALLER_ADDR0~CALLER_ADDR3의 정체
이번에 소개한 패치 코드에서 CALLER_ADDR0~CALLER_ADDR3 매크로가 핵심이다.
CALLER_ADDR0~CALLER_ADDR3 매크로의 정체를 확인하기 위해 전처리 코드를 열어보자.
kernel/sched/.tmp_core.i
..
delta = sched_clock();
trace_sched_preemption_execute(delta, ((unsigned long)__builtin_return_address(0)), ((unsigned long)return_address(1)),
((unsigned long)return_address(2)), ((unsigned long)return_address(3)));
코드를 통해 CALLER_ADDR0~CALLER_ADDR3 매크로의 정체는 return_address(n) 함수라는 사실을 알 수 있다.
■ CALLER_ADDR1 = ((unsigned long)return_address(1)
■ CALLER_ADDR2 = ((unsigned long)return_address(2)
■ CALLER_ADDR3 = ((unsigned long)return_address(3)
sched_preemption_execute 이벤트의 출력 결과
위 패치 코드를 반영한 후 출력되는 결과는 아래와 같다.
rcu_preempt-9 [000] ...1 5429.091523: sched_preemption_execute: delta=8890468(ns) Callers: (schedule<-rcu_nocb_kthread<-kthread<-ret_from_fork)
ftrace 메시지로 콜 스택을 바로 확인할 수 있다.
헤더 파일
이 패치를 적용하기 위해서 다음 목록과 같은 헤더 파일을 추가해야 한다.
■ #include <trace/events/sched.h>
■ #include <linux/ftrace.h>
■ #include <linux/sched/clock.h>
샘플 패치 코드를 보자. fs/proc/interrupts.c에 있는 int_seq_start() 함수를 수정했다.
diff --git a/fs/proc/interrupts.c b/fs/proc/interrupts.c
index cb0edc7..a4bbc19 100644
--- a/fs/proc/interrupts.c
+++ b/fs/proc/interrupts.c
@@ -5,12 +5,23 @@
#include <linux/irqnr.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
-
+#include <trace/events/sched.h>
+#include <linux/ftrace.h>
+#include <linux/sched/clock.h>
/*
* /proc/interrupts
*/
static void *int_seq_start(struct seq_file *f, loff_t *pos)
{
+ u64 delta = 0;
+
+ delta = sched_clock();
+ trace_sched_preemption_execute(delta, CALLER_ADDR0, CALLER_ADDR1,
+ CALLER_ADDR2, CALLER_ADDR3);
+
+ printk("delta=%llu(ns) Callers:(%ps<-%ps<-%ps<-%ps)",
+ delta, CALLER_ADDR0, CALLER_ADDR1, CALLER_ADDR2, CALLER_ADDR3);
+
return (*pos <= nr_irqs) ? pos : NULL;
}
이 코드를 참고하면 다양한 방식으로 디버깅할 수 있다.
# Reference: For more information on 'Linux Kernel';
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널] 유저 프로세스의 레지스터 세트인 struct pt_regs 파악하기 (0) | 2023.05.06 |
---|---|
[리눅스커널] ARMv8: 슬럽 오브젝트의 트랙(track) 구조체를 TRACE32로 디버깅하기 (0) | 2023.05.06 |
[리눅스커널] 특정 CPU를 Isolation 시키고 싶은 경우 (0) | 2023.05.06 |
[리눅스커널] GCC: notrace 옵션 - no_instrument_function (0) | 2023.05.06 |
[리눅스커널] ssize_t와 size_t의 실체 (0) | 2023.05.06 |