메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

커널에서 CPU 정보 처리하기

한빛미디어

|

2007-09-18

|

by HANBIT

19,365

제공 : 한빛 네트워크
저자 : 한동훈

cat /proc/cpuinfo 명령을 사용하면 CPU 정보를 볼 수 있습니다.
curl:/proc# cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 15
model name      : Intel(R) Core(TM)2 CPU         T7200  @ 2.00GHz
stepping        : 8
cpu MHz         : 1993.769
cache size      : 4096 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 
                  clflush dts acpi mmx fxsr sse sse2 ss nx constant_tsc up pni ds_cpl ssse3
bogomips        : 4020.51
clflush size    : 64
proc 파일 시스템은 커널 내부의 정보를 파일 형태로 노출시켜줍니다. 즉, 사용자 영역에서 간단히 cat 명령을 실생하는 것으로 커널이 알고 있는 정보를 볼 수 있습니다. 그 중에 하나가 커널이 부팅되면서 탐색한 CPU 정보입니다. proc 파일 시스템의 구현은 커널 소스에서 fs/proc 디렉터리에 있습니다. /proc 디텍러리에 등록되는 다양한 파일들은 모두 fs/proc/proc_misc.c 파일에 구현되어 있습니다.

proc_misc.c 파일을 보면 proc_misc_init()이 초기화루틴입니다. 이 루틴에서 create_seq_entry() 함수를 사용해서 /proc/cpuinfo, /proc/slabinfo 같은 파일들을 차례대로 등록하는 것을 볼 수 있습니다. 다음은 커널 2.6.20의 proc_misc.c 소스 pre의 일부이지만 이전 버전의 커널 소스 pre와 동일할 겁니다.
707   create_seq_entry("devices", 0, &proc_devinfo_operations);
708   create_seq_entry("cpuinfo", 0, &proc_cpuinfo_operations);
709 #ifdef CONFIG_BLOCK
710   create_seq_entry("partitions", 0, &proc_partitions_operations);
711 #endif
712   create_seq_entry("stat", 0, &proc_stat_operations);
713   create_seq_entry("interrupts", 0, &proc_interrupts_operations);
714 #ifdef CONFIG_SLAB
715   create_seq_entry("slabinfo",S_IWUSR|S_IRUGO,&proc_slabinfo_operations);
716 #ifdef CONFIG_DEBUG_SLAB_LEAK
717   create_seq_entry("slab_allocators", 0 ,&proc_slabstats_operations);
718 #endif
719 #endif
720   create_seq_entry("buddyinfo",S_IRUGO, &fragmentation_file_operations);
721   create_seq_entry("vmstat",S_IRUGO, &proc_vmstat_file_operations);
722   create_seq_entry("zoneinfo",S_IRUGO, &proc_zoneinfo_file_operations);
proc 파일 시스템에 어떤 항목을 만들기 위해 create_proc_entry() 함수를 사용해왔습니다. proc 파일 시스템도 결국은 파일이고, 데이터를 어딘가에 저장하는 공간이 필요합니다. 가상의 파일 시스템이므로 데이터는 메모리에 저장했고, 이는 한 페이지(4k)의 크기를 넘지 못하는 제한이 있었습니다. 그래서, seq_file 인터페이스가 커널에 추가되었습니다. create_seq_entry() 함수로 항목을 만들게 되면 한 페이지가 넘어가는 큰 데이터도 사용자 영역에 전달할 수 있습니다. 때문에, seq_file 인터페이스를 지원하지 않던 시절의 커널은 create_proc_entry()를, 지원하는 커널은 create_seq_entry()를 사용합니다.

