빌드 시스템 (Build System)

Linux 커널의 빌드 시스템인 Kbuild와 Kconfig를 상세히 다룹니다. Makefile 구조, 설정 문법, 컴파일 과정뿐 아니라 ARCH, CROSS_COMPILE, O, M, V, W, LLVM 같은 실전 make 인자와 환경변수까지 커널 빌드의 전체 흐름을 설명합니다.

문서 구조 재정렬: 이 문서는 Kbuild/Kconfig 흐름 중심으로 유지합니다. GNU Make 문법 심화는 GNU Make 문서를 우선 참고하세요.
관련 표준: GNU Make (IEEE Std 1003.1-2017), POSIX.1 (make 명령) — 커널 빌드 시스템이 따르는 표준 빌드 도구 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
전제 조건: 커널 아키텍처 문서를 먼저 읽으세요. 입문 문서는 개발 환경, 코드 위치, 변경 절차를 연결해 보는 것이 핵심이므로 기본 작업 흐름을 먼저 고정해야 합니다.
일상 비유: 이 주제는 현장 작업 시작 전 안전 교육과 비슷합니다. 도구 사용법과 작업 순서를 먼저 익혀야 실수를 줄일 수 있듯이, 커널 개발도 기본 루틴을 먼저 갖추는 것이 중요합니다.

핵심 요약

  • Kconfig — 커널 설정 시스템. .config 파일에 어떤 기능을 포함할지(y/m/n) 저장합니다.
  • Kbuild — GNU Make 기반 빌드 시스템. 각 디렉토리의 Makefileobj-y/obj-m으로 빌드 대상을 선언합니다.
  • make menuconfig — 터미널 UI로 커널 옵션을 선택하는 가장 일반적인 방법입니다.
  • 모듈(m) vs 내장(y)y는 커널 이미지에 포함, m은 별도 .ko 파일로 빌드됩니다. 자세한 내용은 커널 모듈 페이지를 참고하세요.
  • Cross-compilationARCHCROSS_COMPILE 변수로 다른 아키텍처용 커널을 빌드할 수 있습니다.

단계별 이해

  1. 소스 받기git clone으로 커널 소스를 내려받거나, kernel.org에서 tarball을 다운로드합니다.

    빌드에 필요한 도구(gcc, make, flex, bison 등)도 설치합니다.

  2. 설정 생성make defconfig 또는 make menuconfig.config 파일을 만듭니다.

    이 파일이 "무엇을 빌드할지"의 청사진입니다.

  3. 빌드 실행make -j$(nproc)으로 병렬 빌드합니다. Kbuild가 의존성을 분석하고 올바른 순서로 컴파일합니다.

    결과물: vmlinux(ELF 형식), bzImage(부팅용 압축 이미지), *.ko(모듈).

  4. 설치make modules_install로 모듈을, make install로 커널을 설치합니다.

    부트로더(GRUB 등) 설정을 업데이트하면 새 커널로 부팅할 수 있습니다.

Kbuild 시스템 개요 (Kbuild System Overview)

Linux 커널의 빌드 시스템은 Kbuild라 불리며, 수만 개의 소스 파일과 수천 개의 설정 옵션을 효율적으로 관리합니다. Kbuild는 단순한 Makefile의 집합이 아니라, Kconfig(설정 시스템)와 Kbuild Makefile(빌드 규칙)이 유기적으로 결합된 정교한 인프라입니다.

ℹ️

Kbuild 시스템은 Linux 커널 소스 트리의 scripts/ 디렉토리에 핵심 스크립트가 위치하며, scripts/kconfig/에 Kconfig 파서가, scripts/Makefile.*에 빌드 규칙이 정의되어 있습니다.

역사와 발전 (History)

Linux 커널 빌드 시스템은 시간이 지나면서 크게 발전해왔습니다.

구성 요소 (Components)

Kbuild 시스템은 다음과 같은 핵심 구성 요소로 이루어져 있습니다.

구성 요소 위치 역할
Kconfig 각 디렉토리의 Kconfig 파일 커널 설정 옵션 정의, 의존성 기술
Top Makefile 소스 루트 Makefile 빌드 진입점, 아키텍처 설정, 전역 빌드 규칙
Kbuild Makefile 각 디렉토리의 Makefile obj-y/obj-m으로 빌드 대상 선언
scripts/ scripts/Makefile.* 빌드 규칙, 링커 스크립트, 도구 모음
.config 소스 루트 .config 사용자가 선택한 커널 설정 결과물
fixdep scripts/basic/fixdep 헤더 의존성 추적, 증분 빌드 지원

Kconfig 문법 (Kconfig Syntax)

Kconfig는 커널의 설정 옵션을 선언적으로 기술하는 언어입니다. 각 서브시스템 디렉토리에 Kconfig 파일이 존재하며, 최상위 Kconfig가 이들을 source 지시자로 포함합니다.

기본 문법 (Basic Syntax)

Kconfig의 가장 기본적인 단위는 config 항목입니다. 각 항목은 심볼 이름, 타입, 기본값, 도움말 등으로 구성됩니다.

config MY_DRIVER
    tristate "My Example Driver"
    depends on PCI && NET
    default n
    help
      이 드라이버는 PCI 버스에 연결된 예제 네트워크 장치를
      지원합니다. 확실하지 않다면 N을 선택하세요.

      모듈로 빌드하려면 M을, 커널에 내장하려면 Y를 선택합니다.

위 예제에서 config MY_DRIVERCONFIG_MY_DRIVER라는 심볼을 생성합니다. .config 파일에는 CONFIG_MY_DRIVER=y, CONFIG_MY_DRIVER=m, 또는 주석 처리된 # CONFIG_MY_DRIVER is not set으로 기록됩니다.

설정 타입 (Config Types)

Kconfig에서 사용할 수 있는 타입은 다음과 같습니다.

타입 가능한 값 설명
bool y / n 커널에 내장하거나 비활성화
tristate y / m / n 내장(y), 모듈(m), 비활성화(n)
int 정수 정수 값 (예: 버퍼 크기)
hex 16진수 16진수 값 (예: 기본 주소)
string 문자열 문자열 값 (예: 디바이스 이름)

의존성과 선택 (Dependencies & Selection)

Kconfig의 핵심 기능 중 하나는 의존성 관리입니다. depends on, select, imply 키워드를 사용하여 옵션 간의 관계를 정의합니다.

# depends on: 선행 조건이 만족되어야 이 옵션이 보임
config USB_STORAGE
    tristate "USB Mass Storage support"
    depends on USB

# select: 이 옵션이 활성화되면 대상을 강제 활성화
config EXT4_FS
    tristate "The Extended 4 (ext4) filesystem"
    select CRC16
    select CRYPTO
    select CRYPTO_CRC32C
    select FS_IOMAP

# imply: select와 비슷하나, 사용자가 거부할 수 있음
config WATCHDOG
    bool "Watchdog Timer Support"
    imply WATCHDOG_CORE
⚠️

select 사용 시 주의: select는 대상의 depends on 조건을 무시하고 강제로 활성화합니다. 의존성 체인이 깨질 수 있으므로, 가능하면 가시적인(visible) 심볼에 select를 사용하지 않는 것이 권장됩니다. 라이브러리 성격의 숨겨진 심볼에만 사용하세요.

choice 블록 (Choice Blocks)

choice 블록은 여러 옵션 중 하나만 선택할 수 있는 라디오 버튼 그룹을 정의합니다. 예를 들어, 커널 압축 방식을 선택하는 경우입니다.

choice
    prompt "Kernel compression mode"
    default KERNEL_GZIP
    help
      커널 이미지 압축 방식을 선택합니다.

config KERNEL_GZIP
    bool "Gzip"
    help
      가장 오래된 표준 압축 방식. 호환성이 좋고 안정적입니다.

config KERNEL_BZIP2
    bool "Bzip2"
    help
      Gzip보다 압축률이 높지만 압축 해제가 느립니다.

config KERNEL_LZ4
    bool "LZ4"
    help
      가장 빠른 압축 해제 속도. 임베디드 시스템에 적합합니다.

config KERNEL_ZSTD
    bool "Zstandard"
    help
      높은 압축률과 빠른 해제 속도를 동시에 제공합니다.

endchoice

Kconfig 종합 예제 (Comprehensive Kconfig Example)

실제 드라이버에서 볼 수 있는 복합적인 Kconfig 패턴입니다.

# menuconfig: 하위 메뉴를 가진 설정 블록
menuconfig NETDEVICES
    bool "Network device support"
    depends on NET
    default y

if NETDEVICES

config ETHERNET
    bool "Ethernet driver support"
    default y

if ETHERNET

config NET_VENDOR_INTEL
    bool "Intel devices"
    default y
    help
      Intel 이더넷 장치 드라이버 모음. e1000, e1000e, igb, ixgbe,
      ice 등의 드라이버를 포함합니다.

if NET_VENDOR_INTEL

config E1000E
    tristate "Intel(R) PRO/1000 PCI-Express Gigabit Ethernet"
    depends on PCI
    select CRC32
    help
      Intel PRO/1000 PCI-Express 기가비트 이더넷 어댑터 드라이버.

config ICE
    tristate "Intel(R) Ethernet Connection E800 Series"
    depends on PCI_MSI
    select NET_DEVLINK
    select DIMLIB
    imply PTP_1588_CLOCK

endif # NET_VENDOR_INTEL
endif # ETHERNET
endif # NETDEVICES

Kconfig 고급 문법 (Advanced Kconfig Syntax)

기본 config 항목 외에도 Kconfig는 복잡한 설정 구조를 표현하기 위한 다양한 고급 문법을 제공합니다.

menu / endmenu 블록

menu 블록은 설정 항목들을 논리적 그룹으로 묶어 메뉴 계층을 만듭니다. menuconfig와 달리 menu 자체는 심볼을 생성하지 않으며, 단순히 UI 계층 구조만 정의합니다.

# menu: 심볼 없이 순수 UI 그룹 (항상 보임)
menu "Networking options"
    depends on NET

config PACKET
    tristate "Packet socket"

config UNIX
    tristate "Unix domain sockets"

endmenu

# menuconfig: 심볼을 가진 토글 가능한 메뉴
# 비활성화하면 하위 항목 전체가 숨겨짐
menuconfig NETDEVICES
    bool "Network device support"
    depends on NET

if NETDEVICES
    # NETDEVICES=y일 때만 보이는 하위 항목들
    config ETHERNET
        bool "Ethernet driver support"
endif
구분 menu menuconfig
심볼 생성 없음 있음 (CONFIG_*)
활성/비활성 항상 표시 (depends on 제외) 사용자가 토글 가능
하위 항목 제어 자체 제어 불가 비활성 시 하위 전체 숨김
용도 순수 UI 그룹핑 기능 블록 단위 토글

source 지시자와 파일 포함 구조

source 지시자는 다른 Kconfig 파일을 현재 위치에 포함합니다. 커널 전체의 Kconfig 트리는 최상위 Kconfig에서 시작하여 source로 재귀적으로 확장됩니다.

# 최상위 Kconfig (소스 루트)
mainmenu "Linux/$(ARCH) $(KERNELVERSION) Kernel Configuration"

source "scripts/Kconfig.include"

source "init/Kconfig"
source "kernel/Kconfig.freezer"
source "fs/Kconfig"
source "mm/Kconfig"
source "net/Kconfig"
source "drivers/Kconfig"
source "security/Kconfig"
source "crypto/Kconfig"
source "lib/Kconfig"
# drivers/Kconfig — 서브디렉토리별 추가 source
menu "Device Drivers"

source "drivers/base/Kconfig"
source "drivers/bus/Kconfig"
source "drivers/pci/Kconfig"
source "drivers/usb/Kconfig"
source "drivers/net/Kconfig"
source "drivers/gpu/Kconfig"
# ... 수십 개의 서브시스템

endmenu
ℹ️

Kconfig 파일 포함 구조는 계층적이며, 전체 커널 설정 트리는 약 2,000개 이상의 Kconfig 파일로 구성됩니다. find . -name "Kconfig*" | wc -l로 현재 커널의 Kconfig 파일 수를 확인할 수 있습니다.

comment 지시자

comment는 설정 UI에 읽기 전용 텍스트를 표시합니다. 사용자에게 정보를 전달하거나, 특정 조건이 만족되지 않았을 때 안내 메시지를 보여주는 데 사용됩니다.

comment "Ethernet drivers require NETDEVICES"
    depends on !NETDEVICES

# menuconfig에서는 다음과 같이 표시:
# --- Ethernet drivers require NETDEVICES

comment "SCSI Transport Attributes"
    depends on SCSI

config SCSI_FC_ATTRS
    tristate "FiberChannel Transport Attributes"
    depends on SCSI && NET

visible if와 range

visible if는 메뉴의 가시성을 조건부로 제어하고, range는 정수/16진수 값의 유효 범위를 제한합니다.

# visible if: 메뉴 자체의 가시성을 조건부로 제어
# depends on과 달리, 조건이 거짓이어도 하위 항목의 기본값은 유지됨
menu "Power management options"
    visible if ACPI || APM

config SUSPEND
    bool "Suspend to RAM and standby"

endmenu

# range: int/hex 타입의 유효 범위 지정
config NR_CPUS
    int "Maximum number of CPUs"
    range 2 8192
    default 64 if SMP
    default 1
    help
      최대 CPU 수를 지정합니다. 이 값은 per-cpu 데이터 구조의
      크기에 영향을 미칩니다. 실제 CPU 수보다 너무 크게
      설정하면 메모리가 낭비됩니다.

config LOG_BUF_SHIFT
    int "Kernel log buffer size (16 => 64KB, 17 => 128KB)"
    range 12 25
    default 17

config ILLEGAL_POINTER_VALUE
    hex
    default 0xdead000000000000 if 64BIT
    default 0

Kconfig 표현식과 조건문 (Expressions & Conditionals)

Kconfig의 의존성과 조건에는 논리 표현식을 사용합니다. 표현식은 심볼 비교, 논리 연산, 그리고 조건부 기본값에 활용됩니다.

표현식 의미 예시
A 심볼 A가 y 또는 m depends on NET
!A 부정 (NOT) depends on !EXPERT
A && B 논리곱 (AND) depends on PCI && NET
A || B 논리합 (OR) depends on X86 || ARM64
A = B 값 동등 비교 depends on ARCH = "x86"
A != B 값 불일치 비교 depends on COMPILER != "clang"
(expr) 그룹핑 depends on (A || B) && C
# 복합 조건식 예시
config KVM
    tristate "Kernel-based Virtual Machine (KVM) support"
    depends on HAVE_KVM
    depends on HIGH_RES_TIMERS
    depends on X86_LOCAL_APIC
    # 여러 depends on은 암묵적 AND 결합

# 조건부 기본값: 여러 default를 조건으로 분기
config HZ
    int
    default 100 if HZ_100
    default 250 if HZ_250
    default 300 if HZ_300
    default 1000 if HZ_1000

# if/endif 블록: 조건부 설정 그룹
if X86_64

config X86_X2APIC
    bool "Support x2APIC"
    depends on X86_LOCAL_APIC && IRQ_REMAP

config X86_5LEVEL
    bool "Enable 5-level page tables support"
    select DYNAMIC_MEMORY_LAYOUT
    select SPARSEMEM_VMEMMAP

endif # X86_64

# tristate 의존성과 모듈 프로모션
# tristate 옵션이 모듈(m)에 depends on하면
# 최대 'm'까지만 선택 가능 (y 불가)
config USB_STORAGE
    tristate "USB Mass Storage support"
    depends on USB
    # USB=m이면 USB_STORAGE도 최대 m까지만 가능
    # USB=y이면 USB_STORAGE는 y/m/n 모두 가능
ℹ️

tristate 값의 대소 관계: Kconfig에서 tristate 값은 n < m < y의 순서를 가집니다. depends onm으로 평가되면, 해당 옵션의 최대값도 m으로 제한됩니다. 이것이 "모듈 프로모션 규칙"이며, 상위 옵션이 모듈이면 하위도 모듈까지만 가능합니다.

Kconfig 매크로 언어 (Kconfig Macro Language)

현대 Linux 커널의 Kconfig는 매크로 확장 기능을 지원합니다. scripts/Kconfig.include에 정의된 매크로를 통해 컴파일러 기능 감지, 아키텍처별 조건 등을 동적으로 처리할 수 있습니다.

# scripts/Kconfig.include — 커널 매크로 정의

# 셸 명령 실행 결과를 변수로 사용
cc-option = $(success,$(CC) -Werror $(1) -c -x c /dev/null -o /dev/null)
ld-option = $(success,$(LD) -v $(1))

