본문 바로가기

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

[리눅스커널] CPU 코어의 주파수(Frequency) 확인하기 - cpufreq_cpu_data

리눅스 커널에서 CPU Frequency는 성능을 측정할 때 중요한 척도 중 하나입니다.
이번 시간에는 리눅스 커널에서 CPU Frequency와 관련된 자료 구조를 소개합니다.
 
cpufreq_cpu_data 선언부
 
cpufreq_cpu_data는 커널에서 CPU 주파수를 저장하는 중요한 변수입니다.
 
먼저 cpufreq_cpu_data 변수의 선언부를 봅시다.
 
static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data);
 
보시다시피 DEFINE_PER_CPU 키워드와 함께 cpufreq_policy 구조체로 선언된 변수임을 알 수 있습니다.
즉, CPU 코어 별로 cpufreq_policy 구조체의 정보를 cpufreq_cpu_data 변수가 저장하는 것입니다.
 
cpufreq_cpu_data 디버깅해보기
 
이어서 percpu 타입인 cpufreq_cpu_data 변수를 확인해보겠습니다.
다음은 크래시 유틸리티에서 cpufreq_cpu_data 변수를 확인한 결과입니다.
 
crash> p cpufreq_cpu_data
PER-CPU DATA TYPE:
  struct cpufreq_policy *cpufreq_cpu_data;
PER-CPU ADDRESSES:
  [0]: e6b1f838
  [1]: e6b32838
  [2]: e6b45838
  [3]: e6b58838
  [4]: e6b6b838
  [5]: e6b7e838
  [6]: e6b91838
  [7]: e6ba4838
 
친절하게 struct cpufreq_policy 구조체가 8개가 있다는 정보를 표현합니다. 코어의 갯수가 8개이니 인덱스의 범위가 0~7입니다.
 
이번에는 TRACE32로 cpufreq_cpu_data 변수를 확인해봅시다.
먼저 cpufreq_cpu_data 변수의 주소를 점검하겠습니다.
 
$ v.v %t %h &cpufreq_cpu_data
(struct cpufreq_policy * *) &cpufreq_cpu_data = 0xC16F0838
 
보시다시피 cpufreq_cpu_data 변수는 0xC16F0838 주소에 위치합니다.
cpufreq_cpu_data 변수가 percpu 타입이니 __per_cpu_offset[] 배열을 활용할 필요가 있습니다.
 
다음과 같은 명령어를 입력합시다.
 