708 라인의 소스 pre를 보면 cpuinfo 항목을 등록하고 있으며, 읽기를 할때의 동작은 proc_cpuinfo_operations 변수에 정의된 것을 볼 수 있습니다.
269 static struct file_operations proc_cpuinfo_operations = {
270   .open   = cpuinfo_open,
271   .read   = seq_read,
272   .llseek   = seq_lseek,
273   .release  = seq_release,
274 };
이 구조체는 file_operations 구조체로 되어 있으며, open, read, llseek, release만 정의하고 있습니다. read, llseek, release 연산에 대해서는 커널에서 제공하는 공통 루틴인 seq_read(), seq_lseek(), seq_release() 함수가 정의된 것을 알 수 있습니다. 이들 함수의 실제 구현은 fs/seq_file.c에 정의되어 있습니다. 지금은 seq_file 인터페이스에 관심이 없으니 이 부분은 생략하겠습니다. read 연산에 대해서만 cpuinfo_open() 함수가 정의되어 있습니다. 즉, cat /proc/cpuinfo 명령을 실행했을 때 관심있는 뭔가를 볼 수 있음을 의미합니다. 여기서 등록한 cpuinfo_open() 함수는 fs/proc/proc_misc.c에 정의되어 있습니다.
263 extern struct seq_operations cpuinfo_op;
264 static int cpuinfo_open(struct inode *inode, struct file *file)
265 {
266   return seq_open(file, &cpuinfo_op);
267 }
cpuinfo_open()도 커널에 정의된 공통 루틴인 seq_open()을 호출하지만 실제 정보 출력을 위해 어떤 함수를 호출해야하는지가 정의된 cpuinfo_op 변수를 넘겨주는 것을 알 수 있습니다. cpuinfo_op 변수는 seq_operations 구조체로 정의되어 있습니다. 이 구조체는 file_operations 구조체와 동작이 유사하며, start, stop, next 연산 등을 정의하고 있습니다. start 연산은 처리의 시작을 나타내며, stop은 처리가 끝났을 때 실행되는 루틴입니다. create_proc_entry()를 사용했을 때는 eof=1이라고 설정했던 것과 비슷한 역할입니다. next 연산은 데이터가 한 페이지(4k)를 넘어갔을 때의 처리를 다루고 있습니다. 여기서는 seq_file 인터페이스에는 관심이 없으니 바로 cpuinfo_op의 정의가 무엇인지 찾아보겠습니다.

cpuinfo_op 변수는 각 CPU 아키텍처별로 정의되어 있습니다. 여기서는 arch/i386/kernel/cpu/proc.c 파일을 살펴보겠습니다. 그러니까, proc 항목에 있는 시스템 정보들은 특정 CPU 아키텍처에 종속적인 것들이 있습니다. 하지만, 그 위에 추상적인 계층은 fs/proc에 정의하고, 실제 CPU 아키텍처에 종속적인 부분은 arch//kernel/cpu/proc.c 파일에 정의하고 있는 겁니다.
175 struct seq_operations cpuinfo_op = {
176   .start  = c_start,
177   .next = c_next,
178   .stop = c_stop,
179   .show = show_cpuinfo,
180 };
여기서 보면 show 연산에 등록된 show_cpuinfo() 함수가 실제로 시스템 정보를 보여주는 것을 알 수 있습니다.

show_cpuinfo() 함수의 앞 부분은 CPU 플래그와 관련된 지루한 pre의 반복이므로 조금 흥미있는 부분으로 건너뛰겠습니다. 소스 pre는 arch/i386/kernel/cpu/proc.c 입니다.
 75   struct cpuinfo_x86 *c = v;
 76   int i, n = c - cpu_data;
 77   int fpu_exception;
 78
 79 #ifdef CONFIG_SMP
 80   if (!cpu_online(n))
 81     return 0;
 82 #endif
 83   seq_printf(m, "processort: %dn"
 84     "vendor_idt: %sn"
 85     "cpu familyt: %dn"
 86     "modeltt: %dn"
 87     "model namet: %sn",
 88     n,
 89     c->x86_vendor_id[0] ? c->x86_vendor_id : "unknown",
 90     c->x86,
 91     c->x86_model,
 92     c->x86_model_id[0] ? c->x86_model_id : "unknown");
 93
 94   if (c->x86_mask || c->cpuid_level >= 0)
 95     seq_printf(m, "steppingt: %dn", c->x86_mask);
 96   else
 97     seq_printf(m, "steppingt: unknownn");
 98
 99   if ( cpu_has(c, X86_FEATURE_TSC) ) {
100     unsigned int freq = cpufreq_quick_get(n);
101     if (!freq)
102       freq = cpu_khz;
103     seq_printf(m, "cpu MHztt: %u.%03un",
104       freq / 1000, (freq % 1000));
105   }
CPU에 대한 정보는 cpuinfo_x86 구조체로 정의되어 있습니다. 76 라인의 cpu_data는 전역 변수입니다. CPU 정보를 나타내는 전역 심볼이 몇 가지 있습니다. 부팅시의 CPU 정보를 담고 있는 boot_cpu_data가 있으며, SMP 환경에서 모든 CPU 정보를 배열형태로 관리하는 cpu_data가 있고, SMP 환경에서 현재 실행중인 CPU의 정보를 얻기 위한 current_cpu_data가 있습니다. current_cpu_data는 실제로 다음과 같은 매크로로 되어 있습니다.
#define current_cpu_data cpu_data[smp_processor_id()]
smp_processor_id()는 현재 실행중인 CPU 번호를 반환합니다. 첫번째 CPU는 0, 두번째 CPU는 1.. 과 같은 형태로 값을 반환합니다. 실제로 커널이 SMP 지원을 하느냐, 안하느냐에 따라 다음과 같이 재정의하고 있습니다.(include/asm-i386/processor.h)
105 #ifdef CONFIG_SMP
106 extern struct cpuinfo_x86 cpu_data[];
107 #define current_cpu_data cpu_data[smp_processor_id()]
108 #else
109 #define cpu_data (&boot_cpu_data)
110 #define current_cpu_data boot_cpu_data
111 #endif
83 ~ 92 라인에서는 CPU 제조사, CPU 종류, 모델 번호, 모델 이름을 출력합니다. create_seq_entry()로 만든 항목은 출력을 위해 seq_printf() 함수를 사용합니다. 사용법은 printf(), printk()와 동일합니다.

