리눅스 커널에서는 CPU Frequency를 트레이싱할 수 있는 ftrace event를 제공합니다.
그 정체는 cpu_frequency 이벤트입니다. 먼저 ftrace 이벤트를 정의하는 코드를 보겠습니다.
다음은 cpu_frequency 이벤트의 선언부입니다.
01 DEFINE_EVENT(cpu, cpu_frequency,
02
03 TP_PROTO(unsigned int frequency, unsigned int cpu_id),
04
05 TP_ARGS(frequency, cpu_id)
06 );
01번째 줄과 같이 cpu_frequency 이벤트는 cpu란 이벤트 클래스를 오버로딩한다는 사실을 알 수 있습니다.
cpu 이벤트 클래스의 선언부는 다음과 같습니다.
DECLARE_EVENT_CLASS(cpu,
TP_PROTO(unsigned int state, unsigned int cpu_id),
TP_ARGS(state, cpu_id),
TP_STRUCT__entry(
__field( u32, state )
__field( u32, cpu_id )
),
TP_fast_assign(
__entry->state = state;
__entry->cpu_id = cpu_id;
),
TP_printk("state=%lu cpu_id=%lu", (unsigned long)__entry->state,
(unsigned long)__entry->cpu_id)
);
ftrace의 이벤트 클래스는 ftrace 이벤트의 공통 skeleton 형식으로 구성돼 있습니다.
'state=%lu'는 주파수, 'cpu_id=%lu'는 CPU 코어의 번호를 출력합니다.
이번에는 'cpu_frequency' 이벤트를 출력하는 커널 코드를 분석하겠습니다.
다음은 cpufreq_notify_transition() 함수의 선언부입니다.
1 static void cpufreq_notify_transition(struct cpufreq_policy *policy,
2 struct cpufreq_freqs *freqs,
3 unsigned int state)
4 {
5 BUG_ON(irqs_disabled());
6
7 if (cpufreq_disabled())
8 return;
9
10 freqs->flags = cpufreq_driver->flags;
11 pr_debug("notification %u of frequency transition to %u kHz\n",
12 state, freqs->new);
13
14 switch (state) {
15 ...
16 case CPUFREQ_POSTCHANGE:
17 adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
18 pr_debug("FREQ: %u - CPUs: %*pbl\n", freqs->new,
19 cpumask_pr_args(policy->cpus));
20
21 for_each_cpu(freqs->cpu, policy->cpus) {
22 trace_cpu_frequency(freqs->new, freqs->cpu);
23 srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24 CPUFREQ_POSTCHANGE, freqs);
25 }
26
27 cpufreq_stats_record_transition(policy, freqs->new);
28 policy->cur = freqs->new;
29 }
30 }
위 코드에서 cpu_frequency이란 ftrace 이벤트는 22번째 줄이 실행될 때 출력합니다.
22 trace_cpu_frequency(freqs->new, freqs->cpu);
이어서 23~24번째 줄을 주의깊게 눈여겨 볼 필요가 있습니다.
23 srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24 CPUFREQ_POSTCHANGE, freqs);
cpufreq_transition_notifier_list 이란 notifier call을 수행합니다.
notifier call은 어떤 함수가 동작이 완료된 후 호출되도록 등록하는 인터페이스인데요.
cpufreq_transition_notifier_list 이란 notifier call에 등록하기 위해서는 cpufreq_register_notifier() 함수를 호출해야 합니다.
여기서 한 가지 의문이 생깁니다.
* 'cpufreq_transition_notifier_list' notifier-call에 등록된 notifier의 정체는 무엇인가?
이 의문을 해소하려면 cpufreq_transition_notifier_list 변수를 직접 확인하면 됩니다.
다음은 cpufreq_transition_notifier_list입니다.
(static struct srcu_notifier_head) cpufreq_transition_notifier_list = (
(struct mutex) mutex = ((atomic_t) count = ((int) counter = 1 = 0x1 = '....'),
(struct srcu_struct) srcu = ((long unsigned int) completed = 1 = 0x1 = '....',
(struct notifier_block *) head = 0xC17129C4 = cpufreq_notifier -> (
(notifier_fn_t) notifier_call = 0xC0110CF4 = cpufreq_callback -> ,
(struct notifier_block *) next = 0xC17FAE28 = notifier_trans_block -> (
(notifier_fn_t) notifier_call = 0xC0BDA378 = cpufreq_stat_notifier_trans -> ,
(struct notifier_block *) next = 0xC1768DA0 = ppm_cpu_freq_notifier -> (
(notifier_fn_t) notifier_call = 0xC059CD20 = ppm_cpu_freq_callback -> ,
(struct notifier_block *) next = 0xC1F2B110 = freq_transition -> (
(notifier_fn_t) notifier_call = 0xC05C0F90 = cpufreq_transition_handler -> ,
(struct notifier_block *) next = 0x0 = -> NULL,
위에서 본 디버깅 정보를 정리하면 다음과 같습니다.
notifier | notifier-call 콜백 함수
cpufreq_notifier: cpufreq_callback()
notifier_trans_block: cpufreq_stat_notifier_trans()
ppm_cpu_freq_notifier: ppm_cpu_freq_callback()
freq_transition: cpufreq_transition_handler()
아까 봤던 cpufreq_notify_transition() 함수로 되돌아 가서 분석을 해보면...
1 static void cpufreq_notify_transition(struct cpufreq_policy *policy,
2 struct cpufreq_freqs *freqs,
3 unsigned int state)
4 {
...
14 switch (state) {
15 ...
16 case CPUFREQ_POSTCHANGE:
17 adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
18 pr_debug("FREQ: %u - CPUs: %*pbl\n", freqs->new,
19 cpumask_pr_args(policy->cpus));
20
21 for_each_cpu(freqs->cpu, policy->cpus) {
22 trace_cpu_frequency(freqs->new, freqs->cpu);
23 srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24 CPUFREQ_POSTCHANGE, freqs);
25 }
23번째 줄이 실행되면 다음 목록의 함수들이 차례로 호출되는 것입니다.
* cpufreq_callback()
* cpufreq_stat_notifier_trans()
* ppm_cpu_freq_callback()
* cpufreq_transition_handler()
(where)
notifier | notifier-call 콜백 함수
cpufreq_notifier: cpufreq_callback()
notifier_trans_block: cpufreq_stat_notifier_trans()
ppm_cpu_freq_notifier: ppm_cpu_freq_callback()
freq_transition: cpufreq_transition_handler()
English version
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다.
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스] LK(Little Kernel): 전처리 파일을 위한 설정(--save-temps) (0) | 2023.05.06 |
---|---|
[리눅스커널] 커널 로그 레벨 수정 - console_printk 확인 (0) | 2023.05.06 |
[리눅스커널] CPU 코어의 주파수(Frequency) 확인하기 - cpufreq_cpu_data (0) | 2023.05.06 |
[리눅스커널] 디버깅: 커널 로그 레벨(/proc/sys/kernel/printk)을 누가 설정하나? (0) | 2023.05.06 |
[리눅스커널] 유저 프로세스의 레지스터 세트인 struct pt_regs 파악하기 (0) | 2023.05.06 |