比驱动更早出问题的:内核头文件
한국어 원문으로 표시합니다.
한 줄 요약
드라이버 반입에서 가장 많이 깨지는 것은 드라이버가 아니라 커널 헤더다. DKMS 는 실행 중인 커널과 정확히 같은 버전의 빌드 트리로 모듈을 빌드하고, 반입한 헤더가 한 글자만 달라도 컴파일을 시작하기 전에 멈춘다. Secure Boot 가 켜진 서버라면 그렇게 만든 모듈에 서명하고 그 키를 펌웨어에 등록하는 단계까지 반입 절차에 들어간다.
왜 이게 필요했나
앞 모듈들은 더미 .deb 로 드라이버의 의존성과 설치 순서를 연습했다. 실제 NVIDIA 드라이버는 재배포할 수 없어서다. 그런데 현장의 사고는 대개 그 다음에 난다. "드라이버 패키지는 설치됐는데 nvidia-smi 가 couldn't communicate with the NVIDIA driver" — 커널 모듈이 만들어지지 않은 것이다. 원인을 따라가면 헤더가 없거나, 받은 헤더가 linux-headers-generic(가장 최신 커널을 가리키는 메타 패키지)이라 실행 중인 커널과 다르거나, 모듈은 만들어졌는데 Secure Boot 가 서명 없는 모듈을 거절한 경우다.
어떻게 동작하나
DKMS 의 규칙. DKMS 문서에 따르면 소스는 /usr/src/<PACKAGE_NAME>-<PACKAGE_VERSION>/ 에 두고, 그 안의 dkms.conf 에는 PACKAGE_NAME·PACKAGE_VERSION 이 반드시 있어야 한다. 모듈이 여럿이면 BUILT_MODULE_NAME[#](.ko 없이)가 필요하고, DEST_MODULE_LOCATION[#] 은 /kernel 로 시작해야 하지만 우분투·페도라·RHEL 등에서는 무시된다(배포판이 정한 곳에 넣는다). AUTOINSTALL="yes" 면 새 커널이 들어올 때 autoinstall 이 다시 빌드한다. MAKE[#] 를 적지 않으면 커널 빌드 트리의 make 가 쓰이고 KERNELRELEASE 가 붙는다. CLEAN 은 요즘 DKMS 가 쓰지 말라고 경고한다.
명령의 순서. dkms add 가 소스를 등록하고, dkms build -k <커널> 이 /var/lib/dkms/<이름>/<버전>/<커널>/<아키텍처>/module/ 에 모듈을 만들고, dkms install 이 커널 모듈 트리로 옮긴다. 대상 커널의 헤더가 없으면 문서에 적힌 대로 Your kernel headers for kernel <버전> cannot be found at /lib/modules/<버전>/build or /lib/modules/<버전>/source 로 멈춘다(dkms 소스의 종료 코드는 21 이지만, 이 실습 VM 의 dkms 3.0.11 은 명령 전체를 1 로 끝냈다 — 스크립트는 코드 값이 아니라 0 이 아닌지를 보라). dkms status 의 모양은 판마다 조금 다르다 — 요즘 판은 이름/버전, 커널, 아키텍처: installed 다.
헤더는 커널 문자열 그대로. 우분투의 헤더는 linux-headers-<uname -r>(플레이버별)과 그것이 요구하는 linux-headers-<버전>(공통)으로 나뉜다. 반입 목록은 uname -r 에서 시작해야 한다. linux-headers-generic 은 편리하지만 받는 날의 최신 커널을 따라가므로 폐쇄망 서버의 커널과 어긋나기 쉽다.
vermagic. 모듈에는 어떤 커널용으로 빌드됐는지를 적은 vermagic 문자열이 들어 있고 modinfo -F vermagic 으로 볼 수 있다. 다른 커널용 모듈은 로드되지 않는다. modprobe 는 depmod 가 만든 목록으로 의존 모듈까지 올리고, insmod 는 파일 하나를 올릴 뿐이다.
Secure Boot 와 MOK. 우분투 문서에 따르면 Secure Boot 가 켜진 시스템에서 DKMS 는 새 모듈을 MOK(Machine Owner Key)로 자동 서명한다. 키는 /var/lib/shim-signed/mok/MOK.priv·MOK.der 이고, 없으면 만들어 등록 요청을 건다. 등록은 다음 부팅의 MokManager 화면에서 비밀번호를 넣어야 끝난다 — 원격으로만 접근하는 폐쇄망 서버라면 콘솔 작업을 반입 일정에 넣어야 한다는 뜻이다. mokutil --sb-state 가 상태를, mokutil --import 가 DER 키의 등록 요청을 만든다. 강제 모드에서 서명 없는 모듈은 Key was rejected by service 로 거절되고, 강제하지 않는 커널은 module verification failed: signature and/or required key missing - tainting kernel 을 남기고 올린다.
현장에서 만나는 모습
이 실습 VM(우분투 24.04, 커널 6.8.0-139-generic, dkms 3.0.11)에서 재 보았다. 헤더가 없을 때 없는 커널로 빌드를 시키면 Error! Your kernel headers for kernel 6.8.0-9999-generic cannot be found at ... 다음 줄에 Please install the linux-headers-6.8.0-9999-generic package or use the --kernelsourcedir option 이 나왔다. 반입한 헤더로 빌드한 모듈은 /lib/modules/6.8.0-139-generic/updates/dkms/airgap_hello.ko.zst 로 압축되어 들어갔다. 흥미로운 것은 서명이다. Secure Boot 를 쓸 수 없는 VM(mokutil --sb-state 가 EFI variables are not supported on this system)인데도 DKMS 는 모듈에 서명했고 modinfo -F signer 는 <호스트이름> Secure Boot Module Signature key 였다. 그런데 로드할 때 커널은 module verification failed: signature and/or required key missing - tainting kernel 을 남겼다. 서명은 있지만 그 키가 커널이 믿는 키 목록에 없기 때문이다 — 강제 모드였다면 여기서 거절됐을 것이고, 그래서 MOK 등록이 반입 절차에 들어간다.
폐쇄망 GPU 서버의 커널이 반입 사이에 업데이트되면, 드라이버는 그대로인데 다음 부팅부터 모듈이 없다. AUTOINSTALL 이 새 커널로 다시 빌드하려 해도 그 커널의 헤더가 반입되지 않았기 때문이다. 커널 패키지와 그 커널의 헤더는 항상 같은 반입 묶음으로 들여야 한다. 이 실습의 VM 은 Secure Boot 를 켤 수 없어 서명 단계는 기록으로만 확인한다 — 그 한계는 실습 안내에도 적었다. 또 이 VM 의 클라우드 이미지에는 실행 중인 커널(재 본 날은 6.8.0-139-generic)의 헤더가 linux-headers-virtual 을 통해 처음부터 깔려 있었다. 폐쇄망 서버는 대개 그렇지 않으므로, 실습 준비 단계가 그 헤더를 지워 "헤더 없는 서버" 에서 시작하게 했다.
다음 실습에서 할 것
실행 중인 커널을 확인하고 그 커널의 헤더 두 패키지를 설치하지 않고 받아 해시 목록을 만든다. 바깥을 막고 반입한 .deb 로 헤더를 설치한 뒤, 20줄짜리 GPL 모듈을 DKMS 로 add·build·install 해 매개변수와 함께 로드한다. vermagic·서명·Secure Boot 상태를 기록하고, 헤더가 없는 커널로 빌드를 시켜 DKMS 의 오류를 채증한다.
참고 문서: dkms(8) · Ubuntu — UEFI Secure Boot · mokutil(1) · Kernel module signing · modprobe(8)