94 ~ 98 라인에서는 CPU의 스테핑 정보를 출력합니다. 스테핑 번호는 동일한 CPU지만 CPU 공정상에 변화나 버그를 잡았거나 하는 사소한 변화가 있을 때 추가되는 번호입니다. 그래서, 같은 모델의 CPU를 구매했는데도 스테핑이 다른 경우가 있습니다.

99 ~ 105 라인에서는 CPU의 속도를 Mhz 단위로 출력하는 부분입니다. TSC 레지스터가 있는 CPU라면 100 라인에서 cpufreq_quick_get() 함수를 호출해서 CPU 주파수를 얻습니다. TSC 레지스터가 없는 CPU라면 101 ~ 102 라인에서 부팅과정에서 설정된 전역변수 cpu_khz 값을 freq 변수에 할당합니다. 103 ~ 104에서는 khz 단위로 얻어진 값을 갖고 있는 freq 변수를 1000으로 나눠서 Mhz 단위를 출력합니다. 두번째의 freq % 1000은 Mhz 단위로 표시할 때 소수점 부분을 표시하기 위한 것입니다.

다음의 107 ~ 109 라인은 CPU의 캐시 크기를 출력하는 부분입니다. 110 ~ 117 라인은 하이퍼 스레딩을 지원하는 커널인 경우에 이와 관련된 정보를 출력합니다.
107   /* Cache size */
108   if (c->x86_cache_size >= 0)
109     seq_printf(m, "cache sizet: %d KBn", c->x86_cache_size);
110 #ifdef CONFIG_X86_HT
111   if (c->x86_max_cores * smp_num_siblings > 1) {
112     seq_printf(m, "physical idt: %dn", c->phys_proc_id);
113     seq_printf(m, "siblingst: %dn", cpus_weight(cpu_core_map[n]));
114     seq_printf(m, "core idtt: %dn", c->cpu_core_id);
115     seq_printf(m, "cpu corest: %dn", c->booted_cores);
116   }
117 #endif
이 함수의 마지막에는 다음을 볼 수 있습니다.
155   seq_printf(m, "nbogomipst: %lu.%02lun",
156          c->loops_per_jiffy/(500000/HZ),
157          (c->loops_per_jiffy/(5000/HZ)) % 100);
158   seq_printf(m, "clflush sizet: %unn", c->x86_clflush_size);
159
loops_per_jiffy는 1 지피동안 빈 루프를 반복한 횟수를 의미합니다. 이를 보고밉스라고 부릅니다. 여기서는 CPU의 보고 밉스를 사용하기 위해 loops_per_jiffy 값을 사용하는 것을 볼 수 있습니다.

