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 : 64proc 파일 시스템은 커널 내부의 정보를 파일 형태로 노출시켜줍니다. 즉, 사용자 영역에서 간단히 cat 명령을 실생하는 것으로 커널이 알고 있는 정보를 볼 수 있습니다. 그 중에 하나가 커널이 부팅되면서 탐색한 CPU 정보입니다. proc 파일 시스템의 구현은 커널 소스에서 fs/proc 디렉터리에 있습니다. /proc 디텍러리에 등록되는 다양한 파일들은 모두 fs/proc/proc_misc.c 파일에 구현되어 있습니다.
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()를 사용합니다.
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의 정의가 무엇인지 찾아보겠습니다.
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() 함수가 실제로 시스템 정보를 보여주는 것을 알 수 있습니다.
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 #endif83 ~ 92 라인에서는 CPU 제조사, CPU 종류, 모델 번호, 모델 이름을 출력합니다. create_seq_entry()로 만든 항목은 출력을 위해 seq_printf() 함수를 사용합니다. 사용법은 printf(), printk()와 동일합니다.
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); 159loops_per_jiffy는 1 지피동안 빈 루프를 반복한 횟수를 의미합니다. 이를 보고밉스라고 부릅니다. 여기서는 CPU의 보고 밉스를 사용하기 위해 loops_per_jiffy 값을 사용하는 것을 볼 수 있습니다.
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 에서 확인할 수 있습니다.
CPU0: Intel(R) Core(TM)2 CPU T7200 @ 2.00GHz stepping 08시스템에 있는 각 CPU를 초기화하는 작업은 _cpu_init() 함수에서 시작합니다. 이 역시 /var/log/dmesg에서 확인할 수 있습니다.
이전 글 : 고급 자바스크립트 활용 II
다음 글 : 데비안에서 User Mode Linux 사용하기
최신 콘텐츠