# 아키텍처 감지
$(shell,...)     # 셸 명령 실행 결과 반환
$(success,...)   # 셸 명령 성공 시 y, 실패 시 빈 문자열
$(failure,...)   # 셸 명령 실패 시 y, 성공 시 빈 문자열
$(warning,...)   # 경고 메시지 출력 (빌드 중단 안 함)
$(error,...)     # 오류 메시지 출력 후 빌드 중단
$(info,...)      # 정보 메시지 출력
$(filename)     # 현재 Kconfig 파일의 이름
$(lineno)       # 현재 줄 번호
# 실전 예제: 컴파일러 기능 감지로 옵션 제어
config CC_HAS_ASM_GOTO
    def_bool $(success,$(srctree)/scripts/gcc-goto.sh $(CC))

config CC_HAS_KASAN_GENERIC
    def_bool $(cc-option,-fsanitize=kernel-address)

# 아키텍처별 분기
config ARCH_SUPPORTS_LTO_CLANG
    bool
    default y if X86_64 || ARM64

# GCC 버전 감지를 통한 기능 가드
config CC_HAS_ZERO_LENGTH_VARIADIC_ARGS
    def_bool $(cc-option,-Wno-gnu-zero-variadic-macro-arguments)

# Clang/GCC 분기
config CC_IS_CLANG
    def_bool $(success,echo "$(CC_VERSION_TEXT)" | grep -q clang)

config CC_IS_GCC
    def_bool $(success,echo "$(CC_VERSION_TEXT)" | grep -q gcc)
⚠️

매크로 사용 시 주의: $(shell,...)은 Kconfig 파싱 시점에 실행되므로, 빌드 환경이 달라지면 결과가 바뀝니다. Cross-compilation 환경에서는 호스트가 아닌 타깃 컴파일러 경로가 올바르게 설정되어 있어야 합니다. make ARCH=arm64 CROSS_COMPILE=... menuconfig으로 설정 시 올바른 컴파일러가 감지됩니다.

Kconfig 프론트엔드 내부 구조 (Kconfig Frontend Internals)

Kconfig 시스템은 공통 파서 라이브러리여러 프론트엔드 UI로 분리된 구조입니다. 파서는 동일하지만 사용자 인터페이스만 다릅니다.

프론트엔드 소스 위치 make 타겟 의존 라이브러리
conf scripts/kconfig/conf.c oldconfig, defconfig 없음 (텍스트 전용)
mconf scripts/kconfig/mconf.c menuconfig ncurses (lxdialog)
nconf scripts/kconfig/nconf.c nconfig ncurses (개선 UI)
qconf scripts/kconfig/qconf.cc xconfig Qt5/Qt6
gconf scripts/kconfig/gconf.c gconfig GTK+ 2.0
/* Kconfig 파서 공통 구조체 (scripts/kconfig/lkc.h) */
struct symbol {
    struct symbol *next;
    char *name;           /* 심볼 이름 (예: "NET") */
    enum symbol_type type; /* S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING */
    struct symbol_value curr; /* 현재 계산된 값 */
    struct symbol_value def[S_DEF_COUNT]; /* 기본값 */
    tristate visible;    /* 가시성 */
    struct property *prop; /* 속성 체인 (depends, select 등) */
    unsigned int flags;
};

struct menu {
    struct menu *next;    /* 형제 메뉴 */
    struct menu *parent;  /* 부모 메뉴 */
    struct menu *list;    /* 첫 번째 자식 메뉴 */
    struct symbol *sym;   /* 연결된 심볼 */
    struct property *prompt; /* 프롬프트 텍스트 */
    struct expr *visibility; /* visible if 표현식 */
};
💡

Tip: Kconfig 파서는 렉서(lexer.l)와 파서(parser.y)로 구성된 전통적인 lex/yacc 구조입니다. scripts/kconfig/ 디렉토리의 소스를 분석하면 Kconfig 언어의 정확한 문법 정의를 확인할 수 있습니다.

설정 파일 (.config)

.config 파일은 Kconfig 시스템의 출력물로, 사용자가 선택한 모든 커널 설정을 담고 있습니다. 이 파일은 커널 소스 루트에 생성되며, makeinclude/generated/autoconf.hinclude/config/auto.conf로 변환되어 실제 빌드에 사용됩니다.

#
# Automatically generated file; DO NOT EDIT.
# Linux/x86 6.1.0 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="gcc (GCC) 13.2.0"
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=130200
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_SMP=y
CONFIG_NR_CPUS=512
# CONFIG_PREEMPT_NONE is not set
CONFIG_PREEMPT_VOLUNTARY=y
# CONFIG_PREEMPT is not set
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_EXT4_FS=y
CONFIG_E1000E=m
💡

Tip: .config 파일을 직접 편집하기보다 make menuconfig, make nconfig, make xconfig 같은 설정 도구 사용을 권장합니다. 수동 수정 후에는 make oldconfig로 의존성 정합성을 다시 맞추는 편이 안전합니다.

defconfig

defconfig는 특정 플랫폼이나 사용 시나리오에 최적화된 기본 설정 파일입니다. 아키텍처별로 arch/<arch>/configs/ 디렉토리에 위치합니다.

# x86_64 기본 설정 적용
make defconfig

# 특정 플랫폼 defconfig 적용 (ARM 예시)
make ARCH=arm multi_v7_defconfig

# ARM64 특정 defconfig
make ARCH=arm64 defconfig

# RISC-V defconfig
make ARCH=riscv defconfig

# 현재 설정을 defconfig로 저장
make savedefconfig
cp defconfig arch/x86/configs/my_defconfig

defconfig는 기본값과 다른 설정만 저장하므로, .config보다 훨씬 작습니다. 이를 통해 설정 변경 사항을 버전 관리 시스템에서 효율적으로 추적할 수 있습니다.

.config 변환 파이프라인 (syncconfig)

.config 파일은 Kconfig UI의 출력물이지만, C 코드에서 직접 사용할 수 없습니다. make가 실행되면 syncconfig(내부적으로 conf --syncconfig)가 .config를 여러 형태로 변환하여 빌드 시스템과 C 코드에서 사용할 수 있게 합니다.

syncconfig 변환 파이프라인 (.config → 빌드 파일) .config syncconfig include/generated/autoconf.h — C #define include/config/auto.conf — Makefile 변수 include/config/auto.conf.cmd — 의존성 추적 include/config/*.h — CONFIG_* 타임스탬프 make syncconfig 또는 make oldconfig 실행 시 자동 생성
/* include/generated/autoconf.h — .config에서 자동 생성 */
#define CONFIG_64BIT 1
#define CONFIG_X86_64 1
#define CONFIG_SMP 1
#define CONFIG_NR_CPUS 512
#define CONFIG_PREEMPT_VOLUNTARY 1
/* CONFIG_PREEMPT_NONE is not set → #define 없음 */
/* CONFIG_E1000E=m → MODULE 전용: */
#define CONFIG_E1000E_MODULE 1

/* C 코드에서의 활용 */
#ifdef CONFIG_SMP
static void smp_init(void) { /* SMP 초기화 */ }
#else
static void smp_init(void) { }  /* UP: 빈 함수 */
#endif

/* IS_ENABLED() 매크로 — 컴파일 타임 조건 분기 (추천) */
#include <linux/kconfig.h>

if (IS_ENABLED(CONFIG_SMP)) {
    /* y 또는 m이면 참 (컴파일러가 dead code 제거) */
}

if (IS_BUILTIN(CONFIG_E1000E)) {
    /* y일 때만 참 */
}

if (IS_MODULE(CONFIG_E1000E)) {
    /* m일 때만 참 */
}
# include/config/auto.conf — Makefile에서 include하여 사용
CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_SMP=y
CONFIG_NR_CPUS=512
CONFIG_E1000E=m

# Kbuild Makefile에서 활용:
obj-$(CONFIG_E1000E) += e1000e/
# CONFIG_E1000E=m → obj-m += e1000e/ → 모듈로 빌드
# CONFIG_E1000E=y → obj-y += e1000e/ → 커널에 내장
ℹ️

증분 빌드와 syncconfig: include/config/ 디렉토리에는 각 CONFIG_* 옵션에 대한 빈 헤더 파일이 생성됩니다 (예: include/config/SMP). fixdep은 이 파일들의 타임스탬프를 의존성에 추가하여, 특정 CONFIG 옵션이 변경되었을 때 해당 옵션을 참조하는 소스 파일만 정확히 재컴파일합니다.

Kconfig 디버깅 도구 (Kconfig Debugging Tools)

커널 설정 작업 시 발생하는 문제를 진단하고, 설정 변경 사항을 추적하기 위한 도구들입니다.

scripts/diffconfig — 설정 차이 비교

두 개의 .config 파일 간 차이를 보기 쉽게 요약합니다. diff보다 훨씬 읽기 편하며, 추가/삭제/변경된 옵션만 간결하게 보여줍니다.

# 두 .config 간 차이 비교
scripts/diffconfig .config.old .config

# 출력 예시:
# -DEBUG_INFO n                           (삭제: n으로 변경)
# +DEBUG_INFO_DWARF5 y                    (추가: 새로 활성화)
#  NR_CPUS 256 -> 512                     (변경: 값 수정)
#  PREEMPT_VOLUNTARY y -> n               (변경: 비활성화)
# +PREEMPT y                              (추가: 활성화)

# 실전 워크플로우: menuconfig 전후 비교
cp .config .config.old
make menuconfig
scripts/diffconfig .config.old .config

# 머지 기반 비교 (3-way)
scripts/diffconfig -m .config.old .config

새 옵션 확인 (listnewconfig)

커널 업그레이드 후 기존 .config에 없는 새 옵션을 확인합니다. make oldconfig에서 질문받게 될 항목을 미리 파악할 수 있습니다.

# 이전 커널의 .config를 새 소스에 복사 후
cp /boot/config-$(uname -r) .config

# 새로 추가된 옵션 목록 확인
make listnewconfig

# 출력 예시 (새 심볼과 기본값):
# CONFIG_RUST=n
# CONFIG_SCHED_CLASS_EXT=n
# CONFIG_MITIGATION_RFDS=y

# 새 옵션에 기본값 자동 적용 (대화형 질문 없이)
make olddefconfig

# 새 옵션에 대해 하나씩 질문 (대화형)
make oldconfig

수천 개의 설정 옵션 중 원하는 항목을 찾는 방법입니다.

# menuconfig/nconfig 내 검색: / 키 입력 후 키워드 검색
# menuconfig에서 / 누르고 "KASAN" 입력 시:
# Symbol: KASAN [=n]
# Type  : bool
# Defined at lib/Kconfig.kasan:7
# Prompt: KASan: runtime memory debugger
# Depends on: (SLUB || SLAB) && ...
# Location:
#   -> Kernel hacking
#     -> Memory Debugging

# 터미널에서 .config 직접 검색
grep -i kasan .config

# Kconfig 파일에서 심볼 정의 위치 검색
grep -rn "config KASAN$" --include="Kconfig*"

# 특정 옵션의 의존성 추적
grep -rn "depends on.*KASAN\|select.*KASAN" --include="Kconfig*"

설정 검증과 비교 도구

# 현재 설정의 정합성 확인
make configcheck      # 비정상 설정 감지 (일부 커널 버전)

# 설정 옵션 개수 통계
grep -c "=y" .config   # 내장 옵션 수
grep -c "=m" .config   # 모듈 옵션 수
grep -c "is not set" .config  # 비활성 옵션 수

# 실행 중인 커널에서 설정 비교 (CONFIG_IKCONFIG 필요)
zcat /proc/config.gz > /tmp/running.config
scripts/diffconfig /tmp/running.config .config

# 두 defconfig 간 실질적 차이 확인
make defconfig
cp .config /tmp/defconfig.expanded
make ARCH=arm64 defconfig
scripts/diffconfig /tmp/defconfig.expanded .config

CONFIG_IKCONFIG (/proc/config.gz)

CONFIG_IKCONFIG를 활성화하면 커널 이미지 내에 빌드 당시의 .config를 내장합니다. CONFIG_IKCONFIG_PROC까지 활성화하면 /proc/config.gz를 통해 실행 중인 커널의 정확한 설정을 확인할 수 있습니다.

# lib/Kconfig.debug
config IKCONFIG
    tristate "Kernel .config support"
    help
      커널 이미지에 .config를 내장합니다.

config IKCONFIG_PROC
    bool "Enable access to .config through /proc/config.gz"
    depends on IKCONFIG && PROC_FS
# 활성화 방법
./scripts/config -e IKCONFIG -e IKCONFIG_PROC
make olddefconfig

# 실행 중인 커널의 설정 확인
zcat /proc/config.gz | grep CONFIG_PREEMPT

# 현재 커널의 설정을 .config로 복사하여 재빌드에 활용
zcat /proc/config.gz > .config
make olddefconfig
make -j$(nproc)

# vmlinux에서 설정 추출 (ikconfig 대안, /proc 없이)
scripts/extract-ikconfig vmlinux > extracted.config

# 배포판 커널 설정 확인 (일반적 경로)
ls /boot/config-$(uname -r)        # Debian/Ubuntu
ls /proc/config.gz                  # IKCONFIG 활성 시
ls /usr/src/linux/.config            # Gentoo
💡

Tip: 프로덕션 서버에서 커널 재빌드가 필요할 때, /proc/config.gz에서 현재 설정을 추출한 뒤 make olddefconfig으로 갱신하면 기존 설정을 높은 정확도로 재현하는 데 유용합니다. 단, 최종 빌드 결과는 툴체인 버전, 배포판 패치, 로컬 변경 여부에 따라 달라질 수 있습니다. 이 방법은 /boot/config-* 파일이 없는 환경에서도 동작합니다.

Kconfig 작성 모범 사례 (Kconfig Best Practices)

커널 커뮤니티에서 권장하는 Kconfig 작성 규약과 실무 가이드라인입니다. 새 드라이버나 서브시스템의 Kconfig를 작성할 때 참고하세요.

select vs. depends on 선택 기준

상황 권장 이유
라이브러리 성격의 숨겨진 심볼 (예: CRC32, CRYPTO) select 사용자에게 보이지 않는 인프라는 자동 활성화가 편리
사용자가 인지해야 하는 기능 (예: USB, NET) depends on 사용자가 명시적으로 활성화해야 의도가 분명
복잡한 의존성 체인이 있는 심볼 depends on select는 중간 의존성을 무시하여 빌드 실패 유발 가능
선택적 기능 제안 (있으면 좋지만 필수 아닌 경우) imply 기본 활성화하되 사용자가 거부할 수 있음
# 나쁜 예: visible 심볼에 select 사용
config MY_DRIVER
    tristate "My Driver"
    select USB        # ✗ USB는 사용자가 의식적으로 활성화해야 함
    select NET        # ✗ NET의 depends on 조건이 무시됨

# 좋은 예: 숨겨진 라이브러리 심볼에만 select
config MY_DRIVER
    tristate "My Driver"
    depends on USB && NET   # ✓ 가시적 의존성은 depends on
    select CRC32              # ✓ 라이브러리 심볼은 select
    select FW_LOADER           # ✓ 펌웨어 로더도 라이브러리 성격
    imply HWMON               # ✓ 하드웨어 모니터링은 선택적

심볼 명명 규칙 (Naming Conventions)

# 서브시스템/드라이버 심볼: 계층적 이름
CONFIG_NET_VENDOR_INTEL    # ✓ 계층 구조 명확
CONFIG_INTEL_NET           # ✗ 서브시스템이 앞에 와야 함

# 기능 가드 심볼: HAVE_ / ARCH_ 접두사
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS  # 아키텍처가 선언
CONFIG_ARCH_SUPPORTS_MEMORY_FAILURE     # 아키텍처 능력 표시

# 컴파일러 감지: CC_ 접두사
CONFIG_CC_IS_GCC
CONFIG_CC_HAS_ASM_GOTO

# 디버깅 옵션: DEBUG_ 접두사
CONFIG_DEBUG_INFO
CONFIG_DEBUG_SLAB

# hidden 심볼 (prompt 없음): 다른 심볼에서만 select
config CRYPTO_LIB_SHA256
    tristate
    # prompt 없음 → menuconfig에서 보이지 않음
    # 다른 드라이버가 select로만 활성화

help 텍스트 작성 가이드

config EXAMPLE_DRIVER
    tristate "Example PCIe Network Driver"
    depends on PCI && NET
    help
      첫 번째 줄: 이 옵션이 무엇을 하는지 한 줄로 설명.
      Example Corp의 PCIe 기반 10GbE 네트워크 어댑터
      (모델 EX-1000, EX-2000)를 지원하는 드라이버입니다.

      이 드라이버는 다음 기능을 제공합니다:
      - 10GbE line-rate 성능
      - RSS (Receive Side Scaling)
      - SR-IOV 가상화 지원

      펌웨어 파일이 필요합니다:
        example/fw_ex1000.bin

      확실하지 않다면 N을 선택하세요.

      모듈로 빌드하면 'example_net'이라는 이름으로 생성됩니다.
