BPF 메모리 제어는 BPF 프로그램이 메모리 컨트롤러(memcg)와 상호작용하는 두 가지 메커니즘을 제공합니다. 첫째, BPF 프로그램과 맵의 메모리 할당을 해당 프로세스의 memcg에 계정(charge)하여 컨테이너 환경에서 BPF 리소스가 올바르게 제한되도록 합니다. 둘째, BPF 프로그램에서 memcg 통계(사용량, vm 이벤트, 페이지 상태 등)를 직접 읽을 수 있는 kfunc을 제공하여, BPF 기반 모니터링과 메모리 정책 구현을 가능하게 합니다.
커널 7.0에서 mm/bpf_memcontrol.c는 8개의 BTF kfunc을 등록하고, kernel/bpf/syscall.c와 kernel/bpf/core.c에서 bpf_memcg_flags()를 통해 할당 시 __GFP_ACCOUNT 플래그를 자동 추가합니다. 이를 통해 BPF 프로그램 할당, 맵 생성, arena 할당 등 모든 BPF 리소스가 memcg 계정 체계에 편입됩니다.
일상 비유로 보면 BPF 메모리 제어는 회사의 부서별 비용 센터와 같습니다. 각 부서(BPF 프로그램이 실행되는 memcg)가 사용하는 사무용품(BPF 할당, 맵)의 비용이 해당 부서의 비용 센터에 자동으로 청구됩니다. 또한 경영진(BPF 프로그램)이 각 부서의 비용 현황(memcg 통계)을 실시간으로 조회할 수 있는 대시보드(kfunc)가 제공됩니다.
// 소스 파일 경로
mm/bpf_memcontrol.c // BPF memcg kfunc 구현 (193줄)
include/linux/bpf.h // bpf_memcg_flags, bpf_map_memcg_enter/exit (4012줄)
include/linux/memcontrol.h // mem_cgroup, memcg_bpf_enabled_key (1939줄)
kernel/bpf/syscall.c // bpf_map_save_memcg, bpf_map_memcg_enter/exit (6616줄)
kernel/bpf/core.c // bpf_prog_alloc에서 bpf_memcg_flags 사용 (3394줄)
kernel/bpf/memalloc.c // BPF arena memcg 연동 (1052줄)
# 1. BPF 메모리 제어 활성화 확인 (memcg_bpf_enabled)
cat /proc/config.gz | gunzip | grep CONFIG_MEMCG
# CONFIG_MEMCG=y 일 때만 BPF memcg 지원
# 2. BPF 프로그램별 memcg 사용량 확인 (kfunc 활용 시)
bpftool prog show # 등록된 BPF 프로그램 목록
bpftool map show # 등록된 BPF 맵 목록
# 3. 컨테이너(Docker) 내 BPF 할당 계정 확인
cat /sys/fs/cgroup/memory.current # 현재 메모리 사용량 (바이트)
cat /sys/fs/cgroup/memory.stat | grep sock # 소켓 메모리 통계
# 4. BPF 프로그램 로드 시 memcg 계정 확인
sudo cat /sys/kernel/debug/tracing/trace_pipe &
sudo bpftool prog load /path/to/prog.o /sys/fs/bpf/test
# 5. BPF arena 할당 메모리 추적
cat /proc/bpf_memcg_usage # BPF 메모리 사용량 (커널 7.0+)
# 6. memcg 이벤트 모니터링
cat /sys/fs/cgroup/memory.events
# oom_kill: BPF 할당으로 인한 OOM 발생 시 카운트
# 7. obj_cgroup 통계 확인
cat /sys/fs/cgroup/memory.stat | grep -E "anon|file|sock|vmalloc"
# 8. BPF 맵 생성 시 memcg 계정 디버그
echo 1 > /sys/kernel/debug/bpf_memcg_debug # 디버그 모드 (해당 커널에 따라 다름)
# 9. kfunc 등록 확인
bpftool btf dump file /sys/kernel/btf/vmlinux | grep bpf_mem_cgroup
# 10. BPF 프로그램별 메모리 사용량 비교
cat /sys/fs/cgroup/memory.max # 제한值
cat /sys/fs/cgroup/memory.current # 현재值
BPF memcg kfunc이 접근하는 핵심 구조체. BPF 프로그램은 kfunc을 통해 이 구조체의 통계 필드를 읽습니다.
// include/linux/memcontrol.h:190-239
struct mem_cgroup {
struct cgroup_subsys_state css; // cgroup 서브시스템 상태
struct mem_cgroup_private_id id; // memcg 고유 ID
struct page_counter memory; // 메모리 계정 카운터 (v1 & v2)
union {
struct page_counter swap; // 스왑 카운터 (v2 only)
struct page_counter memsw; // 메모리+스왑 카운터 (v1 only)
};
struct list_head memory_peaks; // 로컬 피크 관찰자 목록
struct list_head swap_peaks;
spinlock_t peaks_lock;
struct work_struct high_work; // 인터럽트 계정 범위 강제
unsigned long zswap_max; // zswap 최대값
bool zswap_writeback; // zswap 쓰기백 제어
struct vmpressure vmpressure; // 메모리 압력 알림
bool oom_group; // OOM 그룹 킬 여부
int swappiness; // 스왑 아웃 선호도
struct cgroup_file events_file; // memory.events 핸들
struct cgroup_file events_local_file;
struct cgroup_file swap_events_file;
/* memory.stat 관련 필드 생략 */
atomic_long_t memory_events[NR_MEMCG_MEMORY_EVENTS];
// 메모리 이벤트 카운터 배열
// BPF kfunc이 직접 읽는 대상
};
BPF 프로그램에서 호출 가능한 8개의 kfunc을 등록합니다.
// mm/bpf_memcontrol.c:164-175
BTF_KFUNCS_START(bpf_memcontrol_kfuncs)
BTF_ID_FLAGS(func, bpf_get_root_mem_cgroup, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_get_mem_cgroup, KF_ACQUIRE | KF_RET_NULL | KF_RCU)
BTF_ID_FLAGS(func, bpf_put_mem_cgroup, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_mem_cgroup_vm_events)
BTF_ID_FLAGS(func, bpf_mem_cgroup_memory_events)
BTF_ID_FLAGS(func, bpf_mem_cgroup_usage)
BTF_ID_FLAGS(func, bpf_mem_cgroup_page_state)
BTF_ID_FLAGS(func, bpf_mem_cgroup_flush_stats, KF_SLEEPABLE)
BTF_KFUNCS_END(bpf_memcontrol_kfuncs)
BPF memcg 계정이 활성화되었는지 확인하는 정적 키. static_branch_likely로 런타임 오버헤드 최소화.
// include/linux/memcontrol.h:1708-1712
extern struct static_key_false memcg_bpf_enabled_key;
static inline bool memcg_bpf_enabled(void)
{
return static_branch_likely(&memcg_bpf_enabled_key);
}
BPF 할당 시 memcg 계정 플래그를 자동 추가하는 인라인 함수.
// include/linux/bpf.h:3941-3946
static inline gfp_t bpf_memcg_flags(gfp_t flags)
{
if (memcg_bpf_enabled())
return flags | __GFP_ACCOUNT;
return flags;
}
BPF 맵 생성 시 해당 프로세스의 obj_cgroup을 저장합니다.
// kernel/bpf/syscall.c:486-495
static void bpf_map_save_memcg(struct bpf_map *map)
{
// 루트 memcg에 속한 프로세스는 obj_cgroup이 NULL
// 그래서 매번 map->objcg가 NULL인지 확인 필요
if (memcg_bpf_enabled())
map->objcg = get_obj_cgroup_from_current();
}
BPF 맵��作 시 memcg 컨텍스트를 전환하는 함수.
// kernel/bpf/syscall.c:511-523
void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg)
{
*new_memcg = bpf_map_get_memcg(map); // 맵의 memcg 획득
*old_memcg = set_active_memcg(*new_memcg); // 현재 활성 memcg 전환
}
void bpf_map_memcg_exit(struct mem_cgroup *old_memcg,
struct mem_cgroup *new_memcg)
{
set_active_memcg(old_memcg); // 원래 memcg로 복원
mem_cgroup_put(new_memcg); // memcg 참조 해제
}
BPF 맵이 memcg에 연결될 때 사용되는 obj_cgroup 인터페이스.
// include/linux/memcontrol.h:1693-1706 (요약)
struct obj_cgroup {
struct cgroup_subsys_state css;
struct obj_cgroup *parent;
struct mem_cgroup *memcg; // 연결된 memcg
atomic_long_t memory_charge; // obj별 메모리 계정
atomic_long_t swap_charge;
};
루트 memcg 포인터를 반환하는 kfunc. memcg가 비활성화된 경우 NULL을 반환합니다.
// mm/bpf_memcontrol.c:23-30
__bpf_kfunc struct mem_cgroup *bpf_get_root_mem_cgroup(void)
{
if (mem_cgroup_disabled())
return NULL;
// css_get() 불필요 - 루트 memcg는 절대 파괴되지 않음
return root_mem_cgroup;
}
KF_ACQUIRE | KF_RET_NULLCSS 포인터로 memcg 참조를 획득하는 kfunc. 어떤 cgroup 컨트롤러의 CSS도 허용합니다.
// mm/bpf_memcontrol.c:44-69
__bpf_kfunc struct mem_cgroup *
bpf_get_mem_cgroup(struct cgroup_subsys_state *css)
{
struct mem_cgroup *memcg = NULL;
bool rcu_unlock = false;
if (mem_cgroup_disabled() || !root_mem_cgroup)
return NULL;
// memcg 서브시스템에 속하지 않은 CSS인 경우
if (root_mem_cgroup->css.ss != css->ss) {
struct cgroup *cgroup = css->cgroup;
int ssid = root_mem_cgroup->css.ss->id;
rcu_read_lock();
rcu_unlock = true;
css = rcu_dereference_raw(cgroup->subsys[ssid]);
// memcg CSS로 변환
}
if (css && css_tryget(css))
memcg = container_of(css, struct mem_cgroup, css);
if (rcu_unlock)
rcu_read_unlock();
return memcg;
}
memcg의 메모리 사용량을 바이트 단위로 반환합니다.
// mm/bpf_memcontrol.c:113-116
__bpf_kfunc unsigned long bpf_mem_cgroup_usage(struct mem_cgroup *memcg)
{
return page_counter_read(&memcg->memory) * PAGE_SIZE;
}
memcg의 VM 이벤트 카운터를 읽습니다.
// mm/bpf_memcontrol.c:92-99
__bpf_kfunc unsigned long bpf_mem_cgroup_vm_events(struct mem_cgroup *memcg,
enum vm_event_item event)
{
if (unlikely(!memcg_vm_event_item_valid(event)))
return (unsigned long)-1; // 잘못된 이벤트 ID
return memcg_events(memcg, event); // 이벤트 카운터 값
}
BPF 맵 생성 시 현재 프로세스의 memcg를 맵에 연결합니다.
// kernel/bpf/syscall.c:486-495
static void bpf_map_save_memcg(struct bpf_map *map)
{
if (memcg_bpf_enabled())
map->objcg = get_obj_cgroup_from_current();
// 루트 memcg에선 obj_cgroup이 NULL
}
BPF 할당 GFP 플래그에 __GFP_ACCOUNT를 자동 추가합니다.
// include/linux/bpf.h:3941-3946
static inline gfp_t bpf_memcg_flags(gfp_t flags)
{
if (memcg_bpf_enabled())
return flags | __GFP_ACCOUNT;
return flags;
}
BPF 맵操作 전후에 memcg 컨텍스트를 전환합니다.
// kernel/bpf/syscall.c:511-523
void bpf_map_memcg_enter(const struct bpf_map *map,
struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg)
{
*new_memcg = bpf_map_get_memcg(map); // 맵의 memcg 획득
*old_memcg = set_active_memcg(*new_memcg); // 활성 memcg 전환
}
void bpf_map_memcg_exit(struct mem_cgroup *old_memcg,
struct mem_cgroup *new_memcg)
{
set_active_memcg(old_memcg); // 원래 memcg로 복원
mem_cgroup_put(new_memcg); // memcg 참조 해제
}
bpf_prog_alloc()
→ bpf_memcg_flags(GFP_KERNEL | __GFP_ZERO | gfp_extra_flags)
→ memcg_bpf_enabled() == true 이면 __GFP_ACCOUNT 추가
→ __vmalloc(size, gfp_flags) // 프로그램 코드 할당
→ kzalloc_obj(*aux, bpf_memcg_flags()) // aux 구조체 할당
→ __alloc_percpu_gfp(..., bpf_memcg_flags()) // active 카운터
bpf CreateMap 시스템 콜
→ bpf_map_save_memcg(map)
→ memcg_bpf_enabled() 확인
→ get_obj_cgroup_from_current() // 현재 프로세스의 obj_cgroup
→ map->objcg = objcg // 맵에 memcg 연결
bpf_map_kmalloc_node(map, size, flags, node)
→ bpf_map_memcg_enter(map, &old_memcg, &memcg)
→ bpf_map_get_memcg(map) // 맵의 memcg 획득
→ map->objcg ? get_mem_cgroup_from_objcg() : root_mem_cgroup
→ set_active_memcg(new_memcg) // 활성 memcg 전환
→ kmalloc_node(size, flags | __GFP_ACCOUNT, node)
→ bpf_map_memcg_exit(old_memcg, memcg)
→ set_active_memcg(old_memcg) // 원래 memcg로 복원
→ mem_cgroup_put(new_memcg) // 참조 해제
BPF 프로그램
→ bpf_get_mem_cgroup(css) // memcg 참조 획득
→ bpf_mem_cgroup_usage(memcg) // 사용량 조회 (바이트)
→ bpf_mem_cgroup_vm_events(memcg, event) // VM 이벤트 조회
→ bpf_mem_cgroup_memory_events(memcg, event) // 메모리 이벤트 조회
→ bpf_mem_cgroup_page_state(memcg, idx) // 페이지 상태 조회
→ bpf_mem_cgroup_flush_stats(memcg) // 통계 플러시 (KF_SLEEPABLE)
→ bpf_put_mem_cgroup(memcg) // memcg 참조 해제
bpf_mem_alloc_init()
→ memcg_bpf_enabled() 확인
→ get_obj_cgroup_from_current() // 현재 프로세스의 obj_cgroup
→ ma->objcg = objcg // arena에 memcg 연결
→ for_each_possible_cpu(cpu):
c->objcg = objcg // per-CPU 캐시에 memcg 설정
| kfunc | 인자 | 반환값 | 플래그 | 용도 |
|---|---|---|---|---|
| `bpf_get_root_mem_cgroup` | 없음 | mem_cgroup* | KF_ACQUIRE, KF_RET_NULL | 루트 memcg 획득 |
| `bpf_get_mem_cgroup` | css* | mem_cgroup* | KF_ACQUIRE, KF_RET_NULL, KF_RCU | memcg 참조 획득 |
| `bpf_put_mem_cgroup` | memcg* | void | KF_RELEASE | memcg 참조 해제 |
| `bpf_mem_cgroup_usage` | memcg* | unsigned long | - | 사용량 (바이트) |
| `bpf_mem_cgroup_vm_events` | memcg*, event | unsigned long | - | VM 이벤트 카운터 |
| `bpf_mem_cgroup_memory_events` | memcg*, event | unsigned long | - | 메모리 이벤트 카운터 |
| `bpf_mem_cgroup_page_state` | memcg*, idx | unsigned long | - | 페이지 상태 (바이트) |
| `bpf_mem_cgroup_flush_stats` | memcg* | void | KF_SLEEPABLE | 통계 상향 플러시 |
| 할당 경로 | 함수 | memcg 연결 방법 | GFP 플래그 |
|---|---|---|---|
| BPF 프로그램 | `bpf_prog_alloc` | `bpf_memcg_flags()` | `__GFP_ACCOUNT` 자동 추가 |
| BPF 맵 (kmalloc) | `bpf_map_kmalloc_node` | `bpf_map_memcg_enter/exit` | `__GFP_ACCOUNT` 명시적 |
| BPF 맵 (vmalloc) | `__bpf_map_area_alloc` | `bpf_memcg_flags()` | `__GFP_ACCOUNT` 자동 추가 |
| BPF arena | `bpf_mem_alloc_init` | `ma->objcg` 직접 설정 | per-CPU 캐시에 objcg 전파 |
| JIT 코드 | `bpf_jit_binary_alloc` | `bpf_memcg_flags()` | `__GFP_ACCOUNT` 자동 추가 |
| 상태 | `memcg_bpf_enabled()` | 동작 |
|---|---|---|
| CONFIG_MEMCG 미설정 | `false` (항상) | BPF memcg 지원 없음, kfunc 등록 안 됨 |
| CONFIG_MEMCG 설정 + memcg 비활성화 | `false` | `bpf_memcg_flags()`가 플래그 추가 안 함 |
| CONFIG_MEMCG 설정 + memcg 활성화 | `true` | 모든 BPF 할당에 `__GFP_ACCOUNT` 추가 |
| 항목 | BPF 할당 | 일반 프로세스 할당 |
|---|---|---|
| 계정 대상 | BPF 프로그램, 맵, arena | 사용자 페이지, 커널 SLAB |
| 플래그 추가 | `bpf_memcg_flags()` → `__GFP_ACCOUNT` | `memcg_charge_slab()` 등 직접 |
| 컨텍스트 전환 | `bpf_map_memcg_enter/exit` | 필요 없음 (현재 태스크 memcg 사용) |
| obj_cgroup 연결 | 맵 생성 시 `bpf_map_save_memcg` | 불필요 (task_struct에 직접 연결) |
| 해제 | `bpf_put_mem_cgroup` (kfunc) | `mem_cgroup_uncharge` (커널 내부) |