/proc/cpuinfo
/proc/cpuinfo 파일은 CPU 아키텍처 정보를 저장합니다.
root:/proc $ cat cpuinfo
Processor : AArch64 Processor rev 0 (aarch64)
processor : 0
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0xd
CPU part : 0x805
CPU revision : 14
processor : 1
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0xd
CPU part : 0x805
CPU revision : 14
processor : 2
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0xd
CPU part : 0x805
CPU revision : 14
processor : 3
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x51
CPU architecture: 8
CPU variant : 0xd
CPU part : 0x805
CPU revision : 14
processor : 4
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x0
CPU part : 0xd0d
CPU revision : 0
processor : 5
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x0
CPU part : 0xd0d
CPU revision : 0
processor : 6
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x0
CPU part : 0xd0d
CPU revision : 0
processor : 7
BogoMIPS : 38.40
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x0
CPU part : 0xd0d
CPU revision : 0
'proc/cpuinfo' 파일은 proc_cpuinfo_init() 함수에서 03번째 줄을 실행할 때 생성됩니다.
01 static int __init proc_cpuinfo_init(void)
02 {
03 proc_create("cpuinfo", 0, NULL, &proc_cpuinfo_operations);
04 return 0;
05 }
06 fs_initcall(proc_cpuinfo_init);
proc_cpuinfo_init() 함수 선언부 키워드에 __init이 있으니 부팅 도중 호출된다는 사실을 알 수 있습니다.
'proc/cpuinfo' 파일 연산
'proc/cpuinfo' 파일로 어떤 연산을 수행하는지 알려면 proc_cpuinfo_operations 전역 변수를 봐야 합니다.
static const struct file_operations proc_cpuinfo_operations = {
.open = cpuinfo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
proc 파일 시스템에서 seq_file 인터페이스를 써서 파일 출력을 하므로 open 함수 연산을 수행하는 cpuinfo_open() 함수를 분석할 필요가 있습니다.
다음 cpuinfo_open() 함수 코드를 보겠습니다.
01 static int cpuinfo_open(struct inode *inode, struct file *file)
02 {
03 arch_freq_prepare_all();
04 return seq_open(file, &cpuinfo_op);
05 }
05번째 줄에서 seq_open() 함수 2번째 인자로 cpuinfo_op seq_file 연산을 지정합니다.
struct seq_operations 구조체 타입인 cpuinfo_op 함수 연산은 아키텍처별로 위치가 다릅니다.
AArch64 ARMv8 아키텍처의 경우 cpuinfo_op 는 다음 코드에서 확인할 수 있습니다.
const struct seq_operations cpuinfo_op = {
.start = c_start,
.next = c_next,
.stop = c_stop,
.show = c_show
};
다음은 'cat /proc/cpuinfo' 명령어를 입력했을 때 CPU 아키텍처를 출력하는 c_show() 함수입니다.
01 static int c_show(struct seq_file *m, void *v)
02 {
03 int i, j;
04 bool compat = personality(current->personality) == PER_LINUX32;
05
06 for_each_online_cpu(i) {
07 struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, i);
08 u32 midr = cpuinfo->reg_midr;
09
10 seq_printf(m, "processor\t: %d\n", i);
11 if (compat)
12 seq_printf(m, "model name\t: ARMv8 Processor rev %d (%s)\n",
13 MIDR_REVISION(midr), COMPAT_ELF_PLATFORM);
14
15 seq_printf(m, "BogoMIPS\t: %lu.%02lu\n",
16 loops_per_jiffy / (500000UL/HZ),
17 loops_per_jiffy / (5000UL/HZ) % 100);
위 함수 코드와 같이 percpu 타입인 cpu_data 변수로 CPU 정보를 출력한다는 사실을 알 수 있습니다.
cpu_data percpu 타입 전역 변수 디버깅해보기
cpu_data를 크래시 유틸리티(Crash-Utility) 프로그램으로 확인하겠습니다.
crash64_kaslr> p cpu_data
PER-CPU DATA TYPE:
struct cpuinfo_arm64 cpu_data;
PER-CPU ADDRESSES:
[0]: ffffffff3f530290
[1]: ffffffff3f549290
[2]: ffffffff3f562290
[3]: ffffffff3f57b290
[4]: ffffffff3f594290
[5]: ffffffff3f5ad290
[6]: ffffffff3f5c6290
[7]: ffffffff3f5df290
출력 결과가 보이듯 percpu 타입 변수란 사실을 알 수 있습니다.
어떤 전역 변수가 percpu 타입 변수면 다음과 같이 떠올릴 필요가 있습니다.
CPU 갯수만큼 주소 공간이 있다.
cpu_data 변수에서 per-cpu0 정보를 확인하면 다음과 같습니다.
crash64_kaslr> struct cpuinfo_arm64 ffffffff3f530290
struct cpuinfo_arm64 {
cpu = {
node_id = 0x0,
hotpluggable = 0x1,
dev = {
parent = 0x0,
p = 0xffffffff364d3380,
kobj = {
name = 0xffffffff36573780 "cpu0",
entry = {
next = 0xffffffff3f5492b0,
prev = 0xffffffff364def68
},
parent = 0xfffffffebfec1890,
kset = 0xfffffffebfeb7280,
ktype = 0xffffff9689a84170,
sd = 0xffffffff3654cae8,
kref = {
refcount = {
refs = {
counter = 0x8
}
}
},
state_initialized = 0x1,
state_in_sysfs = 0x1,
state_add_uevent_sent = 0x1,
state_remove_uevent_sent = 0x0,
uevent_suppress = 0x0
},
init_name = 0x0,
type = 0x0,
mutex = {
owner = {
counter = 0x0
},
wait_lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
osq = {
tail = {
counter = 0x0
}
},
wait_list = {
next = 0xffffffff3f530308,
prev = 0xffffffff3f530308
}
},
bus = 0xffffff9689a84900,
driver = 0x0,
platform_data = 0x0,
driver_data = 0x0,
links = {
suppliers = {
next = 0xffffffff3f530338,
prev = 0xffffffff3f530338
},
consumers = {
next = 0xffffffff3f530348,
prev = 0xffffffff3f530348
},
status = DL_DEV_NO_DRIVER
},
power = {
power_state = {
event = 0x0
},
can_wakeup = 0x0,
async_suspend = 0x0,
in_dpm_list = 0x1,
is_prepared = 0x0,
is_suspended = 0x0,
is_noirq_suspended = 0x0,
is_late_suspended = 0x0,
early_init = 0x1,
direct_complete = 0x0,
driver_flags = 0x0,
lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
entry = {
next = 0xffffffff3f549370,
prev = 0xffffffff364df028
},
completion = {
done = 0xffffffff,
wait = {
lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
head = {
next = 0xffffffff3f530390,
prev = 0xffffffff3f530390
}
}
},
wakeup = 0x0,
wakeup_path = 0x0,
syscore = 0x0,
no_pm_callbacks = 0x1,
must_resume = 0x0,
may_skip_resume = 0x0,
suspend_timer = {
entry = {
next = 0x0,
pprev = 0x0
},
expires = 0x0,
function = 0xffffff9687fe2bd8,
flags = 0x7
},
timer_expires = 0x0,
work = {
data = {
counter = 0xfffffffe0
},
entry = {
next = 0xffffffff3f5303e8,
prev = 0xffffffff3f5303e8
},
func = 0xffffff9687fe2b30
},
wait_queue = {
lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
head = {
next = 0xffffffff3f530408,
prev = 0xffffffff3f530408
}
},
wakeirq = 0x0,
usage_count = {
counter = 0x0
},
child_count = {
counter = 0x0
},
disable_depth = 0x1,
idle_notification = 0x0,
request_pending = 0x0,
deferred_resume = 0x0,
runtime_auto = 0x1,
ignore_children = 0x0,
no_callbacks = 0x0,
irq_safe = 0x0,
use_autosuspend = 0x0,
timer_autosuspends = 0x0,
memalloc_noio = 0x0,
links_count = 0x0,
request = RPM_REQ_NONE,
runtime_status = RPM_SUSPENDED,
runtime_error = 0x0,
autosuspend_delay = 0x0,
last_busy = 0x0,
active_jiffies = 0x0,
suspended_jiffies = 0x0,
accounting_timestamp = 0xffff8bba,
subsys_data = 0x0,
set_latency_tolerance = 0x0,
qos = 0xffffffff364d2480
},
pm_domain = 0x0,
msi_domain = 0x0,
pins = 0x0,
msi_list = {
next = 0xffffffff3f530490,
prev = 0xffffffff3f530490
},
dma_ops = 0x0,
dma_mask = 0x0,
coherent_dma_mask = 0x0,
bus_dma_mask = 0x0,
dma_pfn_offset = 0x0,
dma_parms = 0x0,
dma_pools = {
next = 0xffffffff3f5304d0,
prev = 0xffffffff3f5304d0
},
dma_mem = 0x0,
cma_area = 0x0,
removed_mem = 0x0,
archdata = {
iommu = 0x0,
dma_coherent = 0x0,
mapping = 0x0
},
of_node = 0xffffffff3f604ea8,
fwnode = 0x0,
devt = 0x0,
id = 0x0,
devres_lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
devres_head = {
next = 0xffffffff3f530530,
prev = 0xffffffff3f530530
},
knode_class = {
n_klist = 0x0,
n_node = {
next = 0x0,
prev = 0x0
},
n_ref = {
refcount = {
refs = {
counter = 0x0
}
}
}
},
class = 0x0,
groups = 0xffffff9689a849c0,
release = 0xffffff9687fd6778,
iommu_group = 0x0,
iommu_fwspec = 0x0,
offline_disabled = 0x0,
offline = 0x0,
of_node_reused = 0x0
}
},
kobj = {
name = 0xffffff96890b9755 "regs",
entry = {
next = 0xffffffff3f530598,
prev = 0xffffffff3f530598
},
parent = 0xffffffff3f5302a8,
kset = 0x0,
ktype = 0xffffff96899cdfa8,
sd = 0xffffffff2e1a9b38,
kref = {
refcount = {
refs = {
counter = 0x1
}
}
},
state_initialized = 0x1,
state_in_sysfs = 0x1,
state_add_uevent_sent = 0x0,
state_remove_uevent_sent = 0x0,
uevent_suppress = 0x0
},
reg_ctr = 0x84448004,
reg_cntfrq = 0x124f800,
reg_dczid = 0x4,
reg_midr = 0x51df805e,
reg_revidr = 0xf,
reg_id_aa64dfr0 = 0x10305408,
reg_id_aa64dfr1 = 0x0,
reg_id_aa64isar0 = 0x100010211120,
reg_id_aa64isar1 = 0x100001,
reg_id_aa64mmfr0 = 0x101122,
reg_id_aa64mmfr1 = 0x10212122,
reg_id_aa64mmfr2 = 0x1011,
reg_id_aa64pfr0 = 0x11112222,
reg_id_aa64pfr1 = 0x0,
reg_id_aa64zfr0 = 0x0,
reg_id_dfr0 = 0x4010088,
reg_id_isar0 = 0x2101110,
reg_id_isar1 = 0x13112111,
reg_id_isar2 = 0x21232042,
reg_id_isar3 = 0x1112131,
reg_id_isar4 = 0x11142,
reg_id_isar5 = 0x1011121,
reg_id_mmfr0 = 0x10201105,
reg_id_mmfr1 = 0x40000000,
reg_id_mmfr2 = 0x1260000,
reg_id_mmfr3 = 0x2122211,
reg_id_pfr0 = 0x10000131,
reg_id_pfr1 = 0x10011011,
reg_mvfr0 = 0x10110222,
reg_mvfr1 = 0x13211111,
reg_mvfr2 = 0x43,
reg_zcr = 0x0
}
struct cpuinfo_arm64 구조체 필드 출력 결과를 모두 표시했습니다. 나중에 디버깅을 할 때 필요할지 모르기 때문입니다.
눈치가 빠른 분은 위 출력 결과로 Aarch64 아키텍처이란 사실을 유추할 수 있을 것입니다.
"주소 정보를 16바이트 단위로 출력한다."
이번에 레퍼런스를 위해 ARMv7 아키텍처에서 cpu_data percpu 변수를 확인하겠습니다.
crash> p cpu_data
PER-CPU DATA TYPE:
struct cpuinfo_arm cpu_data;
PER-CPU ADDRESSES:
[0]: e6b16048
[1]: e6b29048
[2]: e6b3c048
[3]: e6b4f048
[4]: e6b62048
[5]: e6b75048
[6]: e6b88048
[7]: e6b9b048
crash> struct cpuinfo_arm e6b16048
struct cpuinfo_arm {
cpu = {
node_id = 0x0,
hotpluggable = 0x0,
dev = {
parent = 0x0,
p = 0xe57a6200,
kobj = {
name = 0xe57a5680 "cpu0",
entry = {
next = 0xe6b2905c,
prev = 0xe57a720c
},
parent = 0xe5e02e08,
kset = 0xe5df0c80,
ktype = 0xc17522ec <device_ktype>,
sd = 0xe57a83c0,
kref = {
refcount = {
counter = 0x3
}
},
state_initialized = 0x1,
state_in_sysfs = 0x1,
state_add_uevent_sent = 0x1,
state_remove_uevent_sent = 0x0,
uevent_suppress = 0x0
},
init_name = 0x0,
type = 0x0,
mutex = {
count = {
counter = 0x1
},
wait_lock = {
{
rlock = {
raw_lock = {
{
slock = 0x20002,
tickets = {
owner = 0x2,
next = 0x2
}
}
},
magic = 0xdead4ead,
owner_cpu = 0xffffffff,
owner = 0xffffffff
}
}
},
wait_list = {
next = 0xe6b16098,
prev = 0xe6b16098
},
owner = 0x0,
magic = 0xe6b16084
},
bus = 0xc17526fc <cpu_subsys>,
driver = 0x0,
platform_data = 0x0,
driver_data = 0x0,
power = {
power_state = {
event = 0x0
},
can_wakeup = 0x0,
async_suspend = 0x0,
is_prepared = 0x0,
is_suspended = 0x0,
is_noirq_suspended = 0x0,
is_late_suspended = 0x0,
early_init = 0x1,
direct_complete = 0x0,
lock = {
{
rlock = {
raw_lock = {
{
slock = 0x20002,
tickets = {
owner = 0x2,
next = 0x2
}
}
},
magic = 0xdead4ead,
owner_cpu = 0xffffffff,
owner = 0xffffffff
}
}
},
entry = {
next = 0xe6b290d0,
prev = 0xe57a7280
},
completion = {
done = 0x7fffffff,
wait = {
lock = {
{
rlock = {
raw_lock = {
{
slock = 0x10001,
tickets = {
owner = 0x1,
next = 0x1
}
}
},
magic = 0xdead4ead,
owner_cpu = 0xffffffff,
owner = 0xffffffff
}
}
},
task_list = {
next = 0xe6b160ec,
prev = 0xe6b160ec
}
}
},
wakeup = 0x0,
wakeup_path = 0x0,
syscore = 0x0,
no_pm_callbacks = 0x1,
suspend_timer = {
entry = {
next = 0x0,
pprev = 0x0
},
expires = 0x0,
function = 0xc055b8a0 <pm_suspend_timer_fn>,
data = 0xe6b16050,
flags = 0x4
},
timer_expires = 0x0,
work = {
data = {
counter = 0xffffffe0
},
entry = {
next = 0xe6b1611c,
prev = 0xe6b1611c
},
func = 0xc055b914 <pm_runtime_work>
},
wait_queue = {
lock = {
{
rlock = {
raw_lock = {
{
slock = 0x0,
tickets = {
owner = 0x0,
next = 0x0
}
}
},
magic = 0xdead4ead,
owner_cpu = 0xffffffff,
owner = 0xffffffff
}
}
},
task_list = {
next = 0xe6b16138,
prev = 0xe6b16138
}
},
wakeirq = 0x0,
usage_count = {
counter = 0x0
},
child_count = {
counter = 0x0
},
disable_depth = 0x1,
idle_notification = 0x0,
request_pending = 0x0,
deferred_resume = 0x0,
run_wake = 0x0,
runtime_auto = 0x1,
ignore_children = 0x0,
no_callbacks = 0x0,
irq_safe = 0x0,
use_autosuspend = 0x0,
timer_autosuspends = 0x0,
memalloc_noio = 0x0,
request = RPM_REQ_NONE,
runtime_status = RPM_SUSPENDED,
runtime_error = 0x0,
autosuspend_delay = 0x0,
last_busy = 0x0,
active_jiffies = 0x0,
suspended_jiffies = 0x0,
accounting_timestamp = 0xfffea0e4,
subsys_data = 0x0,
set_latency_tolerance = 0x0,
qos = 0x0
},
pm_domain = 0x0,
pins = 0x0,
dma_mask = 0x0,
coherent_dma_mask = 0x0,
dma_pfn_offset = 0x0,
dma_parms = 0x0,
dma_pools = {
next = 0xe6b16198,
prev = 0xe6b16198
},
dma_mem = 0x0,
archdata = {
dma_ops = 0x0,
dma_coherent = 0x0
},
of_node = 0xe6bb34e4,
fwnode = 0x0,
devt = 0x0,
id = 0x0,
devres_lock = {
{
rlock = {
raw_lock = {
{
slock = 0x0,
tickets = {
owner = 0x0,
next = 0x0
}
}
},
magic = 0xdead4ead,
owner_cpu = 0xffffffff,
owner = 0xffffffff
}
}
},
devres_head = {
next = 0xe6b161cc,
prev = 0xe6b161cc
},
knode_class = {
n_klist = 0x0,
n_node = {
next = 0x0,
prev = 0x0
},
n_ref = {
refcount = {
counter = 0x0
}
}
},
class = 0x0,
groups = 0xc1f0b0e8 <common_cpu_attr_groups>,
release = 0xc0552c08 <cpu_device_release>,
iommu_group = 0x0,
iommu_fwspec = 0x0,
offline_disabled = 0x1,
offline = 0x0
}
},
cpuid = 0x410fd034,
loops_per_jiffy = 0xd177
}
c_show() 함수 콜스택 확인하기
함수 분석이 끝나면 콜스택을 확인할 필요가 있습니다. 함수 구현부만 보면 시야가 좁아 질 수 있기 때문입니다.
c_show() 함수 콭스택은 다음과 같습니다.
<...>-16076 [002] ...1 4396.616120: seq_printf+0x44/0xf0 <-c_show+0x54/0x418
<...>-16076 [002] ...1 4396.616123: <stack trace>
=> ftrace_graph_call+0x0/0xc
=> seq_printf+0x48/0xf0
=> c_show+0x54/0x418
=> seq_read+0x198/0x478
=> proc_reg_read+0x8c/0xe8
=> __vfs_read+0x54/0x158
=> vfs_read+0xa4/0x140
=> __arm64_sys_read+0x5c/0xc0
=> el0_svc_common+0xa0/0x110
=> el0_svc_handler+0x7c/0x98
=> el0_svc+0x8/0xc
유저 공간에서 read 시스템 콜을 실행해 proc_reg_read() 함수를 통해 c_show() 함수가 호출됩니다.
정리
이번 시간에 코드를 분석한 내용을 정리해보겠습니다.
" 첫째, /proc/cpuinfo 파일은 어느 함수에서 생성할까?"
'proc/cpuinfo' 파일은 proc_cpuinfo_init() 함수에서 03번째 줄을 실행할 때 생성됩니다.
01 static int __init proc_cpuinfo_init(void)
02 {
03 proc_create("cpuinfo", 0, NULL, &proc_cpuinfo_operations);
04 return 0;
05 }
06 fs_initcall(proc_cpuinfo_init);
" 둘째, /proc/cpuinfo 파일을 읽을 때 어느 함수가 호출될까?"
arch/arm64/kernel/cpuinfo.c 파일 c_show() 함수가 호출됩니다. c_show() 함수 위치는 CPU 아키텍처마다 다릅니다.
" 셋째, /proc/cpuinfo 파일에서 출력하는 자료구조는 무엇인가?"
percpu 타입 변수 cpu_data입니다.
"혹시 궁금하신 점이 있으면 댓글 달아 주세요. 아는 한 성실히 답변 올려드리겠습니다."