⚠️

help 들여쓰기 규칙: Kconfig에서 help 텍스트는 2칸 이상 들여쓰기로 시작해야 합니다. 들여쓰기가 줄어드는 첫 번째 줄에서 help 블록이 종료됩니다. 커널 코딩 스타일은 2칸 들여쓰기를 권장합니다. 마지막 줄에는 "확실하지 않다면 N/Y를 선택하세요(If unsure, say N/Y)"를 관례적으로 포함합니다.

Makefile 구조 (Makefile Architecture)

Kbuild의 Makefile은 계층적 구조를 가지며, 각 수준에서 서로 다른 역할을 수행합니다.

최상위 Makefile (Top-level Makefile)

커널 소스 루트의 Makefile은 전체 빌드의 진입점입니다. 이 파일은 다음과 같은 주요 역할을 합니다.

# 최상위 Makefile 주요 구조 (간략화)
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 0
EXTRAVERSION =
NAME = Hurr durr I'ma sheep

# 아키텍처 결정
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=

# 컴파일러 설정
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
AR = $(CROSS_COMPILE)ar

# 커널 빌드 플래그
KBUILD_CFLAGS := -Wall -Wundef -Werror=strict-prototypes
KBUILD_CFLAGS += -fno-common -fno-PIE
KBUILD_CFLAGS += -std=gnu11

# 아키텍처별 Makefile 포함
include arch/$(SRCARCH)/Makefile

# 빌드할 서브디렉토리 목록
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/
drivers-y += drivers/ sound/
net-y += net/
libs-y += lib/

서브디렉토리 Makefile (Sub-directory Makefile)

각 서브디렉토리의 Makefile(또는 Kbuild 파일)은 Kbuild 문법을 따르며, 해당 디렉토리에서 빌드할 대상을 선언합니다. Kbuild 파일이 있으면 Makefile보다 우선합니다.

obj-y, obj-m, obj-n 변수

이 변수들은 Kbuild Makefile의 핵심입니다.

변수 의미 결과물
obj-y 커널 이미지에 내장 built-in.a에 포함
obj-m 로드 가능한 모듈로 빌드 .ko 파일 생성
obj-n / obj- 빌드하지 않음 무시됨

Kconfig 심볼과의 연동이 핵심입니다. CONFIG_FOO=y이면 $(CONFIG_FOO)y로 확장되어 obj-y에 추가되고, CONFIG_FOO=m이면 obj-m에 추가됩니다.

Makefile 예제 (Makefile Examples)

실전에서 자주 볼 수 있는 Kbuild Makefile 패턴들입니다.

Intel Ethernet Kbuild 선택 패턴 drivers/net/ethernet/intel/Makefile `obj-$(CONFIG_*) += /` 형태로 서브디렉토리 선택 CONFIG_E1000 → e1000/ 레거시 NIC CONFIG_E1000E → e1000e/ PCIe 1GbE CONFIG_IGB → igb/ 1/2.5GbE CONFIG_IXGBE → ixgbe/ 10GbE CONFIG_ICE → ice/ 25/100GbE
# drivers/net/ethernet/intel/e1000e/Makefile
# 하나의 모듈이 여러 소스 파일로 구성되는 경우

obj-$(CONFIG_E1000E) += e1000e.o

# e1000e.ko는 다음 오브젝트 파일들로 구성
e1000e-objs := netdev.o \
              ethtool.o \
              param.o \
              82571.o \
              ich8lan.o \
              80003es2lan.o \
              mac.o \
              nvm.o \
              phy.o \
              manage.o \
              ptp.o
조건부 소스/플래그 설정 패턴 모듈 진입점 obj-$(CONFIG_MY_MODULE) += my_module.o my_module-y := core.o utils.o 기본 오브젝트 집합 CONFIG 값에 따라 포함/제외 조건부 오브젝트 my_module-$(CONFIG_MY_MODULE_DEBUG) += debug.o my_module-$(CONFIG_MY_MODULE_STATS) += stats.o 기능별 분리된 소스 선택 파일 단위 플래그 CFLAGS_core.o += -DEXTRA_DEBUG CFLAGS_REMOVE_utils.o += -Werror 파일별 증감 제어 디렉토리 공통 플래그 ccflags-y += -I$(srctree)/include/special ccflags-$(CONFIG_MY_MODULE_VERBOSE) += -DVERBOSE 해당 디렉토리의 모든 C 컴파일에 적용 하위 전파가 필요하면 subdir-ccflags-y 사용

빌드 과정 흐름도 (Build Process Flow)

커널 빌드는 설정 단계와 컴파일 단계로 나뉩니다. 아래 다이어그램은 make menuconfig부터 최종 커널 이미지 생성까지의 전체 흐름을 보여줍니다.

Linux 커널 빌드 과정 (Build Process Flow) Phase 1: Configuration make menuconfig Kconfig 파서 .config 생성 include/generated/ autoconf.h include/config/ Phase 2: Compilation make scripts/ Makefile.build .c -> .o (CC) .S -> .o (AS) built-in.a (AR) fixdep .cmd files Phase 3: Linking 모든 built-in.a 수집 LD (링크) vmlinux (ELF) objcopy + gzip bzImage Phase 4: Modules obj-m 오브젝트 scripts/ Makefile.modpost Module.symvers LD (모듈) *.ko
커널 빌드의 4단계: 설정(Configuration) -> 컴파일(Compilation) -> 링킹(Linking) -> 모듈(Modules)

make 타겟 (Make Targets)

커널 빌드 시스템은 다양한 make 타겟을 제공합니다. 주요 타겟들을 용도별로 분류하면 다음과 같습니다.

설정 관련 타겟 (Configuration Targets)

타겟 인터페이스 설명
make menuconfig ncurses TUI 터미널 기반 메뉴 인터페이스. 가장 널리 사용됨
make nconfig ncurses TUI menuconfig의 개선 버전. 검색 기능이 강력함
make xconfig Qt GUI 그래픽 환경에서 사용. Qt 라이브러리 필요
make gconfig GTK GUI GNOME/GTK 기반 그래픽 인터페이스
make oldconfig 텍스트 프롬프트 기존 .config 기반으로 새 옵션만 질문
make olddefconfig 자동 새 옵션에 기본값 자동 적용. CI에 적합
make allnoconfig 자동 모든 옵션을 n으로. 최소 커널 빌드의 시작점
make allyesconfig 자동 모든 옵션을 y로. 빌드 테스트에 유용
make allmodconfig 자동 가능한 모든 옵션을 m으로
make randconfig 자동 무작위 설정 생성. 빌드 테스트(0-day)에 사용
make tinyconfig 자동 allnoconfig + 크기 최적화 옵션 활성화
make localmodconfig 자동 현재 로드된 모듈만 포함하는 설정 생성

빌드 관련 타겟 (Build Targets)

# 기본 빌드: vmlinux + bzImage + 모듈
make -j$(nproc)

# 커널 이미지만 빌드
make -j$(nproc) bzImage

# 모듈만 빌드
make -j$(nproc) modules

# 특정 디렉토리만 빌드
make -j$(nproc) drivers/net/

# 특정 파일만 빌드 (디버깅용)
make drivers/net/ethernet/intel/e1000e/netdev.o

# 전처리 결과 확인 (디버깅용)
make drivers/net/ethernet/intel/e1000e/netdev.i

# 어셈블리 출력 확인 (디버깅용)
make drivers/net/ethernet/intel/e1000e/netdev.s

# Device Tree Blob 빌드 (ARM/ARM64)
make dtbs

# 커널 헤더 설치 (유저스페이스용)
make headers_install INSTALL_HDR_PATH=/usr

# 빌드 정보 상세 출력 (V=1)
make V=1 drivers/net/ethernet/intel/e1000e/netdev.o
ℹ️

-j$(nproc)는 CPU 코어 수만큼 병렬 빌드를 수행합니다. 메모리가 부족한 환경에서는 -j 값을 줄이세요. 일반적으로 -j$(nproc) 또는 -j$(($(nproc) + 1))이 최적입니다.

설치 관련 타겟 (Install Targets)

# 커널 이미지 설치 (/boot에 복사, initramfs 생성)
sudo make install

# 모듈 설치 (/lib/modules/<version>/에 설치)
sudo make modules_install

# 모듈 설치 경로 지정
make modules_install INSTALL_MOD_PATH=/mnt/rootfs

# Device Tree 설치 (ARM/ARM64)
sudo make dtbs_install

# 전체 빌드 + 설치 일괄 수행
make -j$(nproc) && sudo make modules_install install

# 빌드 산출물 정리
make clean         # 오브젝트 파일 제거, .config 유지
make mrproper       # clean + .config, include/generated 등 제거
make distclean      # mrproper + 에디터 백업 파일, 패치 잔여물 제거

Cross-Compilation 설정

임베디드 시스템이나 다른 아키텍처용 커널을 빌드할 때는 Cross-compilation이 사실상 핵심 경로입니다. ARCHCROSS_COMPILE 두 변수가 핵심입니다.

# ARM 32-bit 커널 크로스 컴파일
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

# ARM 64-bit (AArch64) 크로스 컴파일
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

# RISC-V 64-bit 크로스 컴파일
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc)

# LLVM/Clang 사용한 크로스 컴파일
make ARCH=arm64 LLVM=1 defconfig
make ARCH=arm64 LLVM=1 -j$(nproc)

# 환경 변수로 설정하여 매번 지정 생략
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make -j$(nproc)
⚠️

CROSS_COMPILE 접두사 규칙: CROSS_COMPILE에는 toolchain 바이너리의 접두사를 지정합니다. 예를 들어 aarch64-linux-gnu-를 지정하면, 컴파일러는 aarch64-linux-gnu-gcc, 링커는 aarch64-linux-gnu-ld가 사용됩니다. 끝에 반드시 하이픈(-)을 포함해야 합니다.

주요 아키텍처별 ARCH 값과 일반적인 toolchain prefix를 정리하면 다음과 같습니다.

아키텍처 ARCH 값 CROSS_COMPILE 예시 패키지 (Debian/Ubuntu)
ARM 32-bit arm arm-linux-gnueabihf- gcc-arm-linux-gnueabihf
ARM 64-bit arm64 aarch64-linux-gnu- gcc-aarch64-linux-gnu
RISC-V 64-bit riscv riscv64-linux-gnu- gcc-riscv64-linux-gnu
MIPS mips mips-linux-gnu- gcc-mips-linux-gnu
PowerPC 64 powerpc powerpc64le-linux-gnu- gcc-powerpc64le-linux-gnu

빌드 최적화 (Build Optimization)

대규모 커널 빌드의 시간을 단축하기 위한 다양한 기법을 소개합니다.

ccache 활용

ccache는 컴파일러 캐시로, 동일한 소스 파일의 재컴파일을 획기적으로 가속합니다. 커널 개발 시 설정을 바꿔가며 반복 빌드하는 경우 매우 효과적입니다.

# ccache 설치
sudo apt install ccache

# ccache를 커널 빌드에 적용하는 방법 1: CC 변수 지정
make CC="ccache gcc" -j$(nproc)

# 방법 2: PATH에 ccache symlink 디렉토리 추가
export PATH="/usr/lib/ccache:$PATH"
make -j$(nproc)

# Cross-compilation과 함께 사용
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     CC="ccache aarch64-linux-gnu-gcc" -j$(nproc)

# ccache 캐시 크기 설정 (기본 5GB)
ccache -M 20G

# ccache 통계 확인
ccache -s
💡

Tip: 첫 번째 빌드에서는 ccache의 효과가 없지만, 두 번째 빌드부터는 캐시 히트에 의해 빌드 시간이 대폭 단축됩니다. make clean 후 재빌드에서도 ccache가 동작하므로, 커널 개발 워크플로우에서 특히 유용합니다.

증분 빌드 (Incremental Builds)

Kbuild는 fixdep 도구를 통해 정밀한 의존성 추적을 수행합니다. 소스 파일뿐만 아니라 Kconfig 심볼의 변경까지 추적하여, 실제로 영향받는 파일만 재컴파일합니다.

# 증분 빌드의 핵심: .cmd 파일
# 각 .o 파일마다 .<filename>.o.cmd 파일이 생성됨
cat drivers/net/ethernet/intel/e1000e/.netdev.o.cmd

# 출력 예시 (의존성 목록):
# cmd_drivers/net/ethernet/intel/e1000e/netdev.o := gcc -Wp,-MMD,...
# deps_drivers/net/ethernet/intel/e1000e/netdev.o := \
#   drivers/net/ethernet/intel/e1000e/netdev.c \
#   include/linux/module.h \
#   include/generated/autoconf.h \
#   ...

# Kconfig 변경 시에도 정확히 영향받는 파일만 재빌드
# CONFIG_E1000E를 y에서 m으로 변경하면,
# e1000e 관련 파일만 재컴파일됨

Out-of-tree 빌드 (Separate Build Directory)

소스 트리를 깨끗하게 유지하면서 빌드 산출물을 별도 디렉토리에 생성하는 방법입니다. 여러 설정으로 동시 빌드하거나, 소스를 읽기 전용으로 관리할 때 유용합니다.

# 방법 1: O= 옵션 사용
mkdir -p /home/user/build/x86
make O=/home/user/build/x86 defconfig
make O=/home/user/build/x86 -j$(nproc)

# 방법 2: 빌드 디렉토리에서 직접 실행
mkdir -p /home/user/build/arm64
cd /home/user/build/arm64
make -C /path/to/linux-src O=$(pwd) \
     ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make -C /path/to/linux-src O=$(pwd) \
     ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

# 동시에 여러 아키텍처 빌드
make O=../build-x86 defconfig && make O=../build-x86 -j$(nproc) &
make O=../build-arm64 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     defconfig && make O=../build-arm64 ARCH=arm64 \
     CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) &
wait

Kconfig 의존성 구조 (Kconfig Dependency Structure)

아래 다이어그램은 네트워크 서브시스템의 Kconfig 의존성 관계를 보여줍니다. depends on, select, source의 세 가지 관계가 어떻게 얽혀 있는지 시각적으로 표현합니다.

Kconfig 의존성 관계 예시 (Network Subsystem) depends on select root config sub config CONFIG_NET CONFIG_INET CONFIG_NETDEVICES CONFIG_NETFILTER CONFIG_WIRELESS CONFIG_TCP_CONG_* CONFIG_ETHERNET CONFIG_NF_CONNTRACK CONFIG_CFG80211 CONFIG_NET_VENDOR_INTEL CONFIG_NF_NAT CONFIG_MAC80211 CONFIG_E1000E CONFIG_ICE CONFIG_CRC32 CONFIG_CRYPTO
네트워크 서브시스템 Kconfig 의존성 트리 - 실선: depends on, 점선: select

위 다이어그램에서 볼 수 있듯이, Kconfig 의존성은 트리 구조를 형성합니다. 상위 옵션이 비활성화되면 하위 옵션은 자동으로 숨겨지고 비활성화됩니다. select는 반대 방향으로 동작하여, 하위 옵션이 상위의 라이브러리 심볼을 강제 활성화합니다.

LLVM/Clang 빌드 (Building with LLVM/Clang)

Linux 커널은 6.x부터 LLVM/Clang 툴체인을 공식적으로 지원합니다. GCC 대비 더 엄격한 경고, 빠른 빌드 속도, 그리고 LTO(Link Time Optimization), CFI(Control Flow Integrity) 같은 고급 보안 기능을 활용할 수 있습니다.

기본 LLVM 빌드 (Basic LLVM Build)

# LLVM=1: 전체 LLVM 툴체인 사용 (clang, ld.lld, llvm-ar, llvm-nm 등)
make LLVM=1 defconfig
make LLVM=1 -j$(nproc)

# 특정 LLVM 버전 지정 (접미사 방식)
make LLVM=-17 defconfig    # clang-17, ld.lld-17 등 사용
make LLVM=-17 -j$(nproc)

# LLVM 경로 직접 지정
make LLVM=/usr/lib/llvm-17/bin/ defconfig

# 크로스 컴파일 (Clang은 빌트인 크로스 컴파일 지원)
make LLVM=1 ARCH=arm64 defconfig
make LLVM=1 ARCH=arm64 -j$(nproc)
# Clang은 multi-target이므로 CROSS_COMPILE 불필요
ℹ️

