TT Lab
블로그 시작하기
배우기 러닝패스 코스

리눅스 커널과 eBPF

프로그램은 커널에 부탁해서 산다

TT Lab 에서 이어서 보기

한 줄 요약

사용자 프로그램은 파일·네트워크·시간·메모리 같은 자원을 직접 만질 수 없고 시스템 콜로 커널에 부탁합니다. 그 부탁을 받아 적으면 프로그램이 무엇을 하는지 거의 다 보입니다. 다만 받아 적는 방법에 따라 비용이 크게 다릅니다.

왜 경계가 필요했나

여러 프로그램이 한 기계를 나눠 쓰려면 누군가 자원을 중재해야 합니다. 프로그램이 디스크 블록에 직접 쓰거나 다른 프로세스의 메모리를 읽을 수 있다면 버그 하나가 기계 전체를 망가뜨립니다. 그래서 CPU 는 실행 권한을 두 단계(x86 의 ring 3 과 ring 0)로 나누고, 사용자 프로그램은 낮은 권한에서만 돕니다. 자원이 필요하면 정해진 문(시스템 콜)으로 커널에 부탁하고, 커널은 권한을 확인한 뒤 대신 해 줍니다.

이 구조 덕분에 커널은 모든 부탁을 한곳에서 봅니다. 장애 조사에서 시스템 콜이 강력한 이유가 여기 있습니다. 로그는 프로그램이 남기기로 한 것만 남지만, 시스템 콜은 프로그램이 피해 갈 수 없습니다.

어떻게 동작하나

x86_64 에서 프로그램은 시스템 콜 번호를 rax 에, 인자를 rdi·rsi·rdx 같은 레지스터에 넣고 syscall 명령을 실행합니다. CPU 는 커널 모드로 바뀌어 커널의 진입점으로 뛰고, 커널은 번호표에서 처리 함수를 찾아 실행한 뒤 결과를 rax 에 넣고 돌아옵니다. 실패하면 음수 오류 번호가 돌아오고, C 라이브러리가 그것을 -1 과 errno 로 바꿉니다. strace 가 = -1 ENOENT 라고 찍는 것이 그 값입니다.

strace 는 커널의 ptrace 기능으로 대상 프로세스에 붙습니다. 대상이 시스템 콜에 들어갈 때와 나올 때마다 커널이 대상을 멈추고 strace 를 깨우며, strace 는 레지스터와 메모리를 읽어 사람이 읽을 글로 바꾼 뒤 대상을 다시 풀어 줍니다. 시스템 콜 하나에 문맥 교환이 네 번 붙는 셈이라, 시스템 콜이 잦은 프로그램일수록 몇십 배 느려집니다.

모든 부탁이 커널까지 가는 것은 아닙니다. 지금 시각처럼 아주 자주 묻고 값이 단순한 것은 커널이 프로세스마다 넣어 주는 작은 공유 라이브러리 vDSO 가 사용자 공간에서 바로 답합니다. 커널은 시각을 공유 페이지에 계속 적어 두고, vDSO 의 clock_gettime 은 그 페이지를 읽기만 합니다. 그래서 date 를 strace 로 보면 시간을 읽었는데도 그 시스템 콜이 보이지 않습니다. 반대로 프로세스가 쓴 CPU 시간처럼 커널이 그 순간 계산해야 하는 값은 진짜 시스템 콜이 납니다.

strace 의 한 줄은 openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3 처럼 시스템 콜 이름, 사람이 읽게 풀어 쓴 인자, 등호 뒤의 반환값으로 이루어집니다. 반환값이 파일 디스크립터 번호이면 그 뒤 read(3, …) 줄을 따라가 무엇을 읽었는지 볼 수 있습니다. 실패한 줄은 = -1 ENOENT (No such file or directory) 처럼 오류 이름과 설명이 붙습니다. -T 를 주면 각 호출에 걸린 시간이, -tt 를 주면 시각이 붙어서 "어느 호출에서 멈췄나" 를 찾을 수 있습니다.

/proc 은 그 반대 방향의 창입니다. 커널은 프로세스마다 /proc/<pid>/ 를 만들어 열린 파일(fd/), 메모리 지도(maps), 상태(status) 같은 내부 상태를 파일 꼴로 보여 줍니다. 프로세스를 멈추지 않고 읽을 수 있다는 점이 strace 와 다릅니다.

현장에서 만나는 모습

설정이 반영되지 않는다는 신고가 오면 먼저 strace -f -e trace=openat 으로 프로그램이 실제로 어느 파일을 여는지 봅니다. 문서에 적힌 경로와 다른 곳을 읽고 있거나, 환경 변수 때문에 다른 디렉터리를 먼저 뒤지는 경우가 흔합니다. 이때 -f 를 빼면 셸 스크립트나 서비스 래퍼가 띄운 자식이 한 일을 놓칩니다.

디스크가 꽉 찼는데 지운 파일 크기만큼 공간이 돌아오지 않는다면 /proc/<pid>/fd 에서 (deleted) 로 끝나는 링크를 찾습니다. 어떤 프로세스가 아직 그 파일을 쥐고 있는 것입니다.

그리고 운영 중인 데이터베이스에 strace 를 오래 붙여 두었다가 응답 시간이 수십 배로 늘어난 사고는 생각보다 흔합니다. strace 는 짧게, 범위를 좁혀 쓰는 도구입니다. 오래 지켜봐야 한다면 이 코스 뒤쪽의 eBPF 로 넘어갑니다.

English corner

커널 메일링 리스트와 커밋 메시지에서 이 단원의 내용이 어떻게 쓰이는지 봅니다.

다음 실습에서 할 것

VM 에서 ls 의 시스템 콜을 세고, 셸 스크립트가 숨겨 둔 설정 파일을 자식 프로세스까지 추적해 찾고, 서비스가 쥔 로그 파일을 /proc 에서 찾습니다. date 와 프로세스 CPU 시간으로 vDSO 의 경계를 확인하고, 마지막으로 strace 가 프로그램을 몇 배 느리게 만드는지 직접 잽니다.