eBPF 함수란 무엇인가?

조성열·2026년 3월 18일

eBPF

목록 보기
4/5
post-thumbnail

들어가며

eBPF에서 함수라고 하는 것은 eBPF Program을 뜻하기도 하고, sub-program, 앞선 포스트에서 몇 번 언급한 Helper 함수 등 여러 가지를 하나로 그냥 함수라고 한다. 이번 포스트에선 함수 호출 규칙과 sub-program 함수 사용에 대한 내용을 정리 해보고자 한다.

함수 호출 규칙

먼저, eBPF 함수 호출시 네이티브 시스템(ex. Linux) 함수 호출과 달리 인자 처리 과정에서 메모리 스택을 사용하지 않고 레지스터에서 값을 처리하고 바로 호출이 된다. R0 레지스터는 함수의 반환 값, R1 ~ R5 레지스터는 함수 인자 저장에 사용되고 함수 인자 순서대로 각 레지스터에 저장된다. 네이티브 시스템과 달리 메모리 스택을 사용하지 않는 이유는 Stack Overflow, 포인터 연산 복잡성과 같은 Kernel Level에서 안정성을 해칠 수 있는 가능성이 존재하기 때문이고, Verifier의 상태 검사에서 복잡성 증가, 하드웨어 레지스터 Mapping을 고려 했을 때 5개로 제한을 한 것이다.

하지만 이러한 제약을 해결하기 위해 간접 참조 방식으로 구조체를 사용하여 더 많은 인자를 전달할 수 있다. 인자 값을 저장하는 R1 ~ R5는 함수 호출 후 초기화 되지만 R6 ~ R9 레지스터는 호출된 함수의 값을 저장하는 기능을 하고, 호출된 함수의 값을 저장하여 호출 값이 유지된다. 마지막으로 eBPF Program, sub-program, Helper 함수, kfuncs 등 모든 함수의 호출 규칙은 eBPF Instruction Set이 정의한다.

정리

  • 함수 호출 규칙 정의 -> eBPF Instruction Set
  • 함수 호출 방식 -> 메모리 스택을 사용하지 않고, 레지스터에서 값을 처리하고 바로 호출
  • 함수 반환값 -> R0 레지스터, 함수 인자값 -> R1 ~ R5(Volatile), 호출된 함수의 값 -> R6 ~ R9(nonVolatile)
  • 함수 반환값은 5개로 제약, but 구조체를 활용해서 간접 참조 방식으로 더 많은 인자 전달 가능

Sub Program

Sub Program은 BPF-to-BPF 함수라고 한다. BPF-to-BPF 함수 호출은 동일한 프로그램 내에서 로직 재사용을 가능하게 한다. 이때도 마찬가지로 최대 5개 인자를 받을 수 있고, 함수는 새로운 스택 프레임을 할당받아 프로그램 종료 후 해제되어 재사용된다. 여기서 말하는 스택 프레임은 네이티브 커널의 메모리 스택 프레임이 아닌 eBPF 가상머신 전용 스택 영역에서의 스택 프레임을 의미한다. 또한 스택에 있는 포인터를 인자로 전달하면 해당 스택에 접근할 수 있다.

함수 Inlining

함수 Inlining이란 컴파일 과정에서 함수를 호출하는 명령(Call)을 생성하는 대신 함수 body 코드를 호출이 발생하는 위치에 직접 복사해서 넣는 최적화 기법을 말한다. 일반적으로 함수가 호출되는 과정은 1) 인자값을 레지스터 또는 스택에 배치, 2) 현재 실행 지점(PC)을 저장하고 함수 주소로 점프, 3) 함수 종료 후 원래 위치(주소)로 점프로 이루어진다. 이 과정에서 오버헤드가 발생하는데, 인라인으로 처리할 경우 이런 과정이 사라져 호출이 발생하는 위치에 함수가 원래 있었던 거처럼 동작한다.
함수 인라인 여부는 __attribute__((always_inline))/ __always_inline 또는 attribute((noinline))과 같은 인수를 사용하여 함수 인라인 여부를 지정할 수 있다.

