CPU 토폴로지 (CPU Topology)
CPU 토폴로지를 커널 스케줄러(Scheduler) 관점에서 심층적으로 다룹니다. AMD Chiplet(CCD/CCX), Intel Hybrid(P-core/E-core), ARM DynamIQ/DSU 구조가 커널 topology 탐지와 sched_domain 계층에 반영되는 과정을 분석하고, 비대칭 용량 스케줄링과 NUMA 연계 배치, 관측 도구를 활용한 성능 진단 절차까지 종합적으로 설명합니다.
현대 프로세서는 단순한 "코어 수 × 스레드(Thread) 수" 이상의 복잡한 계층 구조를 가집니다. AMD의 칩렛(Chiplet), Intel의 타일(Tile)/하이브리드, ARM의 DynamIQ 등 각 아키텍처는 고유한 토폴로지를 형성하며, 리눅스 커널은 이를 정확히 인식하고 스케줄링·메모리 배치에 반영해야 최적 성능을 달성할 수 있습니다.
- 계층 구조 (Socket → Die → Core) — 회사(Socket) 안에 사업부(Die/CCD)가 있고, 그 안에 팀(Core)이 있습니다. 소프트웨어는 이 조직도를 읽어야 업무를 올바르게 배분할 수 있습니다.
- SMT (Hyper-Threading) — 한 팀원(코어)이 두 개의 업무 바구니(하드웨어 스레드)를 번갈아 처리합니다. 한쪽 업무가 자료 조회로 잠시 멈출 때, 다른 업무를 진행해 유휴 시간을 줄입니다.
- 캐시 공유 계층 — 같은 팀원끼리는 개인 자료함(L1/L2 캐시)을 공유하고, 같은 사업부 팀들은 공용 자료실(L3 캐시)을 나눠 씁니다. 다른 사업부 간에는 복도(인터커넥트)를 통해 자료를 주고받아 시간이 더 걸립니다.
- 스케줄링 도메인 — 업무 분배 담당자(커널 스케줄러)는 팀 내부에서 먼저 자리를 찾고, 없으면 사업부, 그다음 회사 전체 순으로 넓혀 갑니다. 가까울수록 협업 비용이 낮습니다.
- EAS / CPU 격리 — EAS는 "급한 업무는 빠른 직원(P-core·big 코어)에게, 백그라운드 업무는 효율적인 직원(E-core·LITTLE 코어)에게" 배치하는 관리자입니다. CPU 격리는 특정 직원을 핵심 프로젝트 전담으로 지정해 다른 업무가 끼어들지 못하게 합니다.
핵심 요약
- Socket → Die → Core → Thread — CPU의 물리적 계층 구조입니다.
- SMT(Hyper-Threading) — 하나의 코어가 두 개 이상의 논리 CPU로 동작합니다.
- CPUID — 프로세서가 자신의 토폴로지 정보를 소프트웨어에 알려주는 명령어입니다.
- sched_domain — 커널 스케줄러가 토폴로지를 인식하여 부하를 분산하는 계층 구조입니다.
- lscpu / sysfs —
lscpu명령어와/sys/devices/system/cpu/로 토폴로지를 확인합니다. - EAS (Energy-Aware Scheduling) — 이종 코어 시스템에서 성능과 전력의 균형을 맞추는 스케줄러 알고리즘입니다.
- CPU 격리 (isolcpus) — 특정 코어를 OS 스케줄러에서 분리하여 실시간(Real-time)/지연(Latency) 중요 태스크(Task) 전용으로 사용합니다.
- SMT 보안 — Spectre V2, L1TF 등 투기적 실행(Speculative Execution) 취약점(Vulnerability)은 SMT의 자원 공유 구조와 직접 연관됩니다.
단계별 이해
- 토폴로지 확인 —
lscpu를 실행하여 소켓(Socket) 수, 코어 수, 스레드 수를 확인합니다./sys/devices/system/cpu/cpu0/topology/에서 상세 정보를 볼 수 있습니다. - 계층 이해 — 같은 코어의 SMT 스레드는 L1 캐시를, 같은 다이의 코어는 L3 캐시를 공유합니다.
이 공유 관계가 스케줄링과 성능에 직접적인 영향을 줍니다.
- 스케줄링 도메인(Scheduling Domain) — 커널은 토폴로지를 기반으로 SMT → Core → LLC → NUMA 순의 스케줄링 도메인을 구성합니다.
부하 균형 시 가까운 도메인부터 먼 도메인 순으로 태스크를 이동합니다.
- 성능 최적화 —
taskset이나 cgroup의 cpuset으로 프로세스를 특정 코어에 바인딩할 수 있습니다.캐시 공유 관계를 고려한 바인딩이 성능 최적화의 핵심입니다.
- 전력 관리 — EAS는 에너지 모델(Energy Model)을 사용해 태스크를 가장 전력 효율적인 코어에 배치합니다.
/sys/kernel/debug/energy_model/로 EM 등록 상태를 확인할 수 있습니다. - 보안과 격리 — SMT 비활성화(
nosmt)나 CPU 격리(isolcpus)로 사이드채널 공격 위험을 줄입니다./sys/devices/system/cpu/vulnerabilities/에서 취약점 완화 상태를 확인합니다.
- 캐시 공유 경계 — 같은 CCX/LLC 안에 있는 코어끼리는 캐시 일관성(Cache Coherency) 비용이 낮습니다. 병렬 프로세스를 엉뚱한 코어에 배치하면 캐시 핑퐁(Cache Ping-Pong)으로 성능이 급격히 저하됩니다.
- 이종 코어 혼재 — Intel 12세대 이후 P-core·E-core 혼합, ARM big.LITTLE 구성에서는 코어마다 IPC와 전력이 다릅니다. 스케줄러가 토폴로지를 모르면 실시간 태스크가 느린 코어에 배정될 수 있습니다.
- NUMA 지연 — 멀티소켓 서버에서 다른 소켓의 메모리에 접근하면 로컬 접근보다 수십 나노초 더 걸립니다.
numactl이나taskset으로 NUMA 경계를 의식한 배치가 필수입니다.
AMD Chiplet 아키텍처
AMD는 Zen 아키텍처부터 칩렛(Chiplet) 설계를 도입하여, 하나의 패키지에 여러 개의 소형 다이를 조합하는 방식으로 프로세서를 구성합니다. 이 접근법은 제조 수율 향상, 유연한 코어 수 확장, 비용 효율성이라는 장점을 제공합니다.
CCX (Core CompleX)
CCX는 AMD 프로세서의 기본 컴퓨팅 단위로, 일정 수의 코어가 L3 캐시를 공유하는 클러스터입니다.
| 세대 | CCX 구성 | L3 캐시/CCX | 비고 |
|---|---|---|---|
| Zen1 / Zen+ | 4코어/CCX | 8MB | 2 CCX = 1 CCD |
| Zen2 | 4코어/CCX | 16MB | 2 CCX = 1 CCD, L3 2배 증가 |
| Zen3 | 8코어/CCX | 32MB | 1 CCX = 1 CCD, 통합 L3 |
| Zen4 | 8코어/CCX | 32MB | 5nm, AVX-512 |
| Zen5 | 8코어/CCX | 32MB | 듀얼 디코드 파이프라인 (8-wide 디코드/디스패치(Dispatch)), 4nm/3nm |
CCX 내부의 코어들은 L3 캐시를 공유하므로 inter-core 지연(Latency)이 매우 낮습니다. 커널의 스케줄링 도메인에서 MC(Multi-Core) 레벨에 해당합니다.
CCD (Core Chiplet Die)
CCD는 코어가 집적된 물리적 다이(die)입니다.
- Zen1/Zen2: 1 CCD = 2 CCX. 같은 CCD 내 두 CCX 간 통신은 Infinity Fabric을 통하지만, IOD를 경유하지 않아 비교적 빠릅니다.
- Zen3 이후: 1 CCD = 1 CCX. CCX-CCD 구분이 사라지고, 8코어 전체가 32MB L3를 공유합니다.
IOD (I/O Die)
IOD는 I/O 기능을 전담하는 별도의 다이입니다. Zen2부터 컴퓨팅 다이(CCD)와 I/O 다이(IOD)를 물리적으로 분리하여, 각각 최적의 공정을 적용합니다.
IOD가 담당하는 주요 기능:
- 메모리 컨트롤러(UMC): DDR4/DDR5 채널 관리. EPYC 9004 기준 12채널
- PCIe 컨트롤러: PCIe Gen4/Gen5 레인
- Infinity Fabric 스위치: CCD 간, 소켓 간 통신 라우팅(Routing)
- xGMI 링크: 멀티소켓 연결 (socket-to-socket)
- 보안 프로세서(PSP): AMD Platform Security Processor
/* 커널에서 AMD IOD 토폴로지 확인 (dmesg) */
// [ 0.123456] node 0 deferred pages: init 2097152
// AMD EPYC 9654 (Genoa): 12 UMC channels, 128 PCIe Gen5 lanes
/* arch/x86/kernel/cpu/amd.c - 토폴로지 파싱 */
static void amd_get_topology(struct cpuinfo_x86 *c)
{
int cpu = c->cpu_index;
/* CPUID Fn8000_001E: Node/Core/Thread 토폴로지 */
if (c->extended_cpuid_level >= 0x8000001e) {
u32 eax, ebx, ecx, edx;
cpuid(0x8000001e, &eax, &ebx, &ecx, &edx);
c->cpu_core_id = eax; /* Compute Unit ID */
c->cpu_die_id = ecx & 0xff; /* Node(CCD) ID */
c->threads_per_core = ((ebx >> 8) & 0xff) + 1;
}
}
Infinity Fabric
Infinity Fabric(IF)는 AMD의 온-다이/온-패키지/인터-소켓 인터커넥트입니다. Scalable Data Fabric(SDF)과 Scalable Control Fabric(SCF)으로 구성됩니다.
| 통신 경로 | 경유 | 대략적 지연 | 대역폭(Bandwidth) |
|---|---|---|---|
| intra-CCX (코어 간) | L3 직접 | ~10-15ns | L3 대역폭 |
| inter-CCX, same CCD (Zen1/2) | IF on-die | ~25-40ns | IF 대역폭 |
| inter-CCD (same socket) | IF → IOD → IF | ~50-80ns | IF 대역폭 |
| inter-socket | xGMI | ~120-180ns | xGMI 대역폭 |
NPS (Nodes Per Socket)
AMD EPYC 프로세서는 NPS(Nodes Per Socket) BIOS 설정으로 단일 소켓 내부를 여러 NUMA 노드로 분할합니다.
| 모드 | NUMA 노드/소켓 | CCD→메모리 매핑(Mapping) | 적합한 워크로드 |
|---|---|---|---|
| NPS1 | 1 | 모든 CCD → 전체 메모리 채널 | 범용, 대용량 메모리 풀 |
| NPS2 | 2 | CCD 절반씩 → 메모리 채널 절반 | VM 2개 분할 |
| NPS4 | 4 | CCD 1/4씩 → 메모리 채널 1/4 | HPC, 지연 최소화 |
# NPS 모드 확인 (NUMA 노드 수로 추정)
$ numactl --hardware
available: 4 nodes (0-3) # NPS4 모드
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 64169 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 64256 MB
...
node distances:
node 0 1 2 3
0: 10 12 12 12
1: 12 10 12 12
2: 12 12 10 12
3: 12 12 12 10
Zen 세대별 진화
| 세대 | 공정 | CCX/CCD | 최대 CCD | L3/CCX | IF 세대 | 메모리 |
|---|---|---|---|---|---|---|
| Zen1 | 14nm | 2 CCX(4c)/CCD | 4 (EPYC 7001) | 8MB | IF 1.0 | DDR4-2666 |
| Zen2 | 7nm CCD / 14nm IOD | 2 CCX(4c)/CCD | 8 (EPYC 7002) | 16MB | IF 2.0 | DDR4-3200 |
| Zen3 | 7nm | 1 CCX(8c)=CCD | 8 (EPYC 7003) | 32MB | IF 3.0 | DDR4-3200 |
| Zen4 | 5nm CCD / 6nm IOD | 1 CCX(8c)=CCD | 12 (EPYC 9004) | 32MB | IF 4.0 | DDR5-4800 |
| Zen5 | 4nm/3nm | 1 CCX(8c)=CCD | 16 (EPYC 9005) | 32MB | IF 5.0 | DDR5-6000 |
EPYC / Ryzen / Threadripper 비교
| 제품군 | 소켓 | 최대 CCD | 최대 코어 | 메모리 채널 | PCIe 레인 | NPS 지원 |
|---|---|---|---|---|---|---|
| EPYC 9004 (Genoa) | SP5 | 12 | 96 | 12×DDR5 | 128 Gen5 | NPS1/2/4 |
| EPYC 9005 (Turin) | SP5 | 16 | 128 (Zen5) / 192 (Zen5c Turin Dense) | 12×DDR5 | 128 Gen5 | NPS1/2/4 |
| Threadripper 7000/9000 | sTR5/sTR8 | 12 | 96 | 4~8×DDR5 | 48~128 Gen5 | NPS1 |
| Ryzen 9000 (Granite Ridge) | AM5 | 2 | 16 | 2×DDR5 | 28 Gen5 | N/A |
| Ryzen 9000 (Strix Point) | FP8 | 1 (모놀리식) | 12 | LPDDR5X | 내장 | N/A |
3D V-Cache (X3D) 아키텍처
AMD의 3D V-Cache는 TSV(Through-Silicon Via) 기반 3D 적층 기술로 CCD 위에 대용량 L3 캐시 다이(L3D)를 적층하여 캐시 용량을 대폭 확장합니다. 커널 토폴로지 관점에서 3D V-Cache는 CCD/CCX 구조를 변경하지 않으므로 die_id, core_siblings 등의 sysfs 속성은 일반 CCD와 동일합니다.
| 세대 | 제품 | 적층 구조 | L3 (온다이 + V-Cache) | 토폴로지 영향 |
|---|---|---|---|---|
| 1세대 | Ryzen 7 5800X3D (Zen3) | L3D가 CCD 위에 적층 (구조 실리콘으로 코어 보호) | 32MB + 64MB = 96MB | 동일 CCD — 코어 클럭 제한 (전압/클럭 고정) |
| 2세대 | Ryzen 7 7800X3D (Zen4) | 1세대와 동일 구조 (L3D 상단 적층) | 32MB + 64MB = 96MB | 동일 CCD — 비X3D 대비 클럭 제약 존재 |
| 3세대 (2세대 적층) | Ryzen 7 9800X3D (Zen5) | 반전 적층(Inverted Stacking): CCD가 L3D 위에 적층. TSMC 직접 구리-구리(Copper-to-Copper) 본딩(Bonding) | 32MB + 64MB = 96MB | 동일 CCD — 클럭 제한 해제, 일반 Ryzen 9000과 동일 오버클럭 지원 |
Intel 아키텍처
2.1 Tile 아키텍처
Intel은 서버 프로세서(Sapphire Rapids, Granite Rapids)에서 EMIB(Embedded Multi-die Interconnect Bridge)를 사용한 타일 기반 설계를 도입했습니다.
- Sapphire Rapids (SPR): 4타일 구성, 각 타일에 코어 + LLC 슬라이스 + UPI + PCIe. 최대 60코어. Intel 7 공정.
- Granite Rapids (GNR): Performance-core(P-core) 전용 타일 + I/O 타일, 최대 128 P-core. Intel 3 공정(Redwood Cove 마이크로아키텍처). 12 DDR5 채널, 136 PCIe 5.0 레인, CXL 2.0 지원. LGA 4710 / LGA 7529 소켓.
- Sierra Forest (SF): E-core 전용 서버 프로세서(Sierra Glen E-core). 최대 288 E-core. 클라우드 컴퓨팅, 고밀도(density) 워크로드에 최적화. P-core가 없으므로 모든 코어가 동일 용량을 가지며, 비대칭 스케줄링(SD_ASYM_CPUCAPACITY)이 불필요합니다.
- Diamond Rapids: Granite Rapids의 후속 세대. 차세대 Xeon 서버 라인업.
- SNC(Sub-NUMA Clustering): 타일 경계를 따라 소켓 내부를 NUMA 노드로 분할. SNC2(2 NUMA) 또는 SNC4(4 NUMA) 모드로 지연 최소화.
클라이언트 프로세서에서도 Meteor Lake부터 분리형 타일 설계(Foveros 3D 패키징)가 도입되어, Compute/SoC/GFX/IOE 타일로 구성됩니다(자세한 내용은 2.12 분리형 SoC 설계 참조).
# SNC 모드 확인 (Sapphire Rapids, SNC4)
$ lscpu | grep -i numa
NUMA node(s): 4
NUMA node0 CPU(s): 0-14,60-74
NUMA node1 CPU(s): 15-29,75-89
NUMA node2 CPU(s): 30-44,90-104
NUMA node3 CPU(s): 45-59,105-119
UPI (Ultra Path Interconnect)
UPI(Ultra Path Interconnect)는 Intel Xeon Scalable 프로세서 간을 연결하는 점대점(point-to-point) 인터커넥트입니다. AMD의 xGMI(Gigabit Extensible Memory Interface)에 대응하는 기술입니다.
Sapphire Rapids에서는 최대 8-way 구성이 가능하며, 각 UPI 링크는 링(topology=Ring) 또는 메시(Mesh)로 구성됩니다. UPI 3.0은 최대 링크당 10.4GT/s(10.4 Giga-Transfer/sec)로, 방향별 대역폭이 ~12.5GB/s입니다. 4-link 구성일 경우 방향별 ~50GB/s의 이론적 대역폭을 제공합니다.
| 특성 | UPI 1.0 (SKX) | UPI 2.0 (ICX) | UPI 3.0 (SPR/GNR) |
|---|---|---|---|
| 링크당 대역폭 | ~10.4 GT/s | ~10.4 GT/s | ~10.4 GT/s |
| 최대 링크 수/SOCKET | 2 | 3 | 4 (SPR) / 6 (GNR) |
| 최대 소켓 수 | 4 | 4 | 8 |
| 트랜잭션(Transaction) 프로토콜 | Coherent | Coherent | Coherent (확장) |
Sapphire Rapids 2-socket 기준 각 CPU는 최대 4개의 UPI link를 사용하며, 10.4 GT/s × 2(bytes/link) × 4 = ~83 GB/s의 방향별 이론적 대역폭을 제공합니다. Granite Rapids는 최대 6개의 UPI link를 지원하여 대역폭이 추가 확장됩니다. 8-socket 구성에서는 Mesh 토폴로지로 전환되어 다중 홉(Hop) 경로가 형성됩니다.
2.2 P-core / E-core 하이브리드
Alder Lake(12세대)부터 클라이언트 프로세서에 하이브리드 아키텍처를 도입했습니다. 이는 단일 다이(또는 패키지)에 고성능 P-core(Performance)와 고효율 E-core(Efficient)를 혼합 배치하는 설계입니다.
하이브리드 도입 배경:
- 전력 효율: 무어의 법칙 둔화로 단순히 코어를 늘리는 것만으로는 성능/와트 개선이 한계에 도달. 경량 워크로드를 소형 코어로 처리하면 전체 패키지 전력을 대폭 절감
- 열 제약: 데스크탑 65~125W, 랩탑 15~45W TDP 내에서 코어 수 확장. E-core는 P-core 대비 1/4~1/5 면적으로 동일 다이에 더 많은 코어 집적 가능
- 멀티태스킹 최적화: 포그라운드(게임, 컴파일)는 P-core, 백그라운드(인덱싱, 업데이트)는 E-core에서 처리하여 체감 성능 향상
| 특성 | P-core (Performance) | E-core (Efficient) |
|---|---|---|
| 마이크로아키텍처 | Golden Cove / Raptor Cove / Lion Cove | Gracemont / Crestmont / Skymont |
| SMT | 지원 (2 스레드/코어) | 미지원 (1 스레드/코어) |
| L2 캐시 | 1.25~3MB/코어 (개별) | 2~4MB/4코어 클러스터 (공유) |
| ISA 차이 | AVX-512 (세대별 상이) | AVX-512 미지원 |
| IPC | 높음 | P-core 대비 ~70-85% (세대별 향상) |
| 전력 효율 | 성능 우선 | 와트당 성능 우수 |
| 다이 면적 | 큽니다 (~4mm² @Intel 7) | 작습니다 (~1mm² @Intel 7) |
E-core 클러스터 토폴로지: E-core 4개가 하나의 클러스터(Cluster)를 이루며, 클러스터 내 L2 캐시를 공유합니다. 커널에서는 이 클러스터를 CL(Cluster) 스케줄링 도메인 레벨로 인식합니다. 같은 클러스터 내 E-core 간 데이터 교환은 L2를 통해 이루어지므로 지연이 매우 낮지만, 다른 클러스터의 E-core나 P-core와의 통신은 링 버스(Bus)/LLC를 경유합니다.
2.3 P-core 마이크로아키텍처
P-core는 Intel의 고성능 마이크로아키텍처 계열로, 넓은 프론트엔드와 깊은 OoO(Out-of-Order) 파이프라인(Pipeline)을 특징으로 합니다.
| 마이크로아키텍처 | 세대 | 디코드 폭 | ROB 크기 | 실행 포트 | L2/코어 | 주요 특징 |
|---|---|---|---|---|---|---|
| Golden Cove | Alder Lake (12th) | 6-wide | 512 | 12 | 1.25MB | 최초 하이브리드 P-core, 10-wide alloc |
| Raptor Cove | Raptor Lake (13th) | 6-wide | 512 | 12 | 2MB | L2 확대, 클럭 개선 (최대 6.0GHz) |
| Redwood Cove | Meteor Lake (Core Ultra 1) | 6-wide | 512 | 12 | 2MB | 전력 게이팅 향상, Compute tile 분리 |
| Lion Cove | Arrow/Lunar Lake (Core Ultra 2) | 8-wide | 576 | 12 | 2.5~3MB | 디코드 폭 확대, AVX-512 복원(P-core 한정) |
Golden Cove (Alder Lake) — P-core의 첫 하이브리드 설계입니다. 6-wide 디코드, 512-entry ROB(Reorder Buffer), 12개 실행 포트를 갖추어 이전 세대(Cypress Cove) 대비 ~19% IPC 향상을 달성했습니다. L2 캐시는 코어당 1.25MB이며, 프리페칭 개선과 브랜치 예측기 확장이 적용되었습니다.
Raptor Cove (Raptor Lake) — Golden Cove의 파이프라인을 유지하면서 L2를 2MB로 확대하고, 클럭 스케일링을 개선하여 최대 부스트 6.0GHz를 달성했습니다. 캐시 프리페칭 개선으로 메모리 집약적 워크로드에서 추가 IPC 향상이 있습니다.
Redwood Cove (Meteor Lake) — 분리형 Compute tile(Intel 4 공정)에 탑재됩니다. 파이프라인은 Golden Cove와 유사하지만, 타일 단위 전력 게이팅으로 유휴 시 P-core 타일 전체를 전력 차단할 수 있어 배터리 수명이 크게 개선됩니다.
Lion Cove (Arrow Lake / Lunar Lake) — 디코드 폭을 8-wide로 확대하고 ROB를 576개로 늘렸습니다. 가장 주목할 변화는 P-core 한정으로 AVX-512를 복원한 것입니다. E-core에서는 여전히 AVX-512를 지원하지 않으므로, 커널은 ISA 비대칭성을 처리해야 합니다(2.9 ISA 호환성 참조).
2.4 E-core 마이크로아키텍처
E-core는 "느린 코어"가 아닌 "효율 코어"입니다. Skylake급 IPC를 달성하면서 훨씬 낮은 전력과 작은 다이 면적으로 동작하도록 설계되었습니다.
| 마이크로아키텍처 | 세대 | 디코드 | 클러스터 구성 | L2/클러스터 | IPC (vs P-core) |
|---|---|---|---|---|---|
| Gracemont | Alder Lake / Raptor Lake | 2-wide × 2 파이프 | 4코어/클러스터 | 2MB (ADL) / 4MB (RPL) | ~70% |
| Crestmont | Meteor Lake / Lunar Lake LP | 향상된 프론트엔드 | 4코어/클러스터 | 4MB | ~80-85% |
| Skymont | Arrow Lake / Lunar Lake | 추가 IPC 개선 | 4코어/클러스터 | 4MB | ~85% |
Gracemont (Alder Lake/Raptor Lake) — 2개의 독립적인 2-wide 디코드 파이프를 사용하는 독특한 클러스터 구조입니다. SMT를 지원하지 않는 대신 코어 자체를 소형화하여, P-core 1개의 다이 면적에 E-core 4개를 집적합니다. 4코어가 하나의 클러스터를 이루어 L2 캐시를 공유하며, 이 공유 구조가 커널에서 CL(Cluster) 스케줄링 도메인을 형성합니다.
Crestmont (Meteor Lake) — 프론트엔드 분기 예측(Branch Prediction) 개선과 백엔드 실행 유닛 확장으로 IPC가 P-core 대비 ~80-85% 수준으로 향상되었습니다. Meteor Lake SoC tile의 LP E-core(2.5절 참조)로도 사용되며, 이 경우 낮은 클럭(1~2GHz)으로 동작합니다.
Skymont (Arrow Lake / Lunar Lake) — 추가적인 IPC 개선으로 Gracemont 대비 최대 ~38% 향상을 달성했습니다. 벡터 처리 능력도 강화되어 256-bit SIMD 처리량(Throughput)이 증가했습니다.
2.5 LP E-core와 3계층 코어
Meteor Lake부터 SoC tile에 LP E-core(Low-Power Efficient core)가 추가되어, 프로세서가 3계층 코어 구조를 갖게 되었습니다.
| 계층 | 코어 타입 | 위치 | 특성 | 용도 |
|---|---|---|---|---|
| 1 (최고성능) | P-core | Compute tile | 높은 클럭, 넓은 파이프라인 | 게임, 컴파일, 싱글스레드 집약 |
| 2 (효율) | E-core | Compute tile | 중간 클럭, 4코어 클러스터 | 멀티스레드, 백그라운드 |
| 3 (초저전력) | LP E-core | SoC tile | 1~2GHz, 2코어 구성 | 경량 작업, 대기 시 유지 |
LP E-core의 특성:
- 위치: SoC tile(TSMC N6 공정)에 위치하여, Compute tile이 전력 차단(power gate)된 상태에서도 독립 동작 가능
- 구성: 2코어, Crestmont 마이크로아키텍처 기반이지만 낮은 클럭(1~2GHz)으로 동작
- 시나리오: 메신저 알림, 음악 재생, 시스템 유지 관리 등 Compute tile을 깨울 필요 없는 경량 작업
- Lunar Lake: LP E-core 2코어(Crestmont)가 SoC tile에 위치, Compute tile에는 P-core 4개 + E-core 4개
커널 영향: 3계층 코어 구조에서 스케줄러는 추가적인 우선순위(Priority) 레벨을 관리해야 합니다. ITMT(2.8절)에서 LP E-core는 가장 낮은 우선순위를 받으며, 스케줄링 도메인에서도 별도의 Module 레벨로 인식될 수 있습니다.
2.6 세대별 진화
| 세대 | 공정 | P-core | E-core | LP E | P수 | E수 | P-L2 | E-L2/CL | L3 | 메모리 | 설계 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Alder Lake (12th) | Intel 7 | Golden Cove | Gracemont | - | 8 | 8 | 1.25MB | 2MB | 30MB | DDR5-4800 | 모놀리식 |
| Raptor Lake (13th) | Intel 7 | Raptor Cove | Gracemont | - | 8 | 16 | 2MB | 4MB | 36MB | DDR5-5600 | 모놀리식 |
| Meteor Lake (Ultra 1) | Intel 4/N6/N5 | Redwood Cove | Crestmont | Crestmont | 6 | 8 | 2MB | 4MB | 12MB | LPDDR5X | 분리형(Foveros) |
| Arrow Lake (Ultra 2) | TSMC N3/N6 | Lion Cove | Skymont | - | 8 | 16 | 3MB | 4MB | 36MB | DDR5-5600 | 분리형 |
| Lunar Lake (Ultra 2) | TSMC N3/N6 | Lion Cove | Skymont | Crestmont | 4 | 4 | 2.5MB | 4MB | 12MB | LPDDR5X | 분리형 |
2.7 코어 타입 탐지 — CPUID Leaf 0x1A
하이브리드 프로세서에서 커널은 각 논리 CPU가 P-core인지 E-core인지 식별해야 합니다. Intel은 CPUID Leaf 0x1A (Native Model ID Enumeration)를 통해 이 정보를 제공합니다.
| EAX 비트 | 필드 | 값 | 의미 |
|---|---|---|---|
| [31:24] | Core Type | 0x20 | Atom (E-core) |
| [31:24] | Core Type | 0x40 | Core (P-core) |
| [23:0] | Native Model ID | - | 마이크로아키텍처별 고유 ID |
/* arch/x86/kernel/cpu/intel.c — 하이브리드 코어 타입 탐지 */
static void detect_hybrid_cpu(struct cpuinfo_x86 *c)
{
u32 eax, ebx, ecx, edx;
if (!cpu_has(c, X86_FEATURE_HYBRID_CPU))
return;
cpuid(0x1a, &eax, &ebx, &ecx, &edx);
/* EAX[31:24] = Core Type
* 0x20 = Intel Atom (E-core)
* 0x40 = Intel Core (P-core) */
c->topo.core_type = (eax >> 24) & 0xff;
c->topo.native_model_id = eax & 0xffffff;
}
/* X86_FEATURE_HYBRID_CPU는 CPUID Leaf 7, ECX bit 15로 탐지
* (CPUID.07H:EDX[15] = Hybrid 지원 여부) */
CPUID 0x1F V2 Extended Topology와의 연동: Leaf 0x1F에서 Module 레벨(level_type=3)을 통해 E-core 클러스터를 식별할 수 있습니다. 같은 Module에 속한 E-core들은 L2를 공유하며, 커널은 이를 CL 스케줄링 도메인으로 매핑합니다.
# 코어 타입 확인 (sysfs)
$ for cpu in /sys/devices/system/cpu/cpu*/topology; do
if [ -f "$cpu/core_type" ]; then
echo "$(basename $(dirname $cpu)): $(cat $cpu/core_type)"
fi
done
cpu0: 64 # 0x40 = P-core (Intel Core)
cpu1: 64
...
cpu16: 32 # 0x20 = E-core (Intel Atom)
cpu17: 32
...
2.8 ITMT 비대칭 스케줄링
ITMT(Intel Turbo Boost Max Technology 3.0)는 코어별 성능 차이를 커널 스케줄러에 반영하는 메커니즘입니다. 하이브리드 프로세서에서 P-core > E-core > LP E-core 우선순위를 설정하여 비대칭 스케줄링을 구현합니다.
/* arch/x86/kernel/itmt.c — ITMT 우선순위 설정 */
/* 코어별 성능 우선순위를 스케줄러에 등록 */
void sched_set_itmt_core_prio(int prio, int cpu)
{
per_cpu(sched_core_priority, cpu) = prio;
}
/* ITMT 활성화 — sched_domain에 SD_ASYM_PACKING 플래그 설정 */
void sched_set_itmt_support(void)
{
sysctl_sched_itmt_enabled = 1;
rebuild_sched_domains();
}
/*
* 동작 원리:
* 1. intel_pstate 또는 HFI 드라이버가 각 코어의 성능 순위를 파악
* 2. sched_set_itmt_core_prio()로 코어별 우선순위 설정
* 예: P-core = 200, E-core = 100, LP E-core = 50
* 3. SD_ASYM_PACKING이 설정된 도메인에서 스케줄러는
* 높은 우선순위 코어를 먼저 채움
* 4. 워크로드가 줄어들면 낮은 우선순위 코어에서 먼저 제거
*/
주요 커널 인터페이스:
/proc/sys/kernel/sched_itmt_enabled: ITMT 활성화/비활성화 (0 또는 1)SD_ASYM_PACKING: 스케줄링 도메인 플래그로, 비대칭 코어 배치 시 우선순위 기반 태스크 이동- HFI 드라이버가
perf_cap값을 ITMT 우선순위로 변환하여, Thread Director의 실시간 판단이 스케줄러에 반영됨
# ITMT 활성 상태 확인
$ cat /proc/sys/kernel/sched_itmt_enabled
1
# 스케줄링 도메인에서 ASYM_PACKING 플래그 확인
$ cat /proc/sys/kernel/sched_domain/cpu0/domain0/flags
... SD_ASYM_PACKING ...
2.9 ISA 호환성
하이브리드 아키텍처의 핵심 과제 중 하나는 P-core와 E-core 간 ISA(Instruction Set Architecture) 차이입니다. 특히 AVX-512가 세대별로 달라지는 점이 커널과 사용자 공간(User Space) 모두에 영향을 줍니다.
| 세대 | P-core AVX-512 | E-core AVX-512 | OS에 노출 |
|---|---|---|---|
| Alder Lake (12th) | 하드웨어 존재 | 미지원 | E-core 비활성 시 일부 SKU에서 사용 가능 (BIOS) |
| Raptor Lake (13th) | 퓨즈 차단 | 미지원 | 완전 비활성 |
| Meteor Lake | 미지원 | 미지원 | 비활성 |
| Arrow Lake / Lunar Lake | 복원 (지원) | 미지원 | P-core에서만 사용 가능 |
커널의 ISA 처리 원칙 — 최소 공통 분모:
기본적으로 커널은 모든 코어가 지원하는 ISA의 교집합만 사용자 공간에 노출합니다. E-core가 AVX-512를 지원하지 않으면, CPUID를 통해 OS에 AVX-512가 광고되지 않습니다. 따라서 Alder Lake~Meteor Lake에서는 AVX-512를 사용할 수 없습니다.
Arrow Lake+에서의 변화: Lion Cove P-core에서 AVX-512가 복원되었지만, Skymont E-core는 여전히 미지원입니다. Intel은 이 비대칭 ISA를 처리하기 위해:
- AVX-512 명령어가 E-core에서 실행되면
#UD(Undefined Instruction) 예외 발생 - 커널은 AVX-512를 사용하는 스레드를 P-core에 고정(pin)하거나, 사용자 공간에서
sched_setaffinity()로 P-core만 지정해야 함 - CPUID Leaf 0x1A의 코어 타입 정보와 연동하여 ISA 능력을 코어별로 구분
taskset이나 sched_setaffinity()로 P-core에 명시적으로 고정하지 않으면, 스케줄러가 E-core로 마이그레이션할 때 SIGILL이 발생할 수 있습니다. 커널 6.x에서 이를 자동 처리하는 패치(Patch)가 진행 중이지만, 현재는 사용자 공간에서 주의가 필요합니다.
2.10 Intel Thread Director
Intel Thread Director(ITD)는 하드웨어 기반의 스레드 분류기로, 각 스레드의 명령어 mix를 실시간 분석하여 P-core/E-core 배치를 최적화합니다.
IPC 클래스 분류: Thread Director는 스레드가 실행하는 명령어 패턴을 모니터링하여 4개 IPC 클래스 중 하나를 할당합니다:
| IPC 클래스 | 명령어 특성 | 권장 코어 | 예시 |
|---|---|---|---|
| Class 0 | 범용 코드 | P-core 또는 E-core | 일반 애플리케이션 |
| Class 1 | 정수 연산 중심 | E-core 충분 | 웹 브라우징, 스크립트 |
| Class 2 | 벡터/SIMD 연산 | P-core 선호 | 미디어 인코딩 |
| Class 3 | FP/AVX 집중 | P-core 강력 선호 | 과학 계산, 게임 물리 |
/* HFI (Hardware Feedback Interface) 테이블 구조 */
struct hfi_cpu_data {
u8 perf_cap; /* 성능 능력: 0(최저)~255(최고) */
u8 ee_cap; /* 에너지 효율: 0(최저)~255(최고) */
};
/*
* 커널 드라이버: drivers/thermal/intel/intel_hfi.c
*
* HFI 테이블은 MMIO 영역에 매핑되며, 인터럽트로 갱신 알림.
* 갱신 주기: 약 ~100ms (워크로드 변화 시 동적 조절)
*
* 열 제한(thermal throttling) 시 perf_cap이 동적으로 감소:
* 정상 → P-core perf_cap=255, E-core perf_cap=128
* 열 제한 → P-core perf_cap=180, E-core perf_cap=100
* 이를 통해 스케줄러가 열 상태에 따라 배치를 자동 조절
*/
static void intel_hfi_process_event(struct work_struct *work)
{
struct hfi_instance *hfi = container_of(work, ...);
/* HFI 테이블에서 각 CPU의 perf_cap/ee_cap 읽기 */
raw_spin_lock_irq(&hfi->table_lock);
memcpy(hfi->local_table, hfi->hw_table, hfi->table_size);
raw_spin_unlock_irq(&hfi->table_lock);
/* ITMT 우선순위 갱신 → 스케줄러에 용량 변경 알림 */
sched_update_cpu_topology();
}
HFI → ITMT → 스케줄러 연동 흐름:
- Thread Director가 IPC 클래스를 계산하여 HFI 테이블에 기록
- HFI 인터럽트(Interrupt) →
intel_hfi_process_event()→ HFI 테이블 복사 perf_cap/ee_cap값을 ITMT 우선순위로 변환 →sched_set_itmt_core_prio()sched_update_cpu_topology()→ 스케줄링 도메인 재구축- CFS 스케줄러가
SD_ASYM_PACKING플래그에 따라 높은 우선순위 코어에 태스크 배치
2.11 캐시 토폴로지
Intel의 LLC(Last-Level Cache)는 슬라이스 형태로 분산되어 있으며, 링 버스 또는 메시 인터커넥트로 연결됩니다.
- 링 버스: 클라이언트 및 초기 서버(~Broadwell). 코어마다 LLC 슬라이스가 링에 연결.
- 메시 인터커넥트: Skylake-SP 이후 서버. 2D 메시에 코어·LLC·Home Agent 분산.
- SNC 모드: 메시를 2~4개 영역으로 분할하여, 각 영역이 로컬 메모리 컨트롤러와 매핑. NUMA 노드로 노출.
2.12 분리형 SoC 설계 — Meteor Lake+
Meteor Lake부터 Intel은 Foveros 3D 패키징을 사용하여 여러 타일을 하나의 패키지에 조합하는 분리형(disaggregated) 설계를 클라이언트 프로세서에 도입했습니다.
4-Tile 구조 (Meteor Lake):
- Compute tile (Intel 4): P-core(Redwood Cove) + E-core(Crestmont) + L3 캐시
- SoC tile (TSMC N6): LP E-core(Crestmont 2코어) + NPU(AI 엔진) + 미디어 엔진 + 디스플레이 엔진
- GFX tile (TSMC N5): Xe-LPG GPU (128 EU)
- IOE tile (TSMC N6): PCIe Gen5, Thunderbolt 4, USB 4, 기타 I/O
각 타일은 독립적인 FIVR(Fully Integrated Voltage Regulator)를 탑재하여 전압/주파수를 개별 제어합니다. Compute tile이 유휴 상태(Idle State)이면 전력을 완전히 차단하고, SoC tile의 LP E-core만으로 경량 작업을 처리할 수 있습니다.
Lunar Lake 변형: GPU를 SoC tile에 통합하고, 메모리(LPDDR5X)를 패키지 위에 직접 적층(Package-on-Package)하여 더 작은 폼팩터를 달성했습니다.
커널 토폴로지 영향:
- CPUID 0x1F에서
Tile레벨(level_type=4)이 보고되어, 커널이 크로스 타일 코어 간 높은 지연을 인식 - Compute tile과 SoC tile의 코어 간 통신은 Foveros 인터커넥트를 경유하므로, 같은 tile 내 코어 간 통신보다 지연이 높음
- 캐시 코히런시: L3는 Compute tile에만 존재하며, LP E-core(SoC tile)는 L3 없이 동작
ARM 아키텍처
big.LITTLE / DynamIQ
ARM은 이기종 멀티프로세싱(HMP)의 선구자입니다.
| 기술 | 세대 | 특징 |
|---|---|---|
| big.LITTLE | 2011~ | big/LITTLE 코어가 별도 클러스터, 클러스터 단위 전환 또는 HMP |
| DynamIQ | 2017~ | 단일 클러스터 내 이종 코어 혼합 가능, DSU 기반 L3 공유 |
DynamIQ에서는 하나의 클러스터에 Cortex-X4 + Cortex-A720 + Cortex-A520 같은 3종 코어를 혼합할 수 있습니다.
DSU (DynamIQ Shared Unit)
DSU는 DynamIQ 클러스터의 핵심 IP로, L3 캐시 관리와 클러스터 내 코히런시를 담당합니다.
- DSU-AE (Automotive Enhanced): 자율주행 등 안전 등급 요구사항 충족
- DSU-110: 최대 8코어, L3 최대 16MB
- DSU-120: 최대 12코어, L3 최대 32MB, 3종 코어 지원
- Snoop Filter를 내장하여 코히런시 트래픽 최적화
CMN (Coherent Mesh Network)
ARM 서버 SoC(Neoverse)에서는 CMN(Coherent Mesh Network)으로 다수의 클러스터를 연결합니다.
| 노드 유형 | 역할 |
|---|---|
| XP (Crosspoint) | 메시 라우터, 패킷(Packet) 라우팅 |
| RN-F (Request Node - Full) | 코어 클러스터 연결점, 풀 코히런시 |
| HN-F (Home Node - Full) | SLC(System-Level Cache) 슬라이스, 디렉토리 기반 코히런시 |
| SN-F (Slave Node - Full) | 메모리 컨트롤러, PCIe 인터페이스 |
| MN (Miscellaneous Node) | DVM(Distributed Virtual Memory) 브로드캐스트 |
/* ARM CMN PMU 드라이버: drivers/perf/arm-cmn.c
* CMN 메시 토폴로지를 런타임에 탐지 */
static int arm_cmn_discover(struct arm_cmn *cmn, unsigned int rgn_offset)
{
void __iomem *reg;
u64 val;
/* Configuration 영역에서 메시 크기, 노드 수 읽기 */
val = readq_relaxed(cmn->base + CMN_CFGM_INFO_GLOBAL);
cmn->mesh_x = FIELD_GET(CMN_INFO_MESH_X, val);
cmn->mesh_y = FIELD_GET(CMN_INFO_MESH_Y, val);
...
}
Cortex-X/A 시리즈
| 코어 | 등급 | 특징 | 대표 SoC |
|---|---|---|---|
| Cortex-X4/X925 | 초고성능 | 넓은 OoO, 대형 L2 | Snapdragon 8 Gen 3, Dimensity 9300 |
| Cortex-A720/A725 | 고성능 | A78 후속, 균형 잡힌 IPC | 대부분의 플래그십 SoC |
| Cortex-A520 | 효율 | A510 후속, 인오더 스칼라 설계 (2코어 실행 유닛 공유) | 모든 최신 모바일 SoC |
| Neoverse V2/V3 | 서버 | SVE2, 넓은 파이프라인, 대형 캐시 | Graviton 4, Ampere |
| Neoverse N2/N3 | 서버(효율) | 코어 수 확장 최적화 | Graviton 3, Cobalt 100 |
ARM 심층 분석: DSU, OPP, PPTT, 용량
ARM 비대칭 멀티프로세싱 시스템은 코어 종류별 OPP(Operating Performance Point), 에너지 모델, ACPI PPTT 구조가 복합적으로 맞물립니다. 이 섹션에서는 DynamIQ 클러스터 내부 구조, ACPI PPTT 상세, ARM OPP 커널 구현, CPU 용량 계산 방법을 분석합니다.
DynamIQ 클러스터 내부 구조
DynamIQ 클러스터는 하나의 DSU(DynamIQ Shared Unit)가 최대 12개의 이종 코어를 관리합니다. 각 코어는 독립적인 OPP(Operating Performance Point) 테이블을 가질 수 있어, X4/A720/A520 코어가 동시에 서로 다른 주파수로 동작할 수 있습니다.
| DSU 버전 | 최대 코어 | 최대 L3 | Snoop Filter | 비고 |
|---|---|---|---|---|
| DSU-110 | 8코어 | 16MB | 내장 | A55/A75/A77 기반 SoC |
| DSU-120 | 12코어 | 32MB | 내장 (확장) | X4/A720/A520 혼합, Dimensity 9300+ |
| DSU-AE | 4코어 | 8MB | 내장 | 자율주행 ISO 26262 등급 |
| CMN-600 | 128코어 | — | HN-F 디렉토리 | Neoverse 서버 메시 |
| CMN-700 | 256코어 | — | HN-F 확장 | 최신 서버 SoC (Neoverse V2 기반) |
Neoverse V3 서버 코어
ARM의 최신 서버용 CPU 코어 Neoverse V3(2025)는 Neoverse V2 대비 IPC를 ~38% 개선하고, SVE2(Scalable Vector Extension 2) 128bit 레지스터를 8배로 확장하는 등 데이터 센터 워크로드에 집중된 설계입니다. AMD Zen 5, Intel Sierra Forest와 직접 경쟁하는 라인업입니다.
| 특성 | V2 | V3 | V4 (예상) |
|---|---|---|---|
| 공정 | TSMC N5/N6 | TSMC N3E | TSMC N2/N3P (예상) |
| 파이프라인 폭 | 4-wide | 4-wide (향상된 디코드) | 4-wide+ |
| ROB 크기 | 224 | 280 | 300+ (예상) |
| SVE width | 2048-bit | 2048-bit (8×128b 레지스터) | 2048-bit+ |
| L1D / L1I | 48KB / 32KB | 48KB / 48KB | 64KB / 64KB (예상) |
| L2 | 1MB~2MB | 2MB | 2MB~4MB (예상) |
| SLC | 100MB~200MB | 200MB~500MB | 500MB+ (예상) |
| MPAM | 지원 | 지원 (PartID 확장) | 지원 (예상) |
| 대표 | Graviton4 (AWS), Ampere Altra Max | Ampere X(2025), AWS Graviton5 (2026) | — |
Neoverse V3에서 커널 관측상 중요한 변화:
- MPAM PartID 확장: L3/SLC의 파티션 식별자가 10bit → 16bit로 확장되어, NUMA 도메인 내 더 많은 리소스 그룹을 독립 제어 가능
- SVE2 레지스터 확장: SVE 레지스터(Z0-Z31) 너비가 최대 2048-bit(256 바이트)로, 커널의 VLA(Vector Language Architecture) 코드가 이에 대응
- MTE(Memory Tagging Extension) Stage 2: stage 2 페이지 테이블 기반 메모리 테깅으로 커널 스택/heap 무결성(Integrity) 검증 성능 개선
- RNG(Random Number Generator) 하드웨어: PTP(Platform Trusted Point) 기반 true random generator를 CPU 내부에 통합
DynamIQ의 핵심 특징은 독립 전압/주파수 도메인(DVFS Island)입니다. 각 코어(또는 코어 그룹)가 별도의 OPP 테이블을 가지므로, 조성이 다른 코어들이 각자의 최적 주파수에서 동작할 수 있습니다.
ACPI PPTT 상세 구조
PPTT(Processor Properties Topology Table)는 ARM64 ACPI 시스템에서 코어의 계층 구조, 캐시 공유 관계, CPU 능력(capacity)을 기술합니다.
/* drivers/acpi/pptt.c - PPTT 캐시 탐지 */
static struct acpi_pptt_cache *acpi_pptt_find_cache(
struct acpi_table_header *table,
struct acpi_pptt_processor *cpu_node,
int level, int type)
{
struct acpi_pptt_processor *node = cpu_node;
while (node) {
struct acpi_pptt_cache *cache;
cache = acpi_find_cache_node(table, node, level, type);
if (cache)
return cache;
node = fetch_pptt_node(table, node->parent);
}
return NULL;
}
PPTT 계층 레벨과 커널 매핑:
| 레벨 | 의미 | 커널 필드 | 캐시 |
|---|---|---|---|
| 0 (Thread) | SMT 스레드 | cpu_topology[cpu].thread_id | — |
| 1 (Core) | 물리 코어 | cpu_topology[cpu].core_id | L1/L2 공유 |
| 2 (Cluster) | DynamIQ 클러스터 | cpu_topology[cpu].cluster_id | DSU L3 공유 |
| 3 (Package) | SoC 패키지 | cpu_topology[cpu].package_id | SLC(서버) |
OPP 커널 구현
리눅스 OPP(Operating Performance Point) 프레임워크는 각 전압/주파수 쌍을 추상화하여 DVFS를 구현합니다.
/* include/linux/pm_opp.h - OPP 데이터 구조 */
struct dev_pm_opp {
struct list_head node;
bool available;
unsigned long rate; /* 주파수 (Hz) */
unsigned long u_volt; /* 전압 (μV) */
unsigned long power; /* 소비전력 (μW) */
};
/* OPP 테이블 API */
struct dev_pm_opp *dev_pm_opp_find_freq_ceil(
struct device *dev, unsigned long *freq);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
/* Cortex-A55 OPP 테이블 (Device Tree 예시) */
cpu0_opp_table: opp-table-0 {
compatible = "operating-points-v2";
opp-shared;
opp-400000000 {
opp-hz = /bits/ 64 <400000000>;
opp-microvolt = <600000>;
opp-microwatt = <35000>;
};
opp-1800000000 {
opp-hz = /bits/ 64 <1800000000>;
opp-microvolt = <850000>;
opp-microwatt = <480000>;
};
};
CPU 용량 계산
이종 코어 시스템에서 스케줄러는 각 코어의 상대적 성능을 CPU 용량(capacity)으로 추상화합니다. SCHED_CAPACITY_SCALE = 1024가 최대값이며, 가장 강력한 코어(big 코어)가 1024를 가집니다.
/* drivers/base/arch_topology.c - CPU 용량 정규화 */
void topology_normalize_cpu_scale(void)
{
unsigned long capacity, max_capacity = 0;
int cpu;
for_each_possible_cpu(cpu) {
capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu);
max_capacity = max(max_capacity, capacity);
}
for_each_possible_cpu(cpu) {
capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu);
capacity = capacity * SCHED_CAPACITY_SCALE / max_capacity;
topology_set_cpu_scale(cpu, capacity);
}
}
/* arch_scale_cpu_capacity() — 스케줄러가 현재 CPU 용량을 조회 */
static inline unsigned long arch_scale_cpu_capacity(int cpu)
{
return per_cpu(cpu_scale, cpu);
}
Snapdragon 8 Gen 3 (SM8650) 기준 코어별 용량 예시:
| 코어 | 수 | 최대 주파수 | capacity-dmips-mhz | CPU capacity |
|---|---|---|---|---|
| Cortex-X4 | 1 | 3.3GHz | 1024 | 1024 |
| Cortex-A720 | 3 | 3.15GHz | 870 | ~895 |
| Cortex-A520 | 4 | 2.27GHz | 480 | ~450 |
ARM Energy Model 드라이버
Energy Model(EM) 프레임워크는 OPP별 소비전력 정보를 커널에 등록하여 EAS가 에너지 비용을 계산할 수 있도록 합니다.
/* include/linux/energy_model.h - EM 퍼포먼스 상태 */
struct em_perf_state {
unsigned long frequency; /* 주파수 (KHz) */
unsigned long power; /* 소비전력 (mW) */
unsigned long cost; /* power / frequency (에너지 비용 계수) */
};
struct em_perf_domain {
struct em_perf_state *table;
int nr_perf_states;
struct cpumask cpus;
};
/* EM 등록 — drivers/cpufreq/cpufreq-dt.c 등 */
int em_dev_register_perf_domain(struct device *dev,
unsigned int nr_states,
struct em_data_callback *cb,
struct cpumask *cpus,
bool microwatts);
아키텍처 간 비교
토폴로지 구조 비교
| 항목 | AMD (Zen4/5) | Intel (SPR/RPL) | ARM (Neoverse/DynamIQ) |
|---|---|---|---|
| 기본 단위 | CCX (8코어+L3) | 코어 (P/E 구분) | 클러스터 (DSU) |
| 중간 단위 | CCD (물리 다이) | Tile / E-core 클러스터 | CMN 메시 노드 |
| 인터커넥트 | Infinity Fabric (IOD 경유) | Ring Bus / Mesh | CMN 메시 / NoC |
| 이종 코어 | 없음 (동종) | P-core + E-core | X + A(big) + A(LITTLE) |
| NUMA 분할 | NPS1/2/4 | SNC2/4 | ACPI PPTT |
| 소켓 간 | xGMI (IF) | UPI | CCIX / CXL / 커스텀 |
| 칩렛/타일 | CCD + IOD 분리 | EMIB 타일 | 모놀리식 (서버 일부 칩렛) |
캐시 계층 · 코히런시 프로토콜
| 항목 | AMD | Intel | ARM |
|---|---|---|---|
| L1D/L1I | 32K/32K (Zen4: 48K/32K) | 48K/32K (P), 32K/64K (E) | 코어별 상이 |
| L2 | 1MB/코어 | 1.25-2MB/P, 2-4MB/4E | 256K~2MB/코어 |
| L3/LLC | 32MB/CCD | 슬라이스 분산 (1.875MB/코어) | DSU L3 + SLC(서버) |
| 코히런시 | MOESI | MESIF | MESI / AMBA CHI |
| 디렉토리 | Probe Filter | Snoop Filter | HN-F 디렉토리 |
인터커넥트 지연(Interconnect Latency) 비교
AMD와 Intel의 인터커넥트 설계 차이는 코어 간 통신 지연에 직접적인 영향을 미칩니다.
아래 수치는 공개된 벤치마크 및 mlc(Intel Memory Latency Checker), lmbench 측정 기반입니다.
| 경로 | AMD EPYC 9004 (Zen4) | Intel Xeon 4세대 (SPR) | 측정 방법 |
|---|---|---|---|
| 동일 CCX/타일 내 코어 간 | ~15~20 ns (L3 hit) | ~20~25 ns (L3 슬라이스 hit) | lmbench lat_ctx |
| 동일 CCD / 동일 소켓 이웃 코어 | ~40~50 ns (CCD 내, IF 경유) | ~30~40 ns (메시 홉 2~4개) | mlc --loaded_latency |
| 다른 CCD / 다른 타일 | ~80~120 ns (CCD→IOD→CCD) | ~50~70 ns (메시 홉 4~8개) | numactl --membind |
| 소켓 간 (2P) | ~130~180 ns (xGMI 4링크) | ~130~160 ns (UPI 3~4링크) | mlc --bandwidth_matrix |
sched_domain 계층에서 NUMA 거리(distance)로 반영되며,
NPS(NUMA Per Socket) 설정에 따라 CCD 그룹을 별도 NUMA 노드로 분리하여 지역성을 개선할 수 있습니다.
Intel의 SNC(Sub-NUMA Clustering)도 유사한 목적으로 소켓 내 NUMA 분할을 제공합니다.
소프트웨어 아키텍처 관점
앞선 섹션에서는 하드웨어 관점의 토폴로지 구조(AMD Chiplet, Intel Hybrid, ARM DynamIQ)를 비교했습니다. 이제 소프트웨어 아키텍처 관점에서 커널 토폴로지 서브시스템의 설계 원칙, 계층 구조, 데이터 흐름, 확장성 모델, 횡단 관심사(Cross-Cutting Concerns)를 분석합니다.
계층적 추상화 아키텍처
커널 토폴로지 서브시스템은 5계층 추상화 모델로 설계되어, 하드웨어 특화 코드와 아키텍처 독립 코드를 철저히 분리합니다. 각 계층은 하위 계층이 제공하는 원시 데이터(Raw Data)를 추상화하여 상위 계층에 정규화된 형태로 전달합니다.
| 계층 | 소스 위치 | 역할 | 입력 | 출력 |
|---|---|---|---|---|
| L1: 하드웨어 인터페이스 | CPUID / MPIDR / DT | 프로세서가 자신의 토폴로지를 HW 명령어로 보고 | 전기적 신호 / 마이크로아키텍처 상태 | APIC ID, core_id, die_id 비트 필드 |
| L2: 아키텍처 계층 | arch/x86/kernel/cpu/arch/arm64/kernel/ | CPUID/MPIDR/DT 파싱, 아키텍처별 매핑 | HW 보고 원시 값 | cpu_die_id, cpu_core_id, threads_per_core |
| L3: 제네릭 토폴로지 계층 | drivers/base/topology.cinclude/linux/topology.h | 아키텍처 독립적 토폴로지 표현, sysfs 노출 | arch 계층의 매핑 결과 | topology_*_cpumask(), sysfs 속성 |
| L4: 스케줄러 토폴로지 계층 | kernel/sched/topology.c | sched_domain/sched_group 계층 구조 조립 | 제네릭 토폴로지 + NUMA 거리 | sched_domain 트리, SD 플래그 |
| L5: 소비자 계층 | kernel/sched/fair.ckernel/sched/cpufreq.cdrivers/base/arch_topology.c | 토폴로지 정보 소비: 스케줄링, EAS, cpufreq, cgroup | sched_domain 트리, EM, cpu_capacity | 태스크 배치 결정, 주파수 선택 |
데이터 흐름 아키텍처
토폴로지 정보는 단방향 파이프라인으로 흐릅니다. 부팅 시 한 번 구축되며, 그 후에는 핫플러그(Hotplug)/CPU 온라인 상태 변화 시에만 증분 갱신됩니다. 이 "구축 후 읽기(Build-then-Read)" 패턴은 런타임 오버헤드(Overhead)를 최소화합니다.
/* 부팅 시 토폴로지 데이터 흐름 (단방향 파이프라인) */
/* 1단계: BSP(Boot Strap Processor) 초기화 */
start_kernel()
└→ boot_cpu_init() /* CPU 0 온라인 */
└→ setup_arch() /* 아키텍처별 초기화 */
└→ prefill_possible_map() /* possible CPUs 설정 */
/* 2단계: AP(Application Processor) 부팅 */
smp_init()
└→ for_each_possible_cpu(cpu)
└→ do_cpu_up(cpu)
└→ arch_cpuhp_init() /* L1: CPUID/MPIDR 읽기 */
└→ store_cpu_topology(cpu) /* L2: arch 매핑 */
└→ topology_set_dev(cpu) /* L3: sysfs 생성 */
/* 3단계: sched_domain 구축 (L4) */
sched_init_smp()
└→ build_sched_domains(cpu_map)
└→ for_each_sd_topology(tl) /* SMT → CL → MC → DIE → NUMA */
└→ 각 CPU별 sched_domain 할당
└→ sched_group 링 구성
└→ SD 플래그 설정
/* 4단계: 소비자 초기화 (L5) */
sched_init_fair()
└→ init_cfs_bandwidth() /* cgroup 준비 */
└→ EAS 활성화 검사 (EM + schedutil)
cpuhp_setup_state() 콜백 체인이 실행되어, 토폴로지 갱신 → sched_domain 재구축 → NUMA 거리 재계산이 순차적으로 발생합니다. rebuild_sched_domains()가 전체 도메인 트리를 재조립하므로 핫플러그 빈도가 높은 시스템에서는 오버헤드가 발생할 수 있습니다.
확장성 모델 — 콜백(Callback) 기반 플러그인 아키텍처
토폴로지 서브시스템은 콜백 함수 포인터(callback function pointer) 기반 플러그인 아키텍처를 사용합니다. 각 아키텍처는 함수 포인터 집합을 등록하고, 제네릭 계층은 이를 간접 호출하여 아키텍처 독립 코드를 유지합니다.
/* include/linux/topology.h — 제네릭 토폴로지 인터페이스 */
/* 아키텍처가 구현해야 할 콜백 함수들 (weak 기본값 제공) */
/* 코어 ID — 같은 물리 코어의 SMT 형제 식별 */
static inline int topology_core_id(int cpu);
/* SMT 형제 cpumask 반환 — sched_domain SMT 레벨 구성 */
static inline const struct cpumask *topology_sibling_cpumask(int cpu);
/* 코어 그룹 cpumask 반환 — sched_domain MC 레벨 구성 */
static inline const struct cpumask *topology_core_cpumask(int cpu);
/* 다이 cpumask 반환 — sched_domain DIE 레벨 구성 */
static inline const struct cpumask *topology_die_cpumask(int cpu);
/* CPU 용량 — 비대칭 시스템에서 코어별 상대 성능 (0~1024) */
static inline unsigned long topology_get_cpu_scale(struct sched_domain *sd, int cpu);
/* 스케줄링 도메인 토폴로지 레벨 정의 — 아키텍처별 커스터마이징 가능 */
struct sched_domain_topology_level;
/* x86: arch/x86/kernel/smpboot.c에서 topology_smt/cluster/core/die_cpumask 구현
* ARM64: arch/arm64/kernel/topology.c에서 store_cpu_topology()로 저장
* RISC-V: arch/riscv/kernel/topology.c에서 DT 파싱 후 동일 인터페이스 구현 */
arch/riscv/kernel/topology.c에서 Device Tree의 cpu-map 노드를 파싱, (2) topology_*_cpumask() 콜백 구현, (3) CONFIG_SCHED_SMT/CONFIG_SCHED_MC 활성화. 이후 제네릭 계층(L3~L5)은 수정 없이 자동 동작합니다.
횡단 관심사 — 토폴로지를 소비하는 서브시스템
토폴로지 정보는 단일 소비자가 아닌 다수 서브시스템이 횡단적으로 소비합니다. 이는 토폴로지가 커널의 "공유 인프라스트럭처(Shared Infrastructure)" 역할을 한다는 것을 의미합니다.
| 소비자 서브시스템 | 사용하는 토폴로지 정보 | 용도 | 갱신 트리거 |
|---|---|---|---|
| CFS 스케줄러 | sched_domain 트리, cpu_capacity | 태스크 배치, 로드 밸런싱, SMT 형제 선택 | 도메인 재구축, HFI 갱신 |
| EAS (Energy Aware Scheduling) | SD_ASYM_CPUCAPACITY, Energy Model | 에너지 효율적 CPU 선택 | EM 등록, schedutil 거버너 변경 |
| CPUFreq (주파수 관리) | performance domain, 클러스터 구조 | 주파수 도메인 결정, DVFS 정책 | 정책 등록/해제 |
| CPUIdle (유휴 관리) | 코어/클러스터 유휴 상태 | 깊은 C-state 진입 여부 (형제 모두 유휴 시) | 유휴 상태 전환 |
| NUMA 밸런싱 | NUMA 노드, node_distance | 태스크 메모리 지역성 최적화 | NUMA 거리 테이블 |
| cgroup cpuset | CPU 범위, NUMA 노드 | 태스크 CPU 제한, 메모리 노드 제한 | cgroup 계층 변경 |
| IRQ 관리 | NUMA 노드, CPU 친화도(Affinity) | IRQ 분산, irqbalance, affinity 설정 | IRQ 등록, 핫플러그 |
| 보안 완화 | SMT 형제 관계, 코어 공유 | Core Scheduling 쿠키 기반 격리(Isolation) | prctl(PR_SCHED_CORE) |
cpuhp_setup_state()의 단계적 콜백 체인으로 이를 보장합니다: 토폴로지 갱신 → sched_domain 재구축 → cgroup/EM 갱신 → IRQ 재분산 순서로 실행됩니다.
API/ABI 안정성 설계
커널 토폴로지 정보는 두 가지 안정성 경계를 가집니다.
sysfs ABI — 사용자 공간 계약
/sys/devices/system/cpu/cpuN/topology/ 디렉터리의 파일들은 Stable ABI로 관리됩니다. Documentation/ABI/stable/sysfs-devices-system-cpu에 문서화되어 있으며, 호환성 보장이 필요한 파일만 drivers/base/topology.c에서 노출됩니다.
| sysfs 파일 | 안정성 | 조건 |
|---|---|---|
physical_package_id | Stable | 항상 존재 |
core_id | Stable | 항상 존재 |
thread_siblings_list | Stable | 항상 존재 |
core_siblings_list | Stable | 항상 존재 |
die_id | Stable | 아키텍처가 die 지원 시에만 생성 (커널 4.18+) |
cluster_id | Stable | 아키텍처가 cluster 지원 시에만 생성 (커널 5.16+) |
book_id / drawer_id | Stable | s390 전용 — x86/ARM에서는 생성 안 함 |
die_id, cluster_id, book_id, drawer_id는 해당 아키텍처가 관련 매크로(Macro)를 정의할 때만 sysfs에 나타납니다. x86 시스템에서 book_id/drawer_id가 보이지 않는 것은 정상 동작이며, s390에서는 반대로 die_id가 없을 수 있습니다.
커널 내부 API — 아키텍처 계약
커널 내부에서는 topology_* 함수들이 약한 심볼(weak symbol)로 정의되어, 아키텍처가 재정의하지 않으면 기본 동작을 수행합니다. 이는 새 아키텍처가 모든 콜백을 구현할 필요 없이 필요한 부분만 오버라이드(Override)할 수 있게 합니다.
성능 아키텍처 — 자료구조와 알고리즘
토폴로지 서브시스템의 성능은 cpumask 연산 비용에 직접 연결됩니다. 대형 시스템(192~288코어)에서 cpumask는 수십 워드(word)에 달하므로, 비트 연산의 캐시 지역성(Cache Locality)이 전체 스케줄링 지연에 영향을 줍니다.
| 자료구조 | 크기 | 주요 연산 | 복잡도 | 최적화 |
|---|---|---|---|---|
cpumask (NR_CPUS=256) | 32 bytes (4 × 64bit) | 비트 AND/OR/설정/검색 | O(N/64) | find_next_bit() 최적화 |
cpumask (NR_CPUS=8192) | 1024 bytes | 동일 | O(N/64) | per-NUMA 분할 (sched_ext v6.15) |
sched_domain | ~200 bytes | 도메인 트리 순회 | O(도메인 수) | RCU 보호, 캐시 라인(Cache Line) 정렬 |
sched_group (순환 링) | ~64 bytes | 링 순회 | O(그룹 수) | 그룹 수 = 도메인 내 CPU 수 |
struct cpu_topology (ARM) | ~32 bytes/CPU | 직접 접근 | O(1) | per-CPU 배열 |
for_each_cpu() 루프의 비용이 스케줄링 결정 지연에 직접 영향을 줍니다. sched_ext의 per-NUMA cpumask 분할(v6.15)은 이 문제에 대한 아키텍처 수준의 해결책으로, 전역 마스크 대신 노드별 마스크를 순회하여 검색 범위를 축소합니다.
신뢰 경계와 보안 아키텍처
토폴로지 정보는 하드웨어에서 발생하지만, 신뢰 경계(Trust Boundary)를 두 번 통과합니다. 각 경계에서의 신뢰 모델을 이해하는 것이 보안 아키텍처 관점에서 중요합니다.
| 신뢰 경계 | 정보 제공자 | 정보 소비자 | 위협 모델 | 완화 |
|---|---|---|---|---|
| HW → 펌웨어 | CPU 마이크로아키텍처 | BIOS/UEFI | 펌웨어(Firmware) 버그로 인한 잘못된 토폴로지 보고 | 커널 검증 (cpu_smt_check_ancestors()) |
| 펌웨어 → 커널 | ACPI PPTT / Device Tree / CPUID | 커널 L2 계층 | 악의적 펌웨어가 SMT 형제를 숨기거나 위조 | 토폴로지 교차 검증, 커널 5.10+ 강화 |
| 커널 → 사용자 공간 | sysfs topology/ | 애플리케이션, 라이브러리 | 토폴로지 정보 기반 사이드채널 (L1TF, MDS) | 정보 접근 제한, 완화 기법 적용 |
테스트 및 검증 아키텍처
토폴로지 서브시스템은 다음 검증 경로를 제공합니다.
- 커널 내부 검증:
CONFIG_DEBUG_PER_CPU_MAPS— cpumask 일관성 런타임 검사. 잘못된 SMT 형제 매핑이나 누락된 도메인 레벨을 부팅 시 감지합니다. - debugfs 인터페이스:
/sys/kernel/debug/sched/domains/— 구축된 sched_domain 트리의 전체 구조를 런타임에 검사할 수 있습니다. 각 도메인의 name, span, flags, groups를 읽을 수 있습니다. - 단위 테스트:
tools/testing/selftests/sched/— sched_ext 기반 토폴로지 테스트. KUnit으로 topology callback의 반환값을 검증합니다. - QEMU 토폴로지 주입:
-smp옵션으로 가상 머신의 토폴로지를 제어하여, 다양한 CCD/NUMA/SMT 구성을 소프트웨어로 재현할 수 있습니다.-smp 8,sockets=2,cores=4,threads=1 -numa node,cpus=0-3,mem=1G -numa node,cpus=4-7,mem=1G로 2-NUMA 8코어 시스템을 시뮬레이션합니다.
# QEMU로 다양한 토폴로지 시뮬레이션
# AMD EPYC 64코어 NPS4 시뮬레이션
$ qemu-system-x86_64 -smp 64,sockets=1,cores=32,threads=2 \
-numa node,cpus=0-15,mem=4G \
-numa node,cpus=16-31,mem=4G \
-numa node,cpus=32-47,mem=4G \
-numa node,cpus=48-63,mem=4G
# Intel 하이브리드 시뮬레이션 (P-core 4 + E-core 4, SMT 없음)
$ qemu-system-x86_64 -smp 8,sockets=1,cores=8,threads=1 \
-cpu host -enable-kvm
# 검증: lscpu로 토폴로지 확인
$ lscpu -e=CPU,SOCKET,NODE,CORE,ONLINE
커널의 토폴로지 탐지
커널은 부팅 초기에 각 CPU의 하드웨어 식별자를 수집하고, 물리적 계층 관계(소켓 · 다이 · 코어 · SMT 스레드)를 struct cpu_topology에 저장합니다. 이 데이터는 스케줄러가 sched_domain 계층을 조립하고, NUMA 메모리 정책을 설정하며, EAS가 에너지 모델을 구성하는 데 공통으로 사용됩니다.
x86 CPUID 기반 탐지
x86 커널은 CPUID 명령어를 사용하여 CPU 토폴로지를 탐지합니다. 핵심 Leaf:
| Leaf | 용도 | 제공 정보 |
|---|---|---|
| 0x0B | Extended Topology | SMT/Core 레벨 ID, 토폴로지 유형 |
| 0x1F | V2 Extended Topology | Module/Tile/Die 레벨 추가 (SPR+) |
| 0x8000001E | AMD 토폴로지 | Compute Unit ID, Node(CCD) ID |
| 0x04 / 0x8000001D | 캐시 토폴로지 | 캐시 레벨별 공유 범위 |
/* arch/x86/kernel/cpu/topology.c - detect_extended_topology() */
int detect_extended_topology(struct cpuinfo_x86 *c)
{
unsigned int eax, ebx, ecx, edx;
unsigned int sub_index = 0;
unsigned int leaf;
/* Leaf 0x1F를 우선 시도 (V2), 없으면 0x0B 사용 */
leaf = 0x1f;
cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
if (ebx == 0) {
leaf = 0x0b;
cpuid_count(leaf, 0, &eax, &ebx, &ecx, &edx);
if (ebx == 0)
return -1;
}
/* ECX[15:8] = 토폴로지 레벨 유형
* 1=SMT, 2=Core, 3=Module, 4=Tile, 5=Die */
do {
cpuid_count(leaf, sub_index, &eax, &ebx, &ecx, &edx);
unsigned level_type = (ecx >> 8) & 0xff;
unsigned shift = eax & 0x1f;
switch (level_type) {
case 1: /* SMT */
c->x86_max_threads = ebx & 0xffff;
break;
case 2: /* Core */
c->x86_max_cores = ebx & 0xffff;
break;
case 5: /* Die */
c->x86_max_dies = ebx & 0xffff;
break;
}
sub_index++;
} while (level_type != 0);
/* x2APIC ID로 고유 ID 설정 */
c->topo.apicid = edx;
return 0;
}
ARM DT/ACPI 기반 탐지
ARM64 시스템은 Device Tree 또는 ACPI PPTT(Processor Properties Topology Table)로 토폴로지를 기술합니다.
/* Device Tree 예시: cpu-map 노드 */
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 { /* big 클러스터 */
core0 { cpu = <&cpu0>; };
core1 { cpu = <&cpu1>; };
core2 { cpu = <&cpu2>; };
core3 { cpu = <&cpu3>; };
};
cluster1 { /* LITTLE 클러스터 */
core0 { cpu = <&cpu4>; };
core1 { cpu = <&cpu5>; };
core2 { cpu = <&cpu6>; };
core3 { cpu = <&cpu7>; };
};
};
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-x4";
capacity-dmips-mhz = <1024>; /* 상대적 성능 */
};
cpu4: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a520";
capacity-dmips-mhz = <480>; /* big 대비 ~47% */
};
};
/* ACPI PPTT 파싱: drivers/acpi/pptt.c */
static int topology_get_acpi_cpu_tag(struct acpi_table_header *table,
unsigned int cpu,
int level, int flag)
{
struct acpi_pptt_processor *node;
/* PPTT에서 CPU의 프로세서 노드를 탐색
* level 0 = thread, 1 = core, 2 = cluster, 3 = package */
node = acpi_find_processor_node(table, cpu);
while (node && level--) {
node = fetch_pptt_node(table, node->parent);
}
return node ? node->acpi_processor_id : -1;
}
/* arch/arm64/kernel/topology.c - parse_acpi_topology() */
int __init parse_acpi_topology(void)
{
int cpu;
for_each_possible_cpu(cpu) {
int topology_id;
topology_id = find_acpi_cpu_topology(cpu, 0); /* thread */
cpu_topology[cpu].thread_id = topology_id;
topology_id = find_acpi_cpu_topology(cpu, 1); /* core */
cpu_topology[cpu].core_id = topology_id;
topology_id = find_acpi_cpu_topology(cpu, 2); /* cluster */
cpu_topology[cpu].cluster_id = topology_id;
topology_id = find_acpi_cpu_topology_package(cpu);
cpu_topology[cpu].package_id = topology_id;
}
return 0;
}
초기화 흐름
커널 부팅 시 토폴로지 구축 순서:
- BSP(Bootstrap Processor)에서
identify_cpu()→ CPUID/PPTT 파싱 - 각 AP(Application Processor) 깨움 →
smp_callin()에서 개별 토폴로지 ID 설정 topology_init()→/sys/devices/system/cpu/cpuN/topology/sysfs 노출sched_init_domains()→ 토폴로지 기반 스케줄링 도메인 구축
start_kernel()
→ setup_arch()
→ early_cpu_init() /* x86: CPUID 기본 파싱 */
→ init_intel_cacheinfo() /* 캐시 토폴로지 */
→ smp_init()
→ cpu_up() (per AP)
→ identify_secondary_cpu()
→ set_cpu_sibling_map() /* SMT 형제 설정 */
→ sched_init_domains() /* 스케줄링 도메인 */
→ topology_init() /* sysfs 노출 */
CPUID leaf 0x1F (Topological Extensions)
Sapphire Rapids/Granite Rapids/EMRaps Rapids부터 Intel이 도입한 Topological Extensions(topoext)는 CPUID leaf 0x1F를 통해 기존 leaf 0xB보다 더 정밀한 토폴로지 레벨을 노출합니다. X2APIC 이상 모드에서 CPUID leaf 0xB의 5개 레벨(SMT=1, Core=2, Module=3, Tile=4, Die=5)을 넘어 Package=6까지 제공합니다.
/* arch/x86/kernel/cpu/topology.c - CPUID leaf 0x1F 파싱 */
/*
* Leaf 0x1F: Extended Topology Enumeration
* sub-leaf (ECX) = 토폴로지 레벨 인덱스 (0, 1, 2, ...)
* EAX[4:0] : 동일 레벨 내 sibling 수 - 1
* EAX[31:5] : Reserved
* EBX[15:0] : x2APIC ID (xAPIC일 경우 APIC ID)
* ECX[4:0] : 동일 레벨 내 thread ID
* ECX[7:5] : 토폴로지 레벨 유형 (SMT=1, Core=2, Module=3,
* Tile=4, Die=5, Package=6)
* ECX[15:8] : Reserved
*/
static void identify_cpu_topology_0x1f(struct cpuinfo_x86 *c)
{
unsigned eax, ebx, ecx,edx;
int level = 0;
if (!cpu_has(c, X86_FEATURE_TOPOEXT))
return;
do {
cpuid_count(0x1f, level, &eax, &ebx, &ecx, &edx);
int lvl_type = (ecx >> 5) & 7;
switch (lvl_type) {
case 6: /* Package — physical socket */
c->x86_package = ebx & 0xffff;
break;
case 5: /* Die */
c->cpu_die_id = (ebx >> 16) & 0xff;
break;
case 4: /* Tile (Sapphire Rapids+) */
c->cpu_tile_id = (ebx >> 16) & 0xff;
break;
case 2: /* Core */
c->cpu_core_id = ecx & 0x1f;
break;
case 1: /* SMT */
c->x86_max_threads = (eax & 0x1f) + 1;
break;
}
level++;
} while (lvl_type > 0 && level < 8);
}
| 레벨 유형 | 값 | 설명 | 대응 sysfs |
|---|---|---|---|
| SMT | 1 | 하이퍼스레딩 스레드 | thread_siblings |
| Core | 2 | 물리 코어 | core_id |
| Module | 3 | 모듈 (Intel E-core 클러스터 등) | cluster_id |
| Tile | 4 | 타일 (Sapphire Rapids+ tile 단위) | — |
| Die | 5 | 다이 (Sapphire Rapids+: compute tile, I/O tile) | die_id |
| Package | 6 | 물리 소켓 (최상위) | physical_package_id |
X86_FEATURE_TOPOEXT 플래그가 설정된 경우 0x1F 기반 파싱을 우선합니다.
sysfs 토폴로지 인터페이스
cpu topology
커널은 /sys/devices/system/cpu/cpuN/topology/에 각 논리 CPU의 토폴로지 정보를 노출합니다.
| 파일 | 설명 | 예시 (AMD EPYC) |
|---|---|---|
physical_package_id | 물리 소켓 ID | 0 |
die_id | 다이(CCD) ID | 0~11 |
cluster_id | 클러스터 ID (ARM, Intel E-core) | 0 |
core_id | 물리 코어 ID | 0~95 |
thread_siblings | SMT 형제 CPU 비트맵(Bitmap) | 00000003 |
thread_siblings_list | SMT 형제 CPU 목록 | 0,96 |
book_id | 책(Book) ID (x86 topoext; 소켓 이상 단위 계층) | 0 |
drawer_id | 서랍(Drawer) ID (최상위 물리 계층) | 0 |
core_siblings | 같은 패키지 내 CPU 비트맵 | (전체) |
die_cpus_list | 같은 다이의 CPU 목록 | 0-7,96-103 |
topology.h는 Socket(physical_package) → Die → Core → Thread 외에도
topology_book_id()와 topology_drawer_id() 매크로를 정의합니다. Book과 Drawer는
대규모 서버 시스템(예: IBM POWER, 일부 x86 multi-socket chasis)에서 소켓 이상 단위의 계층적 그룹화를 위해 존재합니다.
현재 주류 x86 서버 CPU에서 Book과 Drawer는 0으로 고정되며, 아키텍처에서 관련 매크로를 정의하지 않으면
include/linux/topology.h의 기본값(-1)이 적용됩니다. Device Tree 기반 ARM 시스템에서는
이러한 상위 레벨이 ACPI PPTT 또는 DT cpu-map 노드의 최상위 컨테이너(Container)로 매핑될 수 있습니다.
# CPU 0의 토폴로지 정보 확인
$ for f in /sys/devices/system/cpu/cpu0/topology/*; do
echo "$(basename $f): $(cat $f)"
done
physical_package_id: 0
die_id: 0
cluster_id: 0
core_id: 0
thread_siblings_list: 0,96
cache hierarchy
# CPU 0의 캐시 계층 확인
$ ls /sys/devices/system/cpu/cpu0/cache/
index0 index1 index2 index3
$ for idx in 0 1 2 3; do
dir=/sys/devices/system/cpu/cpu0/cache/index${idx}
echo "L$(cat $dir/level) $(cat $dir/type): $(cat $dir/size), shared: $(cat $dir/shared_cpu_list)"
done
L1 Data: 32K, shared: 0,96 # SMT 형제 공유
L1 Instruction: 32K, shared: 0,96
L2 Unified: 1024K, shared: 0,96 # 코어 단위
L3 Unified: 32768K, shared: 0-7,96-103 # CCD(CCX) 단위
확인 도구
# lscpu: 가장 간단한 토폴로지 요약
$ lscpu
Architecture: x86_64
CPU(s): 192
Thread(s) per core: 2
Core(s) per socket: 96
Socket(s): 1
NUMA node(s): 4
...
# lstopo (hwloc): 그래픽 토폴로지 뷰
$ lstopo --of ascii
$ lstopo --of png topology.png
# /proc/cpuinfo: 상세 CPU 정보
$ grep -m1 "siblings\|cpu cores\|physical id\|core id" /proc/cpuinfo
# numactl: NUMA 토폴로지 + 메모리
$ numactl --hardware
SMT/Hyper-Threading
SMT(Simultaneous Multi-Threading)는 하나의 물리 코어가 두 개 이상의 논리 스레드를 동시에 실행하는 기술입니다. Intel의 구현을 Hyper-Threading(HT)라고 부릅니다. 공유 자원에서 발생하는 성능 특성과 보안 취약점, 커널 스케줄링 알고리즘을 분석합니다.
공유/분리 자원 비교
SMT의 핵심은 물리 코어 자원을 두 논리 스레드가 어떻게 공유하거나 분리하는지입니다.
| 자원 | 공유/분리 | Intel HT | AMD SMT | 비고 |
|---|---|---|---|---|
| 실행 유닛 (ALU/FPU/SIMD) | 공유 | 동적 할당 | 동적 할당 | 스레드별 발행 슬롯 경쟁 |
| ROB (Reorder Buffer) | 공유 | 절반씩 분할 | 절반씩 분할 | ROB 크기의 절반만 사용 가능 |
| RS (Reservation Station) | 공유 | 동적 할당 | 동적 할당 | 두 스레드 합산 → 단일 스레드보다 넓음 |
| 분기 예측기 (BTB) | 공유 ⚠ | 공유 | 공유 | Spectre V2 취약점의 원인 |
| L1 명령어 캐시 | 공유 | 공유 | 공유 | 코드 밀도 높으면 경합(Contention) |
| L1 데이터 캐시 | 공유 ⚠ | 공유 | 공유 | L1TF 취약점의 원인 |
| TLB (L1 DTLB/ITLB) | 공유 | 공유 | 공유 | 페이지 테이블(Page Table) 워크 경합 가능 |
| 레지스터 파일 | 분리 | 개별 | 개별 | 각 스레드가 완전한 레지스터 집합 보유 |
| 프로그램 카운터(IP/PC) | 분리 | 개별 | 개별 | 독립적인 실행 흐름 |
| LAPIC | 분리 | 개별 | 개별 | 각 논리 CPU가 독립 APIC ID |
커널 SMT 스케줄링
커널은 SMT 형제(sibling) 코어를 인식하고 select_idle_sibling()으로 태스크 배치를 최적화합니다.
/* kernel/sched/fair.c - SMT 형제 idle CPU 탐색 */
static int select_idle_sibling(struct task_struct *p,
int prev, int target)
{
struct sched_domain *sd;
int i, recent_used_cpu;
/* 1. target CPU가 idle이면 바로 반환 */
if (available_idle_cpu(target) || sched_idle_cpu(target))
return target;
/* 2. prev CPU의 SMT 형제 중 idle 탐색
* → L1/L2 캐시를 공유하므로 캐시 온도(warm) 유지 가능 */
for_each_cpu(i, cpu_smt_mask(prev)) {
if (available_idle_cpu(i))
return i;
}
/* 3. LLC 도메인 내 idle CPU 탐색 */
sd = rcu_dereference(per_cpu(sd_llc, target));
if (sd)
return select_idle_cpu(p, sd, prev == target, target);
return target;
}
/* sched_smt_power_savings: SMT 코어 내 두 번째 스레드를
* 활성화하기 전에 idle 코어를 먼저 채우는 정책 (기본 비활성화) */
SMT 제어
# SMT 상태 확인
$ cat /sys/devices/system/cpu/smt/control
on # on | off | forceoff | notsupported | notimplemented
$ cat /sys/devices/system/cpu/smt/active
1 # 1=활성화, 0=비활성화
# SMT 런타임 비활성화 (보안/전력 절약 목적)
$ echo off > /sys/devices/system/cpu/smt/control
# 부팅 시 SMT 비활성화 (Spectre/L1TF 완화)
# GRUB_CMDLINE_LINUX="nosmt" ← /etc/default/grub에 추가
# SMT 비활성화 시 성능 영향 확인
$ lscpu | grep -E "Thread|CPU\(s\)"
Thread(s) per core: 1 # nosmt 시 1
CPU(s): 48 # 물리 코어 수만 활성
SMT와 보안 취약점
SMT의 자원 공유 구조는 여러 사이드채널 공격의 근본 원인입니다. 상세 내용은 13. 보안 취약점과 토폴로지 섹션을 참조하세요.
| 취약점 | SMT 공유 자원 | 영향 | 완화 |
|---|---|---|---|
| Spectre V2 | BTB (Branch Target Buffer) | sibling 스레드의 분기 패턴 유출 | IBRS/IBPB/STIBP, Retpoline |
| L1TF (Foreshadow) | L1D 캐시 | 가상 메모리(Virtual Memory) 내용 유출 | PTI, L1D 플러시(Flush), nosmt |
| MDS (RIDL/Fallout) | 내부 버퍼(Buffer) (LFB/SB) | 파이프라인 버퍼 내용 유출 | VERW, MD_CLEAR |
스케줄링 도메인 계층
커널 스케줄러는 토폴로지 탐지 결과를 바탕으로 sched_domain 계층을 조립합니다. 각 레벨은 그 경계 안의 CPU들이 얼마나 "가깝게" 연결되어 있는지를 나타내며, 부하 균형(Load Balancing) 시 가까운 레벨부터 먼 레벨 순으로 태스크 이동 후보를 탐색합니다.
SMT → MC → DIE → NUMA
커널 스케줄러는 토폴로지를 기반으로 스케줄링 도메인(sched_domain)의 계층 구조를 구축합니다. 각 레벨은 캐시 공유 범위와 통신 비용을 반영합니다.
| 도메인 레벨 | 의미 | SD 플래그 | 대표 예 |
|---|---|---|---|
| SMT | 하이퍼스레드 형제 | SD_SHARE_CPUCAPACITY | CPU 0 ↔ CPU 96 |
| MC (Multi-Core) | L3 공유 코어 | SD_SHARE_PKG_RESOURCES | CCD 내 8코어 |
| CL (Cluster) | L2 공유 클러스터 | SD_SHARE_CLS_RESOURCES | Intel E-core 4개 |
| DIE | 같은 다이 | — | Intel Tile |
| NUMA | NUMA 노드 | SD_NUMA | NPS 분할 영역 |
NUMA 경계를 넘는 밸런싱의 구체적인 메커니즘 — Automatic NUMA Balancing, 태스크 선호 노드 결정, NUMA 그룹, 메모리 정책(mbind/set_mempolicy) 등은 NUMA — NUMA-aware 스케줄링에서 상세히 다룹니다.
도메인 구축
/* kernel/sched/topology.c - 스케줄링 도메인 구축 */
/* 기본 토폴로지 레벨 정의 */
static struct sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT
{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) },
#endif
#ifdef CONFIG_SCHED_CLUSTER
{ cpu_clustergroup_mask, cpu_cluster_flags, SD_INIT_NAME(CL) },
#endif
#ifdef CONFIG_SCHED_MC
{ cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
#endif
{ cpu_cpu_mask, SD_INIT_NAME(PKG) },
{ NULL, },
};
/* cpu_coregroup_mask: L3를 공유하는 코어 그룹 반환 */
static const struct cpumask *cpu_coregroup_mask(int cpu)
{
return topology_core_cpumask(cpu);
}
/* build_sched_domains: 도메인 트리 구축 */
static int build_sched_domains(const struct cpumask *cpu_map,
struct sched_domain_attr *attr)
{
/* 각 토폴로지 레벨에 대해:
* 1. sched_domain 할당
* 2. sched_group 링 구성
* 3. 로드 밸런싱 파라미터 초기화 */
for_each_sd_topology(tl) {
for_each_cpu(i, cpu_map) {
sd = __build_sched_domain(tl, cpu_map, attr, ...);
...
}
}
...
}
도메인 확인
# 스케줄링 도메인 계층 확인
$ ls /sys/kernel/debug/sched/domains/cpu0/
domain0 domain1 domain2 domain3
# 또는 /proc/sys/kernel/sched_domain/
$ ls /proc/sys/kernel/sched_domain/cpu0/
domain0 domain1 domain2 domain3
# 각 도메인의 이름과 범위
$ cat /proc/sys/kernel/sched_domain/cpu0/domain0/name
SMT
$ cat /proc/sys/kernel/sched_domain/cpu0/domain1/name
MC
$ cat /proc/sys/kernel/sched_domain/cpu0/domain2/name
DIE
$ cat /proc/sys/kernel/sched_domain/cpu0/domain3/name
NUMA
# 밸런싱 간격, 플래그 확인
$ cat /proc/sys/kernel/sched_domain/cpu0/domain1/min_interval
4
$ cat /proc/sys/kernel/sched_domain/cpu0/domain1/flags
559
비대칭 스케줄링
ASYM_PACKING
SD_ASYM_PACKING은 SMT 레벨에서 사용되는 플래그로, 하이퍼스레드 형제 코어에 태스크를 비대칭적으로 배치합니다.
/* kernel/sched/fair.c - SMT 비대칭 패킹 로직 */
/*
* ASYM_PACKING 정책:
* - SMT 형제 중 높은 우선순위(smt_gain이 큰) 스레드를 먼저 채움
* - 두 번째 스레드는 가능하면 다른 물리 코어로 이동
* - 결과: 물리 코어를 최대한 활용하여 IPC 극대화
*
* Intel: 보통 thread 0이 우선순위 높음
* AMD: Preferred Core 기능으로 최적 코어 선별
*/
static int find_busiest_group(struct lb_env *env,
struct sd_lb_stats *sds)
{
if (env->sd->flags & SD_ASYM_PACKING) {
/* 우선순위가 낮은 CPU에서 높은 CPU로 태스크 이동 */
if (sched_asym_prefer(env->dst_cpu, busiest->asym_prefer_cpu))
return busiest;
}
...
}
ASYM_CPUCAPACITY (EAS)
Energy-Aware Scheduling(EAS)은 이종 코어 시스템에서 성능과 에너지 효율의 균형을 맞추는 스케줄러 기능입니다.
/* kernel/sched/fair.c - find_energy_efficient_cpu() */
static int find_energy_efficient_cpu(struct task_struct *p,
int prev_cpu)
{
struct root_domain *rd = cpu_rq(prev_cpu)->rd;
unsigned long best_delta = ULONG_MAX;
int best_cpu = -1;
for_each_cpu_and(cpu, sched_domain_span(sd), p->cpus_ptr) {
unsigned long cur_delta;
/* 각 CPU(performance domain)별 에너지 소비 예측 */
cur_delta = compute_energy(p, cpu, pd);
if (cur_delta < best_delta) {
best_delta = cur_delta;
best_cpu = cpu;
}
}
/* 에너지 절감량이 6% 미만이면 prev_cpu 유지 (마이그레이션 비용) */
if ((prev_delta - best_delta) * 100 < prev_delta * 6)
return prev_cpu;
return best_cpu;
}
/* EAS 활성화 조건:
* 1. SD_ASYM_CPUCAPACITY 플래그 설정
* 2. Energy Model(EM) 등록 (em_dev_register_perf_domain)
* 3. schedutil CPUFreq 거버너 사용 (EAS는 schedutil과만 동작)
* 4. Energy Model의 복잡도가 관리 가능 수준일 것
* (공식 문서에 명시된 CPU 수 hard limit는 없으나,
* EM 탐색 비용이 선형 증가하므로 대규모 시스템에서는
* 스케줄링 오버헤드 증가가 실질적 제약이 됨) */
Intel Thread Director 커널 통합
/* drivers/thermal/intel/intel_hfi.c - HFI 커널 통합 */
/* HFI 인터럽트 핸들러: 하드웨어가 성능/효율 테이블 갱신 시 호출 */
static void intel_hfi_online(unsigned int cpu)
{
struct hfi_instance *hfi = per_cpu(hfi_instance, cpu);
/* CPU별 성능 능력(perf_cap)과 에너지 효율(ee_cap) 읽기 */
u8 perf_cap = hfi->local_table[cpu].perf_cap;
u8 ee_cap = hfi->local_table[cpu].ee_cap;
/* 스케줄러에 CPU 용량 업데이트 알림
* → CFS의 capacity_of()에 반영
* → 높은 perf_cap = P-core, 높은 ee_cap = E-core 선호 */
topology_set_cpu_capacity(cpu, perf_cap);
}
/* IPC 클래스 기반 코어 선택 (커널 6.6+) */
/* Thread Director가 실시간으로 IPC 클래스를 업데이트하면,
* 스케줄러가 해당 태스크에 적합한 코어 유형을 선택 */
ARM cpu-capacity
ARM에서는 Device Tree의 capacity-dmips-mhz 속성으로 각 코어의 상대적 성능을 지정합니다.
/* drivers/base/arch_topology.c - CPU 용량 설정 */
void topology_normalize_cpu_scale(void)
{
unsigned long capacity, max_capacity = 0;
int cpu;
/* DT의 capacity-dmips-mhz 기반 최대값 탐색 */
for_each_possible_cpu(cpu) {
capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu);
max_capacity = max(max_capacity, capacity);
}
/* 최대 코어 = 1024, 나머지 비례 계산 */
for_each_possible_cpu(cpu) {
capacity = raw_capacity[cpu] * per_cpu(freq_factor, cpu);
capacity = capacity * SCHED_CAPACITY_SCALE / max_capacity;
topology_set_cpu_scale(cpu, capacity);
}
}
/* 결과: big 코어 = 1024, LITTLE = ~480, mid = ~700 등
* → EAS가 이 값을 사용하여 태스크 배치 결정 */
전력 도메인(Power Domain)과 EAS
Energy-Aware Scheduling(EAS)은 이종 코어 시스템에서 에너지 모델(Energy Model)을 기반으로 태스크를 가장 에너지 효율적인 CPU에 배치합니다. ARM 모바일 SoC에서 처음 도입되었으며, 리눅스 5.0부터 mainline에 포함되었습니다. 리눅스 6.16에서는 Intel 하이브리드 플랫폼(P-core + E-core, SMT 없음)에 대한 EAS 지원이 intel_pstate 드라이버에 통합되어, EAS가 ARM 전용에서 처음으로 x86 하이브리드 아키텍처로 확장되었습니다.
Energy Model 프레임워크 구조
EAS가 동작하려면 세 가지 조건이 모두 충족되어야 합니다:
SD_ASYM_CPUCAPACITY플래그 — 이종 코어 시스템임을 나타내는 스케줄링 도메인 플래그- Energy Model 등록 —
em_dev_register_perf_domain()으로 각 성능 도메인의 전력 정보 등록 - schedutil 거버너 — CPUFreq 거버너가 schedutil이어야 EAS와 연동 가능 (
ondemand/performance불가)
/* kernel/sched/fair.c - EAS 활성화 조건 확인 */
static bool sched_energy_enabled(void)
{
static int enabled = -1;
if (enabled == -1)
enabled = (sysctl_sched_energy_aware &&
cpufreq_get_hw_max_freq(0) &&
arch_scale_freq_capacity(0) != SCHED_CAPACITY_SCALE);
return enabled;
}
/* /proc/sys/kernel/sched_energy_aware = 1 이면 EAS 활성화 */
EAS 알고리즘
find_energy_efficient_cpu()는 태스크를 배치할 최적 CPU를 찾는 EAS의 핵심 함수입니다. 각 performance domain(PD)별로 에너지 소비를 예측하고 가장 낮은 PD를 선택합니다.
/* kernel/sched/fair.c - EAS 핵심 로직 */
static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu)
{
struct root_domain *rd = cpu_rq(prev_cpu)->rd;
unsigned long prev_delta, best_delta = ULONG_MAX;
int best_cpu = prev_cpu;
/* prev_cpu에서의 에너지 기준값 계산 */
prev_delta = compute_energy(p, prev_cpu, find_pd(rd, prev_cpu));
/* 각 performance domain 순회 */
rcu_read_lock();
for_each_pd(pd, rd) {
int cpu;
for_each_cpu_and(cpu, perf_domain_span(pd), p->cpus_ptr) {
unsigned long cur_delta = compute_energy(p, cpu, pd);
if (cur_delta < best_delta) {
best_delta = cur_delta;
best_cpu = cpu;
}
}
}
rcu_read_unlock();
/* 에너지 절감이 6% 미만이면 prev_cpu 유지 (마이그레이션 비용 방지) */
if ((prev_delta - best_delta) * 100 < prev_delta * 6)
return prev_cpu;
return best_cpu;
}
OPP와 전력 예산
EAS의 에너지 계산은 각 OPP(Operating Performance Point)의 주파수·전력 정보를 기반으로 합니다. Cortex-A55 기준 예시:
| 주파수 | 전압 | 전력 (mW) | capacity | cost (power/freq) |
|---|---|---|---|---|
| 400MHz | 0.60V | 35 | 180 | 87.5 |
| 800MHz | 0.70V | 105 | 360 | 131.3 |
| 1200MHz | 0.75V | 185 | 540 | 154.2 |
| 1600MHz | 0.80V | 310 | 720 | 193.8 |
| 1800MHz | 0.85V | 480 | 810 | 266.7 |
cost 값의 의미: cost = power / frequency가 낮을수록 주파수 단위당 에너지 효율이 높습니다. EAS는 태스크의 utilization에 맞는 OPP의 cost를 사용하여 에너지 예측값을 계산합니다.
DTPM 계층
DTPM(Dynamic Thermal Power Management)은 열/전력 제약 내에서 SoC 전체의 전력 분배를 관리합니다.
# powercap 인터페이스 확인 (Intel RAPL 포함)
$ ls /sys/class/powercap/
intel-rapl intel-rapl:0 intel-rapl:0:0 ...
# DTPM 계층 확인 (ARM 시스템)
$ ls /sys/class/powercap/dtpm/
constraint_0_max_power_uw constraint_0_name
constraint_0_power_limit_uw energy_uj ...
Energy Model 디버깅(Debugging)
# EAS 활성화 여부 확인
$ cat /proc/sys/kernel/sched_energy_aware
1
# Energy Model 등록 상태 (debugfs)
$ cat /sys/kernel/debug/energy_model/cpu0/nr_perf_states
5
$ cat /sys/kernel/debug/energy_model/cpu0/table
freq power cost
400000 35 87
800000 105 131
1200000 185 154
1600000 310 193
1800000 480 266
# schedutil 거버너 확인 (EAS 필수 조건)
$ cat /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
schedutil
EEVDF 스케줄러와 EAS 연동
리눅스 6.6부터 EEVDF(Earliest Eligible Virtual Departure First)가 기본 스케줄러로 채택되었습니다. EEVDF는 CFS의 vruntime 기반 Red-Black Tree를 버리고, Lag(지연량) + Eligibility(실행 자격) + Virtual Deadline(가상 마감시간) 모델로 전환합니다. 이 전환이 EAS 및 하이브리드 CPU 스케줄링에 미치는 영향을 분석합니다.
EEVDF 스케줄링 모델
CFS는 각 태스크에 가상의 실행 시간(vruntime)을 부여하고, Red-Black Tree로 순서를 관리했습니다. EEVDF는 대신 두 가지 핵심 개념을 사용합니다:
- Lag(지연량) — 태스크가 받아야 할 공정한 실행 시간과 실제 받은 시간의 차이. Lag > 0이면 실행이 부족했음을, Lag < 0이면 초과했음을 의미합니다. Lag >= 0인 엔티티만 실행 자격(Eligibility)을 갖습니다.
- Virtual Deadline(가상 마감시간) — 태스크가 큐에 도착한 시점의 vruntime에 time_slice를 더한 값. EEVDF는 Eligible 엔티티 중 deadline이 가장 이른 것을 선택합니다.
/* include/linux/sched.h - sched_entity (EEVDF 관련 필드) */
/* EEVDF 핵심 필드:
* lag: 태스크가 받아야 할 공정한 실행량과 실제 실행량의 차이
* deadline: 가상 마감시간 = vruntime + time_slice
*
* CFS의 vruntime은 유지되지만, 스케줄링 결정은
* lag 기반 eligibility + deadline 기반 선택으로 변경
*/
struct sched_entity {
u64 vruntime; /* 가상 실행 시간 (CFS에서 유지, EEVDF에서도 사용) */
s64 lag; /* Lag: 공정 실행량과 실제 실행량의 차이 */
u64 deadline; /* Virtual Deadline: vruntime + time_slice */
u64 min_vruntime; /* 큐의 최소 vruntime (cfs_rq->min_vruntime) */
/* PELT (Per-Entity Load Tracking)은 유지 */
unsigned long load_avg;
unsigned long util_avg;
unsigned long util_est;
};
EEVDF + EAS 통합 메커니즘
EEVDF는 EAS의 핵심 함수인 find_energy_efficient_cpu()를 그대로 유지하되, 엔티티 선택 시 lag 기반 eligibility와 deadline 기반 선택을 사용합니다.
/* kernel/sched/fair.c - EEVDF 핵심 함수 (6.6+) */
/* pick_eevdf(): Eligible 엔티티 중 deadline이 가장 이른 것 선택 */
static struct sched_entity *
pick_eevdf(struct cfs_rq *cfs_rq)
{
/* 1. lag >= 0 (Eligible)인 엔티티만 후보 */
/* 2. 후보 중 deadline이 가장 작은 엔티티 선택 */
struct rb_node *node = cfs_rq->tasks_timeline.rb_root.rb_node;
...
}
/* select_task_rq_fair(): wakeup 시 CPU 선택 */
static int select_task_rq_fair(struct task_struct *p, int prev_cpu)
{
if (static_branch_unlikely(&sched_asym_cpucapacity)) {
/* EAS 경로: energy efficient CPU 탐색 */
int best = find_energy_efficient_cpu(p, prev_cpu);
if (best >= 0)
return best;
}
/* 비-EAS: 기존 select_idle_sibling() 경로 */
return select_idle_sibling(p, prev_cpu, ...);
}
- 공정성 기준 변경 — CFS의 vruntime 비교에서 lag 기반 eligibility로 변경되어, 이종 코어 환경에서 공정성(Fairness) 계산이 더 정확해짐. 예를 들어 저전력 코어에서 느리게 실행된 태스크의 lag가 양수가 되어 다음 실행 기회를 더 빨리 얻음
- deadline 기반 선택 — EAS가 energy efficient CPU를 찾은 후, 해당 CPU의 런큐(Runqueue)에서 pick_eevdf()가 deadline이 가장 이른 eligible 엔티티를 선택하므로 응답성이 향상됨
- PELT 유지 — Per-Entity Load Tracking은 EEVDF에서도 유지되어, EAS의 utilization 기반 에너지 예측이 기존과 동일하게 동작
EEVDF + Intel 하이브리드 스케줄링
Intel 하이브리드 CPU(P-core + E-core)에서 EEVDF는 기존 CFS 때와 같은 SD_ASYM_CPUCAPACITY / SD_ASYM_PACKING 플래그를 활용하지만, CPU 선정 로직이 다음과 같이 변화했습니다.
| 항목 | CFS (5.x~6.5) | EEVDF (6.6~) |
|---|---|---|
| 데이터 구조 | Red-Black Tree (vruntime 순) | Red-Black Tree + augmented lag/deadline (pick_eevdf) |
| CPU 선정 기반 | vruntime이 가장 작은 CPU | lag >= 0 (Eligible) 중 deadline이 가장 이른 엔티티 |
| EAS 연동 | wakeup path에서 find_energy_efficient_cpu() | 동일 경로 유지, 엔티티 선택만 pick_eevdf()로 변경 |
| ITMT/HFI 연동 | capacity_of() 기반 정적 가중치 | capacity_of() 유지, lag 기반 공정성 보정 추가 |
EEVDF는 짧은 burst 태스크에 대해 더 낮은 latency를 제공하므로, EAS가 E-core에 배치한 경량 태스크가 P-core보다 더 신속히 실행될 수 있습니다. 동시에 긴 실행시간 태스크는 P-core에서 적절히 처리되므로 전체 시스템 에너지 효율이 개선됩니다.
v6.16 변경: SD_ASYM_PACKING이 SMT 도메인에서 제거되고 코어 단위 도메인으로 이동했습니다. 이로 인해 SMT 형제 간에 ASYM_PACKING 효과가 사라지고, 코어 간 부하 균형에 더 집중됩니다. intel_pstate 드라이버 또한 하이브리드 CPU 용량 계산을 개선하여 perf_cap/ee_cap 값을 더 정확하게 반영합니다.
CPU 격리
CPU 격리(CPU Isolation)는 특정 코어를 리눅스 스케줄러의 일반 태스크 배치에서 제외하여, 실시간 처리나 지연에 민감한 애플리케이션 전용으로 사용하는 기법입니다.
부팅 파라미터 비교
| 파라미터 | 역할 | 대상 | 완전 격리 여부 |
|---|---|---|---|
isolcpus=N | 일반 스케줄러 배치 제외 | CFS/RT 태스크 | 부분 (커널 스레드(Kernel Thread) 미포함) |
nohz_full=N | 틱리스(Tickless) 모드 활성화 | 타이머(Timer) 인터럽트 | 타이머 오버헤드(Overhead) 제거 |
rcu_nocbs=N | RCU 콜백(Callback) 오프로드 | RCU 처리 | RCU 스레드 분리 |
irqaffinity=M | IRQ 친화도(Affinity) 기본값 설정 | 하드웨어 인터럽트 | IRQ 특정 코어 제한 |
kthread_cpus=M | 커널 스레드 친화도 | 커널 스레드 | 커널 스레드 격리 |
isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3 irqaffinity=0,1
isolcpus와 nohz_full의 합집합이 시스템의 모든 CPU를 덮으면 하우스키핑(하우스키핑(Housekeeping) — 타이머, RCU, 워크큐 등 커널 백그라운드 작업)을 수행할 CPU가 남지 않아 타이머 휠 마이그레이션 등 여러 서브시스템이 손상됩니다. Gabriele Monaco의 2025 패치 시리즈(sched/isolation: Force housekeeping)는 이 구성을 감지하여 마지막 설정을 무효화(Invalidation)하고 최소 하나의 하우스키핑 CPU를 강제로 확보합니다. 모든 CPU를 격리하려는 시도는 커널에서 거부됩니다.
IRQ 친화도 설정
# IRQ 목록과 현재 친화도 확인
$ cat /proc/interrupts | head -20
# 특정 IRQ를 코어 0,1에만 허용 (비트마스크)
$ echo 3 > /proc/irq/42/smp_affinity # 0b11 = CPU 0,1
# 또는 목록 형식으로
$ echo 0,1 > /proc/irq/42/smp_affinity_list
# 모든 IRQ를 코어 0,1로 제한 (격리 코어에서 IRQ 제거)
$ for irq in /proc/irq/[0-9]*/smp_affinity_list; do
echo "0-1" > "$irq" 2>/dev/null
done
# irqbalance 비활성화 (IRQ 친화도 자동 변경 방지)
$ systemctl stop irqbalance
$ systemctl disable irqbalance
CPU 피닝
/* sched_setaffinity() 시스템 콜 사용 예 */
#include <sched.h>
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(2, &mask); /* CPU 2 전용 */
CPU_SET(3, &mask); /* CPU 3 전용 */
if (sched_setaffinity(0, sizeof(mask), &mask) == -1)
perror("sched_setaffinity");
/* 커널 내부: kernel/sched/core.c
* sys_sched_setaffinity() → __set_cpus_allowed_ptr()
* → migrate_task_to() (필요 시 즉시 마이그레이션) */
# taskset으로 CPU 친화도 설정/확인
$ taskset -c 2,3 ./realtime_app # 새 프로세스를 CPU 2,3에서 시작
$ taskset -cp 2,3 1234 # 기존 PID 1234를 CPU 2,3으로 이동
$ taskset -cp 1234 # 현재 친화도 확인
커널 스레드 격리
/* kernel/kthread.c - 커널 스레드 CPU 바인딩 */
/* 특정 CPU에서만 실행되는 커널 스레드 생성 */
struct task_struct *kthread_create_on_cpu(
int (*threadfn)(void *data),
void *data,
unsigned int cpu,
const char *namefmt);
/* 이미 생성된 커널 스레드를 특정 CPU에 바인딩 */
void kthread_bind(struct task_struct *p, unsigned int cpu);
/* 예: CPU 핫플러그 워커를 CPU 0에만 허용 */
kthread_bind(cpuhp_thread, 0);
격리 효과 측정
# cyclictest로 wakeup latency 측정 (isolcpus 적용 전후 비교)
# 격리 전:
$ cyclictest -t 1 -p 99 -n -i 1000 -l 100000 -q
T: 0 (12345) P:99 I:1000 C:100000 Min: 8 Act: 12 Avg: 15 Max: 245
# 격리 후 (isolcpus=2 nohz_full=2 rcu_nocbs=2):
$ taskset -c 2 cyclictest -t 1 -p 99 -n -i 1000 -l 100000 -q
T: 0 (12346) P:99 I:1000 C:100000 Min: 4 Act: 5 Avg: 5 Max: 18
# 격리 효과 확인 (최대 레이턴시 245us → 18us)
보안 취약점과 토폴로지
현대 CPU 아키텍처의 성능 최적화 기능(투기적 실행, 비순차 실행, 분기 예측)은 하드웨어 토폴로지(SMT, 캐시 공유)와 결합하여 심각한 보안 취약점을 만들어냈습니다.
Spectre V1/V2
Spectre V1 (CVE-2017-5753): 배열 범위 검사를 우회하는 조건부 분기 오예측을 통해 임의 메모리를 읽습니다. Spectre V2 (CVE-2017-5715): SMT 형제 스레드 간 BTB(Branch Target Buffer) 공유를 이용해 피해자 프로세스의 간접 분기를 조작합니다.
| 항목 | Spectre V1 | Spectre V2 |
|---|---|---|
| 원인 자원 | 조건부 분기 예측기 (PHT) | 간접 분기 예측기 (BTB) |
| 공격 방법 | Array Index Out-of-Bounds 투기 | BTB 중독(Poisoning) → 임의 가젯 실행 |
| SMT 연관성 | 낮음 | 높음 (sibling 스레드 BTB 공유) |
| 커널 완화 | array_index_nospec() 배리어 | IBRS/eIBRS, Retpoline, IBPB |
| 성능 영향 | ~1% | IBRS ~10-20%, eIBRS ~3% |
/* arch/x86/include/asm/barrier.h - Spectre V1 완화 */
/* array_index_nospec(): 투기적 실행 경로에서 배열 접근 방지 */
static inline unsigned long array_index_mask_nospec(
unsigned long index, unsigned long size)
{
/*
* index < size 이면 mask = ~0UL (모든 비트 1)
* index >= size 이면 mask = 0 (모든 비트 0)
* 투기적 실행 경로에서 부호 비트를 이용한 마스킹
*/
return ~(long)(index | (size - 1UL - index)) >> (BITS_PER_LONG - 1);
}
#define array_index_nospec(index, size) (typeof(index))(array_index_mask_nospec(index, size) & (index))
Meltdown과 L1TF
Meltdown (CVE-2017-5754): 커널 메모리를 사용자 공간에서 읽는 취약점. PTI(Page Table Isolation)로 완화합니다. L1TF/Foreshadow (CVE-2018-3620/3646): L1D 캐시 플러시 없이 SMT sibling이 hyper-guest의 L1D 캐시 내용을 투기적으로 접근합니다.
| 항목 | Meltdown | L1TF |
|---|---|---|
| 원인 자원 | 페이지 테이블 검사 지연 | L1D 캐시 (SMT sibling 공유) |
| 완화 방법 | PTI (KPTI) | PTE inversion + L1D 플러시 |
| SMT 영향 | 낮음 | 높음 (nosmt 권장) |
| 성능 영향 | PTI ~5-30% (syscall 집약적) | L1D 플러시 ~10% |
# PTI 활성화 여부 확인
$ cat /sys/devices/system/cpu/vulnerabilities/meltdown
Mitigation: PTI
# L1TF 상태 확인
$ cat /sys/devices/system/cpu/vulnerabilities/l1tf
Mitigation: PTE Inversion; VMX: cache flushes, SMT vulnerable
MDS와 SRBDS
MDS(Microarchitectural Data Sampling, CVE-2018-12126/12127/12130): CPU 내부 버퍼(Line Fill Buffer, Store Buffer, Load Port)에서 데이터가 누출됩니다. SRBDS(Special Register Buffer Data Sampling, CVE-2020-0543): RDRAND/RDSEED/EGETKEY 명령어의 결과가 내부 버퍼를 통해 sibling에 노출됩니다.
| 취약점 | 영향 버퍼 | 완화 | SMT 영향 |
|---|---|---|---|
| MFBDS/RIDL | Line Fill Buffer | VERW + MD_CLEAR | 높음 |
| MLPDS/Fallout | Store Buffer | VERW + MD_CLEAR | 높음 |
| MDSUM | Load Port | VERW + MD_CLEAR | 높음 |
| SRBDS | 특수 레지스터 버퍼 | SRBDS_CTRL MSR | 중간 |
/* arch/x86/kernel/cpu/bugs.c - MDS/SRBDS 완화 */
/* VERW 명령어로 CPU 내부 버퍼 클리어
* 커널→사용자 전환 시 실행되어 버퍼 내용을 제거 */
static inline void mds_clear_cpu_buffers(void)
{
static const u16 ds = __KERNEL_DS;
/* VERW는 메모리 접근 없이 DS 세그먼트 검증 → CPU 버퍼 플러시 */
asm volatile("verw %[ds]" : : [ds] "m" (ds) : "cc");
}
취약점 sysfs 인터페이스
# 모든 취약점 상태 한 번에 확인
$ grep -r . /sys/devices/system/cpu/vulnerabilities/
/sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: usercopy/swapgs barriers and __user pointer sanitization
/sys/devices/system/cpu/vulnerabilities/spectre_v2:Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; RSB filling; PBRSB-eIBRS: SW sequence
/sys/devices/system/cpu/vulnerabilities/meltdown:Mitigation: PTI
/sys/devices/system/cpu/vulnerabilities/l1tf:Mitigation: PTE Inversion; VMX: cache flushes, SMT vulnerable
/sys/devices/system/cpu/vulnerabilities/mds:Mitigation: Clear CPU buffers; SMT vulnerable
/sys/devices/system/cpu/vulnerabilities/tsx_async_abort:Mitigation: TSX disabled
/sys/devices/system/cpu/vulnerabilities/srbds:Mitigation: Microcode
/sys/devices/system/cpu/vulnerabilities/retbleed:Mitigation: IBRS
/sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl
완화 오버헤드 종합표
| CVE | 취약점명 | 원인 자원 | 완화 기법 | SMT 영향 | 성능 오버헤드 |
|---|---|---|---|---|---|
| CVE-2017-5753 | Spectre V1 | PHT | array_index_nospec | 낮음 | ~1% |
| CVE-2017-5715 | Spectre V2 | BTB (SMT 공유) | eIBRS/Retpoline | 높음 | ~3-20% |
| CVE-2017-5754 | Meltdown | 페이지 테이블 | PTI (KPTI) | 낮음 | ~5-30% |
| CVE-2018-3620 | L1TF | L1D (SMT 공유) | PTE inversion + L1D flush | 매우 높음 | ~10% |
| CVE-2018-12126 | MFBDS/RIDL | LFB (SMT 공유) | VERW/MD_CLEAR | 높음 | ~3% |
| CVE-2020-0543 | SRBDS | 특수 레지스터 버퍼 | SRBDS_CTRL MSR | 중간 | ~1% |
| CVE-2022-29901 | Retbleed | RSB (RET 예측) | IBRS/RSB fill | 낮음 | ~14-39% |
| CVE-2024-36350 | AMD TSA-L1 | L1 캐시 마이크로태그 (AMD) | VERW 버퍼 플러시 | 중간 | ~3-5% |
| CVE-2024-36357 | AMD TSA-SQ | 스토어 큐 (AMD) | VERW 버퍼 플러시 | 중간 | ~3-5% |
| CVE-2024-28956 | Intel Spectre v2 변종 | 투기적 실행 (Intel) | IBRS/eIBRS + RSB padding | 높음 | ~5-15% |
AMD TSA (Transient Scheduler Attack, 2025)
2025년 7월 AMD가 공개한 TSA(Transient Scheduler Attack)는 스케줄러의 컨텍스트 스위치 시점에서 발생하는 투기적 실행을 악용하는 새로운 부채널 공격입니다. Meltdown/Spectre와 유사하지만, 스케줄러의 태스크 전환 타이밍을 노립니다.
| CVE | 변종 | 원인 자원 | 영향 범위 | 완화 |
|---|---|---|---|---|
| CVE-2024-36350 | TSA-L1 | L1 캐시 마이크로태그 오류 → 잘못된 데이터 적재 | EPYC 3rd/4th gen, Ryzen, Instinct, Athlon | VERW 명령어로 버퍼 플러시 (성능 영향 존재) |
| CVE-2024-36357 | TSA-SQ | 스토어 큐 오류 → 커널 데이터 추론 | 동일 | VERW 명령어로 버퍼 플러시 |
| CVE-2024-36348 | TSA (저위험) | 관련 마이크로아키텍처 버퍼 | 동일 | 마이크로코드 업데이트 |
| CVE-2024-36349 | TSA (저위험) | 관련 마이크로아키텍처 버퍼 | 동일 | 마이크로코드 업데이트 |
Intel 신규 취약점 (2025)
2025년 5월 ETH 취리히와 VUSec(Vrije Universiteit Amsterdam) 연구진이 공개한 CVE-2024-28956과 CVE-2025-24495는 Intel CPU에서 Spectre v2 스타일의 커널 메모리 누출을 가능하게 합니다. 최대 17 KB/s 속도로 커널 메모리를 추출할 수 있는 것으로 보고되었습니다.
| CVE | 특성 | 공격 메커니즘 | 완화 |
|---|---|---|---|
| CVE-2024-28956 | Spectre v2 변종 | 투기적 실행을 통한 커널 메모리 누출 (~17 KB/s) | IBRS/eIBRS 강화, RSB padding |
| CVE-2025-24495 | Spectre v2 변종 | BTB 중독(Poisoning) 기반 간접 분기 조작 | IBPB + eIBRS 조합 |
nosmt 커널 파라미터로 SMT를 비활성화하는 것이 권장됩니다. 단, 멀티스레드 성능이 최대 50% 감소할 수 있습니다.
Core Scheduling (XSMT/XSA 완화)
Core Scheduling은 Spectre Variants (XSMT: eXecution Speculation Mitigation Technology / XSA: eXecution Speculation Attack)로 인한 SMT 공유 코어의 보안 위협을 완화하는 커널 기능입니다. 핵심 원리는 "같은 보안 경계(Security Domain)에 속한 태스크만 SMT 형제 스레드에서 동시에 실행"라는 것입니다.
동작 원리
SMT는 하나의 물리 코어가 두 논리 스레드를 동시에 사용합니다. Spectre V2/V4/MDS/XSA 공격은 공격자 태스크가 SMT 형제 스레드에서 실행되는 피해자 태스크의 투기적 실행 결과를 공유 캐시/레지스터를 통해 추측하는 방식입니다. Core Scheduling은 이를 차단합니다.
/* kernel/sched/core.c - Core Scheduling 핵심 */
/* rq->core: 각 runqueue가 속한 core_sched 구조체 */
/* core->cookies: 현재 코어에서 실행 중인 보안 도메인 식별자 집합 */
/* 스케줄링 결정 시: 실행될 태스크의 스케줄링 클래스가
* 이미 다른 스레드와 "같은 도메인"인지 확인 */
static void core_sched_balance_tail(struct rq *rq)
{
struct core_sched *core = rq->core;
int cookie = core_sched_cookie(rq->curr);
/* 다른 스레드의 현재 태스크와 cookie(보안 도메인)가
* 다른 경우, 현재 태스크를 다른 코어로 마이그레이션 */
if (!core_sched_preempt(core, cookie))
return;
/* incompatible 도메인이 실행 중이면 preempt() 호출 */
preempt_schedule();
}
/* cookie: 같은 프로세스 = 같은 cookie
* kernel thread = unique cookie(다른 kernel thread와 격리) */
int core_sched_cookie(struct task_struct *p)
{
if (p->flags & PF_KTHREAD)
return kthread_core_sched_cookie(p);
return p->tgid; /* user task: tgid = cookie */
}
활성화/비활성화
# Core Scheduling은 sysctl이 아닌 prctl(PR_SCHED_CORE)로 제어
# 커널 CONFIG_SCHED_CORE=y 필요 (커널 5.14+)
# 프로세스 단위로 Core Scheduling 활성화
# PR_SCHED_CORE_CREATE = 새 쿠키 생성
# PR_SCHED_CORE_SHARE = 기존 쿠키 공유
$ python3 -c "import ctypes; libc=ctypes.CDLL(None); libc.prctl(62, 0, 0, 0, 0)"
# systemd에서 cgroup 기반 Core Scheduling 활성화
$ systemctl set-property my-service.service CPUAffinity=0-3
# 확인: /proc//status에서 Core Scheduling 쿠키 확인
$ cat /proc/$$/status | grep -i core
Cpus_allowed: f
# dmesg에서 부팅 시 Core Scheduling 지원 확인
$ dmesg | grep -i "core sched"
[ 0.123456] sched: Core scheduling enabled
대응 공격 및 적용 범위
| 공격 | CVE | 핵심 메커니즘 | Core Scheduling 대응 |
|---|---|---|---|
| Spectre V2 변종 (SMT) | CVE-2018-12130 등 | SMT 형제 스레드 via BHB/Branch Predictor | 형제 격리로 branch predictor 공격 경로 차단 |
| Speculative Store Bypass | CVE-2018-36393 | SMT 형제 via Store Port | 동일한 원리 — 형제 격리 |
| MDS (Microarchitectural Data Sampling) | CVE-2018-12130 등 | SMT 형제 via L3/FILO/SBO | 동일한 원리 — L3 공유 경로 차단. 추가로 L1DF/MDS Flush 필요 |
| L1TF (Fooding Attack) | CVE-2018-3646 | SMT 형제 via L1D | KPTI + L1DF flush 필요. Core Scheduling만 불충분 |
커널 소스 참조
x86 토폴로지
| 파일 | 역할 |
|---|---|
arch/x86/kernel/cpu/topology.c | CPUID 기반 토폴로지 탐지, detect_extended_topology() |
arch/x86/kernel/cpu/amd.c | AMD 전용 토폴로지 (CPUID 0x8000001E) |
arch/x86/kernel/cpu/intel.c | Intel 전용 토폴로지, Thread Director |
arch/x86/kernel/smpboot.c | SMP 부팅, AP 초기화, sibling map |
arch/x86/kernel/cpu/cacheinfo.c | CPUID 기반 캐시 토폴로지 |
ARM64 토폴로지
| 파일 | 역할 |
|---|---|
arch/arm64/kernel/topology.c | ARM64 토폴로지 초기화, PPTT 파싱 |
drivers/base/arch_topology.c | 아키텍처 공통: cpu-capacity, freq-factor |
drivers/acpi/pptt.c | ACPI PPTT 테이블 파서 |
drivers/perf/arm-cmn.c | CMN 메시 PMU 드라이버 |
스케줄러 토폴로지
| 파일 | 역할 |
|---|---|
kernel/sched/topology.c | sched_domain 구축, build_sched_domains() |
kernel/sched/fair.c | CFS 로드 밸런싱, EAS, ASYM_PACKING |
include/linux/topology.h | 토폴로지 매크로(Macro)/함수 선언 |
drivers/thermal/intel/intel_hfi.c | Intel HFI/Thread Director 드라이버 |
전력/EAS 관련 파일
| 파일 | 역할 |
|---|---|
kernel/sched/cpufreq.c | schedutil 거버너, EAS-CPUFreq 연동 |
drivers/cpufreq/cpufreq-dt.c | OPP 테이블 기반 CPUFreq 드라이버 |
drivers/base/power/opp/core.c | OPP 프레임워크 핵심 구현 |
kernel/power/energy_model.c | Energy Model 프레임워크 |
drivers/powercap/intel_rapl_common.c | Intel RAPL 전력 측정 |
drivers/base/arch_topology.c | CPU 용량 정규화, freq-factor |
보안 취약점 완화 파일
| 파일 | 역할 |
|---|---|
arch/x86/kernel/cpu/bugs.c | Spectre/Meltdown/MDS 완화, 취약점 검출 |
arch/x86/kernel/cpu/amd.c | AMD 전용 취약점 완화 (Retbleed, IBPB) |
arch/x86/mm/pti.c | PTI (Page Table Isolation) 구현 |
arch/x86/kernel/itmt.c | ITMT/HFI 코어 우선순위 설정 |
arch/x86/include/asm/barrier.h | 투기적 실행 배리어, array_index_nospec |
실전 진단 명령 모음
토폴로지 확인
# 전체 토폴로지 요약
$ lscpu -e=CPU,SOCKET,NODE,CORE,L1d:,L1i:,L2:,L3:,ONLINE
# hwloc: 상세 그래픽 토폴로지 (텍스트)
$ lstopo-no-graphics --of ascii
# 특정 CPU의 상세 토폴로지
$ cat /sys/devices/system/cpu/cpu0/topology/die_id
$ cat /sys/devices/system/cpu/cpu0/topology/core_id
$ cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list
# 캐시 공유 관계 확인
$ cat /sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_list
# CPU 용량 확인 (ARM/하이브리드)
$ cat /sys/devices/system/cpu/cpu*/cpu_capacity
sched_domain 확인
# 스케줄링 도메인 이름 확인
$ for d in /proc/sys/kernel/sched_domain/cpu0/domain*/; do
echo "$(basename $d): $(cat ${d}name)"
done
domain0: SMT
domain1: MC
domain2: DIE
domain3: NUMA
# 도메인별 밸런싱 통계 (debugfs)
$ cat /proc/schedstat | head -20
# 태스크의 현재 CPU, 선호도 확인
$ taskset -cp $$
$ cat /proc/$$/status | grep Cpus_allowed
성능 측정
# inter-CCD vs intra-CCD 지연 비교 (perf)
$ perf stat -e cache-misses,cache-references,L1-dcache-load-misses \
taskset -c 0,1 ./benchmark # 같은 CCX 내 코어
$ perf stat -e cache-misses,cache-references,L1-dcache-load-misses \
taskset -c 0,16 ./benchmark # 다른 CCD 코어
# NUMA 지연 측정
$ numactl --hardware
$ numactl --cpunodebind=0 --membind=0 ./benchmark # 로컬
$ numactl --cpunodebind=0 --membind=1 ./benchmark # 리모트
# Intel Thread Director 상태 확인
$ cat /sys/devices/system/cpu/cpu*/topology/ppin 2>/dev/null
$ dmesg | grep -i "hfi\|thread director"
# 스케줄러 디버깅 (ftrace)
$ echo 1 > /sys/kernel/debug/tracing/events/sched/sched_migrate_task/enable
$ cat /sys/kernel/debug/tracing/trace_pipe | head -50
EAS 진단
# EAS 활성화 여부 확인
$ cat /proc/sys/kernel/sched_energy_aware
1
# EAS 비활성화 (디버깅용)
$ echo 0 > /proc/sys/kernel/sched_energy_aware
# Energy Model 등록 상태
$ ls /sys/kernel/debug/energy_model/
cpu0 cpu4 cpu7 # 각 performance domain 대표 CPU
$ cat /sys/kernel/debug/energy_model/cpu0/table
# schedutil 상태 확인
$ cat /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
schedutil
# CPU별 현재 utilization 확인 (cfs bandwidth)
$ cat /proc/sched_debug | grep -A 5 "cpu#0"
# perf로 EAS 동작 추적
$ perf sched record -g -- sleep 5
$ perf sched map
보안 취약점 진단
# 모든 취약점 상태 한 번에 확인
$ grep -r . /sys/devices/system/cpu/vulnerabilities/
# SMT 상태와 보안 권고 확인
$ cat /sys/devices/system/cpu/smt/active
$ dmesg | grep -i "SMT\|spectre\|meltdown\|mds"
# spectre_v2 완화 방법 확인
$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
# 커널 부팅 파라미터에서 완화 설정 확인
$ cat /proc/cmdline | tr ' ' '
' | grep -E "spectre|mds|pti|ibrs|retpoline"
# 완화 비활성화 시 성능 차이 측정 (개발/테스트 환경 전용)
# GRUB에 mitigations=off 추가 후 비교
CPU 격리 진단
# 격리된 CPU 목록 확인
$ cat /sys/devices/system/cpu/isolated
2-3
# nohz_full 활성화 CPU 확인
$ cat /sys/devices/system/cpu/nohz_full
2-3
# 격리 CPU에서 실행 중인 프로세스 확인
$ ps -eo pid,psr,comm | awk '$2 == 2 || $2 == 3'
# IRQ 친화도 현황
$ for i in $(ls /proc/irq/); do
aff=$(cat /proc/irq/$i/smp_affinity_list 2>/dev/null)
[ -n "$aff" ] && echo "IRQ $i: $aff"
done
# 격리 효과 cyclictest 측정
$ taskset -c 2 cyclictest -t 1 -p 99 -n -i 1000 -l 10000 -q
sysfs 토폴로지 파싱 실전
커널이 노출하는 /sys/devices/system/cpu/cpuN/topology/ 디렉터리에는 물리 패키지, 다이, 코어, SMT 형제 등 모든 토폴로지 정보가 담겨 있습니다. 이 절에서는 셸 스크립트와 Python을 이용하여 시스템 토폴로지를 자동으로 파싱하고 시각화하는 실전 기법을 다룹니다.
core_id / physical_package_id 파싱
가장 기본적인 토폴로지 파싱은 각 CPU의 physical_package_id(소켓)와 core_id(물리 코어)를 읽어 CPU 배치도를 만드는 것입니다.
#!/bin/bash
# sysfs 토폴로지 전체 파싱 스크립트
# 각 논리 CPU의 소켓, 다이, 코어, SMT 형제 정보를 테이블로 출력
printf "%-6s %-8s %-6s %-6s %-20s\n" "CPU" "SOCKET" "DIE" "CORE" "SMT_SIBLINGS"
printf "%-6s %-8s %-6s %-6s %-20s\n" "---" "------" "---" "----" "------------"
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
[ ! -d "$cpu/topology" ] && continue
id=$(basename "$cpu")
pkg=$(cat "$cpu/topology/physical_package_id")
die=$(cat "$cpu/topology/die_id" 2>/dev/null || echo "-")
core=$(cat "$cpu/topology/core_id")
smt=$(cat "$cpu/topology/thread_siblings_list")
printf "%-6s %-8s %-6s %-6s %-20s\n" "$id" "$pkg" "$die" "$core" "$smt"
done | sort -t "u" -k2 -n
die_id는 커널 4.18+에서만 존재합니다. AMD EPYC은 각 CCD에 고유한 die_id를 부여하지만, Intel 데스크톱 칩에서는 보통 0입니다.
thread_siblings_list 해석
thread_siblings_list는 동일 물리 코어에서 SMT를 공유하는 논리 CPU 목록입니다. 이 값이 같은 CPU들은 실행 자원을 공유하므로, 성능에 민감한 워크로드에서는 같은 형제에 두 스레드를 배치하지 않는 것이 좋습니다.
#!/bin/bash
# SMT 형제 그룹별 정리
# 같은 물리 코어를 공유하는 논리 CPU 쌍을 나열
echo "=== SMT 형제 그룹 ==="
declare -A seen
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
[ ! -d "$cpu/topology" ] && continue
siblings=$(cat "$cpu/topology/thread_siblings_list")
if [ -z "${seen[$siblings]}" ]; then
seen[$siblings]=1
core=$(cat "$cpu/topology/core_id")
pkg=$(cat "$cpu/topology/physical_package_id")
echo " Package $pkg, Core $core: CPUs [$siblings]"
fi
done | sort
# 출력 예시 (듀얼소켓 96코어, SMT-2):
# Package 0, Core 0: CPUs [0,96]
# Package 0, Core 1: CPUs [1,97]
# ...
cache shared_cpu_map 파싱
캐시 토폴로지를 파악하면 같은 L3을 공유하는 CPU 그룹을 알 수 있고, 이를 기반으로 스레드 배치를 최적화할 수 있습니다.
#!/bin/bash
# 캐시 공유 관계 파싱 — L3 공유 그룹 도출
echo "=== L3 캐시 공유 그룹 ==="
declare -A l3_groups
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
for idx in "$cpu"/cache/index*; do
[ ! -f "$idx/level" ] && continue
level=$(cat "$idx/level")
if [ "$level" -eq 3 ]; then
shared=$(cat "$idx/shared_cpu_list")
size=$(cat "$idx/size")
l3_groups["$shared"]="$size"
fi
done
done
for group in "${!l3_groups[@]}"; do
echo " L3 ${l3_groups[$group]}: CPUs [$group]"
done | sort
# 출력 예시 (AMD EPYC 9654, 12 CCD):
# L3 32768K: CPUs [0-7,96-103]
# L3 32768K: CPUs [8-15,104-111]
# ...
Python 토폴로지 맵 생성기
Python을 사용하면 토폴로지 정보를 구조화된 데이터로 수집하고, JSON 출력이나 시각화에 활용할 수 있습니다.
#!/usr/bin/env python3
"""sysfs 기반 CPU 토폴로지 맵 생성기"""
import os, json
from pathlib import Path
from collections import defaultdict
def read_sysfs(path):
try:
return Path(path).read_text().strip()
except (FileNotFoundError, PermissionError):
return ""
def build_topology():
base = Path("/sys/devices/system/cpu")
topology = defaultdict(lambda: defaultdict(list))
cpus = []
for cpu_dir in sorted(base.glob("cpu[0-9]*"),
key=lambda p: int(p.name[3:])):
topo = cpu_dir / "topology"
if not topo.exists():
continue
info = {
"cpu": cpu_dir.name,
"package": read_sysfs(topo / "physical_package_id"),
"die": read_sysfs(topo / "die_id") or "0",
"core": read_sysfs(topo / "core_id"),
"smt": read_sysfs(topo / "thread_siblings_list"),
}
cpus.append(info)
pkg, die = info["package"], info["die"]
topology[pkg][die].append(info)
return {"cpus": cpus, "hierarchy": topology}
if __name__ == "__main__":
result = build_topology()
print(json.dumps(result, indent=2, ensure_ascii=False))
# 요약 출력
packages = result["hierarchy"]
print(f"\n=== 토폴로지 요약 ===")
print(f"소켓: {len(packages)}")
for pkg_id, dies in sorted(packages.items()):
print(f" 패키지 {pkg_id}: {len(dies)} 다이")
for die_id, cpus in sorted(dies.items()):
print(f" 다이 {die_id}: {len(cpus)} 논리 CPU")
cron이나 모니터링 시스템에 통합하면 서버 증설/교체 시 토폴로지 변경을 자동으로 감지할 수 있습니다. JSON 출력은 Ansible/Puppet 등 구성 관리 도구와도 쉽게 연동됩니다.
lscpu 내부 동작 분석
lscpu는 util-linux 패키지에 포함된 사용자 공간 도구로, sysfs와 /proc/cpuinfo, CPUID 명령어를 조합하여 토폴로지를 출력합니다. 단순해 보이지만 내부적으로 여러 데이터 소스를 크로스체크하며, 하이브리드 아키텍처 등 복잡한 환경에서의 정확한 출력을 위해 정교한 로직을 사용합니다.
lscpu -e 확장 출력 분석
lscpu -e (또는 --extended)는 각 논리 CPU의 소켓, 코어, 온라인 상태 등을 테이블 형태로 출력합니다. 하이브리드 아키텍처에서는 코어 타입 컬럼이 추가됩니다.
# 확장 출력 — 전체 컬럼 지정
$ lscpu -e=CPU,SOCKET,NODE,CORE,L1d:L1i:L2:L3,ONLINE,MAXMHZ,MINMHZ
# 출력 예시 (Intel 13세대 하이브리드)
CPU SOCKET NODE CORE L1d:L1i:L2:L3 ONLINE MAXMHZ MINMHZ
0 0 0 0 0:0:0:0 yes 5800.0 800.0 # P-core
1 0 0 1 4:4:4:0 yes 5800.0 800.0 # P-core
16 0 0 16 24:24:8:0 yes 4300.0 800.0 # E-core
17 0 0 17 25:25:8:0 yes 4300.0 800.0 # E-core
# MAXMHZ 차이로 P-core / E-core 구분 가능
# P-core: 5800 MHz, E-core: 4300 MHz
# 특정 컬럼만 추출하여 토폴로지 요약
$ lscpu -e=CPU,SOCKET,CORE | awk 'NR>1 {print $2, $3}' | \
sort -u | wc -l
# → 물리 코어 수 (중복 제거)
lscpu -p 파싱
lscpu -p는 프로그래밍에서 파싱하기 쉬운 CSV 형식을 출력합니다. 주석 줄(#)을 제외하면 각 행이 하나의 논리 CPU입니다.
# CSV 형식 출력 (# 주석 제거)
$ lscpu -p | grep -v '^#'
# 형식: CPU,Core,Socket,Node,,L1d,L1i,L2,L3
0,0,0,0,,0,0,0,0
1,1,0,0,,4,4,4,0
...
# awk로 소켓별 코어 수 집계
$ lscpu -p | grep -v '^#' | \
awk -F, '{cores[$3]++} END {for(s in cores) printf "Socket %s: %d CPUs\n", s, cores[s]}'
# JSON 출력 (util-linux 2.37+)
$ lscpu -J | python3 -m json.tool | head -20
lstopo(hwloc) 비교
lstopo는 hwloc 프로젝트에서 제공하는 도구로, lscpu보다 풍부한 토폴로지 정보를 제공합니다. 특히 PCI 장치와 NUMA 노드 간의 관계, 캐시 계층의 시각적 표현이 강점입니다.
# lstopo: 텍스트 기반 토폴로지 트리
$ lstopo --of ascii
# lstopo: PNG 이미지로 토폴로지 시각화
$ lstopo --of png topology.png
# lstopo: XML로 전체 토폴로지 내보내기
$ lstopo --of xml topology.xml
# hwloc-info: 특정 오브젝트 정보 조회
$ hwloc-info --of console Core:0
$ hwloc-info --of console NUMANode:1
# hwloc-calc: CPU 집합 연산
$ hwloc-calc Core:0-3 # 코어 0~3의 논리 CPU 집합
$ hwloc-calc NUMANode:0 # NUMA 0의 모든 CPU
$ hwloc-calc --intersect Core PU:0-7 # PU 0~7이 속한 코어 수
# hwloc-bind: hwloc 기반 CPU 바인딩 실행
$ hwloc-bind Core:0-1 -- ./my_app # 코어 0,1에 바인드
# lscpu vs lstopo 차이점:
# - lscpu: 빠르고 가벼움, 텍스트 요약, 별도 설치 불필요
# - lstopo: PCI/GPU/NIC 위치 표시, 비주얼, NUMA 거리 시각화
| 기능 | lscpu | lstopo (hwloc) |
|---|---|---|
| 설치 | 기본 포함 (util-linux) | 별도 설치 (hwloc 패키지) |
| CPU 토폴로지 | Socket/Core/Thread 계층 | Socket/Core/PU + 캐시 + 메모리 |
| PCI 장치 위치 | 미지원 | 지원 (GPU, NIC 등) |
| NUMA 거리 | 미표시 | 시각적 표현 |
| 출력 형식 | 텍스트, CSV, JSON | 텍스트, PNG, SVG, XML, 콘솔 |
| CPU 바인딩 | 미지원 | hwloc-bind 제공 |
NUMA 노드 탐색과 배치
NUMA(Non-Uniform Memory Access) 토폴로지는 CPU와 메모리 간의 물리적 거리를 반영합니다. 올바른 NUMA 배치는 메모리 접근 지연시간을 줄이고 대역폭을 극대화하는 핵심 요소입니다. 이 절에서는 /sys/devices/system/node/를 통해 NUMA 토폴로지를 탐색하고, CPU-메모리 매핑을 분석하는 방법을 다룹니다.
numactl --hardware 해석
numactl --hardware는 NUMA 토폴로지의 가장 중요한 세 가지 정보를 한 번에 보여줍니다: 노드별 CPU 목록, 메모리 용량, 노드 간 거리입니다.
# numactl --hardware 출력 분석
$ numactl --hardware
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
node 0 size: 64189 MB
node 0 free: 58421 MB
node 1 cpus: 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 64478 MB
node 1 free: 61283 MB
node 2 cpus: 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
node 2 size: 64478 MB
node 2 free: 60102 MB
node 3 cpus: 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
node 3 size: 64478 MB
node 3 free: 59870 MB
node distances:
node 0 1 2 3
0: 10 12 32 32
1: 12 10 32 32
2: 32 32 10 12
3: 32 32 12 10
# 해석:
# - 노드 0↔1, 2↔3: 거리 12 (같은 소켓 내 NPS2 분할)
# - 노드 0↔2, 0↔3, 1↔2, 1↔3: 거리 32 (소켓 간 xGMI)
# - 거리 비율: 로컬 10 : 같은 소켓 12 : 원격 소켓 32
# - 원격 접근 오버헤드: ~3.2배
sysfs NUMA 노드 파싱
/sys/devices/system/node/에는 각 NUMA 노드의 상세 정보가 제공됩니다.
#!/bin/bash
# NUMA 노드 상세 정보 파싱
for node in /sys/devices/system/node/node[0-9]*; do
id=$(basename "$node")
cpulist=$(cat "$node/cpulist")
meminfo=$(grep "MemTotal" "$node/meminfo" | awk '{print $4}')
distance=$(cat "$node/distance")
echo "=== $id ==="
echo " CPUs: $cpulist"
echo " 메모리: $((meminfo / 1024)) MB"
echo " 거리: $distance"
# 해당 노드의 hugepage 정보
hugepages=$(cat "$node/hugepages/hugepages-2048kB/nr_hugepages" 2>/dev/null)
[ -n "$hugepages" ] && echo " Hugepages (2MB): $hugepages"
echo
done
# 특정 프로세스의 NUMA 메모리 분포 확인
$ cat /proc/$$/numa_maps | head -5
# 7f8a12000000 default anon=256 dirty=256 N0=200 N1=56
# → N0에 200페이지, N1에 56페이지 — 대부분 로컬 할당
numa_maps 분석
/proc/PID/numa_maps는 프로세스의 각 VMA(Virtual Memory Area)가 어느 NUMA 노드에 물리 페이지를 할당받았는지 보여주는 강력한 진단 도구입니다.
#!/bin/bash
# numa_maps 분석: 노드별 페이지 분포 집계
if [ -z "$1" ]; then
echo "Usage: $0 <PID>"
exit 1
fi
PID="$1"
echo "=== PID $PID NUMA 메모리 분포 ==="
# 노드별 총 페이지 수 집계
awk '
{
for (i = 1; i <= NF; i++) {
if ($i ~ /^N[0-9]+=/) {
split($i, a, "=")
node = a[1]
pages = a[2]
total[node] += pages
}
}
}
END {
for (n in total)
printf " %s: %d pages (%d MB)\n", n, total[n], total[n]*4/1024
}' /proc/"$PID"/numa_maps
# 정책별 VMA 분류
echo ""
echo "=== 메모리 정책 분포 ==="
awk '{print $2}' /proc/"$PID"/numa_maps | sort | uniq -c | sort -rn
numa_maps는 읽을 때 내부적으로 페이지 테이블을 순회하므로, 메모리가 큰 프로세스에서는 일시적으로 성능 영향이 있을 수 있습니다. 프로덕션 환경에서는 주기적 자동 수집을 피하세요. 더 깊은 NUMA 분석은 NUMA 페이지를 참고하세요.
cpuset cgroup v1/v2
cpuset은 cgroup의 서브시스템으로, 프로세스 그룹을 특정 CPU와 NUMA 노드에 바인딩합니다. cgroup v2에서는 cpuset 컨트롤러가 통합 계층에 속하며, 파티션(partition) 개념으로 CPU를 배타적으로 분할할 수 있습니다.
cgroup v2 cpuset 생성
#!/bin/bash
# cgroup v2 cpuset 파티션 생성 예제
# 1. cpuset 컨트롤러 활성화
echo "+cpuset" > /sys/fs/cgroup/cgroup.subtree_control
# 2. 파티션 A 생성 — 일반 워크로드
mkdir /sys/fs/cgroup/partition-a
echo "0-47" > /sys/fs/cgroup/partition-a/cpuset.cpus
echo "0-1" > /sys/fs/cgroup/partition-a/cpuset.mems
echo "member" > /sys/fs/cgroup/partition-a/cpuset.cpus.partition
# 3. 파티션 B 생성 — 실시간 격리
mkdir /sys/fs/cgroup/partition-b
echo "48-95" > /sys/fs/cgroup/partition-b/cpuset.cpus
echo "2-3" > /sys/fs/cgroup/partition-b/cpuset.mems
echo "isolated" > /sys/fs/cgroup/partition-b/cpuset.cpus.partition
# 4. 프로세스를 파티션에 배치
echo "$$" > /sys/fs/cgroup/partition-a/cgroup.procs
# 5. 파티션 상태 확인
cat /sys/fs/cgroup/partition-a/cpuset.cpus.effective
cat /sys/fs/cgroup/partition-b/cpuset.cpus.partition
# → "isolated" (격리 파티션 활성화 확인)
cpuset.cpus/cpuset.mems 설정
# cpuset.cpus 표현 형식
echo "0-3" > cpuset.cpus # 범위: CPU 0,1,2,3
echo "0,2,4,6" > cpuset.cpus # 개별 지정
echo "0-3,8-11" > cpuset.cpus # 복합 범위
# cpuset.mems: NUMA 노드 제한
# 메모리 할당은 지정된 노드에서만 발생
echo "0" > cpuset.mems # 노드 0만 사용
echo "0-1" > cpuset.mems # 노드 0,1 사용
# cpuset.cpus.effective: 실제 유효 CPU (상위 제한 반영)
# cpuset.cpus와 다를 수 있음 (오프라인 CPU 제외 등)
cat cpuset.cpus.effective
# cgroup v2 파티션 유형 (cpuset.cpus.partition)
# - member: 기본값, 상위 cgroup과 CPU 공유
# - root: 파티션 루트, 하위에 CPU를 배타적으로 분배
# - isolated: root + isolcpus 효과 (tick/워크큐 제외)
systemd slice를 통한 cpuset
# systemd에서 cpuset 활용
# /etc/systemd/system/my-app.service 또는 transient 유닛 사용
# 방법 1: systemd-run으로 일회성 바인딩
$ systemd-run --scope -p AllowedCPUs=0-7 -p AllowedMemoryNodes=0 ./my-app
# 방법 2: 서비스 파일에 설정
# [Service]
# AllowedCPUs=0-7
# AllowedMemoryNodes=0
# 방법 3: slice로 그룹 관리
$ systemctl set-property my-app.service AllowedCPUs=0-7
# 확인
$ systemctl show my-app.service -p AllowedCPUs
$ cat /sys/fs/cgroup/system.slice/my-app.service/cpuset.cpus.effective
컨테이너(Container) cpuset 실전
# Docker: --cpuset-cpus로 CPU 바인딩
$ docker run --cpuset-cpus="0-3" --cpuset-mems="0" nginx
# Kubernetes: cpuManager 정책 활용
# kubelet 설정: --cpu-manager-policy=static
# Pod spec에 Guaranteed QoS + 정수 CPU 요청 → 배타적 cpuset 할당
# cri-o / containerd: 런타임이 cgroup v2 cpuset 자동 설정
# 확인:
$ cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/"$POD_CGROUP"/cpuset.cpus.effective
# 격리 검증: 컨테이너 내부에서
$ taskset -p 1
pid 1's current affinity mask: f # 0x0f = CPU 0-3
# 컨테이너 간 CPU 중복 확인 스크립트
for cg in /sys/fs/cgroup/kubepods.slice/*/cpuset.cpus.effective; do
echo "$(dirname $cg | xargs basename): $(cat $cg)"
done
isolated 파티션은 커널 부팅 파라미터 isolcpus=와 유사한 효과를 런타임에 동적으로 적용합니다. 부팅 시 고정하지 않아도 되므로 유연한 운영이 가능합니다. 더 자세한 cpuset 정보는 cpusets 페이지를 참고하세요.
irqbalance 튜닝
irqbalance는 하드웨어 인터럽트를 NUMA 토폴로지와 부하 상황에 따라 CPU 간에 자동 분산하는 데몬입니다. 네트워크 집약적 서버에서는 irqbalance의 동작을 이해하고 적절히 튜닝하는 것이 처리량과 지연시간에 큰 영향을 미칩니다.
irqbalance 설정
# /etc/sysconfig/irqbalance 또는 /etc/default/irqbalance
# 특정 CPU를 인터럽트 분배 대상에서 제외
# 실시간 태스크 전용 CPU를 보호
IRQBALANCE_BANNED_CPULIST="48-95"
# 특정 IRQ를 고정 (이동 금지)
IRQBALANCE_BANNED_INTERRUPTS="56 57 58"
# 힌트 정책: EXACT = irq affinity_hint를 엄격히 따름
IRQBALANCE_ARGS="--hintpolicy=exact"
# irqbalance 상태 확인
$ systemctl status irqbalance
# 원샷 모드: 한 번만 분배 후 종료
$ irqbalance --oneshot --debug
# 디버그 모드: 분배 결정 과정 확인
$ irqbalance --foreground --debug 2>&1 | head -50
smp_affinity 수동 설정
irqbalance를 사용하지 않거나, 특정 인터럽트를 정밀하게 제어해야 할 경우 /proc/irq/N/smp_affinity를 직접 설정합니다.
# 현재 인터럽트 분포 확인
$ cat /proc/interrupts | head -3
CPU0 CPU1 CPU2 CPU3
16: 20145 0 0 0 IR-PCI-MSI eth0-TxRx-0
17: 0 18903 0 0 IR-PCI-MSI eth0-TxRx-1
# smp_affinity: 비트마스크 (16진수)
# CPU 0 = 0x01, CPU 1 = 0x02, CPU 0+1 = 0x03
$ echo 1 > /proc/irq/16/smp_affinity # IRQ 16 → CPU 0 고정
$ echo 2 > /proc/irq/17/smp_affinity # IRQ 17 → CPU 1 고정
# smp_affinity_list: CPU 번호 형식 (더 읽기 쉬움)
$ echo "0" > /proc/irq/16/smp_affinity_list
$ echo "1" > /proc/irq/17/smp_affinity_list
# 모든 IRQ의 현재 친화도 목록
for irq in $(ls /proc/irq/ | grep '^[0-9]'); do
aff=$(cat /proc/irq/$irq/smp_affinity_list 2>/dev/null)
name=$(cat /proc/irq/$irq/../*/$irq/.. 2>/dev/null | head -1)
[ -n "$aff" ] && printf "IRQ %-4s → CPU %-10s %s\n" "$irq" "$aff" "$name"
done
네트워크 인터럽트 분산
고성능 네트워크 환경에서는 NIC의 멀티큐(multi-queue)와 인터럽트 분산이 성능의 핵심입니다. RSS(Receive Side Scaling)와 결합하여 패킷 처리를 여러 CPU에 효율적으로 분배합니다.
#!/bin/bash
# 네트워크 인터럽트 NUMA-aware 분산 스크립트
NIC="eth0"
# NIC의 NUMA 노드 확인
NUMA_NODE=$(cat /sys/class/net/$NIC/device/numa_node)
echo "NIC $NIC is on NUMA node $NUMA_NODE"
# 해당 NUMA 노드의 CPU 목록
CPULIST=$(cat /sys/devices/system/node/node${NUMA_NODE}/cpulist)
echo "NUMA $NUMA_NODE CPUs: $CPULIST"
# irqbalance 중지 (수동 관리 시)
systemctl stop irqbalance
# NIC의 각 큐 IRQ를 로컬 NUMA CPU에 1:1 매핑
cpu_idx=0
IFS=',' read -ra CPUS <<< "$(echo $CPULIST | tr '-' ',' )"
for irq in $(grep "$NIC" /proc/interrupts | awk '{print $1}' | tr -d ':'); do
target_cpu=${CPUS[$cpu_idx]}
echo "$target_cpu" > /proc/irq/$irq/smp_affinity_list
echo " IRQ $irq → CPU $target_cpu"
cpu_idx=$(( (cpu_idx + 1) % ${#CPUS[@]} ))
done
# 결과 확인
echo "=== $NIC 인터럽트 분포 ==="
grep "$NIC" /proc/interrupts
커스텀 affinity 스크립트
토폴로지 정보를 실무에 활용하려면 프로세스, 스레드, 인터럽트, 패킷 흐름까지 CPU 배치를 종합적으로 관리해야 합니다. 이 절에서는 taskset, numactl, pthread_setaffinity_np, RPS/XPS 등 다양한 CPU 친화도 설정 기법과 종합 벤치마크 스크립트를 제공합니다.
taskset/numactl 실전 예제
# taskset: CPU 친화도 설정 (비트마스크 또는 리스트)
$ taskset -c 0-3 ./my-app # CPU 0-3에서 실행
$ taskset 0x0f ./my-app # 같은 의미 (비트마스크)
$ taskset -pc 4-7 12345 # 실행 중인 PID 변경
# 실행 중 프로세스의 현재 친화도 확인
$ taskset -p 12345
pid 12345's current affinity mask: ff
# numactl: NUMA 정책 + CPU 바인딩
$ numactl --cpunodebind=0 --membind=0 ./my-app # 노드 0 CPU+메모리
$ numactl --physcpubind=0-7 ./my-app # 물리 CPU 지정
$ numactl --interleave=all ./large-dataset-app # 메모리 인터리빙
$ numactl --preferred=0 ./my-app # 노드 0 선호 (fallback 허용)
# numactl vs taskset 차이:
# - taskset: CPU 바인딩만 (메모리 정책 없음)
# - numactl: CPU + 메모리 + NUMA 정책 (interleave, preferred, bind)
pthread_setaffinity_np C 코드
/* pthread_setaffinity_np를 이용한 스레드별 CPU 바인딩 */
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 4
void *worker(void *arg) {
int tid = *(int *)arg;
cpu_set_t cpuset;
/* 현재 스레드의 CPU 친화도 확인 */
CPU_ZERO(&cpuset);
pthread_getaffinity_np(pthread_self(),
sizeof(cpuset), &cpuset);
printf("Thread %d running on CPU %d\n",
tid, sched_getcpu());
return NULL;
}
int main(void) {
pthread_t threads[NUM_THREADS];
int tids[NUM_THREADS];
int target_cpus[] = {0, 2, 4, 6}; /* SMT 형제 회피 */
for (int i = 0; i < NUM_THREADS; i++) {
tids[i] = i;
pthread_attr_t attr;
cpu_set_t cpuset;
pthread_attr_init(&attr);
CPU_ZERO(&cpuset);
CPU_SET(target_cpus[i], &cpuset);
pthread_attr_setaffinity_np(&attr,
sizeof(cpuset), &cpuset);
pthread_create(&threads[i], &attr,
worker, &tids[i]);
pthread_attr_destroy(&attr);
}
for (int i = 0; i < NUM_THREADS; i++)
pthread_join(threads[i], NULL);
return 0;
}
CPU affinity systemd 서비스
# /etc/systemd/system/latency-critical.service
[Unit]
Description=Latency Critical Application
After=network.target
[Service]
Type=simple
ExecStart=/opt/app/latency-critical
# CPU 바인딩: 격리된 CPU에서만 실행
CPUAffinity=48-55
# NUMA 메모리 정책
NUMAPolicy=bind
NUMAMask=2
# 실시간 스케줄링 (선택)
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=80
# cgroup cpuset 제한 (systemd가 자동 관리)
AllowedCPUs=48-55
AllowedMemoryNodes=2
[Install]
WantedBy=multi-user.target
CPUAffinity=는 sched_setaffinity()를, AllowedCPUs=는 cgroup cpuset을 각각 설정합니다. 두 가지를 함께 사용하면 이중으로 바인딩이 적용되어 더 확실한 격리가 됩니다.
RPS/XPS 설정 스크립트
RPS(Receive Packet Steering)와 XPS(Transmit Packet Steering)는 소프트웨어 수준에서 네트워크 패킷 처리를 CPU에 분산합니다. 하드웨어 RSS를 지원하지 않는 NIC이나, 더 세밀한 제어가 필요한 경우에 유용합니다.
#!/bin/bash
# RPS/XPS NUMA-aware 설정 스크립트
NIC="eth0"
NUMA_NODE=$(cat /sys/class/net/$NIC/device/numa_node)
# NUMA 노드의 CPU 비트마스크 계산
CPUMASK=$(cat /sys/devices/system/node/node${NUMA_NODE}/cpumap)
echo "NIC $NIC (NUMA $NUMA_NODE), CPU mask: $CPUMASK"
# RPS 설정: 수신 패킷 처리를 로컬 NUMA CPU에 분산
for rxq in /sys/class/net/$NIC/queues/rx-*; do
echo "$CPUMASK" > "$rxq/rps_cpus"
# RPS flow table 크기 (NIC 큐당 4096 권장)
echo 4096 > "$rxq/rps_flow_cnt"
echo " $(basename $rxq): rps_cpus=$CPUMASK"
done
# 글로벌 flow table 크기
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
# XPS 설정: 송신 큐별 CPU 매핑
txq_idx=0
for txq in /sys/class/net/$NIC/queues/tx-*; do
# 각 TX 큐를 하나의 CPU에 매핑
cpu_mask=$(printf "%x" $((1 << txq_idx)))
echo "$cpu_mask" > "$txq/xps_cpus"
echo " $(basename $txq): xps_cpus=$cpu_mask (CPU $txq_idx)"
txq_idx=$((txq_idx + 1))
done
echo "RPS/XPS 설정 완료"
종합 벤치마크 스크립트
토폴로지 배치의 효과를 정량적으로 측정하는 벤치마크 스크립트입니다. 로컬 vs 원격 NUMA, SMT 형제 공유 vs 분리 등 다양한 시나리오를 비교합니다.
#!/bin/bash
# CPU 토폴로지 배치 벤치마크
# 조건별 메모리 대역폭 + 지연시간 비교
BENCH_CMD="sysbench cpu --cpu-max-prime=20000 --threads=4 run"
run_bench() {
local label="$1"
local cpuset="$2"
local numa_opt="$3"
echo "--- $label ---"
echo " CPUs: $cpuset"
if [ -n "$numa_opt" ]; then
result=$(numactl $numa_opt taskset -c $cpuset $BENCH_CMD 2>&1)
else
result=$(taskset -c $cpuset $BENCH_CMD 2>&1)
fi
events=$(echo "$result" | grep "events per second" | awk '{print $NF}')
latency=$(echo "$result" | grep "avg:" | awk '{print $2}')
printf " Events/sec: %-12s Avg latency: %s ms\n" "$events" "$latency"
}
echo "===== CPU 토폴로지 배치 벤치마크 ====="
echo
# 시나리오 1: 같은 코어의 SMT 형제 (자원 경쟁)
run_bench "SMT 형제 (같은 코어)" "0,96"
# 시나리오 2: 같은 CCD/L3의 다른 코어
run_bench "같은 L3 (다른 코어)" "0-3"
# 시나리오 3: 다른 CCD (같은 소켓)
run_bench "다른 L3 (같은 소켓)" "0,8,16,24"
# 시나리오 4: 로컬 NUMA 메모리
run_bench "NUMA 로컬 메모리" "0-3" "--membind=0"
# 시나리오 5: 원격 NUMA 메모리
run_bench "NUMA 원격 메모리" "0-3" "--membind=2"
echo
echo "===== 메모리 지연시간 비교 ====="
# numactl을 이용한 NUMA 지연시간 측정
for node in 0 1 2 3; do
echo " Node 0 → Node $node:"
numactl --cpunodebind=0 --membind=$node \
perf stat -e "mem_load_retired.l3_miss" \
sysbench memory --memory-total-size=1G run 2>&1 | \
grep -E "transferred|l3_miss"
done
cpupower frequency-set -g performance로 주파수를 고정하고, isolcpus로 테스트 CPU를 격리한 후 측정하세요.
최신 변경사항 (v6.12~v7.0, 2024-2026)
2024~2026년 CPU 토폴로지 서브시스템의 핵심 변화는 PREEMPT_RT의 mainline 통합, Intel 하이브리드 EAS 도입, sched_ext NUMA 인식 유휴 CPU 선택입니다. 하이브리드 코어(P-core/E-core) 지원이 성숙하면서 스케줄러/리소스 제어가 토폴로지 정보에 더 깊이 의존하게 되었습니다.
| 버전 | 변경 | 영향 |
|---|---|---|
| v6.12 | PREEMPT_RT mainline 통합 + sched_ext(BPF 스케줄러) 병합 | 실시간 커널이 mainline에 통합되어 CPU 격리(isolcpus, nohz_full)와 실시간 스케줄링이 일반 커널에서 직접 사용 가능. sched_ext는 토폴로지 기반 커스텀 스케줄러 작성을 가능하게 함 |
| v6.13 | Intel 하이브리드 코어 식별 확장 (HFI), AMD 드라이버 개선 | P-core/E-core 코어 타입 식별 인프라가 재정비되어 HFI(Hardware Feedback Interface)와 연동성 개선. AMD 아키텍처 관련 업데이트 |
| v6.14 | per-NUMA 유휴 cpumask 초기 작업, x86 TLB 플러시 확장성 개선 | 컨텍스트 스위치 시 일부 TLB 관련 자료구조를 지연 업데이트하여 마이크로벤치마크에서 플러시 오버헤드 감소. sched_ext scx_bpf_pick_idle_cpu() 개선 시작 |
| v6.15 | sched_ext per-NUMA 유휴 cpumask 분할 (Andrea Righi, NVIDIA) + SCX_PICK_IDLE_IN_NODE / SCX_PICK_IDLE_CORE 플래그 |
전역 유휴 CPU 마스크를 NUMA 노드별 cpumask로 분할. DGX B200(224 SMT 스레드, 112코어)에서 scx_simple 병렬 커널 빌드 시 ~4% 속도 향상 확인. setcpuid= 부팅 매개변수로 CPUID 리프 오버라이드 가능 |
| v6.16 | Intel 하이브리드 EAS(Energy Aware Scheduling) 병합 (intel_pstate 드라이버), ITMT 안정화, SMT 도메인 재구성 | EAS가 ARM 전용에서 처음으로 x86 하이브리드 플랫폼(SMT 없음, Lunar Lake 대상)으로 확장. SD_ASYM_PACKING을 SMT 도메인에서 제거하고 코어 단위로 이동. intel_pstate 하이브리드 CPU 용량 계산 개선 |
| 2025 패치 | sched/isolation: Housekeeping 강제 확보 (Gabriele Monaco, v5→v13) | isolcpus + nohz_full 합집합이 전체 CPU를 덮는 구성을 감지하여 마지막 설정을 무효화(Invalidation). 타이머 휠 마이그레이션 등 서브시스템 손상 방지 |
| v6.18 LTS | AMD Zen 5 (Turin) 토폴로지 탐지 경로 대규모 클린업 | Turin(9005) 계열에서 CCD/CCX/IOD 구조 파싱 정확도 제고. CPUID leaf 0x8000001E 기반 amd_get_topology() 함수 정리, 하이브리드 토폴로지(Zen5c 고밀도 CCD) 처리 개선, /sys/devices/system/cpu/cpu*/topology/ 속성 일관화 및 topology_die_id/cpu_llc_id 신뢰성 향상 |
| v6.19 | ARM64 MPAM 드라이버 병합 | 토폴로지 기반 파티션(PARTID)에 의한 캐시·대역폭 분할. 서버 NUMA 도메인과 함께 리소스 제어 계층 확립 |
| v6.19 | RISC-V 병렬 CPU 핫플러그(Hotplug) | 대형 RISC-V 서버에서 nr_cpu_ids가 커질수록 smp_init() 시간이 단축 |
| v7.0 | CXL 2.x + ACPI PRMT 기반 주소 변환(Address Translation) (Zen 5) | CXL 메모리 핫 추가 시 NUMA 노드·CPU-메모리 거리 매트릭스 재계산 경로가 안정화 |
lscpu -e, cpuid -l 0x80000026
(AMD Extended CPU Topology Leaf)로 CCD/CCX/패키지 구조를 정확히 확인할 수 있습니다. Zen 6(2026 하반기)도
동일 인터페이스를 확장 사용합니다.
참고자료
공식 규격 및 표준
- Intel® 64 and IA-32 Architectures Software Developer's Manual — CPUID 토폴로지 열거(Leaf 0Bh/1Fh)를 포함하는 공식 매뉴얼입니다
- ARM Cortex-A Series Programmer's Guide — ARM big.LITTLE/DynamIQ 아키텍처의 프로그래밍 가이드입니다
- AMD64 Architecture Programmer's Manual — AMD CCD/CCX 토폴로지를 포함하는 공식 프로그래머 매뉴얼입니다
커널 문서
- CPU Topology — Kernel Documentation — sysfs를 통한 CPU 토폴로지 조회 방법을 설명합니다
- Scheduler Domains — 토폴로지 기반 스케줄러 도메인 구성을 설명합니다
- Energy Aware Scheduling — 비대칭 토폴로지에서의 에너지 인식 스케줄링 설계입니다
- CPU Capacity — Asymmetric CPU Topology — 하이브리드 아키텍처의 비대칭 CPU 용량 처리 방식입니다
- x86 Topology — x86 아키텍처의 토폴로지 파싱 세부사항입니다
LWN 기사
- An introduction to asymmetric multiprocessing — big.LITTLE 등 비대칭 멀티프로세싱의 커널 지원을 다룹니다 (2019)
- Hybrid scheduling arrives in Linux — Intel 하이브리드 CPU의 스케줄러 통합을 설명합니다 (2020)
- Energy-aware scheduling — 에너지 인식 스케줄링의 설계와 구현을 다룹니다 (2015)
- Per-entity load tracking — PELT 기반 부하 추적과 토폴로지의 관계를 설명합니다 (2011)
- Cluster scheduling for x86 — x86 클러스터 토폴로지의 스케줄링 도메인 지원을 다룹니다 (2021)
커널 소스 코드
- drivers/base/topology.c — sysfs CPU 토폴로지 속성 내보내기 코드입니다
- arch/x86/kernel/smpboot.c — x86 SMP 부팅 시 토폴로지 파싱 코드입니다
- kernel/sched/topology.c — 스케줄러 도메인 계층 구성 핵심 코드입니다
- include/linux/topology.h — 토폴로지 관련 매크로와 인라인 함수(Inline Function) 정의입니다
컨퍼런스 발표 및 기술 자료
- Linux Plumbers Conference — Scheduler Topology — 스케줄러 도메인과 CPU 토폴로지의 관계를 설명하는 발표입니다
- AnandTech — AMD Zen 3 Deep Dive — AMD CCD/CCX 토폴로지 변화를 하드웨어 관점에서 분석합니다
관련 문서
CPU 토폴로지와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.