CPU 정보를 담기 위해 cpuinfo_x86 구조체가 사용되었습니다. 이 구조체의 정의는 include/asm/i386/processor.h 헤더 파일에 정의되어 있으며, 다음과 같습니다.
 49 struct cpuinfo_x86 {
 50   __u8  x86;    /* CPU family */
 51   __u8  x86_vendor; /* CPU vendor */
 52   __u8  x86_model;
 53   __u8  x86_mask;
 54   char  wp_works_ok;  /* It doesn"t on 386"s */
 55   char  hlt_works_ok; /* Problems on some 486Dx4"s and old 386"s */
 56   char  hard_math;
 57   char  rfu;
 58         int cpuid_level;  /* Maximum supported CPUID level, -1=no CPUID */
 59   unsigned long x86_capability[NCAPINTS];
 60   char  x86_vendor_id[16];
 61   char  x86_model_id[64];
 62   int   x86_cache_size;  /* in KB - valid for CPUS which support this
 63             call  */
 64   int   x86_cache_alignment;  /* In bytes */
 65   char  fdiv_bug;
 66   char  f00f_bug;
 67   char  coma_bug;
 68   char  pad0;
 69   int x86_power;
 70   unsigned long loops_per_jiffy;
 71 #ifdef CONFIG_SMP
 72   cpumask_t llc_shared_map; /* cpus sharing the last level cache */
 73 #endif
 74   unsigned char x86_max_cores;  /* cpuid returned max cores value */
 75   unsigned char apicid;
 76   unsigned short x86_clflush_size;
 77 #ifdef CONFIG_SMP
 78   unsigned char booted_cores; /* number of cores as seen by OS */
 79   __u8 phys_proc_id;    /* Physical processor id. */
 80   __u8 cpu_core_id;     /* Core id */
 81 #endif
 82 } __attribute__((__aligned__(SMP_CACHE_BYTES)));
이들 전역 변수의 정의는 arch/i386/kernel/setup.c에서 찾아볼 수 있습니다.
 
 79 /* cpu data as detected by the assembly code in head.S */
 80 struct cpuinfo_x86 new_cpu_data __cpuinitdata = { 0, 0, 0, 0, -1, 1, 0, 0, -1 };
 81 /* common cpu data for all cpus */
 82 struct cpuinfo_x86 boot_cpu_data __read_mostly = { 0, 0, 0, 0, -1, 1, 0, 0, -1 };
 83 EXPORT_SYMBOL(boot_cpu_data);
실제, 부팅 과정이 진행되면서 CPU에 대한 정보를 얻어올 겁니다. 주석에도 있는 것처럼 이는 head.S에 있을 것입니다. 멀티 CPU라고 해도 처음 전원을 넣고, 부팅을 하는 동안에는 첫번째 CPU만 사용됩니다. 하나만 사용됩니다. 그리고, 커널이 부팅되는 동안에 멀티 CPU를 사용할 수 있게 설정하며, 각 CPU 별로 초기화를 수행합니다. 이와 관련된 루틴은 arch/i386/kernel/cpu/common.c 에서 확인할 수 있습니다.

common.c의 cpu_detect() 함수는 제조사 이름, 모델명 등을 발견해냅니다. identify_cpu() 함수는 일반적인 CPU 정보들을 찾아내며, generic_identify() 함수를 호출해서 공통된 정보들을 발견합니다. detect_ht() 함수는 이 CPU가 하이퍼 스레딩을 지원하는지도 알아냅니다. cpu_init()에서는 각 CPU 별로 초기화하는 루틴을 호출해서 적절한 CPU를 찾아냅니다. print_cpu_info() 함수는 커널 부팅 과정동안 익숙하게 보아온 CPU 정보를 출력합니다. /var/log/dmesg나 dmesg 명령을 실행하면 다음과 같은 CPU 정보가 출력되는데, 이는 print_cpu_info() 함수가 호출된 것입니다.
CPU0: Intel(R) Core(TM)2 CPU         T7200  @ 2.00GHz stepping 08
시스템에 있는 각 CPU를 초기화하는 작업은 _cpu_init() 함수에서 시작합니다. 이 역시 /var/log/dmesg에서 확인할 수 있습니다.

/proc/cpuinfo를 통해서 간단하게 커널에서 CPU 정보를 알아내는 것에 대해 살펴봤습니다. /proc/buddyinfo를 통해 버디 시스템의 할당 정보도 알아볼 수 있을 겁니다. proc 파일 시스템은 관심있는 정보를 어디서부터 탐색하면 좋을지 알려주는 좋은 출발점입니다. 만약, proc에서 자신이 관심있는 정보를 제공하고 있다면 이와 관련된 커널 소스부터 탐색하는 것이 가장 좋은 출발점입니다.
TAG :
댓글 입력
자료실