검증

함수 검증을 할 땐 정적 함수(static)와 전역 함수를 다르게 검증한다.

  • 정적함수
    C 코드에서 static 키워드가 붙은 함수로, Verifier는 이를 호출하는 지점마다 매번 안전성을 다시 확인하는 방식을 채택한다. 이 방식은 호출자가 넘긴 인자의 구체적인 값 또는 범위를 알고 있는 상태로 진행하여 함수 내부에서 이 범위를 벗어나는 연산이 없음을 별도 처리없이 증명할 수 있다. 하지만 지난 포스트에서 언급한 상태 폭발 문제가 발생할 수 있다.
  • 전역함수
    전역 함수에 대한 검증은 함수와 호출 Context를 분리해서 독립적으로 검증하는 함수별 검증 방식을 채택한다. 전역함수는 프로그램 내 모든 전역 함수를 순서 상관없이 한 번만 검사한다. 검증 횟수가 고정되어 검증 시간과 복잡도가 선형적으로 관리된다. 하지만 어떤 값이 들어올지 전혀 모르는 상태를 가정하기 때문에 인자로 들어올 수 있는 모든 값을 고려하여 검사가 진행된다. 이러한 특성으로 개발자는 함수 내부에서 인자의 유효 범위를 확인하는 로직을 강제로 추가 해야한다는 제약 조건이 따른다. 또한, 도입 초기에는 반환값은 숫자(Scalar)로, 인수는 컨텍스트 포인터나 숫자로만 제한한다.

정리하면 정적 함수의 검증 방식은 개발 편의성은 높지만 검증으로 인한 시스템 오버헤드가 증가하고, 전역 함수의 검증 방식은 시스템 오버헤드가 낮지만 개발 편의성은 저하되는 결과를 가지고 오기 때문에 상황에 맞게 함수를 작성할 필요가 있다.

Tail Call과 함수의 혼합 사용

Kernel 5.10 이전 버전에서는 eBPF Program 내에서 일반 함수 호출과 Tail Call을 동시에 사용할 수 없었지만 5.10 버전부터 가능해졌다. 하지만 커널 안정성을 위해 몇 가지 제약이 따른다.

  • 스택 크기 감소 (512 byte -> 256 byte)
    Tail Call은 스택 프레임을 재사용하면서 다음 프로그램을 호출하는데, 일반 함수 내부에서 Tail Call이 발생할 경우 누적 스택 사용량이 커널 스택 한계를 초과하여 시스템 패닉으로 이어질 수 있기 때문에 스택 크기를 감소시켜 안정성을 확보하면서 해당 기능을 사용 가능하게 했다.

  • Tail Call Counter 전파
    Tail Call과 일반 함수를 혼합해서 사용할 때 Tail Call의 최대 실행 횟수인 32회를 정확히 체크하기 위해 카운터 값을 일반 함수 호출 시에도 레지스터나 특정 슬롯을 통해 계속 전달하도록 했다.

결론적으로 몇 가지 제약이 분명하여 개발시 어려움이 있지만 Tail Call과 일반 함수를 혼합해서 사용 가능하게 하여 확장성과 모듈화를 가능하게 했다.

마치며

사실상 함수는 eBPF Program의 개념을 더 심층적으로 알아보고 어떻게 활용할 수 있는지 어떤 제약 조건이 있는지 알 수 있었던 섹션이었다. Tail Call은 지금까지 정리한 내용들에서 지속적으로 나오고 있고, 공식 문서에도 따로 정리 되어 있는만큼 핵심적인 기능인거 같다.
다음 포스트에선 동시성이라는 개념에 대해 정리 해보도록 하겠다.

profile
Blue Team

0개의 댓글