Clang은 하나의 바이너리로 여러 아키텍처를 크로스 컴파일할 수 있어 CROSS_COMPILE 지정이 불필요합니다. --target=aarch64-linux-gnu와 같은 triple을 자동으로 설정합니다.

LLVM 전용 기능 (LLVM-specific Features)

기능 Kconfig 옵션 설명
Thin LTO CONFIG_LTO_CLANG_THIN 링크 타임 최적화로 코드 크기 축소, 인라이닝 개선
Full LTO CONFIG_LTO_CLANG_FULL 전체 프로그램 최적화 (빌드 시간 증가, 최대 최적화)
CFI CONFIG_CFI_CLANG Control Flow Integrity — 간접 호출 공격 방어
Shadow Call Stack CONFIG_SHADOW_CALL_STACK ARM64 전용 ROP 공격 방어
KCFI CONFIG_CFI_CLANG (6.1+) 커널 전용 CFI 구현 (하드웨어 독립적)
# Thin LTO 활성화 빌드
make LLVM=1 defconfig
./scripts/config -e LTO_CLANG_THIN
make LLVM=1 olddefconfig
make LLVM=1 -j$(nproc)

# LLVM + ccache 조합
make LLVM=1 CC="ccache clang" -j$(nproc)
⚠️

모든 커널 코드가 Clang과 호환되는 것은 아닙니다. 일부 아키텍처별 어셈블리 코드나 GCC 확장을 사용하는 드라이버는 Clang에서 빌드 실패할 수 있습니다. make LLVM=1 W=1으로 경고를 확인하세요.

Config Fragments (설정 조각 병합)

scripts/kconfig/merge_config.sh는 여러 개의 설정 조각(config fragment)을 기본 설정에 병합하는 스크립트입니다. CI 파이프라인이나 자동화된 빌드 환경에서 매우 유용합니다.

# config fragment 파일 예시 (debug.config)
# .config 형식의 부분적 설정만 포함
$ cat debug.config
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_DWARF5=y
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_UBSAN=y
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y

# 성능 최적화 fragment (performance.config)
$ cat performance.config
CONFIG_PREEMPT_NONE=y
# CONFIG_PREEMPT_VOLUNTARY is not set
# CONFIG_PREEMPT is not set
CONFIG_HZ_1000=y
CONFIG_NO_HZ_FULL=y

# defconfig에 debug fragment 병합
make defconfig
./scripts/kconfig/merge_config.sh .config debug.config

# 여러 fragment 동시 병합
./scripts/kconfig/merge_config.sh .config debug.config performance.config

# 기본 설정 없이 fragment만으로 시작
make allnoconfig
./scripts/kconfig/merge_config.sh .config my_minimal.config

# scripts/config: 개별 옵션 조작
./scripts/config --enable CONFIG_KASAN
./scripts/config --disable CONFIG_PREEMPT
./scripts/config --set-val CONFIG_NR_CPUS 64
./scripts/config --set-str CONFIG_DEFAULT_HOSTNAME "myhost"

# 병합 후 정합성 보장
make olddefconfig
💡

Tip: Config fragment를 버전 관리에 저장하면 팀 전체에서 일관된 빌드 설정을 유지할 수 있습니다. 예를 들어, configs/debug.config, configs/ci.config, configs/release.config으로 용도별 설정을 관리하세요.

DKMS (Dynamic Kernel Module Support)

DKMS는 커널 업데이트 시 out-of-tree 모듈을 자동으로 재빌드하는 프레임워크입니다. NVIDIA 드라이버, VirtualBox, ZFS 등 트리 외부 모듈이 커널 업그레이드 후에도 계속 동작하도록 보장합니다.

# DKMS 설치
sudo apt install dkms    # Debian/Ubuntu
sudo dnf install dkms    # Fedora/RHEL

# DKMS 모듈 디렉토리 구조
# /usr/src/<모듈명>-<버전>/
#   ├── dkms.conf
#   ├── Makefile
#   └── *.c / *.h
# /usr/src/hello-1.0/dkms.conf
PACKAGE_NAME="hello"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="hello"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"
REMAKE_INITRD="no"
CLEAN="make clean"
# DKMS에 모듈 등록
sudo dkms add -m hello -v 1.0

# 현재 커널용으로 빌드
sudo dkms build -m hello -v 1.0

# 설치 (자동 depmod 실행)
sudo dkms install -m hello -v 1.0

# 등록된 DKMS 모듈 목록
dkms status

# 제거
sudo dkms remove -m hello -v 1.0 --all

# 커널 업데이트 시 자동 동작:
# 1. 새 커널 패키지 설치
# 2. DKMS가 AUTOINSTALL=yes인 모듈 자동 재빌드
# 3. 새 커널의 /lib/modules/에 .ko 설치
# 4. depmod 자동 실행
ℹ️

DKMS는 대상 커널 버전에 맞는 빌드 트리(예: /lib/modules/$(uname -r)/build)가 준비되어 있어야 동작합니다. 배포판 커널에서는 보통 헤더/개발 패키지 설치로 충족되며, 커스텀 커널에서는 해당 소스 트리에서 make modules_prepare를 수행해 외부 모듈 빌드 환경을 준비해야 합니다.

커널 소스 관리 (Kernel Source & Release Types)

커널 빌드의 시작점은 올바른 소스를 확보하는 것입니다. 커널 릴리스 체계와 소스 관리 방법을 이해해야 합니다.

릴리스 유형 (Release Types)

유형 예시 설명
Mainline 6.8-rc1 Linus Torvalds가 관리. 새 기능이 merge window에 병합
Stable 6.7.3 Greg KH가 관리. 버그 수정과 보안 패치만 포함
Longterm (LTS) 6.1.75 장기 유지보수 (2~6년). 임베디드/서버에 적합
Next linux-next 다음 merge window에 들어올 패치 통합 테스트 트리
# Mainline 소스 다운로드 (Git)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

# 특정 버전 체크아웃
git tag -l "v6.*" | tail -10
git checkout v6.7

# Stable 소스 (별도 저장소)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

# tarball 다운로드 (Git 없이)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.3.tar.xz
tar -xf linux-6.7.3.tar.xz

# 패치 적용 (incremental patch)
xzcat patch-6.7.3.xz | patch -p1

# 현재 커널 버전 확인
make kernelversion
uname -r

커널 헤더 패키지 (Kernel Headers)

out-of-tree 모듈 빌드에는 완전한 커널 소스가 아닌 커널 헤더 패키지만 있으면 됩니다.

# Debian/Ubuntu: 커널 헤더 설치
sudo apt install linux-headers-$(uname -r)

# 설치 위치 확인
ls /lib/modules/$(uname -r)/build
# → /usr/src/linux-headers-$(uname -r) 심링크

# Fedora/RHEL: 커널 헤더 설치
sudo dnf install kernel-devel

# Arch Linux
sudo pacman -S linux-headers

# 커스텀 커널에서 헤더 설치
make headers_install INSTALL_HDR_PATH=/usr

GNU Make 핵심 문법 (GNU Make Core Syntax)

커널 빌드 시스템의 기반인 GNU Make의 핵심 문법을 이해하면, Kbuild Makefile을 읽고 수정하는 능력이 근본적으로 향상됩니다. 여기서는 커널 소스에서 실제로 사용되는 패턴을 중심으로 설명합니다.

변수 할당 (Variable Assignment)

GNU Make는 5가지 변수 할당 연산자를 제공합니다. 커널 Makefile에서는 이들을 상황에 맞게 구분하여 사용합니다.

연산자 이름 동작 커널 사용 예
= 재귀적 할당 사용 시점에 확장 (lazy) KBUILD_CFLAGS = $(call cc-option,-Wall)
:= 단순 할당 할당 시점에 즉시 확장 ARCH := $(SUBARCH)
?= 조건부 할당 미정의 시에만 할당 CROSS_COMPILE ?=
+= 추가 할당 기존 값에 공백 + 새 값 추가 KBUILD_CFLAGS += -Werror
!= 셸 할당 셸 명령 실행 결과 할당 KERNELRELEASE != cat include/config/kernel.release
# 재귀적 할당: CC가 변경되면 자동 반영
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld

# 단순 할당: 즉시 확장으로 성능 향상
srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
objtree := $(CURDIR)

# 조건부 할당: 환경 변수로 오버라이드 가능
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=
INSTALL_MOD_PATH ?=

# 추가 할당: 플래그 누적
KBUILD_CFLAGS += -Wall -Wundef
KBUILD_CFLAGS += $(call cc-option,-Wno-trigraphs)
💡

= vs := 선택 기준: =(재귀적)은 나중에 정의될 변수를 참조할 때 유용하지만, 매번 확장하므로 복잡한 표현식에서는 성능이 떨어집니다. :=(단순)은 즉시 확장하여 빠르지만, 아직 정의되지 않은 변수를 참조하면 빈 문자열이 됩니다. 커널 최상위 Makefile에서는 경로 변수에 :=, 컴파일러 플래그에 =를 주로 사용합니다.

자동 변수 (Automatic Variables)

자동 변수는 규칙(rule) 내에서 타겟과 의존성을 참조하는 특수 변수입니다. Kbuild 매크로에서 광범위하게 사용됩니다.

변수 의미 예시 (foo.o: foo.c bar.h)
$@ 타겟 파일명 foo.o
$< 첫 번째 의존성 foo.c
$^ 모든 의존성 (중복 제거) foo.c bar.h
$+ 모든 의존성 (중복 유지) foo.c bar.h
$* 패턴 규칙의 stem (% 부분) foo (%.o: %.c에서)
$? 타겟보다 새로운 의존성들 변경된 파일들만
$(@D) 타겟의 디렉토리 부분 dir (dir/foo.o에서)
$(@F) 타겟의 파일명 부분 foo.o (dir/foo.o에서)
# 커널 scripts/Makefile.build의 실제 컴파일 규칙
# $@ = 타겟 .o 파일, $< = 소스 .c 파일
$(obj)/%.o: $(src)/%.c FORCE
	$(call if_changed_dep,cc_o_c)

# quiet_cmd_cc_o_c에서의 자동 변수 사용
quiet_cmd_cc_o_c = CC $(quiet_modtag)  $@
      cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

# 어셈블리 파일 컴파일
$(obj)/%.o: $(src)/%.S FORCE
	$(call if_changed_dep,as_o_S)

# 링킹에서의 $^ 사용
quiet_cmd_link_o_target = LD      $@
      cmd_link_o_target = $(LD) $(ld_flags) -r -o $@ $^

패턴 규칙과 특수 타겟 (Pattern Rules & Special Targets)

패턴 규칙% 와일드카드를 사용하여 여러 파일에 동일한 빌드 규칙을 적용합니다.

# 기본 패턴 규칙
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

# 경로가 포함된 패턴 규칙 (Kbuild)
$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE
	$(call cmd,force_checksrc)
	$(call if_changed_dep,cc_o_c)

# 디렉토리 생성 패턴
%/:
	$(Q)mkdir -p $@

.PHONY는 실제 파일이 아닌 타겟을 선언합니다. 동명 파일이 존재해도 항상 실행됩니다.

# 커널 최상위 Makefile의 .PHONY 선언
.PHONY: all vmlinux modules clean mrproper

# FORCE 타겟: 의존성에 추가하면 항상 rebuild
PHONY += FORCE
FORCE:

# .PHONY 대신 PHONY 변수 사용 (Kbuild 관례)
PHONY += __build __modinst
.PHONY: $(PHONY)

Order-only prerequisites(| 뒤)는 존재 여부만 확인하고 타임스탬프는 비교하지 않습니다.

# 디렉토리가 존재하기만 하면 됨 (변경 시 재빌드 불필요)
$(obj)/%.o: $(src)/%.c | $(obj)/
	$(CC) $(CFLAGS) -c -o $@ $<

# Kbuild에서의 order-only prerequisite
$(obj)/built-in.a: $(real-obj-y) FORCE | $(subdir-ym)
	$(call if_changed,ar_builtin)

조건문 (Conditionals)

Makefile 내 조건문은 파싱 시점에 평가되며, 규칙의 레시피(recipe) 안에서는 사용할 수 없습니다.

# ifeq: 문자열 동등 비교
ifeq ($(ARCH),x86)
  KBUILD_CFLAGS += -march=native
endif

# ifneq: 부등 비교
ifneq ($(CONFIG_CC_IS_GCC),)
  KBUILD_CFLAGS += $(call cc-option,-Wno-maybe-uninitialized)
endif

# ifdef: 변수 정의 여부 확인 (값이 비어도 true)
ifdef CONFIG_MODULES
  obj-y += module/
endif

# ifndef: 변수 미정의 확인
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

# 중첩 조건문 (커널 arch/x86/Makefile)
ifeq ($(CONFIG_X86_64),y)
  KBUILD_CFLAGS += -mno-red-zone
  ifeq ($(CONFIG_RETPOLINE),y)
    KBUILD_CFLAGS += $(call cc-option,-mindirect-branch=thunk-extern)
  endif
else
  KBUILD_CFLAGS += -m32
endif
⚠️

주의: ifdef는 변수가 정의되었는지만 확인합니다. 빈 문자열(VAR =)도 "정의됨"으로 간주됩니다. 값이 비어있는지 확인하려면 ifeq ($(VAR),)를 사용하세요. 커널 Makefile에서 CONFIG_* 확인 시 ifeq ($(CONFIG_FOO),y) 패턴이 더 안전합니다.

내장 함수 (Built-in Functions)

GNU Make는 다양한 내장 함수를 제공합니다. 커널 빌드 시스템에서 자주 사용되는 함수들을 분류별로 정리합니다.

분류 함수 설명
문자열 $(subst from,to,text) 문자열 치환
$(patsubst pattern,replacement,text) 패턴 치환 (% 와일드카드)
$(strip string) 앞뒤 공백 제거
$(findstring find,in) 문자열 검색 (찾으면 반환, 없으면 빈 문자열)
$(filter pattern...,text) 패턴에 맞는 단어만 필터
$(filter-out pattern...,text) 패턴에 맞는 단어 제외
$(sort list) 정렬 + 중복 제거
파일명 $(dir names...) 디렉토리 부분 추출
$(notdir names...) 파일명 부분 추출
$(suffix names...) 확장자 추출
$(basename names...) 확장자 제거
$(addprefix prefix,names...) 접두사 추가
제어 $(foreach var,list,text) 리스트 순회
$(if condition,then[,else]) 조건 분기 (빈 문자열이 false)
$(call var,param1,param2,...) 사용자 정의 함수 호출
$(eval text) 텍스트를 Makefile 코드로 평가
외부 $(shell command) 셸 명령 실행
$(wildcard pattern) 글로브 패턴으로 파일 목록
$(realpath names...) 정규화된 절대 경로
# 커널에서의 실제 함수 사용 예제

# patsubst: .o → .c 변환 (소스 파일 목록 구성)
real-obj-y := $(patsubst %.o,$(obj)/%.o,$(obj-y))

# filter / filter-out: 서브디렉토리와 오브젝트 분리
subdir-ym := $(sort $(filter %/,$(obj-y) $(obj-m)))
obj-y     := $(filter-out %/,$(obj-y))

# addprefix: 경로 접두사 추가
subdir-ym := $(addprefix $(obj)/,$(subdir-ym))

# foreach: 서브디렉토리별 재귀 빌드 규칙 생성
$(foreach d,$(subdir-ym),$(eval $(call descend,$(d))))

