커널 심볼 (Kernel Symbols)
커널 심볼은 단순한 함수 이름 목록이 아니라, 소스 선언, ELF 심볼 테이블, export 정책, modpost 수집, Module.symvers, 런타임 재배치, kallsyms 관측, GPL 및 네임스페이스 제약, CRC 기반 호환성 검사를 하나로 묶는 ABI 계약입니다. 이 문서는 커널 심볼을 "정의되는 이름"이 아니라 "외부 모듈과 디버거, 운영 도구가 의존하는 계약"으로 보고, 정의부터 문제 해결까지 한 흐름으로 정리합니다.
핵심 요약
- 심볼 — 함수, 전역 변수, 어셈블리 라벨, 링커가 만든 주소 이름까지 포함하는 "이름 붙은 엔티티"입니다.
- export — 다른 모듈이 참조할 수 있도록 커널이 ABI 계약을 공개하는 행위입니다.
- modpost — 빌드 마지막에 exported symbol과 undefined symbol을 대조하고
Module.symvers를 생성하는 후처리 단계입니다. - kallsyms — 실행 중 커널이 유지하는 심볼 이름 사전으로, Oops 해석과 디버깅에 직접 쓰입니다.
- CRC/namespace/GPL — 심볼이 존재하더라도 버전 CRC, 네임스페이스 import, GPL 제약을 통과해야 모듈이 로드됩니다.
단계별 이해
- 정의
C 함수, 전역 변수, 어셈블리 라벨, 링커 스크립트 심볼이 ELF 심볼로 먼저 만들어집니다. - 공개 여부 결정
내부 심볼은 그대로 숨기고, 외부 모듈이 반드시 써야 하는 것만EXPORT_SYMBOL*로 공개합니다. - 빌드 후처리
modpost가 undefined symbol과 exported symbol을 연결하고Module.symvers와.mod.c를 만듭니다. - 런타임 해석
모듈 로더가 재배치 엔트리를 따라 심볼 주소를 채우고, CRC, GPL, namespace 조건을 검증합니다. - 운영/디버깅
/proc/kallsyms,System.map,nm,readelf,%pS로 심볼을 해석하고 장애를 추적합니다.
개요: 커널 심볼을 무엇으로 봐야 하는가
커널 문맥에서 "심볼"이라는 단어는 적어도 네 가지 층위를 가집니다. 첫째, C/어셈블리 소스에 등장하는 이름입니다. 둘째, 오브젝트 파일과 vmlinux/.ko 안에 기록되는 ELF 심볼입니다. 셋째, EXPORT_SYMBOL*로 외부 모듈에 공개된 모듈 ABI 심볼입니다. 넷째, 실행 중 주소를 이름으로 되돌리기 위해 kallsyms가 유지하는 런타임 심볼 사전입니다. 실전에서는 이 네 층위가 섞여 보이기 때문에, 어느 단계에서 생긴 이름인지 먼저 구분해야 합니다.
즉, static 함수도 컴파일러와 링커 관점에서는 심볼일 수 있고, EXPORT_SYMBOL()이 붙지 않은 전역 함수도 ELF 심볼일 수 있습니다. 그러나 모든 ELF 심볼이 곧 모듈이 참조 가능한 커널 API는 아닙니다. 반대로, 디버거가 이름을 복원할 수 있다고 해서 그 심볼이 외부 모듈에서 링크 가능한 것도 아닙니다. 이 차이를 놓치면 "nm에는 보이는데 왜 모듈이 못 쓰지?" 같은 혼란이 바로 생깁니다.
| 용어 | 무엇을 가리키는가 | 대표 확인 도구 | 실수 포인트 |
|---|---|---|---|
| 소스 심볼 | C 함수, 전역 변수, 어셈블리 라벨 | 소스 코드, ctags, rg | 정의된 이름이면 모두 export 가능한 것으로 오해 |
| ELF 심볼 | 오브젝트 파일에 기록된 이름/주소/섹션 정보 | readelf -Ws, nm | ELF에 보이면 모듈도 쓸 수 있다고 오해 |
| exported 심볼 | 다른 모듈이 링크할 수 있도록 공개된 심볼 | __ksymtab*, Module.symvers | 최소 공개 원칙 없이 내부 구현까지 공개 |
| 런타임 심볼 | 주소를 이름으로 역해석하기 위한 실행 중 심볼 사전 | /proc/kallsyms, System.map | 보인다고 해서 로딩 가능한 ABI라고 오해 |
| Kconfig 심볼 | CONFIG_FOO 같은 설정 이름 | .config, menuconfig | 코드 심볼과 같은 의미로 혼동 |
심볼 수명주기: 정의에서 Oops 해석까지
커널 심볼의 수명은 소스 정의에서 끝나지 않습니다. 개발자가 함수를 정의하면 컴파일러가 ELF 심볼로 기록하고, 링크 과정이 최종 배치를 결정하며, export 매크로가 붙은 경우에는 별도 테이블에 등록됩니다. 그 뒤 modpost가 심볼 사용 관계를 수집하고, 모듈 로더는 로딩 시 재배치를 통해 실제 주소를 채웁니다. 마지막으로 Oops, ftrace, GDB, %pS 같은 도구가 이 이름을 다시 역으로 사용합니다.
중요한 점은 이 흐름이 순방향과 역방향 모두 존재한다는 것입니다. 순방향으로는 "이름이 주소를 얻는 과정"이고, 역방향으로는 "주소가 다시 이름으로 보이는 과정"입니다. 전자는 모듈 링크와 로딩의 문제이고, 후자는 디버깅과 운영 관측의 문제입니다. Kernel Symbol을 제대로 이해한다는 것은 이 둘을 같이 보는 것입니다.
어떤 심볼이 실제로 존재하는가
커널 개발에서는 흔히 함수 심볼만 떠올리지만, 실제로는 변수 심볼, 어셈블리 진입점, 링커가 만드는 경계 심볼, 모듈이 아직 해결하지 못한 undefined 심볼까지 함께 다뤄야 합니다. 특히 모듈은 최종 실행 파일이 아니라 재배치 가능한 오브젝트에 가깝기 때문에, "나중에 해결될 심볼"이라는 범주가 중요합니다.
| 종류 | 예시 | 언제 보이는가 | 주의점 |
|---|---|---|---|
| 로컬 심볼 | static int helper(void) | 컴파일 후 ELF에는 남을 수 있음 | 외부 모듈에서 링크 불가 |
| 전역 비export 심볼 | int core_fn(void) | nm vmlinux에서는 보일 수 있음 | 모듈 로딩에 필요한 ABI 공개와는 별개 |
| exported 함수 심볼 | EXPORT_SYMBOL(foo) | __ksymtab, Module.symvers | 한 번 공개하면 유지 비용이 커짐 |
| GPL 전용 심볼 | EXPORT_SYMBOL_GPL(foo) | __ksymtab_gpl | 비GPL 모듈은 사용 불가 |
| namespaced 심볼 | EXPORT_SYMBOL_NS(foo, USB_STORAGE) | export 테이블 + namespace 메타데이터 | 소비 모듈의 MODULE_IMPORT_NS 필요 |
| 링커 심볼 | __start___ksymtab, _stext | 링크 후 생성 | 소스에 직접 정의하지 않아도 생김 |
| undefined 심볼 | printk, kmalloc | .ko의 재배치 엔트리 | 런타임 또는 modpost가 해결해야 함 |
/* 제공자 모듈: 외부에 최소 API만 공개 */
#include <linux/module.h>
#include <linux/device.h>
static int do_hw_sequence(struct device *dev)
{
/* 내부 구현: export하지 않음 */
return 0;
}
int mysubsys_register_device(struct device *dev)
{
return do_hw_sequence(dev);
}
EXPORT_SYMBOL_NS_GPL(mysubsys_register_device, MYSUBSYS);
MODULE_LICENSE("GPL");
/* 소비 모듈: namespaced exported 심볼만 사용 */
#include <linux/module.h>
#include <linux/device.h>
extern int mysubsys_register_device(struct device *dev);
MODULE_IMPORT_NS(MYSUBSYS);
static int __init consumer_init(void)
{
/* 실제 예제에서는 유효한 device 포인터를 넘겨야 함 */
return 0;
}
module_init(consumer_init);
MODULE_LICENSE("GPL");
EXPORT 매크로와 공개 정책
EXPORT_SYMBOL*는 "이 함수가 편하니 공개한다"는 매크로가 아닙니다. 이 매크로를 붙이는 순간 다른 모듈이 해당 이름에 의존할 수 있고, 이후 시그니처 변경이나 의미 변경이 빌드/런타임 실패로 이어질 수 있습니다. 따라서 export는 구현 편의보다 ABI 비용을 먼저 계산해야 합니다.
가장 중요한 기준은 세 가지입니다. 첫째, 정말 외부 모듈이 필요로 하는가. 둘째, GPL 전용으로 제한할 근거가 있는가. 셋째, 특정 서브시스템 내부 소비만 허용하려면 네임스페이스를 붙일 것인가. 최근 커널 트리에서 namespace 도입이 강조되는 이유도, 심볼 수 자체를 줄이면서 의존성 경계를 명확히 하기 위해서입니다.
| 매크로 | 의미 | 언제 쓰는가 | 대표 실수 |
|---|---|---|---|
EXPORT_SYMBOL | 일반 공개 | 외부 모듈이 넓게 사용해야 하는 안정 API | 내부 구현 상세까지 무분별하게 공개 |
EXPORT_SYMBOL_GPL | GPL 모듈에만 공개 | 내부 성격이 강하거나 유지정책을 강하게 통제할 때 | MODULE_LICENSE 제약을 빼먹고 사용 |
EXPORT_SYMBOL_NS | namespace 포함 일반 공개 | 서브시스템 경계를 명시하고 싶을 때 | 소비 모듈의 MODULE_IMPORT_NS 누락 |
EXPORT_SYMBOL_NS_GPL | GPL + namespace | 내부 서브시스템 공용 API | namespace 이름이 자주 바뀌는 경우 유지비 증가 |
MODULE_IMPORT_NS | namespace 사용 선언 | namespaced export를 참조하는 모듈 | 오타나 조건부 컴파일로 import가 빠짐 |
symbol_get | 선택적 심볼 획득 | 기능이 있으면 쓰고 없으면 우회하는 선택적 연동 | symbol_put 누락으로 참조 카운트 불균형 |
빌드 단계: __ksymtab, __kcrctab, modpost, Module.symvers
커널 심볼 시스템의 핵심은 "코드에 붙은 export 매크로"가 빌드 후처리에서 실제 메타데이터로 변환된다는 점입니다. 이 변환을 담당하는 것이 scripts/mod/modpost입니다. 모듈 빌드는 컴파일과 링크만으로 끝나지 않고, 마지막에 modpost가 exported symbol과 undefined symbol의 관계를 수집해 Module.symvers, .mod.c, CRC 정보를 만듭니다.
실무에서는 많은 문제가 이 단계에서 먼저 발견됩니다. 예를 들어 undefined symbol, GPL 제약 위반, namespace import 누락, section mismatch, modversions 불일치 가능성 등이 modpost 경고나 오류로 나타납니다. 즉, modpost는 "나중에 로딩해 보면 알겠지"를 막아 주는 첫 번째 방어선입니다.
| 구성 요소 | 역할 | 무엇을 보면 되는가 | 문제 징후 |
|---|---|---|---|
__ksymtab | 일반 export 심볼 목록 | readelf -S, objdump -h | export가 기대와 다르게 누락 |
__ksymtab_gpl | GPL 전용 export 목록 | readelf -S | 비GPL 모듈 로딩 실패 |
__kcrctab* | 심볼 CRC 저장 | readelf -S, Module.symvers | 버전 불일치 경고 |
.mod.c | 자동 생성 모듈 메타데이터 | 빌드 디렉터리의 *.mod.c | versions 배열, vermagic 확인 필요 |
Module.symvers | exported symbol와 CRC 요약표 | head Module.symvers | 기대한 심볼이 목록에 없음 |
modpost | 심볼/라이선스/섹션/namespace 검사 | 빌드 로그 | WARNING:, ERROR: 문구 |
# modpost 이후 생성되는 Module.symvers 예시
0x9f4a1c32 mysubsys_register_device drivers/mysubsys/core EXPORT_SYMBOL_NS_GPL
0x19d4c2c0 mysubsys_unregister_device drivers/mysubsys/core EXPORT_SYMBOL_NS_GPL
0x51eb9c7a mysubsys_get_stats drivers/mysubsys/core EXPORT_SYMBOL_GPL
# 모듈의 섹션과 export 관련 테이블 확인
readelf -S mysubsys.ko | grep -E '__ksymtab|__kcrctab|__versions'
# 심볼 테이블 확인
readelf -Ws mysubsys.ko | less
# modpost 결과물 확인
head -20 Module.symvers
sed -n '1,120p' mysubsys.mod.c
__ksymtab 섹션과 ELF 관점은 ELF 문서가 이어서 설명합니다.
런타임 단계: 모듈 로더와 심볼 해석
빌드가 끝나도 심볼 문제는 아직 끝나지 않습니다. 모듈 파일은 여전히 재배치 정보와 undefined symbol 참조를 포함하고 있기 때문에, 실제 로딩 시 커널이 현재 실행 중인 커널 이미지와 이미 로드된 모듈들의 export 테이블을 기준으로 주소를 채워 넣어야 합니다. 이때 심볼 이름이 존재하는지뿐 아니라, 라이선스 제약, namespace 선언, CRC 일치 여부, 아키텍처별 재배치 타입까지 함께 검증합니다.
따라서 "빌드되었다"와 "로드된다"는 전혀 다른 단계입니다. 빌드 시점에는 헤더와 심볼 이름이 맞았더라도, 런타임 커널이 다른 설정이나 다른 CRC를 가질 수 있고, GPL/namespace 정책이 달라질 수도 있습니다. DKMS나 외부 모듈에서 이런 문제가 특히 자주 나타납니다.
# 모듈이 어떤 외부 심볼을 참조하는지 먼저 본다
readelf -r myconsumer.ko | head -20
# 어떤 심볼이 undefined 상태인지 본다
nm -u myconsumer.ko
# 커널이 현재 제공하는 심볼 목록에서 찾는다
grep mysubsys_register_device /proc/kallsyms
# 로딩 직후 커널 로그를 확인한다
dmesg | tail -50
# 자주 보는 런타임 실패 예시
insmod: ERROR: could not insert module myconsumer.ko: Unknown symbol in module
dmesg:
myconsumer: Unknown symbol mysubsys_register_device (err -2)
myconsumer: module uses symbols from namespace MYSUBSYS, but does not import it
myconsumer: disagrees about version of symbol mysubsys_register_device
/proc/kallsyms에서 이름이 보인다고 해서 항상 로딩 가능한 것은 아닙니다.
동일 이름이 있어도 GPL 전용, namespace 미import, CRC mismatch, vermagic 차이, 아키텍처 재배치 문제 때문에 로딩은 실패할 수 있습니다.
kallsyms, System.map, nm, readelf로 추적하는 법
커널 심볼을 실제로 체감하는 순간은 대개 장애 분석입니다. Oops에 찍힌 주소를 함수명으로 바꾸거나, 함수 포인터를 %pS로 찍거나, 특정 주소가 어느 섹션과 어느 모듈에 속하는지 확인하는 작업은 모두 심볼 정보를 기반으로 합니다. 여기서 가장 많이 쓰이는 도구가 /proc/kallsyms, System.map, nm, readelf, objdump, GDB입니다.
/proc/kallsyms는 현재 실행 중 커널의 심볼 사전이고, System.map은 빌드 또는 설치 시점의 정적 주소-이름 매핑입니다. 전자는 런타임 상태를 반영하지만 권한 제약과 KASLR 영향이 있고, 후자는 설치된 커널 이미지와 정확히 맞을 때 강력합니다. 모듈 자체를 분석할 때는 nm과 readelf가 더 직접적입니다.
| 도구 | 주 용도 | 강점 | 제약 |
|---|---|---|---|
/proc/kallsyms | 실행 중 주소를 이름으로 확인 | 현재 커널 상태 반영 | kptr_restrict, 권한 제약, KASLR 영향 |
System.map | 정적 커널 이미지 주소 확인 | Oops 해석의 기준점 | 실행 중 커널 이미지와 정확히 맞아야 함 |
nm | 심볼 종류와 정의/미정의 확인 | 간단하고 빠름 | 재배치 세부 정보는 부족 |
readelf -Ws/-r | 심볼 테이블과 재배치 확인 | ELF 구조를 정밀하게 볼 수 있음 | 출력이 길고 해석 비용이 큼 |
objdump -dr | 기계어와 relocation 같이 보기 | 실제 참조 지점을 확인 가능 | 어셈블리 독해 필요 |
GDB | 소스/함수/주소 상호 이동 | 백트레이스와 코드 흐름 분석에 강함 | 적절한 vmlinux 심볼 파일 필요 |
# 실행 중 커널에서 함수 주소 확인
grep ' do_sys_open$' /proc/kallsyms
# 모듈에서 미해결 외부 심볼만 추리기
nm -u myconsumer.ko
# 심볼 테이블과 바인딩/타입까지 보기
readelf -Ws myconsumer.ko | grep mysubsys
# 재배치가 어떤 심볼을 향하는지 보기
readelf -r myconsumer.ko | grep mysubsys
# vmlinux에서 주소를 소스 라인으로 역변환
gdb vmlinux
(gdb) info address do_sys_open
(gdb) list *do_sys_open
# printk에서 함수 포인터를 심볼명으로 출력
pr_info("cb=%pS raw=%px\n", cb, cb);
addr2line보다 먼저 %pS, /proc/kallsyms, System.map 정합성을 확인하는 편이 빠릅니다. 특히 KASLR이 켜져 있으면 정적 주소와 런타임 주소를 그대로 비교하면 안 됩니다.
CONFIG_MODVERSIONS와 CRC: 왜 이름이 같아도 거부되는가
CONFIG_MODVERSIONS가 켜져 있으면 커널은 exported symbol마다 시그니처 기반 CRC를 관리합니다. 이 CRC는 단순히 이름이 같은지 보는 것이 아니라, 해당 심볼의 인터페이스가 "같은 계약"인지 확인하기 위한 요약값입니다. 그래서 이름이 그대로여도 인자 타입, 구조체 선언, 일부 전처리 결과가 달라지면 CRC가 바뀌어 모듈 로딩이 거부될 수 있습니다.
이 메커니즘은 안정 ABI를 보장하는 장치가 아닙니다. 오히려 "인터페이스가 바뀌었는데 오래된 모듈이 모르고 로딩되는 것"을 막기 위한 장치입니다. 따라서 외부 모듈 유지보수 관점에서는 CRC mismatch를 해결하기 위해 억지로 강제 로드하는 것이 아니라, 동일한 헤더와 설정으로 재빌드하는 것이 정석입니다.
| 항목 | 의미 | 어디서 확인 | 실수 |
|---|---|---|---|
| 심볼 CRC | exported symbol 인터페이스 요약값 | Module.symvers, __versions | 이름만 같으면 호환된다고 생각 |
__versions | 모듈이 기대하는 외부 심볼 CRC 목록 | readelf -S, *.mod.c | 모듈 안의 CRC와 커널 쪽 CRC를 따로 보지 않음 |
genksyms | 심볼 서명을 계산하는 빌드 도구 | 빌드 로그, Kbuild 내부 | 헤더 차이를 가볍게 봄 |
vermagic | 커널/모듈 빌드 속성 문자열 | modinfo | CRC mismatch와 vermagic mismatch를 혼동 |
# 모듈의 버전 매직 확인
modinfo myconsumer.ko | grep vermagic
# CRC 테이블이 들어갔는지 확인
readelf -S myconsumer.ko | grep __versions
# 현재 커널용 Module.symvers와 비교
grep mysubsys_register_device Module.symvers
# CRC 불일치 대표 예시
myconsumer: disagrees about version of symbol mysubsys_register_device
myconsumer: Unknown symbol mysubsys_register_device (err -22)
insmod: ERROR: could not insert module myconsumer.ko: Invalid module format
보안과 정책: GPL 전용 심볼, namespace, 주소 노출 제약
커널 심볼 시스템은 단지 편의성 구조가 아니라 정책 집행 수단이기도 합니다. GPL 전용 심볼은 내부 API의 무분별한 외부 사용을 억제하고, namespace는 서브시스템 경계를 강제하며, kptr_restrict와 KASLR은 주소 노출을 제한합니다. 즉, 커널 심볼은 ABI와 디버깅뿐 아니라 보안 경계의 일부이기도 합니다.
특히 운영 환경에서 /proc/kallsyms 주소가 0으로 보이는 현상은 이상이 아니라 정책 적용 결과일 수 있습니다. 반대로 개발 환경에서는 너무 쉽게 주소를 열어 두면, 실제 운영 제약을 놓친 상태로 문제를 재현하게 됩니다. 문서화와 재현 환경에서 이 차이를 의식해야 합니다.
| 정책 | 무엇을 막는가 | 어디서 드러나는가 | 개발 시 주의점 |
|---|---|---|---|
| GPL 전용 export | 비GPL 모듈의 내부 API 사용 | 로딩 실패, taint 상태 | MODULE_LICENSE만 바꾸는 식의 우회는 본질 해결이 아님 |
| symbol namespace | 서브시스템 경계 없는 무차별 참조 | modpost 경고, 로딩 실패 | 공급자와 소비자의 namespace를 같이 관리 |
kptr_restrict | 주소 직접 노출 | /proc/kallsyms 주소 마스킹 | 권한 차이 때문에 재현 결과가 달라질 수 있음 |
| KASLR | 정적 주소 예측 | 디버깅 시 주소 불일치 | 정적 이미지 주소와 런타임 주소를 곧바로 비교하지 않음 |
CONFIG_TRIM_UNUSED_KSYMS | 불필요한 export 남발 | 빌드 결과 export 목록 축소 | 구성에 따라 보이던 심볼이 사라질 수 있음 |
/* 선택적 연동 패턴: 심볼이 있을 때만 기능 사용 */
#include <linux/module.h>
extern int optional_accel_submit(const void *buf, size_t len);
static int (*accel_submit_ptr)(const void *buf, size_t len);
static int try_accel(const void *buf, size_t len)
{
accel_submit_ptr = symbol_get(optional_accel_submit);
if (!accel_submit_ptr)
return -EOPNOTSUPP;
if (accel_submit_ptr(buf, len))
pr_warn("accelerator submit failed\n");
symbol_put(optional_accel_submit);
return 0;
}
문제 해결: Unknown symbol에서 CRC mismatch까지
커널 심볼 문제는 대부분 증상은 짧고 원인은 길게 퍼져 있습니다. 빌드는 되었지만 로드가 안 되거나, 주소는 보이는데 함수명이 다르거나, namespace 경고가 나오거나, DKMS 모듈이 업데이트 뒤 갑자기 깨지는 식입니다. 이럴 때는 "어느 단계에서 계약이 끊어졌는가"를 기준으로 조사해야 합니다.
가장 실전적인 순서는 다음과 같습니다. 먼저 빌드 로그에서 modpost 경고를 확인하고, 그다음 nm -u와 readelf -r로 미해결 참조를 보고, 이어서 /proc/kallsyms와 Module.symvers에서 공급자 쪽 상태를 확인합니다. 마지막으로 라이선스, namespace, CRC, vermagic 순서로 좁혀가면 됩니다.
| 증상 | 가장 흔한 원인 | 먼저 볼 것 | 수정 방향 |
|---|---|---|---|
Unknown symbol foo | export 안 됨, 공급 모듈 미로드, 이름 오타 | nm -u, /proc/kallsyms, Module.symvers | 공급자 export 확인, 로드 순서 확인, 이름 정합성 재검토 |
GPL-incompatible module uses GPL-only symbol | 비GPL 모듈이 GPL 심볼 사용 | MODULE_LICENSE, 공급자 export 종류 | 설계 재검토 또는 GPL 호환 정책 정리 |
| namespace 관련 경고/실패 | MODULE_IMPORT_NS 누락 | 빌드 로그, dmesg | 소비 모듈에 namespace import 추가 |
disagrees about version of symbol | CRC mismatch | Module.symvers, __versions, modinfo | 현재 커널과 동일 조건으로 재빌드 |
Invalid module format | vermagic, CRC, 아키텍처, 서명 문제 | modinfo, dmesg | 커널/모듈 빌드 환경 맞추기 |
/proc/kallsyms 주소가 0 | kptr_restrict, 권한 제한 | sysctl 값, root 여부 | 권한/정책 차이를 고려해 확인 |
# 1. 빌드 단계 확인
make V=1
# 2. 미해결 외부 심볼 확인
nm -u myconsumer.ko
# 3. 재배치가 어떤 심볼을 참조하는지 확인
readelf -r myconsumer.ko | less
# 4. 공급자 심볼이 실제로 export됐는지 확인
grep mysubsys_register_device Module.symvers
grep mysubsys_register_device /proc/kallsyms
# 5. 정책 문제 확인
modinfo myconsumer.ko
dmesg | tail -100
dmesg 마지막 한 줄만 보고 끝내지 마세요.
modpost 경고, nm -u, readelf -r, Module.symvers 네 가지를 같이 보면 원인 분류 속도가 크게 빨라집니다.
실무 원칙: 무엇을 export하지 말아야 하는가
Kernel Symbol 문서의 마지막은 "어떻게 쓰는가"보다 "언제 공개하지 않을 것인가"로 끝나야 합니다. export는 재사용성의 출발이 아니라 유지보수 의무의 시작이기 때문입니다. 심볼 하나를 공개하면 다른 모듈이 의존하고, 그 의존은 코드 검색만으로 끝나지 않고 외부 트리, DKMS, 배포판 패키지, 운영 스크립트까지 퍼질 수 있습니다.
따라서 export 전 체크리스트는 기능성보다 경계성을 봐야 합니다. 단순 헬퍼, 락이 노출된 내부 구조, 수명주기가 불안정한 객체 접근자, 디버깅 전용 임시 함수, 직접 필드 접근을 강제하는 구조체 API는 원칙적으로 export 후보가 아닙니다. 대신 wrapper, opaque handle, namespace, 문서화된 수명 규약을 함께 설계해야 합니다.
| 상황 | 권장 선택 | 이유 |
|---|---|---|
| 한 모듈만 쓰는 내부 헬퍼 | static 유지 | ABI 표면을 늘릴 이유가 없음 |
| 여러 함수 조합이 필요한 복잡한 내부 절차 | wrapper 1개만 export | 내부 순서/락/상태 전이를 숨길 수 있음 |
| 서브시스템 내부 공용 API | EXPORT_SYMBOL_NS_GPL | 경계와 라이선스 정책을 같이 명시 가능 |
| 디버깅 전용 함수 | export 대신 tracepoint/debugfs 검토 | 디버그 훅을 ABI로 굳히지 않음 |
| 옵션 기능 연동 | symbol_get 또는 명시적 의존성 | 강한 링크와 약한 링크를 구분 가능 |
- 최소 공개: 외부 사용자가 정말 필요한 의미 단위만 export합니다.
- 수명주기 문서화: 호출자가 락, 참조 카운트, sleep 가능 여부를 지켜야 한다면 commit message와 코드 주석에 남깁니다.
- namespace 우선: 서브시스템 내부 공용 API라면 처음부터 namespace를 붙여 경계를 명확히 합니다.
- 정책 일관성: 비슷한 성격의 심볼은 같은 export 정책을 유지해야 소비자가 예측할 수 있습니다.
- 문제 재현성: export를 추가한 커밋에는 어떤 모듈이 왜 필요한지, modpost/로딩 검증을 어떻게 했는지 남깁니다.
관련 문서와 커널 소스 길잡이
이 문서는 커널 심볼을 중심축으로 묶은 문서입니다. 세부 축은 아래 문서와 소스 파일로 이어서 들어가면 됩니다.
- 커널 모듈 — 모듈 작성, 로딩, 의존성, export 실습 맥락
- 빌드 시스템 — Kbuild, modpost, Module.symvers 생성 흐름
- ELF — 심볼 테이블, 재배치,
__ksymtab, 모듈 ELF 구조 - 디버깅 & 트러블슈팅 — Oops 해석, printk, 관측 도구
- 개발 도구 —
nm,readelf,objdump, GDB와의 연결 - 커널 필수 함수·매크로·심볼 레퍼런스 — export 관련 API를 빠르게 찾는 레퍼런스형 문서
include/linux/export.h— export 매크로 정의kernel/module/main.c— 모듈 로딩과 심볼 해석 핵심 경로kernel/kallsyms.c— kallsyms 생성/조회 로직scripts/mod/modpost.c— 빌드 후처리와 심볼 검증include/linux/module.h— 모듈 메타데이터, 참조 카운트 관련 선언