빌드 시스템 (Build System)

Linux 커널의 빌드 시스템인 Kbuild와 Kconfig를 상세히 다룹니다. Makefile 구조, 설정 문법, 컴파일 과정, Cross-compilation, 빌드 최적화까지 커널 빌드의 모든 것을 설명합니다.

전제 조건: 이 문서를 읽기 전에 홈(리눅스 커널이란?)에서 커널의 큰 그림을 먼저 파악하세요. C 언어 기초와 make 명령어 사용 경험이 있으면 이해가 쉽습니다.
일상 비유: 커널 빌드 시스템은 대규모 공장의 조립 라인과 같습니다. Kconfig는 "어떤 부품을 넣을지" 결정하는 주문서이고, Kbuild(Makefile)는 "어떤 순서로 조립할지" 정의하는 작업 지시서입니다. make menuconfig로 주문서를 작성하고, 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 5.x부터 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
  ├── include/generated/autoconf.h   # C 코드용 #define
  ├── include/config/auto.conf       # Makefile용 변수 정의
  ├── include/config/auto.conf.cmd   # 의존성 추적용
  └── include/config/*.h             # 개별 CONFIG_* 타임스탬프 파일
/* 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 패턴들입니다.

# drivers/net/ethernet/intel/Makefile
# Kconfig 심볼에 따라 빌드 여부 결정

obj-$(CONFIG_E1000)      += e1000/
obj-$(CONFIG_E1000E)     += e1000e/
obj-$(CONFIG_IGB)        += igb/
obj-$(CONFIG_IXGBE)      += ixgbe/
obj-$(CONFIG_ICE)        += ice/
# 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

# 설정에 따른 조건부 소스 추가
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

빌드 과정 흐름도 (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는 커널 헤더 패키지(linux-headers-$(uname -r))가 설치되어 있어야 동작합니다. 커널 업데이트 시 헤더 패키지도 함께 설치되므로 일반적으로 문제가 없지만, 커스텀 커널을 사용할 경우 make headers_install이 필요합니다.

커널 소스 관리 (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) 빌드의 후처리 단계를 담당합니다. 심볼 의존성 해석과 버전 정보 삽입이 핵심입니다.

모듈 빌드 후처리 흐름:

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 호출 관점에서 추적합니다.

make -j$(nproc) 실행 시 재귀 흐름:

Top Makefile
  │
  ├─ include arch/$(ARCH)/Makefile          # 아키텍처별 설정
  ├─ include scripts/Kbuild.include         # 유틸리티 함수
  ├─ -include include/config/auto.conf      # CONFIG_* 변수
  │
  ├─ $(Q)$(MAKE) $(build)=scripts/basic     # 기본 호스트 도구
  ├─ $(Q)$(MAKE) $(build)=scripts           # 빌드 스크립트
  │
  ├─ $(Q)$(MAKE) $(build)=init              # init/ 디렉토리
  │    └─ Makefile.build obj=init
  │         ├─ include init/Makefile         # obj-y 변수 읽기
  │         ├─ include scripts/Makefile.lib  # 플래그 계산
  │         ├─ init/main.o ← init/main.c    # 컴파일
  │         └─ init/built-in.a              # 아카이브
  │
  ├─ $(Q)$(MAKE) $(build)=kernel            # kernel/ 디렉토리
  │    └─ Makefile.build obj=kernel
  │         ├─ kernel/*.o                    # 컴파일
  │         ├─ $(MAKE) $(build)=kernel/sched # 서브디렉토리 재귀
  │         │    └─ kernel/sched/built-in.a
  │         └─ kernel/built-in.a            # 하위 포함
  │
  ├─ $(Q)$(MAKE) $(build)=mm
  ├─ $(Q)$(MAKE) $(build)=fs
  ├─ $(Q)$(MAKE) $(build)=net
  ├─ $(Q)$(MAKE) $(build)=drivers
  ├─ ... (기타 서브디렉토리)
  │
  └─ vmlinux 링킹
       scripts/link-vmlinux.sh
       ├─ LD: 모든 built-in.a → vmlinux.o
       ├─ MODPOST: vmlinux.o 심볼 체크
       ├─ LD: vmlinux.o → vmlinux
       └─ SORTTAB, SYSMAP, BTF 등 후처리

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에서의 서브디렉토리 구성

# 항상 빌드되는 서브디렉토리
obj-y += sched/
obj-y += locking/
obj-y += power/
obj-y += printk/
obj-y += irq/
obj-y += rcu/
obj-y += time/

# 설정에 따라 빌드되는 서브디렉토리
obj-$(CONFIG_BPF)       += bpf/
obj-$(CONFIG_CGROUPS)   += cgroup/
obj-$(CONFIG_TRACING)   += trace/
obj-$(CONFIG_GCOV_KERNEL) += gcov/
obj-$(CONFIG_LIVEPATCH) += livepatch/

플래그 변수 (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 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= 변수로 제어합니다.
  • 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