본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

[리눅스커널] cpu_frequency ftrace 이벤트: CPU 코어의 주파수

리눅스 커널에서는 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());
7     if (cpufreq_disabled())
8         return;
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
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다.