Mountain Kernel 리팩터링: ABI를 깨지 말자
by
gg582 · 2026-06-02 05:39:55 · 55 views
블로그 원본 diff → 현재 코드베이스 리팩터링 요약
원문: 비전공자도 쉽게 따라하는 리눅스 커널 해킹 — 지터를 잡아보자
본 문서는 해당 포스트의 패치가 별개 구조체 분리 + 추상화 헬퍼 도입 + 선택 로직 개선 형태로 리팩터링된 내용을 정리한다.
1. 핵심 구조 변경: dst_entry 직접 확장 → struct dst_power 분리
기존
struct dst_entry 맨 끝에 필드를 직접 박아 넣었다.
/* include/net/dst.h */
struct dst_entry {
...
struct lwtunnel_state *lwtstate;
#endif
/* USER ADDED */
u64 ema_load;
u64 ema_time_delta;
u64 last_update_jiffies;
unsigned int ema_k_factor;
unsigned int power_cost_weight;
};
문제점
sizeof(struct dst_entry)가 커져서 캐시라인/페이지 경계를 넘을 위험dst_entry를 포함하는 모든 경로 구조체(rtable,rt6_info,xfrm_dst등)가 암묵적으로 같은 오프셋을 공유해야 함 → ABI 깨짐 위험
현재
struct dst_power를 별도 구조체로 분리하고, 실제 사용처(IPv4/IPv6 경로 구조체)에만 임베드한다.
/* include/net/dst.h */
struct dst_power {
u64 ema_load;
u64 ema_time_delta;
u64 last_update_jiffies;
unsigned int ema_k_factor;
unsigned int power_cost_weight;
};
struct dst_power *dst_power_ptr(struct dst_entry *dst);
/* include/net/route.h */
struct rtable {
struct dst_entry dst;
...
struct dst_power power; /* ← IPv4 경로에만 임베드 */
};
/* include/net/ip6_fib.h */
struct rt6_info {
struct dst_entry dst;
...
struct dst_power power; /* ← IPv6 경로에만 임베드 */
};
이점
dst_entry의 크기/레이아웃이 그대로 유지됨 → 캐시라인과 ABI 보호dst_power가 필요 없는dst파생 구조체에는 영향을 주지 않음- 향후 추가 프로토콜(SCTP, XFRM 등)에서도 선택적으로 임베드 가능
2. 접근 추상화: dst_power_ptr() 도입
기존
모든 코드가 dst->ema_load처럼 struct dst_entry를 직접 디레퍼런스.
현재
net/core/dst.c에 타입 분기 헬퍼를 중앙 집중화.
/* net/core/dst.c */
struct dst_power *dst_power_ptr(struct dst_entry *dst)
{
if (!dst || !dst->ops)
return NULL;
#if IS_ENABLED(CONFIG_IPV6)
if (dst->ops->family == AF_INET6) {
struct rt6_info *rt = (struct rt6_info *)dst;
return &rt->power;
}
#endif
if (dst->ops->family == AF_INET) {
struct rtable *rt = (struct rtable *)dst;
return &rt->power;
}
return NULL;
}
EXPORT_SYMBOL(dst_power_ptr);
호출처 일원화
net/core/dev.c—update_dst_ems_metrics()net/ipv4/fib_semantics.c—calculate_lowpower_weight()net/ipv6/route.c—calculate_lowpower_weight(),rt6_score_route()net/ipv4/route.c—rt_dst_alloc()초기화net/ipv6/route.c—ip6_dst_alloc()초기화net/xfrm/xfrm_policy.c—xfrm_bundle_create()초기화net/sctp/outqueue.c—calculate_lowpower_weight()
모든 곳에서 dst_power_ptr(dst) → NULL 체크 → 필드 접근 패턴으로 통일됨.
3. EMA 업데이트 로직
변경 폭은 적다. net/core/dev.c의 dev_hard_start_xmit() 성공 시 호출되는 update_dst_ems_metrics()가 dst_power_ptr()로 포인터를 얻어 쓰는 것 외에는 블로그 원본과 거의 동일하다.
static void update_dst_ems_metrics(struct dst_entry *dst, unsigned int tx_bytes)
{
u64 cur_jiffies = get_jiffies_64();
struct dst_power *p = dst_power_ptr(dst);
u64 cur_load_rate;
u64 delta_t;
if (!p)
return;
delta_t = cur_jiffies - READ_ONCE(p->last_update_jiffies);
if (!delta_t)
return;
cur_load_rate = tx_bytes / delta_t;
WRITE_ONCE(p->ema_load,
EMA_UPDATE(READ_ONCE(p->ema_k_factor),
READ_ONCE(p->ema_load), cur_load_rate));
u64 diff = (cur_load_rate > READ_ONCE(p->ema_load))
? cur_load_rate - READ_ONCE(p->ema_load)
: READ_ONCE(p->ema_load) - cur_load_rate;
WRITE_ONCE(p->ema_time_delta,
EMA_UPDATE(READ_ONCE(p->ema_k_factor),
READ_ONCE(p->ema_time_delta), diff));
WRITE_ONCE(p->last_update_jiffies, cur_jiffies);
}
4. 멀티패스 선택 로직 개선 (fib_select_multipath)
기존
두 단계 루프로 구성.
- 1st loop: 모든 nexthop을 순회하며
calculate_lowpower_weight()를 평가 →max_ema_weight를 가진 단일 nexthop을 선별. - 2nd loop: weight가 모두 0이면 기존
fib_nh_upper_bound+hash기반 ECMP fallback.
문제: 루프가 중복되고, hash/saddr/upper_bound 같은 기존 정책과 lowpower 정책이 서로 독립적으로 동작함.
현재
단일 루프로 통합. nh_score 기반 우선순위 + tie-breaker 개념 도입.
void fib_select_multipath(struct fib_result *res, int hash,
const struct flowi4 *fl4)
{
...
int score = -1;
int lowpower_nh_index = -1;
long max_ema_weight = -1;
change_nexthops(fi) {
int nh_score = 0;
...
/* 1) 기존 정책 점수화 */
if (saddr && nexthop_nh->nh_saddr == saddr)
nh_score += 2;
if (hash <= nh_upper_bound)
nh_score++;
/* 2) lowpower weight는 tie-breaker */
dst = get_dst_entry_from_nhc(&nexthop_nh->nh_common);
current_weight = calculate_lowpower_weight(dst);
if (nh_score > score ||
(nh_score == score && current_weight > max_ema_weight)) {
score = nh_score;
max_ema_weight = current_weight;
lowpower_nh_index = nhsel;
/* 3) 완벽한 매칭이면 즉시 리턴 */
if (nh_score == 3 || (!saddr && nh_score == 1)) {
res->nh_sel = nhsel;
res->nhc = &nexthop_nh->nh_common;
return;
}
}
} endfor_nexthops(fi);
if (lowpower_nh_index != -1) {
res->nh_sel = lowpower_nh_index;
res->nhc = fib_info_nhc(fi, lowpower_nh_index);
return;
}
...
}
개선점 요약
| 항목 | AS-IS | TO-BE |
|---|---|---|
| 루프 수 | 2회 | 1회 |
| 정책 관계 | 독립 (weight 우선 → fallback) | 통합 (score 우선 → weight가 tie-break) |
| 완벽 매칭 | 없음 | nh_score == 3 또는 (!saddr && 1) 시 early return |
| saddr 우선 | 단순 boolean | +2점 반영으로 강한 우선순위 부여 |
| hash bound | 단순 boolean | +1점 반영 |
| dead/linkdown | 2nd loop에서 중복 체크 | 1st loop에서 한 번에 필터 |
즉, 기존 ECMP 정책(saddr affinity, hash bound)과 lowpower 메트릭이 충돌하지 않고 계층적으로 조화를 이룬다.
5. 초기화 패턴 일원화
rtable, rt6_info, xfrm_dst 생성 시 기존에는 각자 필드를 직접 찔러 넣었으나, 현재는 모두 dst_power_ptr()를 통해 동일한 패턴으로 초기화한다.
/* IPv4 예시 (net/ipv4/route.c) */
struct dst_power *p = dst_power_ptr(&rt->dst);
if (p) {
WRITE_ONCE(p->ema_k_factor, ...);
WRITE_ONCE(p->power_cost_weight, ...);
WRITE_ONCE(p->ema_load, 0);
WRITE_ONCE(p->ema_time_delta, 0);
p->last_update_jiffies = 0;
}
xfrm_policy.c, net/ipv6/route.c에서도 동일한 템플릿을 재사용.
6. 사라진/유지된 부분
사라진 변경
netif_addr_lock_bh()단순화: 블로그 원본에서local_bh_disable()+spin_lock_nested()→spin_lock_bh()로 교체한 diff는 현재 코드에 반영되지 않음. (커밋 히스토리상 별도 클린업으로 분리되었거나 실험 브랜치에서 제외됨)
그대로 유지
EMA_UPDATE(K, Old, New)매크로 (net/core/dev.c상단)sysctl인터페이스 (lowpower_ema_k_factor,lowpower_power_cost_weight)- 기본값 (
512,100) IPv6 rt6_score_route()에서 weight를m += weight형태로 가산SCTP/XFRM에서의 lowpower 필드 초기화 및 weight 함수 정의
7. 전체 흐름도
패킷 전송 (dev_hard_start_xmit)
└── update_dst_ems_metrics(dst, skb->len)
└── dst_power_ptr(dst) → struct dst_power *
└── EMA_UPDATE(ema_load, ema_time_delta)
멀티패스 선택 (fib_select_multipath / rt6_score_route)
└── get_dst_entry_from_nhc / get_dst_entry_from_fib6_nh
└── calculate_lowpower_weight(dst)
└── dst_power_ptr(dst)
└── (ema_load + ema_time_delta) * power_cost_weight
└── 기존 정책(saddr, hash bound)과 계층적 결합
8. 결론
원본 패치가 단순히 "dst_entry 끝에 필드를 붙이고 쓰자"는 형태였다면, 현재 코드베이스는 다음과 같은 엔지니어링 품질 향상을 이루었다.
- 캡슐화:
struct dst_power분리 +dst_power_ptr()추상화 - ABI/캐시 안전:
dst_entry크기 변경 없이 필요한 구조체에만 임베드 - 정책 통합:
fib_select_multipath를 단일 루프 + score 기반으로 개선하여 기존 ECMP 정책과 lowpower 메트릭이 자연스럽게 결합 - 템플릿화: 초기화/접근 코드가
dst_power_ptr()기반으로 통일되어 유지보수성 향상