BPF와 관측가능성
BPF 책을 보면 볼수록 흥미로워서 내용을 정리해본다. BPF는 패킷 필터로 시작해서 현재는 리눅스 시스템에 관측가능성을 부여하는 도구이다. 만들어진지는 거의 리눅스 급으로 오래됐는데 최근 5년간 새로운 용도(?)를 발견하면서 많이 핫해진것 같다. 이 글에서는 BPF가 어떤 구조로 되어있는지를 간단하게 설명한다. 더 공부하면서 bcc나 bpftrace에서 어떻게 프로그램을 실행하는지, BPF 관련 프로젝트에 무엇이 있는지 등등을 정리하려고 한다.
예전에도 BPF라는게 있구나 정도는 알았는데 관측가능성이라는 말의 의미가 잘 와닿지 않아서 그냥 스쳐 지나갔었다. BPF는 사용자 공간에서 명령어 몇개만으로 커널을 새로 빌드하지 않고도 커널 코드를 안전하게 수행할 수 있다. 심지어 커널 코드를 사용자 프로그램 실행하듯 필요할 때 실행할 수 있다.
BPF는 유연함과 안정성으로 사용자가 커널 내부를 들여다볼 수 있도록 한다. 즉, 리눅스 안에서 무슨 일이 일어나는지를 분석할 수 있는 것이다. 관측가능성을 부여하면 리눅스에서 왜 지연 시간이 발생하는지, 어느 지점에서 병목 현상이 발생하는지 등을 관찰할 수 있게 된다.
BPF를 활용한 도구들
위 그림은 기존의 BPF 도구와 「BPF 성능 분석 도구」에서 소개하는 도구들을 그림으로 나타낸 것으로 BPF를 활용하면 스케줄러, 메모리 관리자, 파일 시스템 등등 커널의 다양한 부분에서 어떤 이벤트가 발생하는지 간편하게 분석할 수 있도록 도와준다.
BPF: Berkely Packet Filter
BPF는 1992년에 패킷 필터로 패킷을 분석하고 필터링하는데에 사용되는 in-kernel virtual machine이다. BSD에서 처음 도입했으며 리눅스에서도 이 개념을 빌려와서 서브시스템을 만들었다. in-kernel virtual machine이라고 함은 정말로 가상의 레지스터와 스택 등을 갖고 있으며 이를 바탕으로 코드를 실행한다는 뜻이다.
BPF를 도입한 논문을 참고용으로 링크로 남긴다. The BSD Packet Filter: A New Architecture for User-level Packet Capture)
처음에는 virtual machine이라고 해서 속도가 느릴거라고 생각했는데 JIT (Just In-Time) 컴파일러가 추가되면서 BPF가 실행하는 명령어와 프로세서 사이에 어떤 계층이 존재하는 것이 아니라 프로세서가 직접 명령어를 수행하게 된다.
eBPF: extended BPF
eBPF는 확장 BPF라는 뜻이다. 기존의 BPF에서 사용하던 머신에서 더 나아가서 레지스터의 크기를 늘려주고 스택과 맵을 도입하는 등의 변화가 있었다. 그래서 기존의 BPF를 cBPF (classic BPF)라고 부르고 새로운 BPF를 eBPF로 부르게 되었다.
위 사진은 정말 간단하게 classic BPF와 extended BPF의 차이를 보여준다. 그런데 BPF는 용어가 정말 헷갈린다. 몇년 전까지는 BPF와 eBPF 용어를 구분해서 말했다고 하는데, Brendan Gregg의 BPF 성능 분석 도구를 보면 이제는 classic BPF로 만들어진 부분도 모두 eBPF로 다시 구현되었기 때문에 eBPF도 BPF라고 부른다고 한다. 용어가 좀 헷갈린다.
아래부터는 ebpf.io를 보면서 정리했다.
How BPF works
BPF 프로그램은 위의 코드처럼 커널 코드 내에 미리 정의된 훅이나 kprobe, uprobe, tracepoint를 사용해서 프로그램을 실행할 수 있다. 위의 그림은 간단한 예시로 execve 시스템 호출이 실행될 때마다 BPF 프로그램을 실행해서 새로운 프로세스가 어떻게 만들어지는지를 관찰할 수 있다.
How are eBPF programs written?
BPF 프로그램은 C 코드에서 LLVM 중간 표현으로 번역된 후 BPF 바이트코드로 다시 번역된다.
Loader & Verification Architecture
그렇게 만들어진 바이트코드를 Verifier로 검증한 후 JIT 컴파일러로 컴파일하면 실행할 수 있는 상태가 된다.
BPF Verifier
BPF는 사용자측에서 가져온 코드를 커널에서 실행하기 때문에 안전성이 매우 중요하다. 따라서 코드를 검증할 필요가 있는데, 시스템의 안정성을 해칠만한 코드는 사용할 수 없다. 예를 들어서 무한 루프가 발생할 수 있기 때문에 반복문은 매우 제한적으로 지원한다. 모든 BPF 프로그램은 Verifier를 통과해야만 실행된다.
BPF의 장점
BPF의 최대 장점은 커널을 새로 빌드할 필요 없이 바로 코드를 실행해볼 수 있다는 점이다. 물론 애초에 커널의 기능을 바꿀 일이 있다면 소스를 고치는게 맞지만, 트레이싱을 하는 경우에는 필요할 때만 트레이싱 코드를 실행하고 작업이 끝나고 나면 다시 그 코드를 비활성화해야 하는데 그럴 떄마다 매번 커널을 새로 빌드할 필요가 없다.
BPF의 단점
- 매우 자주 실행되는 함수를 트레이싱할 경우 오버헤드가 엄청나다.
- 인라인 함수를 트레이싱 하려면 매우 번거롭다.
- 사용자 공간 함수를 트레이싱 하는 경우에는 커널 공간을 들렀다가 가야 하므로 비효율적이다. (2019년 책에서 설명하는 기준이라 유효하지 않을 수 있다.)
- 지원되는 구문이 제한적이다. (위에서 말한 반복문 처럼)
- 고정된 스택을 갖는다 (512바이트)
여담
개인적으로 슬랩 할당자를 보면서 매우 일차원적인 벤치마크 결과만 보다보니까 이해도 잘 안되고 그랬는데 BPF, Perf 같은 성능 분석 도구들을 보다보니까 이걸 왜 이제 알았나 싶은 생각이 든다.
성능 분석에 관심이 있다면 Brendan Gregg의 블로그와 책들은 정말 읽어볼만하다.
블로그: https://www.brendangregg.com/
책들:
- BPF 성능 분석 도구 (원서: BPF Performance Tools)
- System Performance 2판 (1판은 한국어판이 있다.)
참고 문서
'Kernel > BPF & Tracing' 카테고리의 다른 글
[Linux Kernel] kprobe와 kretprobe (2) | 2021.11.02 |
---|
댓글