리눅스 커널에서 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 이벤트와 트레이스 포인트를 설명합니다.
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널] 커널 로그 레벨 수정 - console_printk 확인 (0) | 2023.05.06 |
---|---|
[리눅스커널] cpu_frequency ftrace 이벤트: CPU 코어의 주파수 (0) | 2023.05.06 |
[리눅스커널] 디버깅: 커널 로그 레벨(/proc/sys/kernel/printk)을 누가 설정하나? (0) | 2023.05.06 |
[리눅스커널] 유저 프로세스의 레지스터 세트인 struct pt_regs 파악하기 (0) | 2023.05.06 |
[리눅스커널] ARMv8: 슬럽 오브젝트의 트랙(track) 구조체를 TRACE32로 디버깅하기 (0) | 2023.05.06 |