Core BSP 분석/Core 커널 분석

[리눅스 커널 CPU] CPU 코어 주파수 확인 방법 - cpufreq_cpu_data 사용하기

AustinKim 2025. 9. 17. 21:38

 

리눅스 커널에서 CPU Frequency는 성능을 측정할 때 중요한 척도 중 하나입니다.

이번 시간에는 리눅스 커널에서 CPU Frequency와 관련된 자료 구조를 소개합니다.

 

cpufreq_cpu_data 선언부

 

cpufreq_cpu_data는 커널에서 CPU 주파수를 저장하는 중요한 변수입니다.

 

먼저 cpufreq_cpu_data 변수의 선언부를 봅시다.

 

https://elixir.bootlin.com/linux/v4.19.30/source/drivers/cpufreq/cpufreq.c

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 Hz입니다.

 

cpufreq_cpu_data 변수에 액세스하는 함수 분석하기

이제까지 CPU 코어의 Frequency를 확인하는 방법을 cpufreq_cpu_data 변수로 확인했습니다.

cpufreq_cpu_data 변수는 어느 함수를 통해 액세스를 할까요?

 

정답은 다음 함수입니다.

 

* cpufreq_cpu_get()

* cpufreq_cpu_get_raw()

 

먼저 cpufreq_cpu_get() 함수를 보겠습니다.

 

https://elixir.bootlin.com/linux/v4.19.30/source/drivers/cpufreq/cpufreq.c

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() 함수를 보겠습니다.

 

https://elixir.bootlin.com/linux/v4.19.30/source/drivers/cpufreq/cpufreq.c

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 이벤트와 트레이스 포인트를 설명합니다.