커널 필수 함수·매크로·심볼 레퍼런스
커널 코드 리뷰에서 반복해서 등장하는 함수, 매크로, 심볼을 컨텍스트 중심으로 대규모 정리했습니다. 각 항목은 "언제 쓰는가", "자주 나는 실수", "대체 API", "소스 경로"를 함께 담아 실전 참고서로 바로 사용할 수 있게 구성했습니다.
ERR_PTR, READ_ONCE, container_of의 의미를 이해하고 오면 이 문서를 훨씬 빠르게 사용할 수 있습니다.
핵심 | 핵심 요약
이 요약은 표 전체를 읽기 전에 반드시 고정해야 할 판단 축을 압축한 구간입니다. 아래 항목을 먼저 이해하면 이후 섹션에서 API 이름이 달라도 판단 기준을 일관되게 유지할 수 있습니다. 특히 반환 규약, 락 규약, 수명주기 순서는 코드 리뷰에서 반복적으로 지적되는 핵심 실패 지점입니다.
실무 체크포인트: 구현 전에 이 5개 항목을 PR 설명의 "사전 점검" 항목으로 그대로 복사해 사용하세요.
- 반환 규약 — 포인터 반환 API는
NULL인지ERR_PTR인지 먼저 확인합니다. - 락 규약 — 컨텍스트에 맞는 락을 선택하고 sleep 가능 여부를 반드시 구분합니다.
- 수명주기 — 할당, 등록, 해제의 순서를 쌍으로 기억합니다.
- 가시성 — 심볼 export는 ABI 계약이므로 최소한만 공개합니다.
- 관측성 — 문제 재현 전 로그 레벨, tracepoint, 통계를 먼저 설계합니다.
핵심 | 단계별 이해
단계별 이해는 "문맥 확인 → API 선택 → 실패 경로 설계"를 강제로 순서화해 실수를 줄이기 위한 절차입니다. 항목을 건너뛰면 대부분 해제 누락이나 문맥 오용이 발생하므로, 코드 작성 전에 순서를 먼저 확정하는 것이 안전합니다.
실무 체크포인트: 단계 1에서 컨텍스트를 확정하지 못하면 구현을 진행하지 말고 호출 경로부터 다시 추적하세요.
- 문맥 파악
프로세스 문맥인지 인터럽트 문맥인지 먼저 결정합니다. - 기본 API 선택
메모리, 락, 에러 처리의 기본 세트를 정합니다. - 실패 경로 설계
중간 실패 시 되돌릴 순서를 코드보다 먼저 정합니다. - 심볼 노출 점검
정말 외부 모듈이 필요한 심볼만 export 합니다. - 관측 포인트 추가
pr_debug, tracepoint, 통계 카운터를 최소 단위로 배치합니다.
핵심 | 이 문서를 빠르게 읽는 법
이 페이지는 "API 목록"이 아니라 "의사결정 지원서"입니다. 각 표는 같은 구조(언제 사용/실수 패턴/대체안)로 작성되어 있으므로, 문제가 발생했을 때 아래 순서로 접근하면 가장 빠릅니다.
- 문맥 확인
현재 코드가 프로세스 문맥인지, IRQ 문맥인지, sleep 가능한지 먼저 확정합니다. - 반환 규약 확인
호출 API가NULL반환형인지ERR_PTR반환형인지 먼저 고정합니다. - 짝 API 확인
할당/해제, lock/unlock, get/put 쌍이 모두 존재하는지 체크합니다. - 실수 패턴 역검증
표의 "실수 패턴" 열을 기준으로 현재 코드가 이미 같은 실수를 반복하는지 확인합니다. - 대체안 검토
현재 경로가 문맥과 맞지 않으면 즉시 "대체/보완" API로 치환 전략을 세웁니다.
핵심 | API 선택 의사결정 플로우
| 질문 | Yes일 때 | No일 때 | 핵심 참고 섹션 |
|---|---|---|---|
| 현재 문맥에서 sleep 가능한가? | mutex, GFP_KERNEL 계열 우선 | spinlock, GFP_ATOMIC 계열 우선 | 컨텍스트별 API 선택 매트릭스 |
| 반환값이 포인터인가? | IS_ERR/PTR_ERR 또는 NULL 규약 먼저 확인 | 정수 errno 규약으로 처리 | 반환값 계약 카탈로그 |
| 핫패스(고빈도) 경로인가? | 로그/락/복사 비용 최소화, 관측 필터 적용 | 가독성과 안정성 우선 | 관측성 카탈로그 |
| 모듈 외부에 기능을 노출해야 하는가? | EXPORT_SYMBOL* + 네임스페이스 정책 적용 | static 내부화 유지 | 심볼 Export/네임스페이스 |
| 오류 재현이 간헐적인가? | tracepoint + rate-limit + 재현 스크립트 고정 | 단계별 로그로 충분 | 문제 유형별 대응 인덱스 |
실무 체크포인트: 코드 리뷰 전에 문맥, 반환규약, 해제쌍 3가지를 PR 설명에 먼저 명시하세요.
핵심 | 빠른 이동 인덱스
검색형 레퍼런스로 사용할 때는 아래 인덱스에서 도메인을 먼저 고르고, 해당 카탈로그로 바로 이동하세요.
| 영역 | 즉시 이동 | 메모 |
|---|---|---|
| 핵심 규약 | 컨텍스트 매트릭스, 반환값 계약, 락 선택 | 리뷰 지적의 대부분을 선제 차단 |
| 네트워크 | 네트워킹 카탈로그, 프로토콜 심화 | skb/NAPI/Netfilter 중심 |
| 파일시스템 | 파일시스템 카탈로그, 파일시스템 심화 | VFS와 ext4/XFS/Btrfs 분리 확인 |
| 메모리 | 메모리 관리 카탈로그, GFP 카탈로그 | GFP/folio/reclaim 우선 점검 |
| 디바이스/미디어 | 버스 카탈로그, GPU/V4L2/ALSA | probe/remove + DMA 경계 |
| 보안/운영 | 보안/LSM, 취약점 대응, 패닉 대응 | 운영 절차와 코드 수정을 함께 관리 |
핵심 | 개요
아래 다이어그램은 함수, 매크로, 심볼이 어떻게 연결되는지 보여줍니다. 함수는 동작 단위, 매크로는 규약 단축, 심볼은 모듈 간 계약입니다.
실무 체크포인트: 신규 심볼 export 전에는 반드시 기존 내부 함수로 대체 가능한지 먼저 확인하세요.
/* 실패 경로를 먼저 설계한 전형적인 초기화 패턴 */
static int __init sample_init(void)
{
int ret;
ret = register_chrdev(0, "sample", &fops);
if (ret < 0)
return ret;
if (unlikely(!ready)) {
unregister_chrdev(ret, "sample");
return -EINVAL;
}
pr_info("sample initialized\n");
return 0;
}
핵심 | 로깅과 출력 함수
로깅 API는 단순 출력 함수가 아니라 장애 대응 속도를 결정하는 운영 인터페이스입니다. 이 표는 로그 레벨 선택, 디바이스 컨텍스트 포함 여부, 폭주 억제 전략을 함께 판단하기 위한 기준으로 읽어야 합니다. 특히 핫패스에서는 정보량보다 샘플링 정책과 레이트 리밋이 우선입니다.
실무 체크포인트: 실패 로그에는 반드시 "실패 동작 + 식별자 + errno"를 한 줄에 남기고, 정상 경로 로그는 기본적으로 억제하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
printk | 기본 커널 로그 출력 | 레벨 미지정 | pr_* |
pr_emerg | 시스템 즉시 중단급 장애 | 일반 오류에 남용 | panic |
pr_alert | 즉시 조치가 필요한 오류 | 중복 출력 | pr_err |
pr_crit | 핵심 기능 손상 | 복구 가능 오류에 사용 | WARN_ON |
pr_err | 실패 경로 보고 | 에러 코드 누락 | dev_err |
pr_warn | 비정상 상태 경고 | 정상 경로에서 남용 | dev_warn |
pr_notice | 운영자가 알아야 할 상태 변화 | 정보 로그와 혼용 | pr_info |
pr_info | 일반 상태 정보 | 고빈도 경로에 과다 사용 | pr_debug |
pr_debug | 디버그 빌드 또는 동적 디버그 | 필수 로그를 debug로만 남김 | dynamic_debug |
dev_err | 디바이스 컨텍스트 포함 오류 로그 | struct device 없이 사용 | dev_warn, dev_info |
핵심 | 메모리 할당/해제
메모리 API 선택은 크기보다 실행 문맥과 회수 전략을 먼저 결정해야 안전합니다. 같은 할당이라도 sleep 가능 여부와 reclaim 허용 범위가 다르면 실패 확률과 지연 특성이 크게 달라집니다. 표를 볼 때는 "할당 성공"보다 "실패 시 되돌리기"가 닫히는지부터 확인해야 합니다.
실무 체크포인트: 모든 할당 지점에 대응하는 해제 API를 함수 단위가 아니라 실패 경로 단위로 짝지어 점검하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
kmalloc | 작은 연속 메모리 | 문맥에 맞지 않는 GFP | kzalloc |
kzalloc | 0 초기화가 필요한 구조체 | 불필요한 중복 memset | kmalloc |
kcalloc | 배열 할당 | 곱셈 오버플로 직접 계산 | kvcalloc |
krealloc | 버퍼 크기 변경 | 실패 시 원본 포인터 덮어쓰기 | kvrealloc |
kfree | kmalloc 계열 해제 | 중복 해제 | kvfree |
vmalloc | 큰 비연속 가상 메모리 | DMA 가능한 메모리로 오해 | vzalloc |
vzalloc | 0 초기화된 vmalloc 영역 | 빈번한 소량 할당에 사용 | vmalloc |
kvzalloc | 큰 메모리 할당 fallback | 해제를 kfree로만 처리 | kvfree |
kmem_cache_create | 반복 객체 할당 최적화 | ctor 과도 사용 | KMEM_CACHE |
kmem_cache_alloc | slab 객체 할당 | 캐시 파괴 전 해제 누락 | kmem_cache_free |
핵심 | 사용자 공간 접근
사용자 공간 접근 API는 보안 경계와 오류 전파 규약이 동시에 걸린 고위험 구간입니다. 표의 핵심은 복사 함수 선택 자체보다 반환값 해석과 부분 복사 처리 규칙을 고정하는 데 있습니다. 주소 검증과 실제 복사는 별개의 단계라는 점을 항상 분리해서 보아야 합니다.
실무 체크포인트: `copy_*_user` 반환값을 무시하는 코드는 기능 동작과 무관하게 즉시 수정 대상으로 분류하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
copy_from_user | 유저 버퍼 입력 수신 | 반환값 미검사 | get_user |
copy_to_user | 유저 버퍼 출력 | 부분 복사 처리 누락 | put_user |
get_user | 스칼라 값 1개 읽기 | 포인터 유효성 가정 | copy_from_user |
put_user | 스칼라 값 1개 쓰기 | 에러 코드 무시 | copy_to_user |
strncpy_from_user | 문자열 길이 제한 복사 | NUL 보장 오해 | strscpy_from_user |
strnlen_user | 유저 문자열 길이 확인 | 반환값 단위 해석 오류 | strlen 사용 금지 |
access_ok | 유저 주소 범위 1차 확인 | 복사 안전성 보장으로 오해 | copy_*_user와 병행 |
import_iovec | 벡터형 I/O 인자 검증 | 복잡한 iov 직접 파싱 | iov_iter |
pin_user_pages | 장기 DMA 고정 | unpin 누락 | get_user_pages |
unpin_user_pages | pin 해제 | 참조 카운트 누락 | put_page |
핵심 | 에러 처리 매크로
에러 처리 매크로는 코드 스타일이 아니라 함수 계약을 표현하는 수단입니다. 포인터 계열과 정수 계열 계약을 혼동하면 정상 경로에서도 잘못된 분기가 만들어져 장애를 유발합니다. 이 섹션은 "검사 매크로 선택"보다 "반환 계약 명시"를 먼저 수행하는 기준으로 사용해야 합니다.
실무 체크포인트: 포인터 반환 함수 선언부 주석에 `NULL` 계열인지 `ERR_PTR` 계열인지를 명시하고 그 규약만 허용하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
ERR_PTR | 포인터 반환 함수 에러 인코딩 | 양수 errno 전달 | PTR_ERR |
IS_ERR | 에러 포인터 판별 | NULL 검사로 대체 | IS_ERR_OR_NULL |
PTR_ERR | 에러 코드 추출 | 정상 포인터에 적용 | PTR_ERR_OR_ZERO |
IS_ERR_OR_NULL | NULL 포함 에러 판별 | 설계 문제 은폐 | IS_ERR |
PTR_ERR_OR_ZERO | int 반환 함수 연결 | 반환값 의미 혼동 | PTR_ERR |
WARN_ON | 비정상 조건 경고 | 복구 가능한 조건에서 과다 사용 | WARN_ON_ONCE |
WARN_ON_ONCE | 반복 경고 억제 | 원인 추적 전 너무 일찍 사용 | pr_warn_ratelimited |
BUG_ON | 치명적 불변식 위반 | 운영 경로에서 남용 | WARN_ON |
pr_err_ratelimited | 폭주 로그 억제 | 상태 정보 손실 | net_ratelimit |
might_sleep | sleep 가능 문맥 검증 | atomic 경로에서 호출 | lockdep_assert_held |
핵심 | 자료구조 매크로
자료구조 매크로는 성능 최적화 수단이 아니라 불변식 유지 수단입니다. 순회, 삽입, 삭제에서 한 단계만 어긋나도 use-after-free나 순회 손상이 즉시 발생하므로, 매크로의 전제 조건을 먼저 확인해야 합니다. 특히 RCU, 리스트 safe 순회, ID 할당 회수는 수명주기 규약과 함께 검토해야 합니다.
실무 체크포인트: 순회 중 삭제 가능성이 있으면 기본 순회 매크로 대신 safe 변형을 우선 검토하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
container_of | 멤버 포인터에서 상위 구조체 복원 | 잘못된 member 지정 | list_entry |
ARRAY_SIZE | 정적 배열 길이 계산 | 포인터에 사용 | sizeof |
list_add | 이중 연결 리스트 삽입 | 초기화 없는 노드 삽입 | list_add_tail |
list_del | 리스트 노드 제거 | 다시 순회 시 재사용 | list_del_init |
list_for_each_entry | 타입 안전 리스트 순회 | 순회 중 삭제 | list_for_each_entry_safe |
hlist_add_head | 해시 버킷 헤드 삽입 | pprev 의미 오해 | hlist_del |
xa_store | XArray 저장 | 락 컨텍스트 누락 | xa_load |
xa_load | XArray 조회 | RCU 규약 누락 | xa_for_each |
idr_alloc | IDR 기반 ID 할당 | 해제 누락 | ida_alloc |
idr_remove | IDR ID 회수 | 중복 제거 | ida_free |
핵심 | 락/동기화
락 선택은 동시성 제어와 지연 특성을 함께 결정하는 아키텍처 선택입니다. 이 표는 락의 이름을 외우기보다 호출 문맥, 임계영역 길이, sleep 가능 여부를 먼저 고정하기 위한 기준입니다. 락 자체보다 락 순서와 해제 경로 대칭성을 함께 확인해야 교착과 경합 회귀를 줄일 수 있습니다.
실무 체크포인트: 새 락을 도입할 때 기존 락과의 획득 순서를 문서화하고 lockdep 경고 가능성을 사전에 점검하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
mutex_lock | sleep 가능한 상호배제 | IRQ 문맥에서 호출 | mutex_unlock |
spin_lock | 짧은 atomic 보호 | 락 상태에서 sleep | spin_unlock |
spin_lock_irqsave | 인터럽트 경쟁 동시 보호 | flags 복원 누락 | spin_unlock_irqrestore |
rwlock_t | 읽기 많고 쓰기 적은 경로 | 기아 상태 무시 | rw_semaphore |
down_read | rw_semaphore 읽기 락 | 락 순서 역전 | up_read |
down_write | rw_semaphore 쓰기 락 | 긴 임계영역 | up_write |
rcu_read_lock | 락리스 읽기 보호 | sleep 호출 | rcu_read_unlock |
synchronize_rcu | RCU grace period 대기 | 핫패스에서 사용 | call_rcu |
seqlock_t | 짧은 쓰기, 긴 읽기 | 읽기 재시도 누락 | seqcount_t |
completion | 일회성 이벤트 동기화 | 재초기화 누락 | wait_event |
핵심 | 원자 연산/배리어
원자 연산과 배리어는 "동작한다"가 아니라 "가시성 계약이 맞다"를 검증해야 하는 영역입니다. `READ_ONCE`/`WRITE_ONCE`는 컴파일러 최적화 제어이고, 순서 보장은 acquire/release 또는 메모리 배리어가 담당한다는 역할 분리가 핵심입니다. 락리스 코드에서는 재시도 루프와 수명주기 조건을 반드시 함께 설계해야 합니다.
실무 체크포인트: 락리스 변경에는 쌍이 되는 acquire/release 지점을 코드 리뷰 항목으로 고정하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
atomic_read | atomic_t 값 조회 | 동기화 의미 과대평가 | READ_ONCE |
atomic_set | atomic_t 값 설정 | 초기화 이후 경쟁 고려 누락 | WRITE_ONCE |
atomic_inc | 카운터 증가 | 오버플로 무시 | refcount_inc |
atomic_dec_and_test | 0 도달 판단 | 수명주기와 분리 사용 | refcount_dec_and_test |
refcount_inc_not_zero | UAF 방지 참조 획득 | 반환값 무시 | kref_get_unless_zero |
READ_ONCE | 컴파일러 재정렬 억제 | 배리어 대체로 오해 | smp_load_acquire |
WRITE_ONCE | 단일 저장 보장 | 게시 순서 보장으로 오해 | smp_store_release |
smp_mb | 양방향 메모리 장벽 | 불필요한 과사용 | smp_rmb, smp_wmb |
smp_load_acquire | 획득 의미 읽기 | 쌍 API 누락 | smp_store_release |
cmpxchg | 락리스 CAS 갱신 | 루프 재시도 누락 | try_cmpxchg |
핵심 | 스케줄링/대기
대기와 스케줄링 API는 CPU 사용률보다 깨어남 조건의 정확성이 먼저입니다. 대기 큐, 타임아웃, 인터럽트 가능 여부를 혼동하면 간헐적 행과 응답 지연이 발생합니다. 이 섹션은 "어떻게 기다릴지"보다 "누가 어떤 조건에서 깨우는지"를 먼저 고정하는 용도로 읽어야 합니다.
실무 체크포인트: wait 계열 호출에는 대응되는 wake 경로와 종료 조건을 같은 패치 안에서 함께 제시하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
schedule | 명시적 스케줄 포인트 | 락 보유 상태 호출 | cond_resched |
cond_resched | 긴 루프에서 자발 양보 | atomic 문맥에서 사용 | might_resched |
msleep | 밀리초 단위 sleep | 정밀 타이밍 기대 | usleep_range |
usleep_range | 마이크로초 단위 sleep | 범위 과도하게 좁힘 | fsleep |
wait_event | 조건 대기 | 조건식에 락 규약 누락 | wake_up |
wait_event_interruptible | 시그널 중단 가능한 대기 | 반환값 무시 | wait_event_killable |
wake_up | 대기 큐 깨우기 | 상태 변경 전에 호출 | wake_up_interruptible |
kthread_run | 커널 스레드 생성+실행 | 정지 경로 누락 | kthread_create |
kthread_should_stop | 스레드 종료 조건 확인 | 루프에 미반영 | kthread_stop |
kthread_stop | 스레드 종료 요청+join | 자기 자신에서 호출 | complete_and_exit |
핵심 | 모듈/디바이스 수명주기
모듈/디바이스 수명주기 API는 등록 성공보다 정리 순서의 일관성이 중요합니다. probe/remove, init/exit, get/put 경계가 어긋나면 언로드 실패와 참조 누수가 장기적으로 누적됩니다. 표를 볼 때는 "생성 경로"와 "실패 경로"를 항상 쌍으로 확인해야 합니다.
실무 체크포인트: `devm_*` 사용 범위를 먼저 결정하고 수동 해제 API와 혼용하지 않도록 경계를 명확히 하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
module_init | 모듈 진입점 등록 | 오류 unwind 누락 | module_exit |
module_exit | 모듈 정리 경로 등록 | 등록 해제 순서 역전 | __exit |
MODULE_LICENSE | 라이선스 선언 | 누락으로 taint 발생 | MODULE_AUTHOR |
module_param | 모듈 파라미터 노출 | 권한 설정 부정확 | module_param_named |
devm_kzalloc | 디바이스 관리형 메모리 | 수동 해제 혼용 | kzalloc |
devm_request_irq | 관리형 IRQ 등록 | 핸들러 공유 플래그 누락 | request_irq |
platform_get_resource | 리소스 조회 | 존재 가정 | devm_platform_ioremap_resource |
devm_ioremap_resource | MMIO 매핑+검증 | ERR_PTR 검사 누락 | ioremap |
request_irq | IRQ 핸들러 등록 | free_irq 누락 | devm_request_irq |
free_irq | IRQ 해제 | 잘못된 dev_id 전달 | synchronize_irq |
핵심 | 타이머/워크큐
타이머와 워크큐는 비동기 실행의 편의성만큼 종료 시점 관리가 어렵습니다. 스케줄링 시점보다 취소, 동기 삭제, flush 순서를 먼저 설계해야 use-after-free를 예방할 수 있습니다. 특히 자기 워커 문맥에서 동기 취소 호출 여부를 반드시 검토해야 합니다.
실무 체크포인트: 객체 해제 전 `del_timer_sync`/`cancel_*_sync`/`flush_*` 순서를 문서로 고정하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
timer_setup | 타이머 초기화 | 컨텍스트 제약 무시 | hrtimer_init |
mod_timer | 타이머 재스케줄 | jiffies 변환 누락 | add_timer |
del_timer_sync | 안전한 타이머 제거 | 락 순서 역전 | timer_shutdown_sync |
INIT_WORK | work_struct 초기화 | 스택 객체 사용 | INIT_DELAYED_WORK |
schedule_work | 기본 워크큐 비동기 실행 | 중복 스케줄 오해 | queue_work |
queue_work | 특정 워크큐 실행 | 큐 수명주기 미관리 | flush_workqueue |
queue_delayed_work | 지연 실행 | 취소 경로 누락 | mod_delayed_work |
cancel_work_sync | 워크 취소+완료 대기 | 자기 워커에서 호출 | flush_work |
flush_workqueue | 큐 내 작업 drain | 호출 빈도 과다 | drain_workqueue |
alloc_workqueue | 전용 워크큐 생성 | 플래그 선택 부정확 | system_wq |
핵심 | 디버깅/트레이싱
디버깅 API는 문제를 빨리 찾기 위한 도구이지만 잘못 쓰면 시스템 자체를 불안정하게 만듭니다. 임시 추적, 상시 관측, 운영 경보를 목적별로 분리하고 오버헤드 상한을 먼저 정해야 합니다. 이 표는 계측 정확도와 운영 안전성의 균형을 맞추는 기준으로 사용해야 합니다.
실무 체크포인트: `trace_printk` 같은 임시 도구는 리뷰 단계에서 반드시 제거 여부를 체크리스트로 확인하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
dump_stack | 즉시 호출 경로 확인 | 핫패스 상시 호출 | WARN_ON |
trace_printk | ftrace 버퍼 임시 추적 | 운영 코드 잔존 | trace_event |
tracepoint | 구조화된 이벤트 관측 | 필드 ABI 무시 | ftrace |
DEFINE_DYNAMIC_DEBUG_METADATA | 동적 디버그 제어 | 정적 로그와 혼용 | pr_debug |
lockdep_assert_held | 락 보유 검증 | 락 클래스 오인 | might_lock |
CONFIG_KASAN 계열 | 메모리 오류 탐지 계측 | 운영 커널에 무분별 적용 | KFENCE |
ftrace_set_filter | 함수 추적 범위 축소 | 전체 추적으로 오버헤드 증가 | set_ftrace_notrace |
register_trace_* | tracepoint 핸들러 등록 | unregister 누락 | tracepoint_probe_register |
dynamic_pr_debug | 런타임 디버그 토글 | 포맷 문자열 불일치 | pr_debug |
panic | 복구 불가 오류 처리 | 복구 가능 에러에서 사용 | BUG, WARN |
핵심 | 심볼 Export/네임스페이스
심볼 export는 코드 재사용 편의가 아니라 모듈 간 ABI 계약을 외부에 공개하는 행위입니다. 한 번 노출된 인터페이스는 유지 비용이 커지므로, 최소 공개 원칙과 네임스페이스 정책을 동시에 적용해야 합니다. 이 표는 "export 가능 여부"보다 "내부화 가능성"을 먼저 검토하는 흐름으로 읽어야 합니다.
실무 체크포인트: 새 export에는 사용 모듈, 제거 불가능성, 네임스페이스 선택 근거를 커밋 메시지에 함께 남기세요.
kallsyms, CRC, GPL/namespace 정책, 장애 대응 순서를 포함한 독립 가이드입니다.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
EXPORT_SYMBOL | 모듈 공용 API 공개 | 내부 구현까지 공개 | EXPORT_SYMBOL_GPL |
EXPORT_SYMBOL_GPL | GPL 전용 API 공개 | 라이선스 영향 미고려 | EXPORT_SYMBOL |
EXPORT_SYMBOL_NS | 네임스페이스 포함 export | import 누락 | MODULE_IMPORT_NS |
EXPORT_SYMBOL_NS_GPL | GPL+네임스페이스 export | 네임 충돌 방치 | EXPORT_SYMBOL_NS |
MODULE_IMPORT_NS | 외부 네임스페이스 사용 선언 | 문자열 오타 | depends 관리 |
symbol_get | 선택적 심볼 참조 | put 누락 | symbol_put |
symbol_put | symbol_get 참조 반환 | 중복 호출 | try_module_get |
THIS_MODULE | 모듈 참조 카운트 연계 | NULL 가정 | module_put |
try_module_get | 모듈 언로드 방지 참조 획득 | 실패 경로 무시 | module_put |
module_put | 모듈 참조 반납 | 언밸런스 카운트 | try_module_get |
핵심 | 버전/호환성 보조 매크로
호환성 매크로는 분기문 추가 도구가 아니라 유지보수 비용 제어 장치입니다. 버전 비교, Kconfig 조건, 컴파일 타임 검증을 혼용하면 백포트와 장기 유지에서 오류가 누적됩니다. 이 표는 런타임 분기보다 빌드 타임 계약을 우선 고정하는 기준으로 사용해야 합니다.
실무 체크포인트: 버전 조건을 추가할 때는 제거 시점과 대상 안정 커널 범위를 주석에 함께 기록하세요.
| 항목 | 언제 사용 | 흔한 실수 | 대체/짝 API |
|---|---|---|---|
KERNEL_VERSION | 버전 비교 상수 생성 | 런타임 검사와 혼용 | LINUX_VERSION_CODE |
LINUX_VERSION_CODE | 빌드 대상 커널 버전 조건 | 백포트 정책 무시 | IS_ENABLED |
IS_ENABLED | Kconfig y/m 조건 분기 | #ifdef 과잉 사용 | IS_BUILTIN |
IS_BUILTIN | built-in 여부 분기 | 모듈 경로 무시 | IS_MODULE |
IS_MODULE | 모듈 빌드 조건 분기 | 런타임 로직에 사용 | MODULE |
BUILD_BUG_ON | 컴파일 타임 불변식 검사 | 런타임 값 전달 | static_assert |
static_assert | 표준 정적 검증 | 메시지 누락 | BUILD_BUG_ON |
FIELD_PREP | 레지스터 비트필드 설정 | 마스크 불일치 | FIELD_GET |
FIELD_GET | 비트필드 추출 | 정수 폭 미스매치 | GENMASK |
GENMASK | 비트 마스크 생성 | 상하 비트 순서 실수 | BIT |
핵심 | 핵심 묶음 해설
앞의 핵심 묶음 표는 "자주 쓰는 API 리스트"가 아니라 "실수 예방 인덱스"입니다. 실무에서는 다음 세 가지 축을 동시에 맞춰야 안정적인 코드가 됩니다.
- 문맥 축 — sleep 가능 여부와 인터럽트 상태가 API 선택을 결정합니다.
- 수명 축 — 객체 생성부터 해제까지 참조/락/타이머의 종료 순서를 고정해야 합니다.
- 관측 축 — 문제를 찾기 쉬운 로그/트레이스 포인트를 처음부터 배치해야 운영 비용이 줄어듭니다.
즉, "컴파일이 된다"는 기준만으로는 충분하지 않습니다. "실패 경로가 닫혔는가", "문맥 위반이 없는가", "운영 중 원인 추적이 가능한가"까지 포함해 API를 선택해야 합니다.
핵심 | 도메인별 심화 예제
핵심 항목을 실제 코드 흐름으로 묶는 예제입니다. 아래 3개 패턴은 드라이버 리뷰에서 자주 보는 전형적인 형태입니다.
예제 1: probe 경로 (devm + ERR_PTR + 로그)
static int demo_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct demo_dev *d;
int irq, ret;
d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(dev, irq, demo_irq, 0, "demo", d);
if (ret)
return ret;
dev_info(dev, "demo probe complete\\n");
return 0;
}
예제 2: 락리스 플래그 전달 (READ_ONCE/WRITE_ONCE)
static bool stop_flag;
static int worker_thread(void *arg)
{
while (!READ_ONCE(stop_flag)) {
do_one_job();
cond_resched();
}
return 0;
}
static void stop_worker(void)
{
WRITE_ONCE(stop_flag, true);
}
예제 3: 심볼 export + 네임스페이스
int demo_calc_crc(const void *buf, size_t len)
{
if (!buf || !len)
return -EINVAL;
return crc32_le(~0U, buf, len);
}
EXPORT_SYMBOL_NS_GPL(demo_calc_crc, DEMO_CORE);
/* 소비 모듈 */
MODULE_IMPORT_NS(DEMO_CORE);
확장 | 컨텍스트별 API 선택 매트릭스
같은 함수라도 실행 문맥이 다르면 안전성이 달라집니다. 아래 표는 코드 리뷰에서 가장 먼저 확인해야 하는 "문맥-API 적합성" 규칙입니다.
| 문맥 | 사용 가능 | 주의 필요 | 금지 |
|---|---|---|---|
| 프로세스 문맥 (sleep 가능) | mutex_lock, kmalloc(GFP_KERNEL), copy_from_user | might_sleep로 경계 점검 | 해당 없음 |
| 소프트IRQ | spin_lock_bh, kmalloc(GFP_ATOMIC) | NAPI 경합 구간 최소화 | mutex_lock, msleep |
| 하드IRQ | spin_lock_irqsave, this_cpu_inc | 핸들러는 짧게 유지 | copy_to_user, schedule |
| 타이머 콜백 | queue_work, mod_timer | 공유 데이터 락 보호 | mutex_lock, usleep_range |
| RCU read-side | rcu_dereference, READ_ONCE | 포인터 수명 보장 | 블로킹 API 전반 |
| 워크큐 핸들러 | mutex_lock, flush_work, kvzalloc | 장시간 작업 분할 | IRQ 전용 가정 코드 |
| kthread 루프 | wait_event_interruptible, kthread_should_stop | 종료 경로 보장 | 무한 busy loop |
| atomic section | spin_lock, atomic_inc | 임계영역 최소화 | GFP_KERNEL, 모든 sleep API |
확장 | GFP 플래그 실전 카탈로그
메모리 할당 실패의 절반은 플래그 선택 오류에서 시작됩니다. 아래는 실무에서 반복되는 결정표입니다.
| 플래그 | 의미 | 대표 사용처 | 자주 나는 실수 |
|---|---|---|---|
GFP_KERNEL | sleep 허용, 기본 reclaim | probe/remove, 파일시스템 일반 경로 | IRQ 문맥에서 사용 |
GFP_ATOMIC | sleep 금지, 비상 풀 사용 | IRQ/softirq 핸들러 | 대형 버퍼 반복 할당 |
GFP_NOWAIT | 즉시 실패, reclaim 최소화 | 지연 민감 경로 | 실패 경로 설계 누락 |
GFP_NOIO | IO 재진입 방지 | 블록 계층 reclaim 경로 | 일반 경로에서 남용 |
GFP_NOFS | 파일시스템 재진입 방지 | FS 락 보유 경로 | 필요 없는 전역 적용 |
__GFP_ZERO | 0 초기화 | 민감 구조체 초기값 보장 | 성능 경로에서 불필요 사용 |
__GFP_HIGH | 우선순위 높은 할당 | 핵심 네트워크 버퍼 | 일반 경로 확장 남용 |
__GFP_NOWARN | 경고 로그 억제 | 예상 가능한 실패 경로 | 원인 추적 포인트 삭제 |
__GFP_NORETRY | 강한 reclaim 회피 | 실패 허용 가능한 캐시 | 필수 경로에서 사용 |
__GFP_RETRY_MAYFAIL | 재시도 후 실패 허용 | 큰 버퍼, 희소 경로 | 무조건 성공으로 가정 |
__GFP_COMP | 복합 페이지 표시 | hugepage 관련 버퍼 | 해제 API 혼동 |
__GFP_DMA32 | DMA32 존 요구 | 32비트 DMA 장치 | dma_mask 설정 없이 사용 |
/* 컨텍스트별 API 사용 예시 비교 */
/* 1. 프로세스 문맥 — sleep 가능 */
mutex_lock(&dev->lock);
buf = kmalloc(size, GFP_KERNEL);
if (!buf) { mutex_unlock(&dev->lock); return -ENOMEM; }
mutex_unlock(&dev->lock);
/* 2. 하드IRQ — sleep 금지, 최소 작업 */
static irqreturn_t demo_isr(int irq, void *data)
{
struct demo_dev *d = data;
u32 status = readl(d->regs + REG_STATUS);
if (!(status & IRQ_PENDING))
return IRQ_NONE;
writel(status, d->regs + REG_ACK);
this_cpu_inc(d->stats->irqs);
queue_work(d->wq, &d->rx_work); /* 무거운 처리는 워크큐로 */
return IRQ_HANDLED;
}
/* 3. 소프트IRQ (NAPI) — sleep 금지, atomic 할당만 */
spin_lock(&ring->lock);
skb = netdev_alloc_skb(ndev, len); /* GFP_ATOMIC 내부 */
spin_unlock(&ring->lock);
확장 | 락 선택 매트릭스
락 선택 매트릭스는 성능 최적화 표가 아니라 실패 모드 비교표입니다. 각 락의 장점보다 우선해서, 어떤 워크로드에서 기아·경합·지연이 발생하는지를 먼저 확인해야 합니다. 선택 이후에는 락 교체 기준과 관측 지표를 함께 정의해야 회귀를 빠르게 감지할 수 있습니다.
실무 체크포인트: 락 변경 패치에는 최소 하나의 경합 지표와 회귀 기준을 함께 첨부하세요.
| 락/기법 | 강점 | 약점 | 적합한 워크로드 | 대체안 |
|---|---|---|---|---|
mutex | 단순, 디버깅 용이 | sleep 불가 문맥 미사용 | 드라이버 제어 경로 | ww_mutex |
spinlock | atomic 경로 보호 | 오래 잡으면 지연 증가 | IRQ 공유 자료 | raw_spinlock |
rw_semaphore | 읽기 병렬성 | 쓰기 기아 가능성 | 읽기 우세 메타데이터 | seqlock |
seqlock/seqcount | 읽기 경로 빠름 | 재시도 루프 필요 | 짧은 스냅샷 데이터 | RCU |
RCU | 락리스 읽기 | 수명주기 설계 복잡 | 읽기 압도적 워크로드 | srcu |
SRCU | sleep 가능한 read-side | 오버헤드 큼 | 긴 콜백 기반 경로 | mutex |
percpu_rwsem | 읽기 스케일링 우수 | 쓰기 비용 큼 | CPU 로컬 fast path | rw_semaphore |
completion | 이벤트 동기화 간결 | 다회성 설계에 약함 | 초기화 완료 신호 | wait_event |
waitqueue | 조건 대기 표현력 | 조건/락 연동 실수 | 상태 기반 대기 | completion |
atomic/refcount | 락 없이 카운팅 | 복합 상태 보호 불가 | 참조 카운트 | kref |
확장 | 반환값 계약 카탈로그
| 패턴 | 성공 값 | 실패 값 | 검사 방식 | 대표 API |
|---|---|---|---|---|
| 정수 errno | 0 또는 양수 | 음수 errno | if (ret) | request_irq, clk_prepare_enable |
| 포인터+ERR_PTR | 유효 포인터 | ERR_PTR(-Exxx) | IS_ERR/PTR_ERR | devm_ioremap_resource |
| 포인터+NULL | 유효 포인터 | NULL | if (!ptr) | kmalloc 일부 래퍼 |
| 길이 반환 | 처리 바이트 수 | 음수 errno | ret < 0 | read/write 계열 |
| bool 반환 | true/false | 별도 없음 | 의미 문서 확인 | kthread_should_stop |
| 0/1 의미 | 1 성공 | 0 실패 | API별 주석 확인 | try_module_get |
실무 체크포인트: 포인터 반환 API는 함수 주석에 NULL 또는 ERR_PTR 규약을 명시하고 시작하세요.
if (!ptr)로 처리하면 ERR_PTR를 놓칩니다.
호출 전 해당 API가 NULL 계열인지 ERR_PTR 계열인지 먼저 문서화하세요.
확장 | 비차단 경로 전용 API
비차단 경로는 평균 성능보다 최악 지연을 제한하는 것이 우선 목표입니다. 따라서 "성공률을 높이는 API"보다 "실패를 빠르게 표면화하는 API"를 선택해야 합니다. 표의 각 항목은 대체 경로 설계를 포함해야 의미가 있으므로 fallback 없는 사용은 금지에 가깝게 다뤄야 합니다.
실무 체크포인트: non-blocking 경로에는 실패 시 defer할 큐 또는 재시도 정책을 코드로 명시하세요.
| 분류 | 권장 API | 피해야 할 API | 비고 |
|---|---|---|---|
| 메모리 | kmalloc(GFP_ATOMIC) | kvzalloc(GFP_KERNEL) | 실패 대비 fallback 필수 |
| 락 | spin_trylock | mutex_lock | 실패 시 지연 큐로 이관 |
| 로그 | tracepoint, pr_debug_ratelimited | 대량 pr_info | 폭주 로그 차단 |
| 대기 | 사용 금지 원칙 | wait_event, schedule_timeout | 워크큐로 defer |
| 복사 | 사전 pin + 사후 처리 | copy_from_user 직접 호출 | 문맥별 예외 점검 |
확장 | 핵심 API 소스 경로 맵
소스 경로 맵은 API 사용법보다 구현 근거를 빠르게 찾기 위한 역추적 인덱스입니다. 리뷰에서 의견이 갈리면 표의 헤더와 구현 경로를 동시에 열어 계약과 실제 동작을 교차 확인해야 합니다. 특히 인라인 헤더 매크로는 호출부만 보면 의미가 왜곡되기 쉬워 원본 정의 확인이 필수입니다.
실무 체크포인트: 동작 논쟁이 발생하면 헤더 선언과 구현 경로를 함께 링크해 근거 기반으로 결론을 내리세요.
| 주제 | 주요 헤더 | 핵심 구현 경로 | 리뷰 시 확인 포인트 |
|---|---|---|---|
| 에러 포인터 | include/linux/err.h | include/linux/err.h | IS_ERR 누락 여부 |
| 메모리 할당 | include/linux/slab.h | mm/slub.c, mm/page_alloc.c | GFP 플래그 적합성 |
| 리스트 | include/linux/list.h | 헤더 인라인 중심 | 순회 중 삭제 안전성 |
| 락 | include/linux/spinlock.h | kernel/locking/* | 락 순서/문맥 |
| RCU | include/linux/rcupdate.h | kernel/rcu/* | grace period 비용 |
| 워크큐 | include/linux/workqueue.h | kernel/workqueue.c | 취소/flush 경로 |
| 타이머 | include/linux/timer.h | kernel/time/timer.c | 동기 삭제 필요성 |
| 스케줄링 | include/linux/sched.h | kernel/sched/* | sleep 가능성 |
| 모듈 | include/linux/module.h | kernel/module/* | export 범위 최소화 |
| 트레이싱 | include/linux/tracepoint.h | kernel/trace/* | 이벤트 ABI 안정성 |
확장 | 고밀도 실전 스니펫
고밀도 스니펫은 복붙 예제가 아니라 실패 경로 템플릿을 압축한 참고 코드입니다. 각 스니펫은 정상 경로보다 unwind, 참조 반납, 동기화 종료 조건을 보여주는 데 목적이 있습니다. 적용 시에는 변수명보다 제어 흐름과 정리 순서를 우선 이식해야 합니다.
실무 체크포인트: 스니펫 적용 후에는 의도적으로 중간 실패를 주입해 unwind 경로가 닫히는지 먼저 검증하세요.
스니펫 1: 단계별 unwind 템플릿
static int demo_open(struct inode *inode, struct file *filp)
{
struct demo_ctx *ctx;
int ret;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->lock);
ret = alloc_ring(ctx);
if (ret)
goto err_destroy_lock;
ret = register_fastpath(ctx);
if (ret)
goto err_free_ring;
filp->private_data = ctx;
return 0;
err_free_ring:
free_ring(ctx);
err_destroy_lock:
mutex_destroy(&ctx->lock);
err_free_ctx:
kfree(ctx);
return ret;
}
스니펫 2: RCU 리스트 갱신 패턴
void demo_insert(struct demo_node *n)
{
spin_lock(&demo_lock);
list_add_rcu(&n->link, &demo_head);
spin_unlock(&demo_lock);
}
struct demo_node *demo_find(int id)
{
struct demo_node *pos, *ret = NULL;
rcu_read_lock();
list_for_each_entry_rcu(pos, &demo_head, link) {
if (READ_ONCE(pos->id) == id) {
/* 예제 가정: demo_node는 refcount_t refcnt를 가진다 */
if (refcount_inc_not_zero(&pos->refcnt))
ret = pos;
break;
}
}
rcu_read_unlock();
return ret;
}
확장 | 코드 리뷰 규칙
아래 규칙은 스타일 가이드가 아니라 장애 예방을 위한 최소 수용 기준입니다. 규칙 간 우선순위는 문맥 안전성, 수명주기 완결성, 관측 가능성 순서로 해석해야 합니다. 규칙 위반이 불가피한 경우에는 예외 근거와 복구 계획을 패치 설명에 반드시 포함해야 합니다.
실무 체크포인트: 리뷰 단계에서 규칙 위반 항목은 "후속 수정"으로 미루지 말고 같은 변경 집합에서 닫으세요.
- 규칙 1 — 포인터 반환 함수는 선언 바로 위 주석에 반환 규약(
NULL/ERR_PTR)을 고정합니다. - 규칙 2 — 락 획득/해제는 함수 단위가 아니라 경로 단위로 표로 정리합니다.
- 규칙 3 — IRQ 경로는 100줄을 넘기지 않고, 복잡 로직은 워크큐로 이관합니다.
- 규칙 4 —
copy_from_user반환값을 무시하는 코드는 즉시 수정 대상으로 분류합니다. - 규칙 5 —
devm_*와 수동 해제를 한 함수에서 혼용하지 않습니다. - 규칙 6 — 신규 export 심볼에는 "왜 export가 필요한지"를 커밋 메시지에 명시합니다.
- 규칙 7 —
likely/unlikely는 프로파일링 근거가 있을 때만 허용합니다. - 규칙 8 —
WARN_ON는 운영 경보 노이즈를 고려해 한정적으로 사용합니다. - 규칙 9 — 타임아웃 상수는 매직넘버 대신 의미 있는 이름으로 분리합니다.
- 규칙 10 — 로그 메시지는 "무엇이 실패했는지 + 식별자 + errno"를 함께 출력합니다.
- 규칙 11 — 메모리 해제는 정상 경로와 실패 경로 모두를 단위 테스트 시나리오로 검증합니다.
- 규칙 12 — 트레이스 포인트 추가 시 이벤트 필드 ABI 안정성을 검토합니다.
확장 | 문제 유형별 즉시 대응 인덱스
문제 대응 인덱스는 원인 추정을 빠르게 좁히기 위한 초기 분류표입니다. 증상과 의심 지점을 1:1로 고정하지 말고, 표를 시작점으로 삼아 로그·트레이스·락 상태를 교차 확인해야 합니다. 특히 간헐 장애는 단일 도구 결과로 결론 내리지 않는 것이 중요합니다.
실무 체크포인트: 1차 대응에서 최소 두 가지 독립 증거를 확보한 뒤 수정 방향을 결정하세요.
| 증상 | 1차 의심 지점 | 확인 명령/도구 | 우선 확인 API |
|---|---|---|---|
| Unknown symbol | 모듈 의존성/라이선스 | modinfo, cat /proc/kallsyms | EXPORT_SYMBOL_GPL, MODULE_LICENSE |
| 슬립 경고 (sleeping in atomic) | 문맥 오용 | dmesg, lockdep | mutex_lock, might_sleep |
| 간헐적 UAF | 참조 카운트/RCU 수명 | KASAN, KFENCE | refcount_inc_not_zero, kfree_rcu |
| IRQ 폭주 | ack/마스킹 누락 | /proc/interrupts | disable_irq_nosync, napi_schedule |
| 메모리 누수 | 실패 경로 해제 누락 | kmemleak | kfree, kvfree, devm_* |
| 데드락 | 락 순서 역전 | lockdep graph | spin_lock_irqsave, mutex_lock |
| 성능 회귀 | 핫패스 로그/락 경합 | perf, ftrace | pr_debug, rcu_dereference |
확장 | 네트워킹 카탈로그
네트워킹 경로는 패킷당 수백 ns 단위 최적화가 필요하므로, API 선택 기준을 명확히 고정해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| skb 수명 | alloc_skb | 패킷 버퍼 생성 | 헤드룸 부족 | netdev_alloc_skb |
| skb 수명 | consume_skb | 참조 카운트 기반 해제 | kfree_skb와 혼용 | dev_kfree_skb_any |
| skb 수명 | skb_clone | 헤더 공유 복제 | 쓰기 전 skb_cow 누락 | pskb_copy |
| 헤더 조작 | skb_pull | 헤더 제거 | 길이 검사 누락 | pskb_may_pull |
| 헤더 조작 | skb_put | 테일 확장 | tailroom 오버런 | skb_tailroom |
| NAPI | napi_schedule | poll 트리거 | 인터럽트 마스킹 누락 | __napi_schedule_irqoff |
| NAPI | napi_complete_done | poll 종료 | work_done 불일치 | napi_gro_receive |
| RX 경로 | netif_receive_skb | 일반 수신 경로 진입 | softirq 문맥 혼동 | netif_rx |
| TX 경로 | dev_queue_xmit | L2 송신 | 락 보유 상태로 호출 | sch_direct_xmit |
| 큐 제어 | netif_stop_queue | TX 큐 중지 | wake 누락 | netif_wake_queue |
| 큐 제어 | netif_tx_stop_queue | 멀티큐 stop | 큐 인덱스 오류 | netif_tx_wake_queue |
| 해시 | skb_get_hash | flow 분산 | seed 가정 오류 | skb_set_hash |
| 체크섬 | skb_checksum_help | SW checksum 보조 | 오프로드 플래그 불일치 | csum_partial |
| 오프로드 | skb_is_gso | GSO 여부 판별 | 세그먼트 크기 무시 | skb_gso_segment |
| XDP | bpf_prog_run_xdp | XDP 프로그램 실행 | 리턴 코드 처리 누락 | xdp_do_redirect |
| XDP | xdp_return_frame | XDP 프레임 반환 | page_pool 연계 누락 | page_pool_put_page |
| 소켓 | sock_alloc_send_pskb | 소켓 송신 버퍼 생성 | GFP 문맥 부적합 | sk_stream_alloc_skb |
| 타이머 | sk_reset_timer | 소켓 타이머 갱신 | 락 순서 역전 | inet_csk_reset_xmit_timer |
| 경로탐색 | ip_route_output_key_hash | IPv4 라우팅 | namespace 누락 | ip6_dst_lookup_flow |
| conntrack | nf_conntrack_find_get | CT 조회 | 참조 반납 누락 | nf_ct_put |
/* NAPI poll 최소 패턴 */
static int demo_poll(struct napi_struct *napi, int budget)
{
int work_done = 0;
while (work_done < budget && rx_has_packet()) {
struct sk_buff *skb = build_skb(rx_pop(), 0);
if (!skb)
break;
napi_gro_receive(napi, skb);
work_done++;
}
if (work_done < budget) {
napi_complete_done(napi, work_done);
enable_irq(demo_irq);
}
return work_done;
}
확장 | 파일시스템 카탈로그
파일시스템 코드는 락 계층, 페이지 캐시, writeback 경계가 복잡하므로 VFS 진입점과 address_space 연계를 함께 확인해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| inode | new_inode | 새 inode 할당 | 초기 필드 누락 | alloc_inode_sb |
| inode | iget_locked | inode 캐시 조회/생성 | unlock_new_inode 누락 | ilookup |
| inode | iput | inode 참조 반납 | 이중 iput | ihold |
| dentry | d_make_root | 루트 dentry 생성 | 에러 처리 누락 | d_obtain_root |
| dentry | d_add | lookup 결과 연결 | 음수 dentry 처리 미흡 | d_splice_alias |
| 슈퍼블록 | mount_bdev | 블록 기반 마운트 | kill_sb 누락 | mount_nodev |
| 파일 연산 | generic_file_read_iter | 기본 read_iter 구현 | i_size 경계 처리 누락 | filemap_read |
| 파일 연산 | generic_file_write_iter | 기본 write_iter 구현 | 락 순서 역전 | iomap_file_buffered_write |
| 페이지 캐시 | filemap_get_folio | folio 조회/생성 | folio 잠금 규약 위반 | filemap_grab_folio |
| 페이지 캐시 | folio_mark_dirty | dirty 설정 | writeback 태깅 누락 | set_page_dirty |
| writeback | filemap_fdatawrite | dirty 페이지 flush | 에러 전파 누락 | sync_inode_metadata |
| writeback | filemap_fdatawait | I/O 완료 대기 | timeout 설계 누락 | sync_filesystem |
| 저널링 | jbd2_journal_start | 트랜잭션 시작 | 핸들 종료 누락 | jbd2_journal_stop |
| 디렉터리 | iterate_shared | readdir 구현 | ctx pos 처리 오류 | dir_emit |
| 권한 | inode_permission | 권한 점검 | idmap 무시 | generic_permission |
| 링크 | vfs_link | 하드링크 생성 | nlink 갱신 누락 | simple_link |
| 삭제 | vfs_unlink | unlink 공통 경로 | 락 경계 위반 | simple_unlink |
| rename | vfs_rename | rename 공통 경로 | 교차 디렉터리 규칙 누락 | lock_rename |
| fsnotify | fsnotify_modify | 수정 이벤트 알림 | 이벤트 누락 | fsnotify_access |
| sync | vfs_fsync_range | 부분 fsync | 데이터/메타 경계 오해 | vfs_fsync |
/* 최소 VFS 연산자 골격 */
static const struct file_operations demo_fops = {
.owner = THIS_MODULE,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.mmap = generic_file_mmap,
.llseek = generic_file_llseek,
.fsync = generic_file_fsync,
};
확장 | 메모리 관리 카탈로그
메모리 관리 경로는 할당 정책, reclaim 압력, TLB/페이지 테이블 동기화까지 동시에 고려해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 페이지 할당 | alloc_pages | 고차 페이지 필요 | order 과대 설정 | alloc_page |
| 페이지 할당 | __get_free_pages | 연속 가상 주소 필요 | 해제 API 불일치 | free_pages |
| 페이지 해제 | __free_pages | order 기반 해제 | order mismatch | put_page |
| 매핑 | kmap_local_page | 고메모리 단기 매핑 | 언맵 누락 | kunmap_local |
| 매핑 | vmap | 페이지 배열 가상 연속 매핑 | vmalloc과 혼동 | vunmap |
| folio | folio_get | 참조 카운트 증가 | put 누락 | folio_put |
| folio | folio_lock | folio 변경 보호 | 락 순서 위반 | folio_unlock |
| MMU | flush_tlb_mm_range | 매핑 변경 후 TLB 동기화 | 범위 과대 flush | flush_tlb_page |
| VMA | vma_lookup | 주소 기반 VMA 탐색 | mmap_lock 누락 | find_vma |
| VMA | vma_modify | 속성 변경 | 충돌 영역 처리 누락 | mprotect_fixup |
| fault | handle_mm_fault | 페이지 폴트 처리 | 리턴 플래그 오해 | do_page_fault 경로 추적 |
| reclaim | shrink_node | 노드 reclaim 수행 | 스캔 비율 튜닝 누락 | balance_pgdat |
| reclaim | try_to_free_pages | 직접 reclaim | latency 급증 | wakeup_kswapd |
| 슬랩 | kmem_cache_alloc | 캐시 객체 할당 | ctor 부작용 | kmem_cache_zalloc |
| 슬랩 | kmem_cache_free | 객체 해제 | 다른 cache에 반환 | none |
| mmap | vm_mmap | 커널 내부 mmap 요청 | 권한 플래그 오류 | do_mmap |
| 핀 | pin_user_pages_fast | 빠른 GUP pin | long-term 플래그 누락 | unpin_user_pages |
| DMA | dma_alloc_coherent | 일관성 메모리 | dma_mask 미설정 | dma_map_single |
| 보안 해제 | kvfree_sensitive | 민감 데이터 해제 | 일반 kfree 사용 | memzero_explicit |
| NUMA | alloc_pages_node | 노드 지정 할당 | fallback 정책 무시 | kmalloc_node |
/* folio + writeback 최소 패턴 */
static int demo_write_folio(struct folio *folio)
{
folio_lock(folio);
folio_mark_dirty(folio);
/* 실제 디바이스 I/O 제출 생략 */
folio_unlock(folio);
return 0;
}
확장 | 스토리지 I/O 카탈로그
스토리지 경로는 bio/request/queue 경계와 완료 처리 규약이 핵심입니다. 특히 실패 경로와 타임아웃 회수 정책을 반드시 포함해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| bio 생성 | bio_alloc_bioset | block I/O 요청 생성 | bioset 수명주기 누락 | bio_init |
| bio 관리 | bio_add_page | 페이지 연결 | 길이/오프셋 경계 오류 | bio_iov_iter_get_pages |
| bio 제출 | submit_bio | 블록 계층 제출 | opf 플래그 오설정 | submit_bio_noacct |
| bio 완료 | bio_endio | 완료 콜백 종료 | 반복 호출 | blk_status_to_errno |
| request | blk_mq_alloc_request | 직접 request 확보 | timeout 정책 누락 | blk_get_request |
| request | blk_mq_start_request | 디바이스 전송 시작 | 시작/완료 순서 어긋남 | blk_mq_end_request |
| request | blk_mq_end_request | 요청 완료 처리 | 잔여 바이트 처리 누락 | blk_update_request |
| queue 제어 | blk_mq_stop_hw_queues | 하드웨어 큐 일시 중지 | 재개 누락 | blk_mq_start_stopped_hw_queues |
| timeout | blk_mq_rq_timed_out | 요청 타임아웃 핸들링 | 중복 abort | blk_abort_request |
| 태그셋 | blk_mq_alloc_tag_set | 큐 태그 자원 준비 | hctx 수 과대 설정 | blk_mq_free_tag_set |
| 큐 초기화 | blk_mq_init_sq_queue | 단일 큐 초기화 | queue limits 누락 | blk_mq_init_queue |
| 큐 제한 | blk_queue_max_hw_sectors | 최대 전송 크기 제한 | 장치 한계 초과 | blk_queue_chunk_sectors |
| flush | blkdev_issue_flush | 캐시 flush 요청 | 오류 무시 | REQ_PREFLUSH |
| discard | blkdev_issue_discard | trim/unmap | 정렬 단위 무시 | REQ_OP_DISCARD |
| readahead | page_cache_ra_unbounded | 적응형 readahead | 랜덤 I/O 경로 오남용 | ondemand_readahead |
| 직접 I/O | iomap_dio_rw | DIO 경로 공통 처리 | 정렬 검증 누락 | blockdev_direct_IO |
| 멀티패스 | blk_mq_map_queues | CPU-hctx 매핑 | NUMA 불균형 | set->map 튜닝 |
| 통계 | part_stat_add | 디바이스 통계 갱신 | 완료 경로 누락 | blk_account_io_done |
| 폴링 | blk_poll | busy polling 완료 확인 | CPU 소모 과다 | io_uring IOPOLL |
| 오류 변환 | blk_status_to_errno | blk_status_t를 errno 변환 | 직접 숫자 매핑 | errno_to_blk_status |
/* bio 제출 최소 패턴 */
static void demo_submit_read(struct block_device *bdev,
sector_t sector, struct page *page)
{
struct bio *bio = bio_alloc(bdev, 1, REQ_OP_READ, GFP_KERNEL);
bio->bi_iter.bi_sector = sector;
__bio_add_page(bio, page, PAGE_SIZE, 0);
bio->bi_end_io = demo_bio_done;
submit_bio(bio);
}
static void demo_bio_done(struct bio *bio)
{
if (bio->bi_status)
pr_err("bio error: %d\n",
blk_status_to_errno(bio->bi_status));
bio_put(bio);
}
확장 | 보안/LSM 카탈로그
보안 경로는 훅 호출 순서, cred 수명주기, 정책 캐시 일관성을 동시에 고려해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| cred 조회 | current_cred | 현재 태스크 cred 참조 | 수정 가능한 포인터로 오해 | get_current_cred |
| cred 획득 | prepare_creds | 권한 변경 사본 생성 | abort 누락 | commit_creds |
| cred 반영 | commit_creds | 새 cred 적용 | 검증 없이 적용 | override_creds |
| 임시 권한 | override_creds | 권한 위임 구간 | revert 누락 | revert_creds |
| capability | capable | 전역 CAP 검사 | ns 컨텍스트 누락 | ns_capable |
| LSM 훅 | security_file_open | file open 정책 검사 | 반환값 무시 | security_inode_permission |
| LSM 훅 | security_bprm_check | exec 검증 | 중복 검사 | security_bprm_creds_for_exec |
| inode 정책 | inode_permission | 접근 권한 검사 | MAY_* 플래그 누락 | security_inode_permission |
| seccomp | seccomp_mode | seccomp 상태 확인 | 필터 우회 가정 | secure_computing |
| audit | audit_log_start | 감사 로그 시작 | 메모리 실패 경로 누락 | audit_log_end |
| IMA | ima_file_check | 파일 무결성 검사 | 정책 비활성 가정 | evm_verifyxattr |
| 키 관리 | request_key | 커널 keyring 조회 | 권한 도메인 오해 | key_lookup |
| 하드닝 | CONFIG_FORTIFY_SOURCE | 버퍼 경계 검증 강화 | 경고 무시 | FORTIFY 경고 수정 |
| 메모리 보호 | set_memory_ro | 페이지 읽기전용 전환 | TLB 동기화 누락 | set_memory_rw |
| 사용자 복사 | copy_struct_from_user | 확장 가능한 ABI 복사 | size 호환성 누락 | copy_from_user |
| 랜덤 | get_random_bytes | 커널 난수 획득 | 초기화 전 사용 | get_random_u32 |
| 스택 검증 | check_copy_size | 복사 크기 검증 | 직접 memcpy 사용 | copy_to_user |
| 로그 | pr_warn_ratelimited | 보안 이벤트 경고 | 로그 폭주 | audit_log* |
| 정책 질의 | security_locked_down | lockdown 제약 검사 | 에러 전파 누락 | kernel_is_locked_down |
| 레이블 | security_secid_to_secctx | secid->문자열 변환 | free 누락 | security_release_secctx |
/* cred 변경 안전 패턴 */
static int demo_elevate(void)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
/* capability 추가 검증 */
if (!ns_capable(new->user_ns, CAP_SYS_ADMIN)) {
abort_creds(new);
return -EPERM;
}
commit_creds(new);
return 0;
}
확장 | 가상화/KVM 카탈로그
KVM 경로는 VM-Exit 처리 비용, vCPU 동기화, 메모리 슬롯 일관성이 성능과 안정성을 동시에 좌우합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| VM 생성 | kvm_create_vm | VM 인스턴스 생성 | 아키텍처 init 누락 | kvm_arch_init_vm |
| vCPU 생성 | kvm_vm_ioctl_create_vcpu | vCPU 생성 ioctl | cpuid 설정 누락 | kvm_arch_vcpu_create |
| 실행 루프 | kvm_arch_vcpu_ioctl_run | 게스트 실행/VM-Exit | exit reason 미분기 | vcpu_enter_guest |
| 메모리 슬롯 | kvm_set_memory_region | guest phys map 등록 | 겹침 슬롯 검증 누락 | kvm_arch_prepare_memory_region |
| 페이지 테이블 | kvm_mmu_map | gva/gpa 매핑 | TLB flush 경계 오류 | kvm_flush_remote_tlbs |
| 인터럽트 | kvm_set_irq | 가상 IRQ 주입 | irqchip 모드 오해 | kvm_irq_delivery_to_apic |
| 타이머 | kvm_lapic_expired_hv_timer | APIC 타이머 만료 처리 | 주기 계산 오류 | hrtimer 연계 |
| MSR | kvm_get_msr_common | MSR read 에뮬레이션 | 권한/가시성 누락 | kvm_set_msr_common |
| CPUID | kvm_update_cpuid_runtime | 런타임 cpuid 동기화 | feature mismatch | kvm_set_cpu_caps |
| PIO/MMIO | kvm_emulate_io | I/O exit 에뮬레이션 | 반복 exit 폭주 | coalesced MMIO |
| dirty logging | kvm_get_dirty_log | 라이브 마이그레이션 추적 | 비트맵 스캔 비용 과소평가 | dirty ring |
| async PF | kvm_arch_async_page_ready | APF 완료 처리 | guest wakeup 누락 | kvm_make_request |
| PV clock | kvm_write_wall_clock | 게스트 시간 동기화 | TSC 안정성 가정 | kvm_guest_time_update |
| halt poll | kvm_vcpu_block | vCPU sleep/폴링 | poll tuning 미흡 | halt_poll_ns 조정 |
| IOMMU/VFIO | vfio_pin_pages | 디바이스 패스스루 pin | unpin 누락 | vfio_unpin_pages |
| migration | kvm_arch_save_pending_timer | 상태 저장 | 장치 상태 누락 | KVM_GET/SET_* ioctls |
| nested | nested_vmx_run | 중첩 가상화 실행 | state sync 누락 | vmcs12 검증 |
| trace | trace_kvm_exit | exit reason 프로파일링 | 샘플링 과소 | perf kvm stat |
| 락 | kvm->slots_lock | memslot 보호 | 락 순서 역전 | srcu_read_lock |
| 요청 플래그 | kvm_make_request | vcpu 간 동기 이벤트 전파 | kick 누락 | kvm_vcpu_kick |
/* KVM run 루프 개념 스니펫 */
for (;;) {
int r = kvm_arch_vcpu_ioctl_run(vcpu);
if (r < 0)
break;
switch (vcpu->run->exit_reason) {
case KVM_EXIT_IO:
handle_pio(vcpu);
break;
case KVM_EXIT_MMIO:
handle_mmio(vcpu);
break;
case KVM_EXIT_HLT:
return;
default:
trace_kvm_exit(vcpu->run->exit_reason);
break;
}
}
확장 | 인터럽트/타이머 카탈로그
인터럽트 경로는 지연과 안정성이 직결되므로 핸들러 최소화, 하단 처리 분리, 타이머 취소 순서가 핵심입니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| IRQ 등록 | request_irq | 기본 IRQ 핸들러 등록 | free_irq 누락 | devm_request_irq |
| IRQ 공유 | IRQF_SHARED | 공유 인터럽트 라인 | dev_id 고유성 누락 | request_threaded_irq |
| 스레드 IRQ | request_threaded_irq | sleep 가능한 후반 처리 | top half에서 과도 작업 | IRQ_WAKE_THREAD |
| 마스킹 | disable_irq_nosync | 즉시 인터럽트 차단 | 재활성화 누락 | enable_irq |
| 동기화 | synchronize_irq | 진행 중 핸들러 종료 대기 | 락 보유 상태 호출 | synchronize_hardirq |
| softirq | raise_softirq | softirq 스케줄 | 컨텍스트 혼동 | __raise_softirq_irqoff |
| tasklet | tasklet_schedule | 하단 처리 경량 분리 | 긴 작업 처리 | workqueue |
| timer | timer_setup | 타이머 초기화 | 콜백 문맥 오해 | hrtimer_init |
| timer | mod_timer | 타이머 갱신 | jiffies 계산 오류 | mod_timer_pending |
| timer | del_timer_sync | 동기 제거 | 교착 가능 락 순서 | timer_shutdown_sync |
| hrtimer | hrtimer_start_range_ns | 고정밀 타이머 | 슬랙 과소 설정 | hrtimer_forward_now |
| clock | ktime_get_ns | 단조 시간 측정 | realtime 혼용 | ktime_get_boottime_ns |
| 지연 | usleep_range | 절전 친화 지연 | IRQ 문맥 사용 | fsleep |
| busy wait | udelay | 매우 짧은 대기 | 긴 대기 오남용 | usleep_range |
| workqueue | queue_delayed_work | 지연 하단 처리 | 취소 경로 누락 | mod_delayed_work |
| IPI | smp_call_function_single | 원격 CPU 콜백 | dead cpu 대상 호출 | on_each_cpu |
| irqdomain | irq_domain_alloc_irqs | 논리 IRQ 할당 | 해제 누락 | irq_domain_free_irqs |
| affinity | irq_set_affinity_hint | IRQ CPU 힌트 | NUMA 무시 | irq_set_affinity |
| 통계 | kstat_incr_irq_this_cpu | IRQ 카운터 갱신 | 정확도 오해 | /proc/interrupts |
| 경고 | WARN_ON_ONCE | 핸들러 이상 감지 | 반복 경고 폭주 | pr_warn_ratelimited |
/* 스레드 IRQ + hrtimer 조합 패턴 */
static irqreturn_t demo_hard_isr(int irq, void *data)
{
struct demo_dev *d = data;
u32 status = readl(d->regs + IRQ_STATUS);
if (!(status & DEMO_IRQ_MASK))
return IRQ_NONE;
writel(status, d->regs + IRQ_ACK);
return IRQ_WAKE_THREAD; /* 후반 처리 위임 */
}
static irqreturn_t demo_thread_isr(int irq, void *data)
{
struct demo_dev *d = data;
mutex_lock(&d->lock); /* sleep 가능 */
demo_process_data(d);
mutex_unlock(&d->lock);
return IRQ_HANDLED;
}
/* 등록 */
devm_request_threaded_irq(dev, irq,
demo_hard_isr, demo_thread_isr,
IRQF_SHARED, "demo", d);
확장 | 드라이버 버스(PCI/I2C/SPI) 카탈로그
버스 드라이버는 열거, 자원 매핑, 전원관리, 오류 복구를 일관된 순서로 처리해야 안정적입니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| PCI 등록 | pci_register_driver | PCI 드라이버 등록 | remove 경로 누락 | module_pci_driver |
| PCI enable | pcim_enable_device | 관리형 장치 활성화 | BAR 요청 전 사용 | pci_enable_device_mem |
| PCI BAR | pcim_iomap_regions | BAR 매핑 | region 마스크 오류 | pci_iomap |
| PCI DMA | dma_set_mask_and_coherent | DMA 주소폭 설정 | 실패 무시 | dma_set_mask |
| PCI IRQ | pci_alloc_irq_vectors | MSI/MSI-X 벡터 확보 | fallback 미구현 | pci_irq_vector |
| PCI 에러 | pci_enable_pcie_error_reporting | AER 활성화 | 복구 콜백 미구현 | pci_error_handlers |
| I2C 등록 | i2c_add_driver | I2C 드라이버 등록 | id table 누락 | module_i2c_driver |
| I2C 전송 | i2c_transfer | 복수 메시지 트랜잭션 | retries 누락 | i2c_smbus_read_byte_data |
| I2C 검증 | i2c_check_functionality | 어댑터 기능 확인 | 기능 없는 op 호출 | adapter->algo 점검 |
| SPI 등록 | spi_register_driver | SPI 드라이버 등록 | of_match 누락 | module_spi_driver |
| SPI 준비 | spi_setup | mode/speed 반영 | cs 변화 무시 | spi_sync |
| SPI 동기 | spi_sync_transfer | 동기 전송 | 버퍼 수명주기 오류 | spi_async |
| regmap | devm_regmap_init_i2c | I2C regmap 초기화 | endian 설정 오류 | devm_regmap_init_spi |
| 클럭 | devm_clk_get_enabled | 클럭 획득+enable | rate 설정 누락 | clk_set_rate |
| 리셋 | devm_reset_control_get_optional_exclusive | 리셋 라인 제어 | deassert 누락 | reset_control_deassert |
| 레귤레이터 | devm_regulator_get_enable | 전원 레일 제어 | 전압 범위 검증 누락 | regulator_set_voltage |
| GPIO | devm_gpiod_get | GPIO 리소스 획득 | active-low 처리 누락 | gpiod_set_value_cansleep |
| PM runtime | pm_runtime_resume_and_get | 런타임 PM 활성화 | put 누락 | pm_runtime_put_autosuspend |
| 펌웨어 | device_property_read_u32 | DT/ACPI 속성 공통 읽기 | 기본값 누락 | fwnode_property_read_u32 |
| 매칭 | of_device_get_match_data | SoC별 데이터 선택 | null match 처리 누락 | device_get_match_data |
/* PCI 드라이버 probe 최소 골격 */
static int demo_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct demo_dev *d;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
return ret;
ret = pcim_iomap_regions(pdev, BIT(0), "demo");
if (ret)
return ret;
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->regs = pcim_iomap_table(pdev)[0];
pci_set_master(pdev);
pci_set_drvdata(pdev, d);
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_LEGACY);
if (ret < 0)
return ret;
dev_info(&pdev->dev, "probe complete\n");
return 0;
}
/* I2C regmap 패턴 */
static const struct regmap_config demo_regmap_cfg = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xFF,
};
static int demo_i2c_probe(struct i2c_client *client)
{
struct regmap *map;
unsigned int val;
map = devm_regmap_init_i2c(client, &demo_regmap_cfg);
if (IS_ERR(map))
return PTR_ERR(map);
regmap_read(map, 0x00, &val);
dev_info(&client->dev, "chip id: 0x%x\n", val);
return 0;
}
확장 | 컨테이너/cgroups 카탈로그
컨테이너 경로는 namespace 격리와 cgroup 자원 제어가 결합되어 동작합니다. 계층별 책임을 분리해서 봐야 원인 분석이 빠릅니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| ns 생성 | copy_namespaces | clone 시 namespace 복제 | 참조 카운트 누락 | unshare_nsproxy_namespaces |
| pid ns | task_active_pid_ns | 태스크 pid namespace 조회 | init ns 가정 | ns_of_pid |
| mnt ns | copy_mnt_ns | 마운트 namespace 복제 | propagation 오해 | setns |
| net ns | get_net | net namespace 참조 획득 | put 누락 | put_net |
| user ns | make_kuid | id 매핑 변환 | invalid kuid 무시 | from_kuid_munged |
| cgroup attach | cgroup_attach_task | 태스크 cgroup 이동 | 권한 검사 누락 | cgroup_migrate |
| css 조회 | task_css | subsys state 조회 | rcu 규약 누락 | task_css_check |
| 메모리 제어 | mem_cgroup_charge_skmem | sock 메모리 과금 | uncharge 누락 | mem_cgroup_uncharge_skmem |
| 메모리 reclaim | mem_cgroup_try_charge | 페이지 과금 시도 | oom 경로 미처리 | try_charge_memcg |
| CPU 제어 | sched_cgroup_fork | fork 시 CPU cgroup 연결 | weight 반영 누락 | cpu_cgroup_css_alloc |
| cpuset | cpuset_cpus_allowed | 허용 CPU 집합 조회 | hotplug 변화 무시 | cpuset_cpus_allowed_fallback |
| io 제어 | blkcg_bio_issue_init | bio cgroup 컨텍스트 초기화 | issue path 누락 | blkcg_set_ioprio |
| freezer | cgroup_freezing | freezing 상태 확인 | 중단 불가 작업 누락 | try_to_freeze |
| psi | psi_task_change | pressure 상태 갱신 | 상태 전이 누락 | psi_group_change |
| rstat | cgroup_rstat_flush | 통계 플러시 | 빈도 과다 | cgroup_rstat_updated |
| bpf cgroup | cgroup_bpf_run_filter_skb | cgroup BPF 필터 실행 | 리턴 코드 무시 | BPF_CGROUP_RUN_PROG_* |
| 네트워크 classid | task_cls_state | tc classid 연계 | stale state 사용 | sock_cgroup_set_classid |
| OOM | mem_cgroup_oom_synchronize | memcg OOM 처리 | kthread 예외 누락 | out_of_memory |
| release | css_put | css 참조 반환 | 이중 put | css_get |
| debug | cgroup_path | 현재 cgroup 경로 출력 | 버퍼 크기 과소 | task_cgroup_path |
확장 | 전원관리/열 카탈로그
전원과 열 관리는 성능/안정성/수명에 동시에 영향을 주므로 PM 런타임, cpufreq, thermal 정책의 연결을 함께 봐야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| runtime PM | pm_runtime_resume_and_get | 장치 활성화 | put 누락 | pm_runtime_put_autosuspend |
| runtime PM | pm_runtime_enable | runtime PM 시작 | disable 누락 | pm_runtime_disable |
| 시스템 suspend | dpm_suspend_start | suspend 진입 | 순서 의존성 누락 | dpm_suspend_end |
| 시스템 resume | dpm_resume_start | resume 복귀 | 클럭 복구 누락 | dpm_resume_end |
| cpufreq | cpufreq_register_driver | cpufreq 드라이버 등록 | policy init 미흡 | cpufreq_unregister_driver |
| cpufreq | cpufreq_update_policy | 정책 재평가 | 과도 호출 | cpufreq_cpu_get |
| cpuidle | cpuidle_register_driver | idle state 등록 | target residency 부정확 | cpuidle_unregister_driver |
| 열 구역 | thermal_zone_device_register | thermal zone 등록 | 센서 단위 변환 오류 | thermal_zone_device_unregister |
| 열 완화 | thermal_cdev_update | cooling device 상태 갱신 | state clamp 누락 | thermal_zone_device_update |
| 파워캡 | powercap_register_control_type | powercap 타입 등록 | constraint 노출 누락 | powercap_unregister_control_type |
| OPP | dev_pm_opp_set_rate | OPP 기반 주파수 전환 | 전압 스케일 누락 | dev_pm_opp_get_opp_count |
| EM | em_dev_register_perf_domain | Energy Model 등록 | 비용 테이블 부정확 | em_pd_get |
| wakeup | device_set_wakeup_capable | wakeup 능력 설정 | enable 경로 누락 | device_set_wakeup_enable |
| wakeup source | __pm_stay_awake | 절전 지연 필요 구간 | relax 누락 | __pm_relax |
| QoS | cpu_latency_qos_add_request | 지연 요구 등록 | remove 누락 | cpu_latency_qos_update_request |
| regulator | regulator_set_voltage_triplet | 전압 범위 조정 | enable 상태 무시 | regulator_set_voltage |
| clock | clk_bulk_prepare_enable | 다수 클럭 enable | disable 역순 누락 | clk_bulk_disable_unprepare |
| RAPL | rapl_read_data_raw | 에너지 계측 | 랩어라운드 처리 누락 | powercap sysfs |
| 열 추적 | trace_thermal_temperature | 온도 이벤트 관측 | 과도 추적 오버헤드 | tracefs 필터 |
| 진단 | pm_pr_dbg | PM 경로 디버그 | 운영 빌드 잔존 | dynamic_debug |
확장 | 관측성(perf/ftrace/eBPF) 카탈로그
관측성은 "무엇을, 얼마나, 어디서" 수집할지 설계가 먼저입니다. 이벤트 스키마와 오버헤드 제어를 함께 정의해야 합니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| ftrace | register_ftrace_function | 함수 트레이스 훅 | 필터 없이 전체 계측 | ftrace_set_filter |
| ftrace | unregister_ftrace_function | 훅 해제 | 모듈 언로드 전 누락 | ftrace_shutdown |
| tracepoint | tracepoint_probe_register | 정적 이벤트 구독 | unregister 누락 | register_trace_* |
| tracepoint | tracepoint_probe_unregister | 구독 해제 | 콜백 수명주기 오류 | synchronize_rcu |
| perf event | perf_event_create_kernel_counter | 커널 PMU 카운터 | CPU hotplug 대응 누락 | perf_event_enable |
| perf sampling | perf_output_sample | 샘플 출력 | ring buffer overflow 무시 | perf_event_overflow |
| bpf attach | bpf_prog_attach | cgroup/tc/xdp attach | attach type 불일치 | bpf_link_create |
| bpf run (내부) | bpf_prog_run | BPF 내부 실행 경로 이해 | 직접 호출 가능한 API로 오해 | bpf_prog_attach, bpf_link_create |
| bpf map | bpf_map_lookup_elem | 맵 조회 | per-cpu map 의미 오해 | bpf_map_update_elem |
| kprobe | register_kprobe | 동적 함수 진입 계측 | 핫패스 남용 | tracepoint |
| kretprobe | register_kretprobe | 함수 반환 계측 | maxactive 과소 설정 | fentry/fexit BPF |
| uprobes | uprobe_register | 유저 함수 계측 | symbol offset 오류 | perf probe |
| static key | static_branch_enable | 런타임 분기 토글 | 초기값/토글 불일치 | DEFINE_STATIC_KEY_FALSE |
| ringbuf | bpf_ringbuf_output | BPF 이벤트 전달 | 큰 레코드 빈발 | perf buffer |
| scheduler trace | trace_sched_switch | 문맥 전환 추적 | 전체 시스템 장시간 추적 | trace-cmd filter |
| lock trace | lock_acquire trace | 락 경합 분석 | stack depth 과다 | lockstat |
| latency | trace_irqsoff | irq-off 지연 추적 | 운영 환경 장시간 사용 | osnoise |
| event filter | set_event_pid | pid 범위 축소 | 필터 누락으로 오버헤드 증가 | tracefs filter |
| synthetic event | synth_event_create | 복합 이벤트 생성 | 필드 타입 불일치 | hist trigger |
| debug | trace_printk | 임시 추적 | 운영 코드 잔존 | pr_debug, tracepoint |
/* DEFINE_EVENT tracepoint 정의 패턴 */
/* include/trace/events/demo.h */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM demo
#if !defined(_TRACE_DEMO_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_DEMO_H
#include <linux/tracepoint.h>
TRACE_EVENT(demo_op,
TP_PROTO(const char *name, int ret),
TP_ARGS(name, ret),
TP_STRUCT__entry(
__string(name, name)
__field(int, ret)
),
TP_fast_assign(
__assign_str(name);
__entry->ret = ret;
),
TP_printk("name=%s ret=%d", __get_str(name), __entry->ret)
);
#endif
#include <trace/define_trace.h>
/* 사용 측 */
trace_demo_op("write", ret);
# ftrace 실전 사용 예시
# 1. 함수 추적 활성화
echo demo_probe > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 2. tracepoint 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/demo/demo_op/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 3. 인터럽트 off 지연 추적
echo irqsoff > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace # 최대 지연 확인
확장 | 컴파일러/링커(GCC/binutils) 카탈로그
빌드 옵션은 런타임 동작을 바꾸므로 경고/최적화/보안 옵션과 ELF 결과물을 함께 점검해야 합니다.
| 분류 | 핵심 옵션/도구 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 경고 | -Wall | 기본 경고 활성화 | 경고 무시 문화 | -Wextra |
| 경고 | -Werror | CI 엄격 모드 | 로컬/CI 불일치 | -Wno-error=... |
| 최적화 | -O2 | 기본 커널 최적화 | 무근거 -O3 변경 | per-file CFLAGS |
| 디버그 | -g | 심볼 포함 빌드 | strip 후 분석 불가 | CONFIG_DEBUG_INFO |
| 보안 | -fstack-protector-strong | 스택 보호 | 핵심 파일 제외 빌드 | CONFIG_STACKPROTECTOR |
| 보안 | -D_FORTIFY_SOURCE=2 | 버퍼 검증 강화 | 최적화 옵션과 충돌 오해 | CONFIG_FORTIFY_SOURCE |
| 호환성 | -fno-strict-aliasing | 커널 alias 규약 유지 | 부분 파일 누락 | READ_ONCE 규약 |
| LTO | CONFIG_LTO_CLANG_THIN | 링크단 최적화 | 가시성 속성 누락 | __visible |
| CFI | CONFIG_CFI_CLANG | 제어흐름 무결성 | 함수 포인터 캐스팅 남용 | __nocfi 최소 사용 |
| objdump | objdump -drS | 기계어/소스 대응 확인 | 심볼 없는 바이너리 분석 | llvm-objdump |
| readelf | readelf -Ws | 심볼 테이블 점검 | 가시성/바인딩 미확인 | nm -n |
| 재배치 | readelf -r | relocation 확인 | section mismatch 간과 | objdump -r |
| 사이즈 | size | 텍스트/데이터 크기 비교 | 회귀 추적 누락 | bloat-o-meter |
| 심볼 검색 | nm | 정의/참조 파악 | 정렬 없는 비교 | nm -n |
| 섹션 추출 | objcopy --only-section | 특정 섹션 분석 | 디버그 섹션 유실 | llvm-objcopy |
| asm 확인 | -S -fverbose-asm | 코드 생성 검증 | 최적화 레벨 차이 무시 | Compiler Explorer |
| 링커 스크립트 | SECTIONS, PHDRS | 배치 제어 | 정렬/경계 누락 | vmlinux.lds.h |
| 버전 점검 | scripts/min-tool-version.sh | 최소 툴체인 확인 | 개발기/CI 버전 불일치 | containerized build |
| 경고 분석 | sparse | 주소공간/타입 검증 | C=2 누락 | smatch |
| 패턴 리팩터링 | Coccinelle | 대규모 API 변환 | semantic patch 검증 부족 | git range-diff |
확장 | 네트워크 프로토콜(TCP/UDP/Netfilter) 카탈로그
프로토콜 경로는 상태 전이, 타이머, conntrack, NAT 훅 순서가 복합적으로 얽혀 있습니다. 경로별 핵심 API를 함께 봐야 회귀를 줄일 수 있습니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| TCP 송신 | tcp_sendmsg | 스트림 데이터 송신 | 잠금 범위 과대 | tcp_sendpage |
| TCP 수신 | tcp_recvmsg | 스트림 데이터 수신 | peek 플래그 오해 | tcp_read_sock |
| TCP 상태 | tcp_set_state | 상태 전이 갱신 | 타이머 정리 누락 | tcp_done |
| TCP 타이머 | tcp_write_timer_handler | 재전송/지연 ACK 처리 | RTO 조정 오류 | tcp_retransmit_timer |
| TCP 혼잡 | tcp_cong_avoid_ai | 혼잡 회피 알고리즘 구현 | cwnd 단위 혼동 | tcp_slow_start |
| UDP 송신 | udp_sendmsg | 데이터그램 송신 | MTU 초과 처리 누락 | ip_append_data |
| UDP 수신 | udp_recvmsg | 데이터그램 수신 | trunc 처리 누락 | skb_copy_datagram_msg |
| IP 출력 | ip_local_out | IPv4 local output | netfilter 훅 우회 가정 | ip_output |
| IP 입력 | ip_local_deliver | 로컬 입력 전달 | fragments 처리 누락 | ip_rcv_finish |
| IPv6 출력 | ip6_local_out | IPv6 local output | 확장헤더 길이 오해 | ip6_output |
| conntrack | nf_conntrack_in | 연결 추적 진입 | zone 고려 누락 | nf_ct_get |
| NAT | nf_nat_setup_info | NAT 매핑 설정 | 충돌 처리 누락 | nf_nat_alloc_null_binding |
| Netfilter 훅 | nf_register_net_hook | 훅 등록 | 우선순위 충돌 | nf_unregister_net_hook |
| rule 평가 | nf_hook_slow | 훅 체인 순회 | 성능 경로 과부하 | flowtable offload |
| flowtable | nf_flow_table_offload_add_cb | 플로우 오프로드 등록 | 만료 처리 누락 | nf_flow_table_cleanup |
| socket lookup | __inet_lookup_established | TCP 소켓 조회 | ehash lock 누락 | inet_lookup_listener |
| 큐 제어 | sk_stream_wait_memory | 송신 버퍼 대기 | signal 처리 누락 | sk_wait_event |
| ECN | INET_ECN_set_ce | 혼잡 표시 비트 설정 | 체크섬 재계산 누락 | INET_ECN_encapsulate |
| GRO | tcp_gro_receive | TCP GRO 병합 | 옵션 파싱 누락 | udp_gro_receive |
| XFRM | xfrm_lookup_route | IPsec 정책 경로 조회 | policy fallback 누락 | xfrm_policy_lookup |
확장 | 파일시스템 심화(ext4/XFS/Btrfs) 카탈로그
파일시스템 심화 경로는 저널링/트랜잭션/지연할당/extent 트리 동작을 함께 이해해야 디스크 손상 없이 성능을 올릴 수 있습니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| ext4 트랜잭션 | ext4_journal_start | 메타데이터 변경 시작 | handle 크기 과소 | ext4_journal_stop |
| ext4 블록 매핑 | ext4_map_blocks | logical->physical 매핑 | unwritten extent 처리 누락 | ext4_ext_map_blocks |
| ext4 할당 | ext4_mb_new_blocks | mballoc 블록 할당 | goal block 오해 | ext4_claim_free_clusters |
| ext4 writeback | ext4_writepages | dirty page flush | wbc sync 모드 누락 | mpage_map_and_submit_extent |
| ext4 fsync | ext4_sync_file | 파일 동기화 | journal commit 순서 오류 | jbd2_log_start_commit |
| XFS 트랜잭션 | xfs_trans_alloc | 트랜잭션 확보 | reserve 과소 | xfs_trans_commit |
| XFS inode | xfs_iget | inode 조회 | ilock 모드 오류 | xfs_ilock |
| XFS extent | xfs_bmapi_write | extent 할당 | cow fork 누락 | xfs_bmapi_convert_delalloc |
| XFS log | xlog_cil_commit | CIL commit | push 타이밍 과도 지연 | xfs_log_force |
| XFS reclaim | xfs_reclaim_inodes | inode reclaim | AG 스캔 불균형 | xfs_icwalk |
| Btrfs 트랜잭션 | btrfs_start_transaction | tree 변경 시작 | nested trans 과다 | btrfs_end_transaction |
| Btrfs extent | btrfs_reserve_extent | extent 예약 | space info 오해 | btrfs_free_reserved_extent |
| Btrfs delayed refs | btrfs_run_delayed_refs | 지연 참조 처리 | flush 전략 미흡 | btrfs_start_delalloc_roots |
| Btrfs COW | btrfs_cow_block | 메타데이터 COW | generation 체크 누락 | btrfs_search_slot |
| Btrfs balance | btrfs_balance | 청크 재배치 | 필터 과도 설정 | btrfs_relocate_chunk |
| 공통 iomap | iomap_write_begin | iomap 기반 쓰기 시작 | folio uptodate 처리 누락 | iomap_write_end |
| 공통 dax | dax_iomap_rw | DAX direct access | cache flush 누락 | dax_writeback_mapping_range |
| 공통 quota | dquot_alloc_inode | inode quota 과금 | error unwind 누락 | dquot_free_inode |
| 공통 freeze | freeze_super | fs freeze 진입 | unfreeze 누락 | thaw_super |
| 공통 trim | fstrim_range | discard 전달 | 정렬 제약 누락 | blkdev_issue_discard |
확장 | 디바이스 특화(GPU/V4L2/ALSA) 카탈로그
미디어/그래픽/오디오 서브시스템은 사용자 ABI 호환성과 DMA 버퍼 공유 규약이 핵심입니다.
| 분류 | 핵심 API/심볼 | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| DRM 등록 | drm_dev_register | DRM 디바이스 공개 | unregister 누락 | drm_dev_unplug |
| DRM 메모리 | drm_gem_object_init | GEM 객체 초기화 | refcount 누락 | drm_gem_private_object_init |
| KMS atomic | drm_atomic_helper_commit | 원자적 모드셋 적용 | fence 대기 누락 | drm_atomic_commit |
| DMA-BUF | dma_buf_export | 버퍼 공유 핸들 제공 | map/unmap 불균형 | dma_buf_get |
| GPU 스케줄 | drm_sched_job_init | 작업 초기화 | entity 연결 누락 | drm_sched_entity_push_job |
| GPU 펜스 | dma_fence_signal | 작업 완료 신호 | 중복 signal | dma_fence_wait_timeout |
| V4L2 등록 | video_register_device | 비디오 노드 등록 | minor 충돌 처리 누락 | video_unregister_device |
| V4L2 queue | vb2_queue_init | 버퍼 큐 초기화 | ops 구현 불일치 | vb2_reqbufs |
| V4L2 buffer | vb2_buffer_done | 프레임 완료 반환 | state 잘못 반환 | VB2_BUF_STATE_ERROR |
| V4L2 subdev | v4l2_async_register_subdev | 비동기 센서 연결 | notifier 누락 | v4l2_async_nf_register |
| ALSA card | snd_card_new | 카드 객체 생성 | free 경로 누락 | snd_card_free |
| ALSA PCM | snd_pcm_new | PCM 디바이스 생성 | ops 등록 누락 | snd_pcm_set_ops |
| ALSA DMA | snd_pcm_lib_preallocate_pages | 버퍼 사전 할당 | 크기 과소 | snd_pcm_lib_malloc_pages |
| ALSA trigger | snd_pcm_period_elapsed | period 완료 통지 | IRQ 경로 지연 | snd_timer_interrupt |
| ASoC 컴포넌트 | devm_snd_soc_register_component | codec/platform 등록 | dai link 매칭 오류 | snd_soc_register_component |
| ASoC DAPM | snd_soc_dapm_new_controls | 전원 위젯 구성 | 경로 이름 불일치 | snd_soc_dapm_add_routes |
| media request | media_request_object_bind | 요청 기반 동기화 | lifetime 누락 | media_request_put |
| ioctl 검증 | v4l2_ioctl_ops | 유저 ABI 처리 | compat_ioctl 누락 | video_usercopy |
| 디버그 | drm_dbg, v4l2_dbg, dev_dbg | 서브시스템별 로그 | 정적 로그 남용 | dynamic_debug |
| PM 연계 | pm_runtime_force_suspend | 시스템 suspend 보조 | resume 대칭 누락 | pm_runtime_force_resume |
확장 | 문자열/버퍼 조작 카탈로그
커널 문자열 API는 사용자 공간 libc와 계약이 다릅니다. 특히 NUL 종료 보장, 버퍼 오버런 방지, 반환값 규약이 함수마다 다르므로 표의 "반환값" 열을 가장 먼저 확인해야 합니다. strscpy는 strlcpy/strncpy를 대체하는 커널 권장 API입니다.
실무 체크포인트: 새 코드에서 strcpy/strncpy/strlcpy를 발견하면 strscpy로 교체 가능 여부를 먼저 검토하세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 복사 | strscpy | 고정 크기 버퍼 안전 복사 | 반환값(-E2BIG) 무시 | strncpy 사용 금지 |
| 복사 | strscpy_pad | 나머지 영역 0 채움 복사 | 패딩 필요 없는 곳에 사용 | strscpy |
| 복제 | kstrdup | 힙에 문자열 복제 | kfree 누락 | kstrdup_const |
| 복제 | kstrdup_const | 상수 문자열 중복 방지 | kfree_const 미사용 | kstrdup |
| 복제 | kmemdup | 바이너리 데이터 복제 | 길이 불일치 | memcpy + kmalloc |
| 포맷 | scnprintf | 버퍼 내 실제 출력 길이 반환 | snprintf 반환값과 혼동 | snprintf |
| 포맷 | snprintf | 포맷 문자열 안전 출력 | 잘림 검사 누락(반환값 > size) | scnprintf |
| 포맷 | kasprintf | 동적 포맷 문자열 생성 | NULL 반환 미검사 | devm_kasprintf |
| 비교 | strcmp | NUL 종료 문자열 비교 | 길이 미보장 문자열에 사용 | strncmp |
| 비교 | strncasecmp | 대소문자 무시 비교 | 로케일 가정 | strncmp |
| 검색 | strstr | 부분 문자열 검색 | NUL 미종료 입력 | strnstr |
| 검색 | strchr | 문자 위치 탐색 | NULL 반환 미검사 | strrchr |
| 분리 | strsep | 토큰 분리 | 원본 포인터 변경 인지 부족 | strtok 사용 금지 |
| 변환 | kstrtoul | 문자열→unsigned long | 에러 반환 무시 | kstrtouint |
| 변환 | kstrtoint | 문자열→int | 오버플로 미처리 | kstrtol |
| 변환 | kstrtobool | y/n/1/0 해석 | 빈 문자열 처리 누락 | strtobool deprecated |
| 메모리 | memcpy | 비중첩 메모리 복사 | 겹침 영역에 사용 | memmove |
| 메모리 | memmove | 겹침 허용 복사 | 불필요한 사용으로 오버헤드 | memcpy |
| 메모리 | memset | 메모리 초기화 | kzalloc과 중복 사용 | memset_after |
| 소거 | memzero_explicit | 민감 데이터 확실 소거 | 일반 memset 사용 (최적화 제거됨) | kfree_sensitive |
| 해시 | xxhash | 비암호화 고속 해시 | 보안 용도 사용 | siphash |
| 해시 | siphash | 해시 테이블 보안 해시 | 키 초기화 누락 | hsiphash |
/* strscpy + scnprintf 조합 패턴 */
static ssize_t demo_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct demo_dev *d = dev_get_drvdata(dev);
char name[64];
ssize_t ret;
ret = strscpy(name, d->label, sizeof(name));
if (ret < 0)
strscpy(name, "(truncated)", sizeof(name));
return scnprintf(buf, PAGE_SIZE,
"name=%s ver=%u\n", name, d->version);
}
/* kasprintf 동적 할당 패턴 */
char *label = kasprintf(GFP_KERNEL, "%s-%d", prefix, idx);
if (!label)
return -ENOMEM;
/* ... 사용 ... */
kfree(label);
/* kstrtoul 변환 패턴 */
unsigned long val;
int ret = kstrtoul(buf, 0, &val);
if (ret)
return ret;
if (val > MAX_LIMIT)
return -ERANGE;
확장 | Per-CPU 변수 카탈로그
Per-CPU 변수는 락 없이 CPU별 독립 데이터를 관리하는 핵심 기법입니다. 성능 카운터, 통계, 캐시에 자주 사용되지만, 선점(preemption) 제어를 빠뜨리면 잘못된 CPU 데이터에 접근하는 미묘한 버그가 발생합니다. 이 표는 "어떤 접근 API를 쓸지"보다 "선점 보호를 어떻게 보장할지"를 먼저 고정하는 기준으로 읽어야 합니다.
실무 체크포인트: Per-CPU 변수 접근 시 get_cpu()/put_cpu() 또는 this_cpu_* 시리즈의 선점 보호 범위를 반드시 확인하세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 선언 | DEFINE_PER_CPU | 정적 per-cpu 변수 정의 | 동적 할당과 혼동 | DEFINE_PER_CPU_SHARED_ALIGNED |
| 선언 | DECLARE_PER_CPU | 외부 선언 | 정의와 타입 불일치 | DEFINE_PER_CPU와 짝 |
| 동적 할당 | alloc_percpu | 런타임 per-cpu 할당 | free_percpu 누락 | alloc_percpu_gfp |
| 동적 해제 | free_percpu | 동적 per-cpu 해제 | 이중 해제 | none |
| 단일 연산 | this_cpu_inc | 현재 CPU 카운터 증가 | 오버플로 무시 | this_cpu_add |
| 단일 연산 | this_cpu_read | 현재 CPU 값 읽기 | 선점 후 stale 값 사용 | get_cpu_var |
| 단일 연산 | this_cpu_write | 현재 CPU 값 쓰기 | 복합 연산에서 사용 | this_cpu_xchg |
| 단일 연산 | this_cpu_cmpxchg | CAS 기반 갱신 | 루프 재시도 누락 | this_cpu_xchg |
| 복합 접근 | get_cpu_var | 선점 비활성+참조 | put_cpu_var 누락 | put_cpu_var |
| 복합 접근 | get_cpu() | 선점 비활성+CPU 번호 | put_cpu() 누락 | put_cpu() |
| 타 CPU 접근 | per_cpu(var, cpu) | 특정 CPU 데이터 접근 | 동기화 없이 쓰기 | per_cpu_ptr |
| 총합 | for_each_possible_cpu | 전체 CPU 합산 | online/possible 혼동 | for_each_online_cpu |
| 포인터 | per_cpu_ptr | 동적 per-cpu 포인터 역참조 | NULL per-cpu 주소 | raw_cpu_ptr |
| IRQ safe | __this_cpu_inc | IRQ 컨텍스트 내부 사용 | 선점 가능 문맥에서 사용 | this_cpu_inc |
/* Per-CPU 카운터 패턴 */
static DEFINE_PER_CPU(u64, demo_packets);
static DEFINE_PER_CPU(u64, demo_bytes);
static void demo_account(unsigned int len)
{
this_cpu_inc(demo_packets);
this_cpu_add(demo_bytes, len);
}
static u64 demo_total_packets(void)
{
u64 sum = 0;
int cpu;
for_each_possible_cpu(cpu)
sum += per_cpu(demo_packets, cpu);
return sum;
}
/* 동적 per-cpu 할당 패턴 */
int __percpu *counters = alloc_percpu(int);
if (!counters)
return -ENOMEM;
this_cpu_inc(*counters);
/* 정리 */
free_percpu(counters);
확장 | 알림 체인(Notifier Chain) 카탈로그
알림 체인은 커널 서브시스템 간 이벤트 전파 메커니즘입니다. CPU hotplug, 네트워크 인터페이스 상태 변화, reboot 등의 시스템 이벤트를 구독/통지하는 표준 패턴입니다. 체인 유형(blocking/atomic/raw/SRCU)에 따라 콜백 내부에서 sleep 가능 여부가 달라지므로, 등록 시 체인 유형을 먼저 확인해야 합니다.
실무 체크포인트: 콜백 내부에서 sleep이 필요하면 반드시 blocking notifier를 사용하고, atomic notifier 체인에 등록하지 마세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| blocking 등록 | blocking_notifier_chain_register | sleep 가능 콜백 등록 | unregister 누락 | blocking_notifier_chain_unregister |
| blocking 호출 | blocking_notifier_call_chain | sleep 허용 통지 | 리턴값 무시 | NOTIFY_STOP 검사 |
| atomic 등록 | atomic_notifier_chain_register | IRQ 안전 콜백 등록 | sleep 콜백 등록 | atomic_notifier_chain_unregister |
| atomic 호출 | atomic_notifier_call_chain | atomic 문맥 통지 | 긴 처리 콜백 | raw_notifier_call_chain |
| SRCU 등록 | srcu_notifier_chain_register | SRCU 보호 콜백 등록 | 체인 타입 혼동 | srcu_notifier_chain_unregister |
| raw 등록 | raw_notifier_chain_register | 직접 락 관리 필요 | 동기화 누락 | raw_notifier_chain_unregister |
| netdev | register_netdevice_notifier | 네트워크 장치 이벤트 구독 | NETDEV_* 이벤트 필터 누락 | unregister_netdevice_notifier |
| inetaddr | register_inetaddr_notifier | IPv4 주소 변경 구독 | net namespace 미고려 | register_inet6addr_notifier |
| reboot | register_reboot_notifier | 시스템 종료 전 정리 | 우선순위 무시 | unregister_reboot_notifier |
| CPU | cpuhp_setup_state | CPU hotplug 이벤트 구독 | teardown 콜백 누락 | cpuhp_remove_state |
| PM | register_pm_notifier | 시스템 suspend/resume 구독 | resume 경로 무시 | unregister_pm_notifier |
| OOM | register_oom_notifier | OOM 이벤트 구독 | 경합 증가 | unregister_oom_notifier |
| 콜백 반환 | NOTIFY_DONE | 처리 완료, 계속 전파 | NOTIFY_OK과 혼동 | NOTIFY_STOP |
| 콜백 반환 | NOTIFY_STOP | 처리 완료, 전파 중단 | 불필요한 전파 차단 | NOTIFY_BAD |
/* netdev notifier 최소 패턴 */
static int demo_netdev_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
switch (event) {
case NETDEV_UP:
pr_info("device %s up\n", dev->name);
break;
case NETDEV_DOWN:
pr_info("device %s down\n", dev->name);
break;
}
return NOTIFY_DONE;
}
static struct notifier_block demo_nb = {
.notifier_call = demo_netdev_event,
};
/* init */
register_netdevice_notifier(&demo_nb);
/* exit */
unregister_netdevice_notifier(&demo_nb);
확장 | sysfs/procfs/debugfs 인터페이스 카탈로그
커널-사용자 공간 인터페이스는 용도에 따라 sysfs(장치 속성), procfs(프로세스/시스템 정보), debugfs(디버그 전용)로 분리합니다. 인터페이스 선택을 잘못하면 ABI 유지 비용이 불필요하게 높아지거나, 디버그 전용 정보가 영구 ABI로 굳어지는 문제가 발생합니다.
실무 체크포인트: 새 인터페이스를 추가할 때 "이것이 안정 ABI여야 하는가?"를 먼저 결정하세요. 디버그/개발 전용이면 debugfs, 장치 속성이면 sysfs, 커널 전역 정보면 procfs가 원칙입니다.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| sysfs 속성 | DEVICE_ATTR_RO | 읽기 전용 장치 속성 | show 반환값 오류 | DEVICE_ATTR_RW |
| sysfs 속성 | DEVICE_ATTR_RW | 읽기/쓰기 장치 속성 | store 검증 누락 | DEVICE_ATTR_WO |
| sysfs 그룹 | sysfs_create_group | 속성 그룹 일괄 생성 | remove 누락 | devm_device_add_group |
| sysfs 이진 | BIN_ATTR_RO | 바이너리 속성 | 크기 불일치 | sysfs_create_bin_file |
| procfs 생성 | proc_create | proc 엔트리 생성 | remove 누락 | proc_create_data |
| procfs 단일값 | proc_create_single | 단일 show 함수 proc | 불필요한 seq_file 사용 | proc_create |
| procfs net | proc_create_net | net namespace aware proc | namespace 미전달 | proc_create_net_single |
| seq_file | seq_printf | 순차적 데이터 출력 | 버퍼 오버플로 가정 | seq_puts |
| seq_file | seq_open | seq_file 연결 | stop에서 리소스 해제 누락 | single_open |
| single_open | single_open | 단일 show 함수 seq | single_release 미사용 | single_release |
| debugfs 생성 | debugfs_create_dir | 디버그 디렉터리 | NULL/ERR 혼동 | debugfs_remove_recursive |
| debugfs 파일 | debugfs_create_file | 커스텀 디버그 파일 | fops 불완전 | debugfs_create_u32 |
| debugfs 단순 | debugfs_create_u32 | 단일 정수 노출 | 동기화 없이 변수 공유 | debugfs_create_u64 |
| debugfs 불값 | debugfs_create_bool | bool 토글 | false positive 의존 | debugfs_create_file |
| debugfs 제거 | debugfs_remove_recursive | 디렉터리+하위 전체 제거 | NULL dentry 처리 오류 | debugfs_remove |
| debugfs blob | debugfs_create_blob | 바이너리 덤프 | 수명주기 불일치 | debugfs_create_file |
| configfs | configfs_register_subsystem | 사용자 구성 객체 트리 | item ops 불완전 | configfs_unregister_subsystem |
| sysctl | register_sysctl | sysctl 테이블 등록 | proc_handler 오류 | unregister_sysctl_table |
/* sysfs 속성 패턴 */
static ssize_t status_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct demo_dev *d = dev_get_drvdata(dev);
return sysfs_emit(buf, "%u\n", READ_ONCE(d->status));
}
static DEVICE_ATTR_RO(status);
/* seq_file 패턴 */
static int demo_seq_show(struct seq_file *m, void *v)
{
struct demo_entry *e = v;
seq_printf(m, "%-16s %8u %8u\n", e->name, e->rx, e->tx);
return 0;
}
/* debugfs 디렉터리+파일 패턴 */
static struct dentry *demo_debugfs;
static int __init demo_debugfs_init(void)
{
demo_debugfs = debugfs_create_dir("demo", NULL);
debugfs_create_u32("counter", 0444, demo_debugfs, &demo_cnt);
debugfs_create_bool("enabled", 0644, demo_debugfs, &demo_en);
return 0;
}
static void __exit demo_debugfs_exit(void)
{
debugfs_remove_recursive(demo_debugfs);
}
확장 | 참조 카운팅(kref/kobject) 카탈로그
참조 카운팅은 커널 객체의 수명주기 계약을 코드로 표현하는 핵심 패턴입니다. kref는 단순 참조 카운팅, kobject는 sysfs 노출과 계층 관리를 추가합니다. 참조 카운팅 버그는 UAF(use-after-free)나 메모리 누수로 직결되므로, get/put 경로의 대칭성을 가장 먼저 검증해야 합니다.
실무 체크포인트: 모든 get 호출에 대응하는 put 경로가 정상/실패/종료 경로 모두에서 도달 가능한지 확인하세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| kref 초기화 | kref_init | 참조 카운트 1로 초기화 | 이중 초기화 | refcount_set |
| kref 획득 | kref_get | 참조 카운트 증가 | 이미 0인 객체에 get | kref_get_unless_zero |
| kref 안전 획득 | kref_get_unless_zero | UAF 방지 참조 획득 | 반환값 무시 | refcount_inc_not_zero |
| kref 반납 | kref_put | 참조 감소 + 0이면 release | release 콜백 미구현 | kref_put_lock |
| kref 락+반납 | kref_put_mutex | 락 보호 하에 해제 | 교착 위험 | kref_put_lock |
| kobject 생성 | kobject_init_and_add | 초기화+sysfs 등록 | 실패 시 put 누락 | kobject_create_and_add |
| kobject 해제 | kobject_put | 참조 반납+sysfs 제거 | 직접 kfree 호출 | kobject_del |
| kobject 삭제 | kobject_del | sysfs만 제거(참조 유지) | put 없이 del만 호출 | kobject_put |
| kset 생성 | kset_create_and_add | kobject 그룹 관리 | unregister 누락 | kset_unregister |
| ktype | kobj_type.release | 해제 콜백 정의 | release에서 자원 해제 누락 | none |
| refcount | refcount_set | 원시 참조 카운트 설정 | 초기값 0으로 설정 | refcount_inc |
| refcount | refcount_dec_and_test | 감소+0 판별 | 판별 결과 무시 | refcount_dec_and_lock |
| refcount | refcount_dec_and_mutex_lock | 감소+mutex 획득 | 교착 | refcount_dec_and_lock |
/* kref 기본 수명주기 패턴 */
struct demo_obj {
struct kref refcount;
char *name;
struct list_head link;
};
static void demo_release(struct kref *ref)
{
struct demo_obj *obj = container_of(ref, struct demo_obj, refcount);
kfree(obj->name);
kfree(obj);
}
static struct demo_obj *demo_create(const char *name)
{
struct demo_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
if (!obj)
return NULL;
obj->name = kstrdup(name, GFP_KERNEL);
if (!obj->name) {
kfree(obj);
return NULL;
}
kref_init(&obj->refcount);
return obj;
}
static void demo_get(struct demo_obj *obj) { kref_get(&obj->refcount); }
static void demo_put(struct demo_obj *obj) { kref_put(&obj->refcount, demo_release); }
확장 | MMIO/PIO 접근 카탈로그
MMIO(Memory-Mapped I/O)와 PIO(Port I/O)는 하드웨어 레지스터 접근의 두 축입니다. MMIO는 메모리 주소 공간에 매핑된 레지스터에 접근하고, PIO는 x86의 IN/OUT 명령어 기반입니다. 접근 너비(8/16/32/64비트), 순서 보장(relaxed 여부), 엔디안 변환이 핵심 체크 포인트입니다.
실무 체크포인트: MMIO 접근에는 readl/writel을 기본으로 사용하고, 핫패스에서만 readl_relaxed를 검토하세요. 직접 포인터 역참조로 레지스터를 읽지 마세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| MMIO 매핑 | ioremap | MMIO 주소 매핑 | iounmap 누락 | devm_ioremap |
| MMIO 매핑 | devm_ioremap_resource | 관리형 매핑+검증 | ERR_PTR 검사 누락 | devm_platform_ioremap_resource |
| MMIO 읽기 | readb/readw/readl/readq | 8/16/32/64비트 MMIO 읽기 | 크기 불일치 | ioread8~ioread64 |
| MMIO 쓰기 | writeb/writew/writel/writeq | 8/16/32/64비트 MMIO 쓰기 | 쓰기 후 읽기 확인 누락 | iowrite8~iowrite64 |
| relaxed 읽기 | readl_relaxed | 순서 보장 불필요 핫패스 | DMA 동기화 필요 구간에서 사용 | readl |
| relaxed 쓰기 | writel_relaxed | 순서 보장 불필요 핫패스 | 완료 보장 없이 종료 | writel |
| 추상화 읽기 | ioread32 | MMIO/PIO 자동 판별 | 불필요한 간접 호출 | readl |
| 추상화 쓰기 | iowrite32 | MMIO/PIO 자동 판별 | 성능 오버헤드 | writel |
| PIO 읽기 | inb/inw/inl | x86 포트 I/O 읽기 | 아키텍처 의존성 | ioread8 |
| PIO 쓰기 | outb/outw/outl | x86 포트 I/O 쓰기 | 아키텍처 의존성 | iowrite8 |
| 블록 읽기 | memcpy_fromio | MMIO 영역 블록 읽기 | 비정렬 접근 | ioread32_rep |
| 블록 쓰기 | memcpy_toio | MMIO 영역 블록 쓰기 | 비정렬 접근 | iowrite32_rep |
| 블록 초기화 | memset_io | MMIO 영역 초기화 | 일반 memset 사용 | none |
| 비트필드 | FIELD_GET | 레지스터 필드 추출 | 마스크 불일치 | FIELD_PREP |
| 비트필드 | FIELD_PREP | 레지스터 필드 설정 | OR 연산 순서 실수 | GENMASK |
| write+확인 | writel+readl | 쓰기 완료 보장(posted write flush) | flush 읽기 누락 | none |
| regmap | regmap_read | 추상화된 레지스터 읽기 | endian 설정 오류 | regmap_write |
| regmap | regmap_update_bits | read-modify-write 원자적 수행 | 락 범위 오류 | regmap_write_bits |
/* MMIO 레지스터 접근 기본 패턴 */
static void __iomem *base;
/* 레지스터 읽기/쓰기 래퍼 */
static inline u32 demo_read(u32 offset)
{
return readl(base + offset);
}
static inline void demo_write(u32 offset, u32 val)
{
writel(val, base + offset);
}
/* 비트필드 조작 패턴 */
#define REG_CTRL 0x00
#define CTRL_ENABLE BIT(0)
#define CTRL_MODE_MASK GENMASK(3, 1)
static void demo_set_mode(u32 mode)
{
u32 val = demo_read(REG_CTRL);
val &= ~CTRL_MODE_MASK;
val |= FIELD_PREP(CTRL_MODE_MASK, mode);
val |= CTRL_ENABLE;
demo_write(REG_CTRL, val);
/* posted write flush */
(void)demo_read(REG_CTRL);
}
확장 | DMA 매핑 심화 카탈로그
DMA 매핑은 CPU와 디바이스 간 메모리 가시성 계약입니다. 스트리밍 매핑(map/unmap), 일관성 매핑(alloc_coherent), DMA 풀 패턴을 용도에 따라 분리해야 하며, 방향(direction) 플래그와 dma_mask 설정을 먼저 고정해야 합니다. 매핑 에러 검사를 생략하면 무효 DMA 주소로 인한 데이터 손상이 발생합니다.
실무 체크포인트: 모든 dma_map_* 호출 직후 dma_mapping_error를 검사하세요. 이 검사가 누락된 코드는 즉시 수정 대상입니다.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 마스크 설정 | dma_set_mask_and_coherent | DMA 주소폭 설정 | 실패 무시 | dma_set_mask |
| 일관성 할당 | dma_alloc_coherent | 일관성 메모리 (설명자 링) | dma_handle 보관 누락 | dma_free_coherent |
| 일관성 해제 | dma_free_coherent | 일관성 메모리 해제 | 크기 불일치 | none |
| 스트리밍 매핑 | dma_map_single | 단일 버퍼 스트리밍 매핑 | 에러 검사 누락 | dma_unmap_single |
| 스트리밍 해제 | dma_unmap_single | 스트리밍 매핑 해제 | 방향(direction) 불일치 | none |
| 에러 검사 | dma_mapping_error | 매핑 성공 확인 | 검사 생략 | none |
| SG 매핑 | dma_map_sg | scatter-gather 매핑 | nents 반환값 무시 | dma_unmap_sg |
| SG 해제 | dma_unmap_sg | SG 매핑 해제 | 원래 nents 전달(mapped 아닌) | none |
| 동기화 | dma_sync_single_for_cpu | CPU 접근 전 동기화 | 방향 불일치 | dma_sync_single_for_device |
| 동기화 | dma_sync_single_for_device | 디바이스 접근 전 동기화 | 크기 불일치 | dma_sync_single_for_cpu |
| DMA 풀 | dma_pool_create | 소형 DMA 객체 반복 할당 | destroy 누락 | dma_pool_destroy |
| DMA 풀 할당 | dma_pool_alloc | 풀에서 객체 할당 | GFP 문맥 오류 | dma_pool_free |
| 방향 | DMA_TO_DEVICE | CPU→디바이스 | 양방향 남용 | DMA_FROM_DEVICE |
| 방향 | DMA_FROM_DEVICE | 디바이스→CPU | 방향 반대 | DMA_TO_DEVICE |
| 방향 | DMA_BIDIRECTIONAL | 양방향 전송 | 불필요한 양방향 사용 | 단방향 우선 |
| 페이지 매핑 | dma_map_page | 페이지 단위 매핑 | 에러 검사 누락 | dma_unmap_page |
| 리소스 매핑 | dma_map_resource | 물리 주소 직접 매핑 | 사용처 제한 미인지 | dma_unmap_resource |
| 속성 | dma_alloc_attrs | 속성 지정 할당 | DMA_ATTR_* 오용 | dma_alloc_coherent |
/* DMA 스트리밍 매핑 패턴 */
dma_addr_t dma_handle;
void *buf = kmalloc(BUF_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
dma_handle = dma_map_single(dev, buf, BUF_SIZE, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
kfree(buf);
return -EIO;
}
/* 디바이스에 DMA 전송 시작 */
start_dma_transfer(dma_handle, BUF_SIZE);
/* 완료 후 해제 */
dma_unmap_single(dev, dma_handle, BUF_SIZE, DMA_TO_DEVICE);
kfree(buf);
/* 일관성 메모리 패턴 (descriptor ring) */
dma_addr_t ring_dma;
struct demo_desc *ring;
ring = dma_alloc_coherent(dev, ring_size, &ring_dma, GFP_KERNEL);
if (!ring)
return -ENOMEM;
/* ring 사용 — CPU와 디바이스 모두 동일 내용 참조 */
ring[0].addr = cpu_to_le64(data_dma);
ring[0].len = cpu_to_le32(data_len);
/* 해제 */
dma_free_coherent(dev, ring_size, ring, ring_dma);
확장 | 펌웨어 로딩 카탈로그
펌웨어 로딩은 디바이스 초기화 경로의 외부 의존성을 관리하는 API입니다. 로딩 시점(probe/resume), 실패 정책(선택적/필수), 보안 검증을 함께 결정해야 합니다. 특히 initramfs에 펌웨어가 포함되지 않으면 시스템 부팅이 실패할 수 있으므로, fallback 전략이 필수입니다.
실무 체크포인트: 펌웨어 경로/이름을 하드코딩하지 말고 MODULE_FIRMWARE로 선언하여 빌드 시스템에서 추적 가능하게 하세요.
| 분류 | 핵심 API | 언제 사용 | 실수 패턴 | 대체/보완 |
|---|---|---|---|---|
| 동기 로딩 | request_firmware | 펌웨어 필수 로딩 | release 누락 | release_firmware |
| 선택적 로딩 | firmware_request_nowarn | 펌웨어 없어도 진행 | 실패 시 fallback 누락 | request_firmware |
| 비동기 로딩 | request_firmware_nowait | probe 비차단 | 콜백 수명주기 오류 | request_firmware |
| 직접 로딩 | request_firmware_direct | uevent 비사용 로딩 | 사용자 공간 fallback 기대 | request_firmware |
| 캐시 | firmware_request_cache | suspend 전 캐시 | 캐시 만료 관리 누락 | request_firmware |
| 해제 | release_firmware | 펌웨어 버퍼 해제 | 이중 해제 | none |
| 선언 | MODULE_FIRMWARE | 의존 펌웨어 선언 | 선언 누락 (배포 패키지에서 누락) | none |
| 내장 | CONFIG_EXTRA_FIRMWARE | 커널 이미지에 펌웨어 포함 | 라이선스 충돌 | initramfs 포함 |
| 크기 검증 | fw->size | 펌웨어 크기 확인 | 크기 미검증으로 오버런 | 헤더 매직/CRC 검증 |
| 데이터 접근 | fw->data | 펌웨어 바이너리 접근 | 해제 후 접근 | 복사 후 사용 |
/* 펌웨어 로딩 기본 패턴 */
static int demo_load_fw(struct device *dev)
{
const struct firmware *fw;
int ret;
ret = request_firmware(&fw, "demo/fw.bin", dev);
if (ret) {
dev_err(dev, "firmware load failed: %d\n", ret);
return ret;
}
if (fw->size < sizeof(struct demo_fw_hdr)) {
dev_err(dev, "firmware too small: %zu\n", fw->size);
ret = -EINVAL;
goto out;
}
/* 펌웨어를 디바이스에 전송 */
ret = demo_upload_fw(dev, fw->data, fw->size);
out:
release_firmware(fw);
return ret;
}
MODULE_FIRMWARE("demo/fw.bin");
확장 | 실전 함정 패턴 모음
코드 리뷰에서 반복적으로 발견되는 실전 함정을 패턴화한 모음입니다. 각 항목은 "컴파일은 통과하지만 장애를 유발하는" 코드를 중심으로, 잘못된 코드와 올바른 수정을 나란히 제시합니다. 새 패치를 제출하기 전에 이 목록을 체크리스트로 역검증하면 리뷰 지적의 상당수를 선제적으로 제거할 수 있습니다.
실무 체크포인트: 아래 패턴 중 하나라도 현재 코드에 존재하면, 기능 동작과 무관하게 즉시 수정 우선순위로 분류하세요.
| # | 함정 이름 | 잘못된 코드 | 올바른 수정 | 분류 |
|---|---|---|---|---|
| 1 | ERR_PTR NULL 혼동 | if (!ptr) → ERR_PTR 놓침 | if (IS_ERR(ptr)) | 반환값 |
| 2 | krealloc 원본 유실 | p = krealloc(p, ...) → 실패 시 원본 유실 | tmp = krealloc(p, ...); if (!tmp) { ... } p = tmp; | 수명주기 |
| 3 | copy_from_user 반환값 무시 | copy_from_user(dst, src, n); | if (copy_from_user(...)) return -EFAULT; | 반환값 |
| 4 | IRQ 내 mutex | hardirq 핸들러에서 mutex_lock() | spin_lock_irqsave() 또는 워크큐 defer | 문맥 위반 |
| 5 | devm+수동 혼용 | devm_kzalloc 후 kfree 수동 호출 | devm_*만 사용 또는 수동만 사용 | 수명주기 |
| 6 | 순회 중 삭제 | list_for_each_entry 내 list_del | list_for_each_entry_safe 사용 | 수명주기 |
| 7 | spin_lock 내 sleep | spin_lock() 보유 중 kmalloc(GFP_KERNEL) | GFP_ATOMIC 사용 또는 락 밖 할당 | 문맥 위반 |
| 8 | WRITE_ONCE 게시 오해 | WRITE_ONCE(data, val); WRITE_ONCE(flag, 1); | WRITE_ONCE(data, val); smp_store_release(&flag, 1); | 순서/가시성 |
| 9 | unwind 순서 역전 | 할당 순서: A→B→C, 해제: A→B→C | 해제 역순: C→B→A | 수명주기 |
| 10 | dma_map 에러 무시 | dma_map_single() 후 검사 없이 사용 | if (dma_mapping_error(...)) return -EIO; | 반환값 |
| 11 | tasklet 긴 작업 | tasklet 콜백에서 수백 µs 처리 | workqueue로 이관 | 문맥 위반 |
| 12 | cancel_work 자기 문맥 | 워크큐 핸들러 내 cancel_work_sync(self) | cancel_work_sync는 외부에서만 호출 | 수명주기 |
| 13 | snprintf 반환값 혼동 | len = snprintf(buf, sz, ...) → len > sz 미검사 | scnprintf 사용 또는 잘림 검사 | 반환값 |
| 14 | RCU read 내 sleep | rcu_read_lock() 내 mutex_lock() | SRCU 사용 또는 구조 변경 | 문맥 위반 |
| 15 | refcount 0에서 get | kref_get() 이미 0인 객체에 호출 | kref_get_unless_zero() + 반환값 검사 | 수명주기 |
| 16 | irqsave flags 전달 | flags를 다른 함수에 전달 후 restore | 같은 함수 내에서 save/restore 쌍 유지 | 문맥 위반 |
| 17 | posted write 미완료 | writel(val, reg) 후 즉시 종료 | writel(val, reg); (void)readl(reg); 으로 flush | 순서/가시성 |
| 18 | GFP_KERNEL in softirq | softirq 컨텍스트에서 kmalloc(GFP_KERNEL) | GFP_ATOMIC 사용 | 문맥 위반 |
| 19 | module_put 언밸런스 | try_module_get 없이 module_put | get/put 쌍 유지 | 수명주기 |
| 20 | ARRAY_SIZE 포인터 적용 | 포인터에 ARRAY_SIZE 사용 | 크기를 별도 인자로 전달 | 반환값 |
/* 함정 #2 krealloc 원본 보호 패턴 */
void *tmp = krealloc(buf, new_size, GFP_KERNEL);
if (!tmp) {
/* buf는 여전히 유효 — 기존 데이터 보존 */
pr_warn("realloc failed, keeping old buffer\n");
return -ENOMEM;
}
buf = tmp;
/* 함정 #9 올바른 unwind 순서 */
static int demo_init(void)
{
int ret;
ret = step_a_init(); /* 1번째 */
if (ret)
return ret;
ret = step_b_init(); /* 2번째 */
if (ret)
goto undo_a;
ret = step_c_init(); /* 3번째 */
if (ret)
goto undo_b;
return 0;
undo_b:
step_b_exit(); /* 역순: 2번 해제 */
undo_a:
step_a_exit(); /* 역순: 1번 해제 */
return ret;
}
/* 함정 #8 올바른 게시 순서 */
WRITE_ONCE(shared_data, new_value);
smp_store_release(&data_ready, 1);
/* 읽기 측 */
if (smp_load_acquire(&data_ready))
use(READ_ONCE(shared_data));
운영 | 보안 취약점 대응 플레이북
취약점 대응은 탐지 속도보다 재현 가능성과 완화 우선순위가 중요합니다. 아래 표는 커널 취약점 대응의 실전 기준 절차입니다.
| 단계 | 핵심 액션 | 사용 API/도구 | 실수 패턴 | 완화 포인트 |
|---|---|---|---|---|
| 탐지 | 이상 징후 수집 | audit_log*, tracepoint, dmesg | 단일 로그 소스만 의존 | 증거 다중 수집 |
| 분류 | 취약점 유형 판정(UAF/OOB/경합) | KASAN/KCSAN/UBSAN | 재현 전 단정 | 유형별 재현 템플릿 사용 |
| 재현 | 최소 입력으로 재현 시나리오 고정 | syzkaller repro, kselftest | 환경 차이 방치 | 커널 config/커밋 고정 |
| 영향도 | 권한 상승/정보노출/DoS 범위 평가 | LSM hook 추적, capability 검토 | 로컬/원격 경계 혼동 | CVSS/운영 맥락 병행 |
| 원인 추적 | 문제 커밋/경로 식별 | git bisect, ftrace | 증상 커밋만 수정 | invariant 붕괴 지점 확인 |
| 즉시 완화 | 공격 표면 축소 | sysctl, module blacklist | 서비스 영향 미검토 | 가역적 완화 우선 |
| 패치 설계 | 근본 원인 수정 + 방어코드 | refcount_t, READ_ONCE | 증상 우회 코드 | 수명주기/동기화 재설계 |
| 검증 | 회귀/성능/안정성 확인 | KUnit, kselftest, perf | 정상 경로만 테스트 | 실패 경로 자동화 포함 |
| 배포 | 백포트/릴리스 노트 반영 | stable queue, CVE 공지 | 영향 버전 누락 | 수정 대상 버전 명확화 |
| 사후 | 탐지 룰/가드레일 강화 | Coccinelle, CI policy | 단발성 대응 종료 | 재발 방지 룰 추가 |
/* UAF 완화 예시: refcount로 수명주기 보호 */
struct demo_obj {
refcount_t refcnt;
spinlock_t lock;
struct rcu_head rcu;
};
static bool demo_get(struct demo_obj *o)
{
return refcount_inc_not_zero(&o->refcnt);
}
static void demo_put(struct demo_obj *o)
{
if (refcount_dec_and_test(&o->refcnt))
kfree_rcu(o, rcu);
}
운영 | 커널 패닉/크래시 즉시 대응 절차
패닉 대응은 "재부팅"보다 "증거 보존"이 먼저입니다. 아래 절차는 운영 현장에서의 우선순위를 반영합니다.
| 시점 | 즉시 조치 | 수집 항목 | 도구/명령 | 주의점 |
|---|---|---|---|---|
| T0 | 자동 재부팅 정책 확인 | panic timeout, watchdog | sysctl kernel.panic | 증거 수집 전 재부팅 방지 |
| T0+ | 콘솔 출력 확보 | Oops/Panic trace | serial console, netconsole | 스크린샷만 남기지 말 것 |
| T1 | 메모리 덤프 경로 확인 | vmcore | kdump/crashkernel | 덤프 공간 부족 점검 |
| T1+ | pstore 수집 | ramoops 레코드 | /sys/fs/pstore | 재부팅 시 덮어쓰기 주의 |
| T2 | 락업 유형 분류 | softlockup/hardlockup/RCU stall | watchdog trace | 증상 명칭 혼동 금지 |
| T2+ | 모듈/커널 정보 고정 | 빌드 ID, taint, config | uname -a, /proc/sys/kernel/tainted | 버전 추정 금지 |
| T3 | 재현 조건 최소화 | 입력/로드/시점 | stress, repro script | 동시 변경 최소화 |
| T3+ | 콜트레이스 해석 | faulting RIP/stack | crash, gdb, addr2line | inlined frame 누락 주의 |
| T4 | 완화 패치 적용 | hotfix 결과 | livepatch/quick patch | 근본 수정과 분리 관리 |
| T5 | 사후 리포트 작성 | 원인/영향/재발방지 | incident template | 타임라인 정확성 유지 |
운영 | 성능 튜닝 체크리스트 (워크로드별)
성능 튜닝은 워크로드 유형별 병목이 다르므로 단일 "최적화 옵션"이 없습니다. 측정 지표를 먼저 고정한 뒤 조정해야 합니다.
| 워크로드 | 주요 병목 | 1차 측정 | 튜닝 포인트 | 회귀 체크 |
|---|---|---|---|---|
| 고QPS 네트워크 | irq/NAPI 경합, skb 할당 | perf top, /proc/softirqs | RPS/XPS, napi budget, page_pool | p99 지연, drop율 |
| 저지연 트레이딩 | 스케줄 지터, irq-off 시간 | osnoise, trace_irqsoff | CPU isolation, NO_HZ_FULL | worst-case latency |
| 대용량 스토리지 | 큐 깊이, flush 비용 | iostat, blktrace | mq-deadline/bfq, io_uring iopoll | tail latency, write amp |
| DB OLTP | lock 경합, page cache miss | perf c2c, vmstat | NUMA binding, dirty ratio, hugepage | TPS, 99p commit time |
| 로그 수집/스트리밍 | context switch, copy overhead | perf sched, bpftrace | batching, zero-copy, busy-poll | CPU/throughput 균형 |
| 컨테이너 멀티테넌트 | cgroup 경쟁, PSI 상승 | psi, cgroup stat | cpu.weight, memory.high, io.max | noisy-neighbor 억제율 |
| GPU 추론 | DMA-BUF 동기화, IRQ burst | drm trace, irqstat | fence batching, cpuset 분리 | frame time, jitter |
| 미디어 인코딩 | V4L2 queue underflow | vb2 stats, ftrace | buffer depth, dma burst 조정 | drop frame 비율 |
| 가상화 호스트 | VM-exit 폭증, dirty logging | perf kvm stat | hugepage, posted interrupt, halt poll | guest steal time |
| 파일 서버 | dentry/inode cache thrash | slabtop, vfsstat | readahead, writeback, nfsd thread | ops/sec, cache hitrate |
운영 | 적용 순서 해설
문서를 읽고 실제 코드/시스템에 반영할 때는 아래 순서를 추천합니다. 이 순서는 장애 위험을 낮추고 롤백을 쉽게 만듭니다.
- 관측 먼저
로그 레벨, tracepoint, 기본 메트릭을 먼저 켭니다. 관측 없는 최적화는 회귀를 숨깁니다. - 안전성 다음
반환값 규약, 락 순서, 해제 경로를 고정합니다. 성능 이전에 crash/UAF를 먼저 제거합니다. - 성능 마지막
핫패스에 한정해서 튜닝합니다. 모든 경로를 동시에 건드리면 원인 분리가 불가능해집니다. - 롤백 준비
변경 전/후 수치를 저장하고 즉시 되돌릴 스위치(sysctl/module param)를 남깁니다.
운영 | 실전 체크리스트
이 체크리스트는 문서 전반의 규약을 배포 직전 점검 항목으로 재구성한 것입니다. 각 항목은 단독 통과보다 항목 간 정합성이 중요하며, 하나라도 누락되면 장애 전파 경로가 열릴 수 있습니다. 운영 반영 전에는 기능 테스트와 별도로 이 목록을 독립 검증해야 합니다.
실무 체크포인트: 체크리스트 점검 결과를 릴리스 노트에 남겨 회귀 시점 추적이 가능하도록 관리하세요.
- 포인터 반환 API는
IS_ERR()또는NULL규약을 문서로 고정했는가? - 락 획득 순서와 해제 순서를 함수 주석 또는 설계 문서에 명시했는가?
copy_from_user/copy_to_user반환값을 모두 처리했는가?devm_*와 수동 해제 API를 혼용하지 않았는가?- 심볼 export는 최소 범위만 공개하고 네임스페이스를 설정했는가?
- 로그 레벨이 운영 기준에 맞고 폭주 억제 전략이 있는가?
- 커널 모듈 — 모듈 수명주기와 심볼 의존성 실전
- C 언어 & 커널 C 관용어 — 매크로 내부 구현과 타입 안정성
- 디버깅 & 트러블슈팅 — tracepoint, lockdep, crash 분석 루틴
관련 문서
- GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문
- 크래시 분석 심화 — panic/oops/call trace 해석, crashkernel/kdump vmcore
- GNU Binutils 완전 가이드 — readelf/objdump/nm/objcopy 중심으로 ELF 섹션·심볼·재배치 해석,