# wildcard: 실제 존재하는 파일만 선택
extra-y += $(wildcard $(obj)/*.dtb)

# shell: 컴파일러 버전 확인
GCC_VERSION := $(shell $(CC) -dumpversion)

# call: 사용자 정의 함수 호출
cc-option = $(call try-run,\
  $(CC) $(1) -c -x c /dev/null -o /dev/null,$(1),$(2))
KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement)

include와 다중행 변수 (include & Multi-line Variables)

include는 다른 Makefile을 현재 위치에 삽입하고, define/endef는 여러 줄로 구성된 매크로를 정의합니다.

# include: 필수 포함 (파일 없으면 에러)
include scripts/Kbuild.include
include $(srctree)/scripts/Makefile.lib

# -include (또는 sinclude): 파일 없어도 에러 없음
-include include/config/auto.conf
-include include/config/auto.conf.cmd

# 커널 빌드에서 -include 사용 이유:
# 첫 빌드 시 auto.conf가 아직 생성되지 않았을 수 있음
# syncconfig 타겟이 실행된 후에야 파일이 존재
# define/endef: 다중행 변수 정의
define filechk_kernel.release
  echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion \
    $(srctree))"
endef

# 다중행 매크로를 $(call)로 호출
define rule_cc_o_c
  $(call cmd,checksrc)
  $(call cmd_and_fixdep,cc_o_c)
  $(call cmd,gen_ksymdeps)
  $(call cmd,checkdoc)
  $(call cmd,objtool)
  $(call cmd,modversions_c)
  $(call cmd,record_mcount)
endef

# define에도 할당 연산자 사용 가능 (:=, +=)
define multi_depend
  $(foreach m, $(notdir $1), \
    $(eval $(obj)/$m: \
      $(addprefix $(obj)/, $($(subst $(2),$(3),$m)))))
endef

Kbuild 내부 Makefile 구조 (Kbuild Internal Makefile Structure)

커널 빌드의 핵심 로직은 scripts/ 디렉토리의 Makefile들에 구현되어 있습니다. 이 파일들의 역할과 상호작용을 이해하면 빌드 시스템의 전체 그림이 보입니다.

scripts/ 디렉토리 핵심 파일 (Key Files in scripts/)

파일 역할
Kbuild.include 공통 유틸리티 함수 (try-run, cc-option, echo-cmd, cmd)
Makefile.build 핵심 빌드 엔진 — 재귀적으로 서브디렉토리 빌드, .o 컴파일, built-in.a 생성
Makefile.lib 플래그 수집·필터링, 경로 계산, 컴파일 명령 변수 정의
Makefile.modpost 모듈 후처리 — 심볼 해석, Module.symvers, .mod.c 생성
Makefile.modfinal 최종 .ko 링킹
Makefile.clean make clean 처리
Makefile.host 호스트 프로그램 빌드 (hostprogs)
Makefile.dtbs Device Tree Blob 컴파일
Makefile.extrawarn W=1/2/3 추가 경고 플래그
Makefile.compiler 컴파일러 감지 및 공통 플래그

Makefile.build 상세 (Makefile.build Deep Dive)

Makefile.build는 Kbuild의 핵심 빌드 엔진입니다. make -f scripts/Makefile.build obj=<dir>로 호출되며, 해당 디렉토리의 모든 오브젝트를 컴파일합니다.

# Makefile.build의 핵심 구조 (간략화)

# 1. 기본 변수 초기화
src := $(obj)

# 2. 해당 디렉토리의 Kbuild (또는 Makefile) 읽기
kbuild-dir := $(if $(filter /%, $(src)), $(src), $(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),\
                     $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile)
include $(kbuild-file)

# 3. Makefile.lib로 변수 정리 (경로 추가, 플래그 수집)
include $(srctree)/scripts/Makefile.lib

# 4. 기본 타겟: __build
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
         $(if $(KBUILD_MODULES), $(targets-for-modules)) \
         $(subdir-ym) $(always-y)
	@:

# 5. 서브디렉토리 재귀 진입
$(subdir-ym):
	$(Q)$(MAKE) $(build)=$@

# 6. .o 컴파일 규칙
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
	$(call if_changed_dep,cc_o_c)

# 7. built-in.a 아카이브 생성
$(obj)/built-in.a: $(real-obj-y) FORCE
	$(call if_changed,ar_builtin)
ℹ️

built-in.a란? 각 서브디렉토리에서 obj-y로 선언된 오브젝트 파일들을 하나의 정적 아카이브(.a)로 묶은 것입니다. 상위 디렉토리는 하위의 built-in.a를 다시 자신의 built-in.a에 포함시키며, 최종적으로 vmlinux 링킹 시 최상위 built-in.a들이 합쳐집니다. 과거에는 built-in.o (incremental linking)를 사용했으나, 빌드 속도 향상을 위해 built-in.a (thin archive)로 전환되었습니다.

Makefile.lib 상세 (Makefile.lib Deep Dive)

Makefile.lib는 각 디렉토리 Makefile에서 선언한 변수를 정리하고, 실제 컴파일에 필요한 플래그와 경로를 계산합니다.

# Makefile.lib 핵심 로직 (간략화)

# 1. obj-y/m에서 서브디렉토리와 파일 분리
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y   += $(__subdir-y)
obj-y      := $(filter-out %/, $(obj-y))

# 2. 절대 경로로 변환
real-obj-y := $(addprefix $(obj)/,$(obj-y))
real-obj-m := $(addprefix $(obj)/,$(obj-m))
subdir-ym  := $(addprefix $(obj)/,$(subdir-y) $(subdir-m))

# 3. 컴파일 플래그 수집
orig_c_flags  = $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) \
                $(ccflags-y) \
                $(CFLAGS_$(basetarget).o)
_c_flags      = $(filter-out $(CFLAGS_REMOVE_$(basetarget).o), \
                $(orig_c_flags))
c_flags       = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
                $(_c_flags)

# 4. 인클루드 경로
LINUXINCLUDE := \
  -I$(srctree)/arch/$(SRCARCH)/include \
  -I$(objtree)/arch/$(SRCARCH)/include/generated \
  -I$(srctree)/include \
  -I$(objtree)/include/generated \
  $(if $(KBUILD_SRC), -I$(srctree)/include) \
  -include $(srctree)/include/linux/compiler_types.h

Makefile.modpost 상세 (Module Post-processing)

모듈(.ko) 빌드의 후처리 단계를 담당합니다. 심볼 의존성 해석과 버전 정보 삽입이 핵심입니다.

더 깊게 보기: 커널 심볼 문서는 이 단계에서 생성되는 __ksymtab, __kcrctab, Module.symvers, __versions가 모듈 로딩 시 어떻게 사용되는지까지 이어서 설명합니다.
모듈 빌드 후처리 흐름:

1. 컴파일 단계
   foo.c  →  foo.o     (각 .c를 .o로 컴파일)

2. 링킹 단계
   foo.o + bar.o  →  foo.ko.tmp  (partial link)

3. modpost 단계 (scripts/mod/modpost)
   ├─ 모든 .o / built-in.a에서 EXPORT_SYMBOL 수집
   ├─ 모든 .ko.tmp에서 undefined symbol 수집
   ├─ 심볼 매칭: undefined → exported 확인
   ├─ Module.symvers 생성 (심볼 CRC 테이블)
   └─ .mod.c 생성 (모듈 메타데이터 소스)

4. .mod.c 컴파일
   foo.mod.c  →  foo.mod.o

5. 최종 링킹
   foo.ko.tmp + foo.mod.o  →  foo.ko
/* 자동 생성되는 .mod.c의 구조 (예: foo.mod.c) */
#include <linux/module.h>

/* 모듈 버전 매직 넘버 */
MODULE_INFO(vermagic, VERMAGIC_STRING);

/* 모듈이 사용하는 외부 심볼의 CRC */
static const struct modversion_info ____versions[] = {
    { 0x12345678, "printk" },
    { 0xabcdef01, "kmalloc" },
    { 0x00000000, "module_layout" },
};

/* 모듈이 의존하는 다른 모듈 */
MODULE_INFO(depends, "usbcore,usbhid");

/* 디바이스 테이블 (자동 로딩용) */
MODULE_ALIAS("usb:v*p*d*...");

Kbuild.include 상세 (Common Utility Functions)

Kbuild.include는 모든 Kbuild Makefile이 공유하는 유틸리티 함수를 정의합니다.

# try-run: 컴파일러 기능 테스트
# 성공하면 $(2) 반환, 실패하면 $(3) 반환
try-run = $(shell set -e; \
  TMP=$(TMPOUT)/tmp; \
  if ($(1)) >/dev/null 2>&1; \
  then echo "$(2)"; \
  else echo "$(3)"; \
  fi; rm -f $$TMP)

# cc-option: 컴파일러 옵션 지원 여부 확인
cc-option = $(call try-run,\
  $(CC) -Werror $(1) -c -x c /dev/null -o /dev/null,$(1),$(2))

# 사용 예: GCC가 -Wno-trigraphs를 지원하면 추가
KBUILD_CFLAGS += $(call cc-option,-Wno-trigraphs)

# echo-cmd: 명령어 출력 제어 (V=0/1)
echo-cmd = $(if $($(quiet)cmd_$(1)),\
  echo '  $(call escsq,$($(quiet)cmd_$(1)))';)

# cmd: 명령 실행 + 출력 제어
cmd = @set -e; $(echo-cmd) $(cmd_$(1))

# build 매크로: 서브디렉토리 빌드 진입의 단축형
build := -f $(srctree)/scripts/Makefile.build obj

빌드 재귀 흐름 (Build Recursion Flow)

커널 빌드의 전체 흐름을 재귀적 Makefile 호출 관점에서 추적합니다.

Kbuild 재귀 빌드 흐름 (`make -j`) Top Makefile arch/$(ARCH)/Makefile + scripts/Kbuild.include + auto.conf 포함 CONFIG 변수 확정 후 하위 빌드 진입점 결정 초기 도구 빌드 `$(MAKE) $(build)=scripts/basic` → `scripts` init 경로 `$(MAKE) $(build)=init` Makefile.build obj=init init/Makefile + Makefile.lib init/main.o 컴파일 init/built-in.a 생성 kernel 경로 `$(MAKE) $(build)=kernel` kernel/*.o 컴파일 `$(MAKE) $(build)=kernel/sched` 재귀 kernel/sched/built-in.a kernel/built-in.a 통합 기타 경로 mm / fs / net / drivers 각 서브트리 동일 패턴 재귀 *.o + built-in.a 산출 모듈 대상은 .ko 경로 병행 최종 링크 단계 `scripts/link-vmlinux.sh` 실행 LD: 모든 built-in.a → vmlinux.o MODPOST: 심볼/라이선스/섹션 검증 LD: vmlinux.o → vmlinux 후처리: System.map, BTF, sorttable 등 아키텍처별 부트 이미지 생성 단계로 전달

Kbuild cmd 패턴과 매크로 (Kbuild cmd Pattern & Macros)

Kbuild는 고유한 cmd 패턴으로 빌드 명령의 출력을 제어하고, 불필요한 재컴파일을 방지합니다. 이 패턴은 커널 빌드 시스템의 가장 독창적인 부분입니다.

quiet_cmd_ / cmd_ 접두사 시스템 (Verbosity Control)

모든 빌드 명령은 cmd_<name>quiet_cmd_<name> 한 쌍으로 정의됩니다. V= 변수에 따라 출력이 제어됩니다.

설정 동작 출력 예시
V=0 (기본) quiet_cmd_* 출력 CC kernel/fork.o
V=1 cmd_* 전체 명령 출력 gcc -Wp,-MMD,kernel/.fork.o.d -nostdinc ...
V=2 Make 자체 재귀 추적 추가 왜 타겟이 빌드되는지 이유 출력
# 명령 쌍 정의 예시
quiet_cmd_cc_o_c = CC $(quiet_modtag)  $@
      cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<

quiet_cmd_ar_builtin = AR      $@
      cmd_ar_builtin = rm -f $@; $(AR) cDPrST $@ $(real-prereqs)

quiet_cmd_ld_ko_o = LD [M]  $@
      cmd_ld_ko_o = $(LD) -r $(KBUILD_LDFLAGS) \
                   $(KBUILD_LDFLAGS_MODULE) -T scripts/module.lds \
                   -o $@ $(filter %.o, $^)

# V= 변수에 따른 quiet 접두사 설정 (Top Makefile)
ifeq ($(KBUILD_VERBOSE),1)
  quiet :=
  Q :=
else
  quiet := quiet_
  Q := @
endif

# echo-cmd가 $(quiet)cmd_* 를 선택하는 메커니즘
# V=0: $(quiet_cmd_cc_o_c) → "CC  kernel/fork.o"
# V=1: $(cmd_cc_o_c)       → 전체 gcc 명령줄

if_changed 패밀리 (Rebuild Decision)

if_changed는 Kbuild의 핵심 재빌드 판단 매크로입니다. 타겟이 의존성보다 오래되었거나, 빌드 명령 자체가 변경되었을 때만 재컴파일합니다.

매크로 동작 사용 대상
if_changed 명령어 변경 시 재빌드 링킹, 아카이브
if_changed_dep 명령어 변경 + 의존성 파일 (.d) 사용 C/S 컴파일
if_changed_rule 여러 명령을 하나의 rule로 묶어 실행 복합 빌드 단계
# if_changed의 핵심 로직 (Kbuild.include)
if_changed = $(if $(newer-prereqs)$(cmd-check),\
  $(cmd);$(cmd_$(1)); \
  printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

# 구성 요소 분석:
# newer-prereqs : FORCE를 제외한 의존성 중 타겟보다 새로운 것
# cmd-check     : 저장된 명령과 현재 명령 비교
# → 둘 중 하나라도 참이면 재빌드 실행
# → 재빌드 후 .cmd 파일에 현재 명령 저장

# .cmd 파일 저장 예시 (kernel/.fork.o.cmd)
# cmd_kernel/fork.o := gcc -Wp,-MMD,kernel/.fork.o.d ...

# FORCE의 역할: 항상 newer-prereqs를 true로 만듦
# 하지만 cmd-check가 false이면 (명령 동일) 재빌드 안 함
# → 결과: 의존성 변경 OR 명령 변경 시에만 재빌드
# 실제 사용 예시

# C → .o 컴파일 (의존성 추적 포함)
$(obj)/%.o: $(src)/%.c FORCE
	$(call if_changed_dep,cc_o_c)

# built-in.a 아카이브 (의존성 추적 불필요)
$(obj)/built-in.a: $(real-obj-y) FORCE
	$(call if_changed,ar_builtin)

# vmlinux 링킹 (복합 rule)
$(obj)/vmlinux: $(obj)/vmlinux.o FORCE
	$(call if_changed_rule,link_vmlinux)

KBUILD_CFLAGS 전파 체인 (Flag Propagation Chain)

C 파일 하나를 컴파일할 때 적용되는 플래그는 여러 계층에서 수집됩니다. 우선순위와 결합 순서를 이해해야 빌드 문제를 디버깅할 수 있습니다.

CFLAGS 전파 체인 (좌→우 순서로 결합):

KBUILD_CPPFLAGS          # 전처리기 플래그 (전역)
  ↓
KBUILD_CFLAGS            # C 컴파일 플래그 (전역, Top Makefile)
  ↓
KBUILD_CFLAGS += ...     # 아키텍처 Makefile (arch/x86/Makefile)
  ↓
ccflags-y                # 디렉토리 Makefile (각 서브디렉토리)
  ↓
CFLAGS_foo.o             # 파일별 플래그 (특정 파일 전용)
  ↓
CFLAGS_REMOVE_foo.o      # 파일별 플래그 제거
# 실제 예시: kernel/Makefile

# 디렉토리 전체에 적용
ccflags-y := -I$(srctree)/kernel

# 특정 파일에만 추가 플래그
CFLAGS_fork.o = -DUTS_MACHINE='"$(UTS_MACHINE)"'

# 특정 파일에서 플래그 제거
# (예: -Werror가 전역에 있지만 특정 파일은 제외)
CFLAGS_REMOVE_lockdep.o = -pg

# 최종 결합 (Makefile.lib에서)
# c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)
#           $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS)
#           $(ccflags-y) $(CFLAGS_$(basetarget).o)
#           - $(CFLAGS_REMOVE_$(basetarget).o)

$(call cmd,cc_o_c) 확장 과정 (Command Expansion Trace)

kernel/fork.ckernel/fork.o 컴파일 과정을 단계별로 추적하여 Kbuild 매크로 확장을 이해합니다.

단계 1: 규칙 매칭
  $(obj)/%.o: $(src)/%.c FORCE
  → kernel/fork.o: kernel/fork.c FORCE

단계 2: if_changed_dep 호출
  $(call if_changed_dep,cc_o_c)
  → newer-prereqs 확인: fork.c가 fork.o보다 새로운가?
  → cmd-check: .fork.o.cmd의 저장된 명령과 현재 명령 비교
  → 하나라도 true → 재빌드 진행

단계 3: echo-cmd 확장 (V=0일 때)
  $(quiet)cmd_cc_o_c
  → quiet_cmd_cc_o_c
  → "CC      kernel/fork.o"
  → 화면에 "  CC      kernel/fork.o" 출력

단계 4: cmd_cc_o_c 확장
  $(CC) $(c_flags) -c -o $@ $<

단계 5: $(CC) 확장
  → gcc (또는 $(CROSS_COMPILE)gcc)

단계 6: $(c_flags) 확장
  → -Wp,-MMD,kernel/.fork.o.d    (의존성 생성)
     -nostdinc                     (시스템 헤더 제외)
     -I./arch/x86/include          (LINUXINCLUDE)
     -I./include                   (LINUXINCLUDE)
     -include ./include/linux/compiler_types.h
     -Wall -Wundef                 (KBUILD_CFLAGS)
     -I./kernel                    (ccflags-y)
     -DUTS_MACHINE='"x86_64"'      (CFLAGS_fork.o)

단계 7: 자동 변수 확장
  $@ → kernel/fork.o
  $< → kernel/fork.c

단계 8: 최종 실행 명령
  gcc -Wp,-MMD,kernel/.fork.o.d -nostdinc \
    -I./arch/x86/include -I./include \
    -include ./include/linux/compiler_types.h \
    -Wall -Wundef -I./kernel \
    -DUTS_MACHINE='"x86_64"' \
    -c -o kernel/fork.o kernel/fork.c

단계 9: .cmd 파일 갱신
  → kernel/.fork.o.cmd에 위 명령 저장
  → 다음 빌드 시 cmd-check에서 비교용으로 사용

Kbuild 특수 변수 총정리 (Kbuild Special Variables Reference)

Kbuild Makefile에서 사용하는 특수 변수를 체계적으로 정리합니다. 서브디렉토리 Makefile 작성 시 참조하세요.

빌드 대상 변수 (Build Target Variables)

변수 설명 예시
obj-y vmlinux에 내장될 오브젝트 obj-y += fork.o exec.o
obj-m 모듈로 빌드될 오브젝트 obj-m += btrfs.o
obj-$(CONFIG_*) 설정에 따라 y/m/n 결정 obj-$(CONFIG_EXT4_FS) += ext4/
lib-y 라이브러리 오브젝트 (lib.a) lib-y := string.o memcpy.o
always-y 항상 빌드 (CONFIG 무관) always-y += vmlinux.lds
extra-y 추가 빌드 대상 (obj-y 외) extra-y += head.o
targets if_changed로 빌드할 타겟 목록 targets += vmlinux.lds
hostprogs 호스트에서 실행할 프로그램 hostprogs := genksyms
# 실제 Makefile 예시: fs/ext4/Makefile

# CONFIG_EXT4_FS=y → obj-y, =m → obj-m, =n → 빌드 제외
obj-$(CONFIG_EXT4_FS) += ext4.o

# ext4.o는 여러 .o로 구성된 복합 오브젝트
ext4-y := balloc.o bitmap.o block_validity.o dir.o \
         ext4_jbd2.o extents.o file.o fsmap.o fsync.o \
         hash.o ialloc.o inode.o ioctl.o mballoc.o \
         migrate.o mmp.o move_extent.o namei.o \
         page-io.o readpage.o resize.o super.o \
         symlink.o sysfs.o xattr.o

# 조건부 오브젝트 추가
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
ext4-$(CONFIG_EXT4_FS_SECURITY)  += xattr_security.o
ext4-$(CONFIG_FS_VERITY)         += verity.o

서브디렉토리 변수 (Subdirectory Variables)

변수 설명 예시
obj-y += dir/ 서브디렉토리를 builtin에 포함 obj-y += sched/
obj-m += dir/ 서브디렉토리를 모듈로 빌드 obj-$(CONFIG_NET) += net/
subdir-y 추가 서브디렉토리 (드물게 사용) subdir-y += tools
subdir-ccflags-y 하위 디렉토리에도 전파되는 플래그 subdir-ccflags-y := -I$(src)
`kernel/Makefile` 서브디렉토리 구성 kernel/Makefile 항상 빌드 (`obj-y`) sched/ locking/ power/ printk/ irq/ rcu/ time/ ... vmlinux에 builtin으로 링크 설정과 무관하게 핵심 경로 포함 조건부 빌드 (`obj-$(CONFIG_*)`) obj-$(CONFIG_BPF) += bpf/ obj-$(CONFIG_CGROUPS) += cgroup/ obj-$(CONFIG_TRACING) += trace/ obj-$(CONFIG_GCOV_KERNEL) += gcov/ obj-$(CONFIG_LIVEPATCH) += livepatch/ CONFIG=y/m/n에 따라 포함 여부 결정 결과 선택된 디렉토리의 built-in.a 또는 모듈이 상위 링크 단계로 전달 최종적으로 link-vmlinux.sh에서 vmlinux 및 .ko 생성에 반영

플래그 변수 (Flag Variables)

변수 범위 설명
ccflags-y 디렉토리 해당 디렉토리의 모든 C 파일에 적용
asflags-y 디렉토리 해당 디렉토리의 모든 어셈블리 파일에 적용
ldflags-y 디렉토리 해당 디렉토리의 링킹에 적용
CFLAGS_file.o 파일 특정 파일에만 C 플래그 추가
AFLAGS_file.o 파일 특정 파일에만 어셈블리 플래그 추가
CFLAGS_REMOVE_file.o 파일 특정 파일에서 C 플래그 제거
ccflags-remove-y 디렉토리 디렉토리 전체에서 플래그 제거
subdir-ccflags-y 재귀 하위 디렉토리까지 전파
# 플래그 변수 활용 예시

# 디렉토리 전체에 인클루드 경로 추가
ccflags-y := -I$(srctree)/$(src)/include
ccflags-y += -DCONFIG_MY_DRIVER

# ftrace를 사용하지 않는 파일 (function tracing 비활성)
CFLAGS_REMOVE_core.o = -pg
CFLAGS_REMOVE_smpboot.o = -pg

# GCOV 프로파일링이 문제를 일으키는 파일
GCOV_PROFILE_vsyscall_64.o := n

# KCSAN (Concurrency Sanitizer) 비활성 파일
KCSAN_SANITIZE_core.o := n

# KASAN 비활성 (메모리 접근 초기 코드)
KASAN_SANITIZE_stacktrace.o := n

복합 오브젝트 (Composite Objects)

하나의 모듈이나 builtin 오브젝트가 여러 소스 파일로 구성될 때 사용합니다.

# 기본 패턴: module-y 또는 module-objs

# 방법 1: <module>-y (권장)
obj-$(CONFIG_BTRFS_FS) += btrfs.o
btrfs-y := super.o ctree.o extent-tree.o print-tree.o \
          root-tree.o dir-item.o file-item.o inode-item.o \
          disk-io.o transaction.o

# 방법 2: <module>-objs (동일한 효과)
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-objs := balloc.o dir.o file.o ialloc.o inode.o \
            ioctl.o namei.o super.o symlink.o

# 조건부 구성원 추가
obj-$(CONFIG_XFRM) += xfrm.o
xfrm-y := xfrm_policy.o xfrm_state.o xfrm_input.o xfrm_output.o
xfrm-$(CONFIG_XFRM_STATISTICS) += xfrm_proc.o
xfrm-$(CONFIG_XFRM_ALGO)       += xfrm_algo.o

# 모듈이 단일 파일인 경우 (복합이 아님)
obj-$(CONFIG_FAT_FS) += fat.o
# fat.c가 직접 fat.o로 컴파일

# 복합 모듈 vs 단일 모듈 구분:
# module-y 또는 module-objs가 존재 → 복합 (여러 .o 링크)
# 존재하지 않음 → 단일 (module.c → module.o)
💡

-y vs -objs: 두 가지 모두 같은 동작을 하지만, -y 형태가 -$(CONFIG_*)으로 조건부 추가를 할 수 있어 더 유연합니다. 현대 커널 코드에서는 -y를 권장합니다.

커널 빌드 make 인자와 환경변수 완전 레퍼런스

실전 커널 개발에서는 make 자체 옵션(-j, -C, -f 등)과 Kbuild 변수(ARCH=, O=, M= 등)를 함께 사용합니다. GNU Make 관점에서는 make ARCH=arm64처럼 명령줄에 적는 값과 export ARCH=arm64처럼 환경에 두는 값이 같은 이름공간을 공유하지만, 커널 실무에서는 재현성과 로그 가독성 때문에 명령줄 변수 지정이 기본이고, 환경변수는 CI 기본값이나 반복 실행용 프리셋으로 쓰는 편이 안전합니다.

ℹ️

정리 기준: 이 섹션은 2026년 3월 12일 기준 공식 문서(docs.kernel.org)와 kernel.org의 최신 안정 커널 6.19.6 규약을 기준으로 정리했습니다. 특히 V= vs KBUILD_VERBOSE, W= vs KBUILD_EXTRA_WARN, O= vs KBUILD_OUTPUT, M= vs KBUILD_EXTMOD, MO= vs KBUILD_EXTMOD_OUTPUT처럼 명령줄 우선 규칙이 중요합니다.

전체 지도: 무엇을 어디에 넘기는가

분류 대표 항목 주요 목적 실전 포인트
GNU Make 옵션 -j, -l, -k, -n, -B, -C, -f 병렬도, 디렉터리 이동, 드라이런, 강제 재빌드 커널 빌드에서도 그대로 유효합니다. 단, 의미는 Kbuild 변수가 아니라 GNU Make 엔진 레벨입니다.
Kbuild 핵심 변수 ARCH, CROSS_COMPILE, O, M, MO, V, W, C, CF, LLVM 아키텍처 선택, 출력 경로, 외부 모듈, 경고/정적 분석, 툴체인 선택 커널 개발자가 가장 자주 직접 넘기는 값입니다.
플래그 주입 변수 KCFLAGS, KCPPFLAGS, CFLAGS_KERNEL, CFLAGS_MODULE, LDFLAGS_MODULE 컴파일러/전처리기/링커 인자 추가 로컬 디버깅, 재현 가능한 빌드, 실험용 경고 활성화에 유용합니다.
Kconfig 환경변수 KCONFIG_CONFIG, KCONFIG_ALLCONFIG, KCONFIG_SEED, KCONFIG_PROBABILITY 설정 파일 경로, 미니 설정, randconfig 제어 CI와 fuzzing, 배포판 설정 파이프라인에서 자주 씁니다.
설치/패키징 변수 INSTALL_PATH, INSTALL_MOD_PATH, MODLIB, INSTALL_HDR_PATH, KBUILD_DEBARCH 설치 위치, sysroot, 패키지용 staging root, Debian 패키징 배포판 빌드, initramfs/rootfs 조립, 크로스 패키징에서 중요합니다.
재현성/서명/진단 KBUILD_BUILD_TIMESTAMP, SOURCE_DATE_EPOCH, KBUILD_BUILD_USER, KBUILD_SIGN_PIN 결과물 재현, 모듈 서명, 버전 문자열 제어 CI 캐시 적중률과 배포 재현성에 직접 영향을 줍니다.
커널 빌드 인자 전달 경로 셸 환경 export LLVM=1 export KCFLAGS="-Og -g3" export KBUILD_BUILD_TIMESTAMP=... 명령줄 변수 / Make 옵션 make -j32 ARCH=arm64 O=out V=1 W=1 LLVM=1 M=$PWD MO=$PWD/out Top Makefile 우선순위 적용, 아키텍처 판별, toolchain 선택 sub-make와 scripts/Makefile.*로 변수 전파 arch/$(ARCH)/Makefile 아키텍처별 CFLAGS/AFLAGS 이미지 포맷, 링크 규칙 선택 scripts/Makefile.build obj-y/obj-m, ccflags-y, if_changed 최종 컴파일/링크 명령 생성 산출물 / 설치 단계 vmlinux, bzImage, *.ko, System.map INSTALL_* / MODLIB / deb-pkg

GNU Make 자체 옵션

옵션 의미 커널 빌드에서의 활용 주의점
-jN 동시에 실행할 작업 수 make -j$(nproc)가 기본 패턴입니다. 메모리가 부족한 시스템에서 너무 크게 잡으면 링크 단계나 Rust 컴파일에서 OOM이 날 수 있습니다.
-lN load average 상한 공유 빌드 서버에서 -j64 -l32처럼 사용해 시스템 과부하를 막습니다. CPU는 남는데 I/O가 막히는 환경에서는 체감 효과가 큽니다.
-k 에러가 나도 가능한 타겟은 계속 빌드 대규모 경고/에러 수집 CI에 유용합니다. 첫 실패 지점 추적에는 오히려 로그가 길어질 수 있습니다.
-s 명령 출력 억제 Kbuild는 이미 V=0에서 요약 출력을 하므로 드물게 사용합니다. 문제 분석 시 필요한 정보를 지나치게 숨길 수 있습니다.
-n 실행 없이 어떤 명령이 수행될지 출력 make -n V=1 fs/ext4/ext4.ko처럼 빌드 경로를 확인합니다. 생성 스크립트 부작용은 일어나지 않으므로 실제 의존성 재생성까지는 검증하지 못합니다.
-B 타임스탬프를 무시하고 강제 재빌드 캐시나 .cmd 상태가 의심될 때 사용합니다. 전체 트리를 다시 빌드하므로 시간이 많이 걸립니다.
-C <dir> 실행 전에 해당 디렉터리로 이동 외부 모듈은 보통 make -C /lib/modules/$(uname -r)/build M=$PWD 패턴을 씁니다. 현재 작업 디렉터리가 바뀌므로 스크립트 안에서는 상대 경로가 헷갈리기 쉽습니다.
-f <Makefile> 기본 Makefile 대신 지정 파일 사용 Linux 6.13부터 외부 모듈에서 -f /lib/modules/.../build/Makefile M=$PWD 패턴을 쓸 수 있습니다. -C와 달리 디렉터리를 바꾸지 않기 때문에 현재 경로 기준 산출물 위치를 더 명확히 관리할 수 있습니다.
--output-sync=target 병렬 빌드 로그를 타겟 단위로 묶음 대규모 -j 빌드에서 로그가 뒤섞이지 않게 합니다. GNU Make 옵션이며 Kbuild 고유 기능은 아닙니다.

가장 자주 쓰는 Kbuild 명령줄 변수

변수 역할 대표 사용 예 실무 메모
ARCH 대상 아키텍처 선택 make ARCH=arm64 defconfig x86, x86_64, i386, sparc64처럼 별칭이 있는 아키텍처는 디렉터리 이름과 완전히 일치하지 않을 수 있습니다.
CROSS_COMPILE GNU binutils/GCC 접두사 또는 절대 경로 make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- 일반적인 GNU 툴체인에서는 gcc, ld, objcopy 앞에 동일 접두사가 붙습니다. 일부 환경에서는 ccache와 함께 쓰기도 합니다.
LLVM Clang/LLVM 툴체인 일괄 사용 make LLVM=1, make LLVM=/opt/llvm/, make LLVM=-18 LLVM=1CC=clang, LD=ld.lld, AR=llvm-ar 등 다수 변수를 한 번에 확장합니다. LLVM=0은 "비활성"이 아니라 사실상 LLVM=1처럼 동작합니다.
LLVM_IAS Clang 통합 어셈블러 사용 여부 make LLVM=1 LLVM_IAS=0 LLVM_IAS=0이면 Clang이 외부 assembler를 호출합니다. 이때 크로스 빌드에서는 올바른 assembler를 찾기 위해 CROSS_COMPILE도 함께 필요한 경우가 많습니다.
CC, LD, AR, NM, STRIP, OBJCOPY, OBJDUMP, READELF 개별 툴만 선택적으로 교체 make CC=clang LD=ld.lld OBJCOPY=llvm-objcopy 특정 아키텍처에서 LLVM과 GNU binutils를 혼합할 때 유용합니다. 예를 들어 CC=clang이 가능해도 ld.lld는 미지원인 경우가 있습니다.
O 커널 out-of-tree 출력 디렉터리 make O=build/x86_64 olddefconfig 소스 트리를 깨끗하게 유지하고, 하나의 소스에서 여러 설정/아키텍처를 동시에 빌드하는 핵심 변수입니다.
M 외부 모듈 소스 디렉터리 make -C $KDIR M=$PWD Kbuild는 M=이 있으면 "외부 모듈 빌드"로 간주합니다. 값은 외부 모듈의 소스 디렉터리여야 하며, 보통 절대 경로를 씁니다.
MO 외부 모듈 전용 출력 디렉터리 make -C $KDIR M=$PWD MO=$PWD/out 외부 모듈도 소스와 산출물을 분리할 수 있습니다. 커널 본체의 O=와 역할이 비슷하지만 적용 대상은 외부 모듈만입니다.
V 상세 출력 수준 make V=1, make V=2 V=1은 실제 컴파일/링크 커맨드를, V=2는 재귀 빌드 추적까지 더 보여줍니다. 트러블슈팅 첫 단계로 가장 중요합니다.
W 추가 경고 및 빌드 검사 수준 make W=1, make W=2, make W=3, make W=e W=KBUILD_EXTRA_WARN과 같은 의미입니다. 경고를 늘리거나 에러로 승격할 때 사용합니다.
C sparse 정적 분석 수준 make C=1, make C=2 C=1은 변경 파일 중심, C=2는 전체 대상 검사에 가깝습니다. 실제로는 타겟 범위와 조합해서 씁니다.
CF sparse에 전달할 추가 인자 make C=2 CF="-Wbitwise -D__CHECK_ENDIAN__" C=만으로는 검사 세부 옵션을 바꿀 수 없으므로 CF=를 같이 씁니다.
KBUILD_SIGN_PIN 모듈 서명 키 PIN/패스프레이즈 전달 make KBUILD_SIGN_PIN=123456 modules_install 모듈 서명 자동화에서는 편리하지만, 셸 히스토리와 프로세스 목록 노출 위험 때문에 비밀정보 관리 전략이 필요합니다.
⚠️

우선순위 기억법: V=1이 있으면 KBUILD_VERBOSE보다 강하고, W=1KBUILD_EXTRA_WARN보다 강하며, O=/M=/MO=는 각각 KBUILD_OUTPUT/KBUILD_EXTMOD/KBUILD_EXTMOD_OUTPUT보다 우선합니다. 반복 빌드 스크립트에서 환경변수를 써도, 디버깅할 때는 명령줄에 다시 적으면 현재 실행의 실제 값을 가장 쉽게 확인할 수 있습니다.

# 기본 x86_64 로컬 빌드
make olddefconfig
make -j$(nproc)

# ARM64 GNU toolchain 크로스 빌드
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=out/arm64 defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=out/arm64 -j$(nproc)

# ARM64 LLVM 빌드 (Clang 단독)
make LLVM=1 ARCH=arm64 O=out/arm64-clang defconfig
make LLVM=1 ARCH=arm64 O=out/arm64-clang -j$(nproc)

# 실제 커맨드라인 확인
make O=out/x86_64 V=1 kernel/fork.o

# 경고 + sparse 검사
make W=1 C=2 CF="-Wbitwise -D__CHECK_ENDIAN__" drivers/net/ethernet/

# 외부 모듈 빌드
make -C /lib/modules/$(uname -r)/build M=$PWD

# Linux 6.13+ 외부 모듈 빌드 (-C 대신 -f)
make -f /lib/modules/$(uname -r)/build/Makefile M=$PWD MO=$PWD/out

툴체인과 플래그 주입 변수

커널 트리 내부 Makefile에서 ccflags-y, CFLAGS_file.o처럼 디렉터리/파일 단위로 플래그를 다루는 것과 별개로, make KCFLAGS=... 같은 외부 주입 변수는 빌드 전체 또는 특정 계층에 플래그를 더하는 용도입니다. 로컬 실험, 재현 가능한 빌드, 보안 hardening, sanitizers, 문서 검사, host tool 디버깅에 특히 유용합니다.

변수 적용 대상 설명 예시
KCPPFLAGS 전처리 전체 C와 어셈블리 전처리 단계 모두에 들어가는 추가 옵션 make KCPPFLAGS="-DKBUILD_TRACE=1"
KAFLAGS 어셈블리 전체 builtin과 module 모두에 적용되는 어셈블러 옵션 make KAFLAGS="--fatal-warnings"
AFLAGS_KERNEL builtin 어셈블리 커널 내장 오브젝트에만 적용되는 어셈블리 플래그 make AFLAGS_KERNEL="-Wa,-adhln"
AFLAGS_MODULE 모듈 어셈블리 모듈 빌드에만 적용되는 어셈블리 플래그 make AFLAGS_MODULE="--fatal-warnings"
KCFLAGS C 전체 builtin과 module 모두의 C 컴파일에 추가 make KCFLAGS="-Og -g3 -fno-omit-frame-pointer"
CFLAGS_KERNEL builtin C 커널 이미지에 내장되는 코드에만 적용 make CFLAGS_KERNEL="-Werror"
CFLAGS_MODULE 모듈 C 모듈 오브젝트에만 적용 make CFLAGS_MODULE="-Werror -DMYTEST=1"
KRUSTFLAGS Rust 전체 builtin과 module 모두의 Rust 코드에 추가 make KRUSTFLAGS="-Zmacro-backtrace"
RUSTFLAGS_KERNEL builtin Rust 내장 Rust 코드 전용 플래그 make RUSTFLAGS_KERNEL="--cfg my_builtin_test"
RUSTFLAGS_MODULE 모듈 Rust Rust 모듈 코드 전용 플래그 make RUSTFLAGS_MODULE="--cfg my_module_test"
LDFLAGS_MODULE 모듈 링크 .ko 최종 링크 시 추가 옵션 make LDFLAGS_MODULE="-Map,module.map"
변수 적용 대상 설명 예시
HOSTCFLAGS host C 프로그램 scripts/ 아래 도구처럼 빌드 중 호스트에서 실행되는 프로그램에 적용 make HOSTCFLAGS="-O0 -g3"
HOSTCXXFLAGS host C++ 프로그램 Qt나 기타 host-side C++ 빌드 도구에 적용 make HOSTCXXFLAGS="-O0 -g3"
HOSTRUSTFLAGS host Rust 프로그램 빌드 시점에 호스트에서 실행되는 Rust 도구에 적용 make HOSTRUSTFLAGS="-Cdebuginfo=2"
PROCMACROLDFLAGS Rust proc macro 링크 rustc가 로드하는 proc macro의 링크 호환성 제어 make PROCMACROLDFLAGS="-Wl,-rpath,/opt/rust/lib"
HOSTLDFLAGS host 링크 호스트 도구 링크 단계 플래그 make HOSTLDFLAGS="-fuse-ld=lld"
HOSTLDLIBS host 링크 라이브러리 호스트 프로그램 링크 시 추가 라이브러리 make HOSTLDLIBS="-ltinfo"
USERCFLAGS userprogs C tools/ 또는 일부 user space helper 빌드에 적용 make USERCFLAGS="-O2 -D_GNU_SOURCE"
USERLDFLAGS userprogs 링크 userprogs는 대개 CC로 링크되므로 필요한 경우 -Wl, 접두사를 포함해야 합니다. make USERLDFLAGS="-Wl,-z,relro"
KDOCFLAGS kernel-doc 검사 빌드 중 실행되는 kernel-doc 경고/에러 플래그 make KDOCFLAGS="-Werror"
💡

자주 헷갈리는 구분: KCFLAGS는 "외부에서 전체 빌드에 넣는 덧붙임"이고, ccflags-y는 "소스 트리 내부 Makefile이 해당 디렉터리에 선언하는 플래그"입니다. 전자는 호출자 관점, 후자는 커널 트리 작성자 관점입니다.

# 디버그 친화적 로컬 빌드
make KCFLAGS="-Og -g3 -fno-omit-frame-pointer" -j$(nproc)

# 모듈만 더 엄격하게
make CFLAGS_MODULE="-Werror" M=$PWD

# host tool 디버깅
make HOSTCFLAGS="-O0 -g3" scripts/basic/fixdep

# 재현 가능한 out-of-tree 빌드용 debug path 정규화
make O=out KCFLAGS="-fdebug-prefix-map=$PWD=." -j$(nproc)

Kconfig와 syncconfig 환경변수

menuconfig, oldconfig, randconfig, syncconfig 같은 타겟은 단순한 UI 차이가 아니라 각각 다른 자동화 목적을 가집니다. 이때 KCONFIG_* 환경변수를 적절히 쓰면 설정 파일 위치, 기본 defconfig, mini config, 확률 기반 fuzzing, 자동 갱신 정책까지 세밀하게 제어할 수 있습니다.

변수 대상 단계 설명 실전 예
KBUILD_KCONFIG 최상위 Kconfig 선택 기본 최상위 파일 Kconfig 대신 다른 파일을 진입점으로 사용 make KBUILD_KCONFIG=Kconfig.debug menuconfig
KCONFIG_CONFIG *config 전체 기본 .config 대신 사용할 설정 파일 이름 지정 make KCONFIG_CONFIG=configs/lab.config olddefconfig
KCONFIG_DEFCONFIG_LIST 기본 설정 탐색 .config가 아직 없을 때 사용할 기본 config 후보 목록 export KCONFIG_DEFCONFIG_LIST="arch/arm64/configs/defconfig configs/team_base.config"
KCONFIG_OVERWRITECONFIG *config 전체 .config가 심볼릭 링크일 때 링크를 끊지 않고 그대로 갱신 export KCONFIG_OVERWRITECONFIG=1
KCONFIG_WARN_UNKNOWN_SYMBOLS *config 전체 입력 config 안의 알 수 없는 심볼을 경고 export KCONFIG_WARN_UNKNOWN_SYMBOLS=1
KCONFIG_WERROR *config 전체 Kconfig 경고를 에러로 승격 export KCONFIG_WERROR=1
CONFIG_ config 저장 형식 저장되는 심볼 접두사를 기본 CONFIG_ 대신 다른 문자열로 변경 export CONFIG_=LK_
KCONFIG_ALLCONFIG all*config, randconfig mini config 파일 또는 플래그로 강제할 심볼 집합을 제공 KCONFIG_ALLCONFIG=mini.config make allnoconfig
KCONFIG_SEED randconfig 난수 시드 고정 KCONFIG_SEED=20260312 make randconfig
KCONFIG_PROBABILITY randconfig y:m:n가 나올 확률 편향 KCONFIG_PROBABILITY=10:15:15 make randconfig
KCONFIG_NOSILENTUPDATE syncconfig 묵시적 자동 업데이트를 막고 명시적 갱신을 요구 make KCONFIG_NOSILENTUPDATE=1 syncconfig
KCONFIG_AUTOCONFIG syncconfig auto.conf 경로 재지정 make KCONFIG_AUTOCONFIG=out/include/config/auto.conf syncconfig
KCONFIG_AUTOHEADER syncconfig autoconf.h 경로 재지정 make KCONFIG_AUTOHEADER=out/include/generated/autoconf.h syncconfig
⚠️

KCONFIG_ALLCONFIG 오해 금지: 이 파일은 "무조건 강제 설정"처럼 보이지만, 실제로는 정상적인 의존성 검사 대상입니다. 예를 들어 의존성이 만족되지 않는 CONFIG_FOO=y를 넣어도, Kconfig는 이를 그대로 살려주지 않습니다. 그래서 mini config는 "의도 선언"이지 "강제 덮어쓰기"가 아닙니다.

# 프로젝트별 별도 설정 파일 사용
make KCONFIG_CONFIG=configs/debug-x86.config olddefconfig

# 작은 mini config에서 전체 .config 생성
cat > mini.config <<'EOF'
CONFIG_KALLSYMS=y
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
EOF
make KCONFIG_ALLCONFIG=mini.config allnoconfig

# randconfig 재현
make KCONFIG_SEED=424242 KCONFIG_PROBABILITY=15:25 randconfig

# symlink로 관리하는 .config를 안전하게 갱신
export KCONFIG_OVERWRITECONFIG=1
make olddefconfig

출력 디렉터리, 외부 모듈, 설치 변수

커널 본체와 외부 모듈은 모두 out-of-tree 빌드가 가능하지만, 쓰는 변수가 다릅니다. O=커널 본체의 산출물 위치이고, M=/MO=외부 모듈의 소스/출력 위치입니다. 여기에 INSTALL_*MODLIB가 합쳐지면 실제 배포 이미지나 sysroot로 결과물을 옮길 수 있습니다.

변수 설명 대표 사용 예 실무 포인트
KBUILD_OUTPUT / O 커널 본체 출력 디렉터리 make O=out/x86_64 명령줄 O=가 환경변수 KBUILD_OUTPUT보다 우선합니다.
KBUILD_EXTMOD / M 외부 모듈 소스 디렉터리 make -C $KDIR M=$PWD 외부 모듈 Makefile이 있는 디렉터리를 가리켜야 합니다.
KBUILD_EXTMOD_OUTPUT / MO 외부 모듈 출력 디렉터리 make -C $KDIR M=$PWD MO=$PWD/out 명령줄 MO=가 우선합니다. 모듈 소스 트리를 깨끗하게 유지할 때 좋습니다.
INSTALL_PATH make install 시 커널 이미지와 System.map 설치 위치 make INSTALL_PATH=/tmp/boot install 기본은 /boot입니다.
INSTALLKERNEL make install 시 호출할 설치 스크립트 make INSTALLKERNEL=/usr/local/bin/installkernel-wrapper install 크로스 빌드 결과를 커스텀 이미지 생성 파이프라인에 넘길 때 자주 씁니다.
MODLIB 모듈 설치 실제 디렉터리 make MODLIB=/staging/lib/modules/test modules_install 기본값은 $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)입니다.
INSTALL_MOD_PATH 모듈 설치 prefix make INSTALL_MOD_PATH=$ROOTFS modules_install staging root를 만드는 표준 패턴입니다.
INSTALL_MOD_STRIP 모듈 설치 후 strip 여부와 옵션 make INSTALL_MOD_STRIP=1 modules_install 1이면 기본 --strip-debug, 그 외 값은 strip 옵션으로 그대로 전달됩니다.
INSTALL_HDR_PATH userspace header 설치 경로 make INSTALL_HDR_PATH=$SYSROOT/usr headers_install 기본은 $(objtree)/usr입니다.
INSTALL_DTBS_PATH DTB 설치 경로 make INSTALL_DTBS_PATH=$ROOTFS/boot/dtbs dtbs_install device tree를 루트 파일시스템 또는 이미지 staging 영역으로 옮길 때 씁니다.
KBUILD_ABS_SRCTREE 절대 경로 기반 source tree 참조 강제 make KBUILD_ABS_SRCTREE=1 tags tags 데이터베이스나 절대 경로가 필요한 도구에 유용합니다.
KBUILD_EXTRA_SYMBOLS 외부 모듈이 참조할 추가 Module.symvers 목록 make M=$PWD KBUILD_EXTRA_SYMBOLS=/path/other/Module.symvers 외부 모듈끼리 심볼을 공유할 때 필요합니다.
O= / M= / MO= / INSTALL_* 관계 커널 소스 트리 linux/ Makefile, arch/, drivers/, fs/ ... 소스는 읽고 산출물은 분리 가능 커널 출력 트리 O=out/arm64 또는 KBUILD_OUTPUT .config, include/generated, vmlinux, *.o 헤더/이미지/중간 산출물 저장 외부 모듈 소스 M=$PWD 또는 KBUILD_EXTMOD hello.c, Makefile, Kbuild 모듈 소스 위치 자체 외부 모듈 출력 MO=$PWD/out 또는 KBUILD_EXTMOD_OUTPUT *.o, *.mod, Module.symvers, *.ko 모듈 산출물만 따로 저장 설치/배포 staging root INSTALL_PATH, INSTALL_MOD_PATH, MODLIB, INSTALL_HDR_PATH, INSTALL_DTBS_PATH O= MO= 헤더/빌드 규칙 참조 미리 빌드된 커널 산출물
# 커널 본체를 별도 출력 디렉터리에 빌드
make O=/build/linux-x86_64 defconfig
make O=/build/linux-x86_64 -j$(nproc)

# 미리 빌드된 커널 산출물을 사용해 외부 모듈 컴파일
make -C /build/linux-x86_64 M=$PWD MO=$PWD/out

# 실행 디렉터리를 바꾸지 않는 Linux 6.13+ 패턴
make -f /build/linux-x86_64/Makefile M=$PWD MO=$PWD/out

# 모듈을 루트 파일시스템 staging 영역에 설치
make O=/build/linux-x86_64 INSTALL_MOD_PATH=$ROOTFS modules_install

# userspace 헤더를 sysroot로 설치
make O=/build/linux-x86_64 INSTALL_HDR_PATH=$SYSROOT/usr headers_install

# DTB 설치 위치 지정
make ARCH=arm64 INSTALL_DTBS_PATH=$ROOTFS/boot/dtbs dtbs_install

재현 가능한 빌드, 패키징, CI용 변수

커널 빌드는 단순히 컴파일만 성공하면 끝이 아니라, 재현 가능성, 서명, 패키징, 로그 품질이 중요합니다. 배포판 빌드와 CI에서는 다음 변수들이 자주 등장합니다.

변수 용도 설명 대표 예
KBUILD_BUILD_TIMESTAMP 재현성 uname -v와 initramfs mtime에 들어가는 빌드 타임스탬프를 고정합니다. KBUILD_BUILD_TIMESTAMP="$(git log -1 --format=%cD)" make
SOURCE_DATE_EPOCH 재현성 외부 코드가 __DATE__, __TIME__를 쓸 때 그 기준 시각을 통제합니다. SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)" make
KBUILD_BUILD_USER 재현성 /proc/version과 부트 로그의 빌드 사용자 문자열을 고정합니다. KBUILD_BUILD_USER=builder
KBUILD_BUILD_HOST 재현성 /proc/version과 부트 로그의 빌드 호스트 문자열을 고정합니다. KBUILD_BUILD_HOST=ci-node-01
KBUILD_MODPOST_WARN 테스트 컴파일 최종 모듈 링크 단계의 undefined symbol 오류를 경고로 낮춥니다. make KBUILD_MODPOST_WARN=1 M=$PWD
KBUILD_MODPOST_NOFINAL 빠른 검증 모듈 최종 링크를 생략하여 test compile 속도를 높입니다. make KBUILD_MODPOST_NOFINAL=1 M=$PWD
KBUILD_DEBARCH Debian 패키징 deb-pkg 타겟에서 Debian 아키텍처 판정을 직접 덮어씁니다. make KBUILD_DEBARCH=arm64 bindeb-pkg
ALLSOURCE_ARCHS 코드 인덱싱 tags, TAGS, cscope 생성 시 여러 아키텍처를 포함합니다. make ALLSOURCE_ARCHS="x86 arm64 riscv" tags
IGNORE_DIRS 코드 인덱싱 tags/cscope 데이터베이스에서 제외할 디렉터리를 지정합니다. make IGNORE_DIRS="tools drivers/gpu/drm/radeon" cscope
💡

재현 가능한 out-of-tree 빌드의 핵심 조합: KBUILD_BUILD_TIMESTAMP, KBUILD_BUILD_USER, KBUILD_BUILD_HOST를 고정하고, 디버그 정보에 절대 경로가 남지 않도록 KCFLAGS="-fdebug-prefix-map=$PWD=."를 함께 쓰는 패턴이 가장 많이 사용됩니다. 공식 재현성 문서도 이 조합을 권장합니다.

# 재현 가능한 빌드 예시
export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
export KBUILD_BUILD_TIMESTAMP="$(git log -1 --format=%cD)"
export KBUILD_BUILD_USER="builder"
export KBUILD_BUILD_HOST="ci"
make O=out KCFLAGS="-fdebug-prefix-map=$PWD=." olddefconfig
make O=out KCFLAGS="-fdebug-prefix-map=$PWD=." -j$(nproc)

# Debian 패키지
make O=out KBUILD_DEBARCH=arm64 bindeb-pkg

# 모듈 링크를 생략하는 빠른 테스트 컴파일
make KBUILD_MODPOST_NOFINAL=1 M=$PWD

# 여러 아키텍처를 포함한 tags 생성
make ALLSOURCE_ARCHS="x86 arm64 riscv" IGNORE_DIRS="tools" tags

실전 명령 조합집

아래 조합은 "변수 하나씩 외우기"보다 더 중요합니다. 실제 문제 해결은 거의 항상 여러 변수를 함께 씁니다.

# 1) x86_64 기본 개발 루프
make O=out/x86_64 olddefconfig
make O=out/x86_64 -j$(nproc)
make O=out/x86_64 V=1 kernel/fork.o

# 2) ARM64 GNU cross build + boot 이미지 + DTB
make O=out/arm64 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make O=out/arm64 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs -j$(nproc)

# 3) ARM64 LLVM build
make O=out/arm64-clang LLVM=1 ARCH=arm64 defconfig
make O=out/arm64-clang LLVM=1 ARCH=arm64 -j$(nproc)

# 4) Clang + 외부 assembler 사용
make O=out/arm LLVM=1 LLVM_IAS=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)

# 5) 경고와 sparse를 동시에 강화
make O=out W=1 C=2 CF="-Wbitwise -D__CHECK_ENDIAN__" drivers/net/

# 6) 외부 모듈을 별도 출력 디렉터리로
make -C /lib/modules/$(uname -r)/build M=$PWD MO=$PWD/out

# 7) 루트 파일시스템 staging 영역에 모듈 설치
make O=out INSTALL_MOD_PATH=$ROOTFS INSTALL_MOD_STRIP=1 modules_install

# 8) 헤더를 sysroot로 내보내기
make O=out INSTALL_HDR_PATH=$SYSROOT/usr headers_install

# 9) 재현 가능한 CI 빌드
export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
export KBUILD_BUILD_TIMESTAMP="$(git log -1 --format=%cD)"
export KBUILD_BUILD_USER="builder"
export KBUILD_BUILD_HOST="ci"
make O=out KCFLAGS="-fdebug-prefix-map=$PWD=." -j$(nproc)

# 10) Debian 패키지
make O=out KBUILD_DEBARCH=arm64 bindeb-pkg
상황 먼저 볼 변수 자주 놓치는 포인트
다른 아키텍처용 커널이 안 빌드됨 ARCH, CROSS_COMPILE, LLVM 이전 빌드의 O= 디렉터리를 재사용하면서 서로 다른 아키텍처 산출물이 섞이는 경우가 많습니다.
빌드 로그가 너무 짧아 원인 파악이 안 됨 V=1, V=2 실패한 오브젝트 하나만 지정해서 make V=1 path/to/file.o로 좁히는 편이 빠릅니다.
외부 모듈이 다른 모듈 심볼을 못 찾음 M, MO, KBUILD_EXTRA_SYMBOLS Module.symvers를 추가로 넘겨야 하는데 잊는 경우가 흔합니다.
CI마다 결과 바이너리가 달라짐 KBUILD_BUILD_TIMESTAMP, SOURCE_DATE_EPOCH, KBUILD_BUILD_USER, KBUILD_BUILD_HOST, KCFLAGS 시간/호스트 이름뿐 아니라 debug path에 절대 경로가 남는 것도 원인입니다.
warning을 더 엄격하게 보고 싶음 W, KDOCFLAGS, CFLAGS_MODULE W=는 전체 추가 경고 레벨이고, 특정 영역만 더 엄격하게 하려면 별도 플래그 변수가 더 적합합니다.

Make 디버깅과 문제 해결 (Make Debugging & Troubleshooting)

커널 빌드 문제를 진단하는 다양한 도구와 기법을 소개합니다.

상세 출력 (Verbose Build: V=1, V=2)

# V=1: 실제 실행되는 전체 명령줄 표시
make V=1 drivers/net/ethernet/intel/e1000e/netdev.o
# 출력: gcc -Wp,-MMD,drivers/net/ethernet/intel/e1000e/.netdev.o.d ...

# V=2: V=1 + Make의 재귀 추적 정보
make V=2 kernel/fork.o
# 출력: 왜 이 타겟이 재빌드되는지 이유 표시

# 특정 파일만 빌드 (전체 빌드 불필요)
make V=1 kernel/fork.o
make V=1 drivers/gpu/drm/i915/
make V=1 fs/ext4/ext4.ko

Make 디버그 플래그 (make -n, -p, --debug)

플래그 설명 주요 용도
-n (--dry-run) 명령을 실행하지 않고 출력만 빌드 순서, 명령줄 확인
-p (--print-data-base) 모든 변수·규칙 데이터베이스 출력 변수 값 확인, 암시적 규칙 확인
--debug=b 기본 디버깅 (재빌드 이유) 왜 빌드되는지 추적
--debug=v 상세 디버깅 (Makefile 파싱 포함) include 순서 추적
--debug=j 병렬 빌드 작업 디버깅 작업 슬롯 할당 추적
--debug=m Makefile 리메이크 디버깅 auto-remake 추적
# 드라이 런: 실제 빌드 없이 명령만 확인
make -n kernel/fork.o

# 변수 데이터베이스 출력 (특정 변수 검색)
make -p V=1 | grep 'KBUILD_CFLAGS'
make -p V=1 | grep 'cmd_cc_o_c'

# 기본 디버깅: 재빌드 이유 확인
make --debug=b kernel/fork.o 2>&1 | head -50
# 출력 예:
# Considering target file 'kernel/fork.o'.
#   Prerequisite 'kernel/fork.c' is older than target 'kernel/fork.o'.
#   No need to remake target 'kernel/fork.o'.

# 전체 디버그 정보 (매우 길지만 상세)
make --debug=all kernel/fork.o 2>&1 | less

Makefile 디버그 함수 ($(warning), $(info), $(error))

Makefile 내부에 직접 삽입하여 변수 값과 실행 흐름을 추적합니다.

# $(info ...): 메시지 출력 (빌드 계속)
$(info KBUILD_CFLAGS = $(KBUILD_CFLAGS))
$(info obj-y = $(obj-y))
$(info src = $(src), obj = $(obj))

# $(warning ...): 경고 메시지 출력 + 파일명/줄번호 포함
$(warning ccflags-y is [$(ccflags-y)])
$(warning real-obj-y = $(real-obj-y))

# $(error ...): 에러 메시지 출력 + 빌드 중단
ifndef CONFIG_MODULES
  $(error CONFIG_MODULES must be enabled for this driver)
endif

# 조건부 디버그 출력
ifdef KBUILD_DEBUG
  $(info [DEBUG] Processing $(obj)/Makefile)
  $(info [DEBUG] obj-y = $(obj-y))
  $(info [DEBUG] obj-m = $(obj-m))
endif

# 사용법: make KBUILD_DEBUG=1 drivers/my_driver/

.cmd 파일 구조와 분석 (Analyzing .cmd Files)

Kbuild는 각 오브젝트 파일의 빌드 명령을 .<target>.cmd 파일에 저장합니다. 이 파일은 if_changed의 재빌드 판단에 사용됩니다.

# .cmd 파일 위치와 내용 확인
cat kernel/.fork.o.cmd

# 출력 예시:
# cmd_kernel/fork.o := gcc -Wp,-MMD,kernel/.fork.o.d \
#   -nostdinc -I./arch/x86/include -I./arch/x86/include/generated \
#   -I./include -I./arch/x86/include/uapi \
#   -include ./include/linux/compiler_types.h \
#   -D__KERNEL__ -Wall -Wundef ... \
#   -DKBUILD_MODNAME='"fork"' \
#   -c -o kernel/fork.o kernel/fork.c

# .cmd 파일 검색: 특정 플래그가 적용되었는지 확인
grep '-DCONFIG_DEBUG_LOCK_ALLOC' kernel/.*.cmd

# 의존성 파일 (.d) 확인
head -20 kernel/.fork.o.d
# kernel/fork.o: kernel/fork.c include/linux/mm.h \
#   include/linux/sched.h include/linux/pid.h ...

# 강제 재빌드: .cmd 파일 삭제
rm kernel/.fork.o.cmd
make kernel/fork.o  # cmd-check가 실패하여 반드시 재빌드

흔한 빌드 오류와 해결 방법 (Common Build Errors)

오류 메시지 원인 해결 방법
*** No rule to make target 소스 파일 누락 또는 경로 오류 파일 존재 확인, obj-y 경로 확인
undefined reference to ... 심볼 미정의 (링크 오류) EXPORT_SYMBOL 확인, 빌드 순서 확인
implicit declaration of function 헤더 파일 미포함 #include 추가, CONFIG_* 확인
No such file or directory (헤더) 커널 헤더 미설치 또는 경로 오류 make headers_install, 인클루드 경로 확인
modpost: GPL-incompatible module 비GPL 모듈이 GPL 심볼 사용 MODULE_LICENSE("GPL") 확인
Section mismatch __init/__exit 잘못 사용 섹션 어노테이션 수정
recursive dependency detected Kconfig 순환 의존성 depends on/select 관계 재검토
# 정적 분석 도구로 코드 검사
make C=1 drivers/my_driver/  # sparse 검사 (변경된 파일)
make C=2 drivers/my_driver/  # sparse 검사 (모든 파일)

# 추가 경고 활성화
make W=1 drivers/my_driver/  # 기본 추가 경고
make W=2 drivers/my_driver/  # 더 많은 경고
make W=3 drivers/my_driver/  # 최대 경고
make W=e drivers/my_driver/  # 경고를 에러로 승격

# coccicheck: Coccinelle 의미적 패치 검사
make coccicheck MODE=report M=drivers/my_driver/

# 빌드 환경 진단
make outputmakefile           # 빌드 디렉토리 설정 확인
scripts/ver_linux            # 호스트 도구 버전 확인

# 깔끔한 상태에서 재빌드
make clean                   # 빌드 산출물 제거 (.config 유지)
make mrproper                # 모든 생성 파일 제거 (.config 포함)
make distclean               # mrproper + 에디터 백업 등도 제거
💡

빌드 디버깅 전략:

  • 1단계: make V=1로 실제 실행되는 명령 확인
  • 2단계: 해당 파일의 .cmd 파일을 분석하여 플래그 확인
  • 3단계: Makefile에 $(info)/$(warning)을 삽입하여 변수 추적
  • 4단계: make --debug=b로 재빌드 이유 분석
  • 5단계: make -p로 전체 변수·규칙 데이터베이스 검사

요약 (Summary)

Linux 커널 빌드 시스템의 핵심 요점을 정리합니다.

💡

핵심 요약:

  • Kconfigconfig, menuconfig, choice, depends on, select 등의 키워드로 커널 설정 옵션을 선언적으로 정의합니다.
  • Kbuild Makefileobj-y, obj-m 변수로 빌드 대상을 선언하며, Kconfig 심볼(CONFIG_*)과 자동으로 연동됩니다.
  • .config 파일은 사용자 설정의 결과물이며, autoconf.h로 변환되어 C 코드에서 #ifdef CONFIG_*로 사용됩니다.
  • Cross-compilationARCH=CROSS_COMPILE= 변수로 제어합니다.
  • 실전 명령줄 제어O=, M=, MO=, V=, W=, C=, CF=, LLVM= 조합이 핵심입니다.
  • 재현 가능한 빌드KBUILD_BUILD_TIMESTAMP, SOURCE_DATE_EPOCH, KBUILD_BUILD_USER, KBUILD_BUILD_HOST, KCFLAGS=-fdebug-prefix-map=... 조합으로 안정화합니다.
  • ccache, out-of-tree 빌드, 증분 빌드를 활용하여 빌드 시간을 획기적으로 줄일 수 있습니다.
  • GNU Make의 변수 할당(=, :=, ?=, +=), 자동 변수($@, $<), 패턴 규칙, 내장 함수를 이해하면 Kbuild Makefile을 읽고 수정할 수 있습니다.
  • Makefile.build는 재귀적 빌드 엔진이며, Makefile.lib는 플래그 수집, Makefile.modpost는 모듈 심볼 해석을 담당합니다.
  • Kbuild cmd 패턴(quiet_cmd_/cmd_)과 if_changed 매크로는 빌드 출력 제어와 불필요한 재컴파일 방지의 핵심입니다.
  • KBUILD_CFLAGS는 전역 → 아키텍처 → 디렉토리(ccflags-y) → 파일(CFLAGS_file.o) 순으로 계층적으로 전파됩니다.
  • make V=1, make --debug=b, .cmd 파일 분석, $(warning) 삽입 등으로 빌드 문제를 체계적으로 디버깅할 수 있습니다.
# 커널 빌드 전체 워크플로우 요약

# 1. 소스 다운로드
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

# 2. 설정
make defconfig           # 또는 menuconfig, oldconfig 등

# 3. (선택) 설정 미세 조정
make menuconfig

# 4. 빌드
make -j$(nproc)

# 5. 설치
sudo make modules_install
sudo make install

# 6. 부트로더 업데이트 (GRUB 예시)
sudo update-grub

# 7. 재부팅
sudo reboot

Kbuild 트러블슈팅 플레이북

Kbuild 문제는 소스 자체보다 설정/플래그/의존성 전파에서 발생하는 경우가 많습니다. 아래 순서를 따르면 원인 축소가 빠릅니다.

  1. 명령 확인: make V=1로 실제 컴파일/링크 명령 확인
  2. 플래그 추적: 대상 오브젝트의 .cmd 파일에서 최종 플래그 확인
  3. 설정 확인: scripts/config, grep CONFIG_ .config로 심볼 상태 확인
  4. 규칙 확인: 해당 디렉토리 Makefileobj-y/obj-m 경로 확인
  5. 재현 최소화: make M=drivers/foo처럼 범위를 줄여 반복
# 대상 파일 재빌드 명령 확인
make V=1 M=drivers/net/ethernet/foo

# 실제 적용된 커맨드라인(플래그) 확인
cat drivers/net/ethernet/foo/.bar.o.cmd

# 특정 CONFIG의 활성 여부 확인
grep CONFIG_FOO .config

# Kconfig 차이 비교
scripts/diffconfig old.config .config
오류 유형 첫 점검 지점 실무 대응
undefined reference obj-y/obj-m 누락 여부 링크 대상 포함 여부와 조건부 CONFIG 경로 동시 점검
warning만 계속 증가 디렉토리별 ccflags-y 경고 억제보다 원인 수정, 필요 시 파일 단위 플래그 최소화
수정했는데 재빌드 안 됨 if_changed 조건과 의존성 .cmd 비교로 커맨드라인/입력 파일 변화 여부 확인

빌드 시스템과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.