$ v.v %tree.on %t %h %s %d %y (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0])
01  (struct cpufreq_policy * *) (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0]) = 0xE6B1F838 = _
02    (cpumask_var_t) cpus = (((long unsigned int [1]) bits = (15))),
03    (cpumask_var_t) related_cpus = (((long unsigned int [1]) bits = (15))),
04    (cpumask_var_t) real_cpus = (((long unsigned int [1]) bits = (15))),
05    (unsigned int) shared_type = 3,
06    (unsigned int) cpu = 0,
07    (struct clk *) clk = 0x0 = ,
08    (struct cpufreq_cpuinfo) cpuinfo = (
09      (unsigned int) max_freq = 2001000,
10      (unsigned int) min_freq = 900000,
11      (unsigned int) transition_latency = 1000),
12    (unsigned int) min = 2001000,
13    (unsigned int) max = 2001000,
14    (unsigned int) cur = 2001000,
15    (unsigned int) restore_freq = 2001000,
16    (unsigned int) suspend_freq = 0,
17    (unsigned int) policy = 0,
18    (unsigned int) last_policy = 0,
19    (struct cpufreq_governor *) governor = 0xC171D988 = cpufreq_gov_sched -> (
20      (char [16]) name = "schedplus",
21      (int (*)()) init = 0xC019F810 = cpufreq_sched_policy_init,
22      (void (*)()) exit = 0xC019F5FC = cpufreq_sched_policy_exit,
23      (int (*)()) start = 0xC019F540 = cpufreq_sched_start,
24      (void (*)()) stop = 0xC019F5A0 = cpufreq_sched_stop,
25      (void (*)()) limits = 0x0 = ,
26      (ssize_t (*)()) show_setspeed = 0x0 = ,
27      (int (*)()) store_setspeed = 0x0 = ,
 
이제부터 출력 결과를 확인하겠습니다. 
먼저 08~10번째 줄을 보겠습니다.
 
08    (struct cpufreq_cpuinfo) cpuinfo = (
09      (unsigned int) max_freq = 2001000,
10      (unsigned int) min_freq = 900000,
 
09번째 줄은 최대 frequency, 10번째 줄은 최소 frequency를 나타냅니다.
 
이번에는 14번째 줄을 확인합시다.
 
14    (unsigned int) cur = 2001000,
 
현재 실행 중인 CPU 코어의 주파수를 나타냅니다.
 
이어서 19~24번째 줄을 보겠습니다.
 
19    (struct cpufreq_governor *) governor = 0xC171D988 = cpufreq_gov_sched -> (
20      (char [16]) name = "schedplus",
21      (int (*)()) init = 0xC019F810 = cpufreq_sched_policy_init,
22      (void (*)()) exit = 0xC019F5FC = cpufreq_sched_policy_exit,
23      (int (*)()) start = 0xC019F540 = cpufreq_sched_start,
24      (void (*)()) stop = 0xC019F5A0 = cpufreq_sched_stop,
 
해당 CPU 코어에서 적용된 거버너 정보를 나타냅니다.
"schedplus" CPU 거버너인데 21~24번째 줄에서 함수 포인터의 정보를 확인할 수 있습니다.
 
CPU frequency 테이블 확인해보기
 
커널에서는 로드에 비례해 CPU 코어의 주파수를 설정할 수 있는 CPU frequency 테이블이 있습니다.
CPU frequency 테이블의 주소는 percpu 타입인 cpufreq_cpu_data->freq_table 에 저장돼 있습니다.
 
다음은 0번째 CPU의 cpufreq_cpu_data 변수의 정보입니다.
 
$ v.v %tree.on %t %h %s %d %y (struct cpufreq_policy*)(0xC16F0838+__per_cpu_offset[0])
01  (struct cpufreq_policy * *) (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0]) = 0xE6B1F838 = _
02    (cpumask_var_t) cpus = (((long unsigned int [1]) bits = (15))),
...
03    (struct cpufreq_frequency_table *) freq_table = 0xE4DA2500  
04      (unsigned int) flags = 0 = 0x0,
05      (unsigned int) driver_data = 0 = 0x0,
06     (unsigned int) frequency = 2001000 = 0x001E8868),
 
보사시피 03번째에서 freq_table 필드는 0xE4DA2500 주소를 저장합니다.
 
다음 포멧으로 명령어를 입력하면 CPU 코어의 Frequency 테이블을 확인할 수 있습니다.
 
$ v.v %i %t %d ((struct cpufreq_frequency_table *)0xE4DA2500)[0..15]
  (static xtract) ((struct cpufreq_frequency_table *)0xE4DA2500)[0..15] = (
    [0] = ((unsigned int) flags = 0, (unsigned int) driver_data = 0, (unsigned int) frequency = 2001000),
    [1] = ((unsigned int) flags = 0, (unsigned int) driver_data = 1, (unsigned int) frequency = 1961000),
    [2] = ((unsigned int) flags = 0, (unsigned int) driver_data = 2, (unsigned int) frequency = 1927000),
    [3] = ((unsigned int) flags = 0, (unsigned int) driver_data = 3, (unsigned int) frequency = 1897000),
    [4] = ((unsigned int) flags = 0, (unsigned int) driver_data = 4, (unsigned int) frequency = 1868000),
    [5] = ((unsigned int) flags = 0, (unsigned int) driver_data = 5, (unsigned int) frequency = 1838000),
    [6] = ((unsigned int) flags = 0, (unsigned int) driver_data = 6, (unsigned int) frequency = 1809000),
    [7] = ((unsigned int) flags = 0, (unsigned int) driver_data = 7, (unsigned int) frequency = 1779000),
    [8] = ((unsigned int) flags = 0, (unsigned int) driver_data = 8, (unsigned int) frequency = 1750000),
    [9] = ((unsigned int) flags = 0, (unsigned int) driver_data = 9, (unsigned int) frequency = 1617000),
    [10] = ((unsigned int) flags = 0, (unsigned int) driver_data = 10, (unsigned int) frequency = 1484000),
    [11] = ((unsigned int) flags = 0, (unsigned int) driver_data = 11, (unsigned int) frequency = 1351000),
    [12] = ((unsigned int) flags = 0, (unsigned int) driver_data = 12, (unsigned int) frequency = 1218000),
    [13] = ((unsigned int) flags = 0, (unsigned int) driver_data = 13, (unsigned int) frequency = 1085000),
    [14] = ((unsigned int) flags = 0, (unsigned int) driver_data = 14, (unsigned int) frequency = 979000),
    [15] = ((unsigned int) flags = 0, (unsigned int) driver_data = 15, (unsigned int) frequency = 900000))
 
보다시피 frequency 필드의 범위는 2001000~900000 kHz입니다.
 
 
cpufreq_cpu_data 변수에 액세스하는 함수 분석하기
 
이제까지 CPU 코어의 Frequency를 확인하는 방법을 cpufreq_cpu_data 변수로 확인했습니다.
cpufreq_cpu_data 변수는 어느 함수를 통해 액세스를 할까요?
 
정답은 다음 함수입니다.
 
    * cpufreq_cpu_get()
    * cpufreq_cpu_get_raw()
 
먼저 cpufreq_cpu_get() 함수를 보겠습니다. 
 
01 struct cpufreq_policy *cpufreq_cpu_get(unsigned int cpu)
02 { 
03 struct cpufreq_policy *policy = NULL;
04 unsigned long flags;
05
06 if (WARN_ON(cpu >= nr_cpu_ids))
07 return NULL;
08
09 /* get the cpufreq driver */
10 read_lock_irqsave(&cpufreq_driver_lock, flags);
11
12 if (cpufreq_driver) {
13 /* get the CPU */
14 policy = cpufreq_cpu_get_raw(cpu);
15 if (policy)
16 kobject_get(&policy->kobj);
17 }
18
19 read_unlock_irqrestore(&cpufreq_driver_lock, flags);
20
21 return policy;
22}
23 EXPORT_SYMBOL_GPL(cpufreq_cpu_get);
 
14번째 줄을 보면 cpufreq_cpu_get_raw() 함수를 호출하는데  cpufreq_cpu_get_raw() 함수는 struct cpufreq_policy 구조체 타입의 주소를 반환합니다.
 
이어서 cpufreq_cpu_get_raw() 함수를 보겠습니다.
 
1 struct cpufreq_policy *cpufreq_cpu_get_raw(unsigned int cpu)
2 {
3 struct cpufreq_policy *policy = per_cpu(cpufreq_cpu_data, cpu);
4
5 return policy && cpumask_test_cpu(cpu, policy->cpus) ? policy : NULL;
6 }
 
3번째 줄과 같이 percpu 타입인 cpufreq_cpu_data 변수에 접근해 포인터 형인 policy 변수가 가르키는 주소를 반환합니다. 
 
다음 시간에는 CPU 코어의 주파수를 측정하는 ftrace 이벤트와 트레이스 포인트를 설명합니다.