Skip to content
오늘을살자
Go back

lwIP RAW UDP callback 뒤에 수신이 멈춘다: pbuf_free를 놓치면 PBUF_POOL이 먼저 마른다

Edit page

lwIP로 UDP 수신을 붙이면 처음에는 멀쩡한데
조금 지나서 갑자기 멈추는 경우가 있다.

이때 링크나 ARP부터 의심하기 쉽다.
그런데 원인은 더 단순한 경우가 많다.

오늘 메모는 RAW UDP receive callback에서 pbuf_free()를 놓쳤을 때 보이는 전형적인 증상이다.

결론부터

RAW API의 UDP receive callback으로 넘어온 pbuf
애플리케이션이 사용을 마치면 해제해야 한다.

그걸 놓치면 작은 패킷 누수가 쌓여
결국 PBUF_POOL이 먼저 마른다.

UDP packet arrives
-> lwIP allocates pbuf
-> udp recv callback called
-> app returns without pbuf_free()
-> pool element stays occupied

초반에는 티가 안 나도,
트래픽이 계속 들어오면 결국 수신 경로 전체에 영향을 준다.

왜 처음에는 정상처럼 보이나

PBUF_POOL은 보통 여러 개를 미리 잡아 둔다.

그래서 한두 개씩 새어도 바로 안 드러난다.

결국 체감상으로는
“가끔 멈춘다”가 된다.

흔한 실수 1: 정상 경로에서만 free하고 예외 경로는 빠뜨린다

제일 자주 보는 패턴은 이거다.

static void udp_rx(void *arg, struct udp_pcb *pcb, struct pbuf *p,
                   const ip_addr_t *addr, u16_t port)
{
  if (p == NULL) {
    return;
  }

  if (!is_valid_packet(p)) {
    return;
  }

  handle_packet(p);
  pbuf_free(p);
}

겉으로는 free를 하고 있다.
하지만 invalid packet 경로에서는 빠져 있다.

이렇게 되면 노이즈 패킷, 길이 오류, 포맷 오류가 들어올수록
누수가 더 빨리 커진다.

흔한 실수 2: payload만 복사하고 원본 pbuf를 잊는다

이 경우도 많다.

callback 진입
-> app buffer로 payload 복사
-> queue에 app buffer 전달
-> pbuf 해제 누락

복사는 끝났으니 일이 끝난 것처럼 느껴진다.
하지만 원래 pbuf는 여전히 pool을 잡고 있다.

즉 ownership을 이렇게 분리해서 봐야 한다.

복사를 했다고 원본이 자동으로 해제되지는 않는다.

흔한 실수 3: 다른 태스크로 넘기는데 ref/free 규칙이 없다

callback에서 바로 파싱하지 않고
worker task로 넘기는 구조도 흔하다.

이때 규칙이 불분명하면 금방 꼬인다.

callback
-> pbuf pointer를 queue에 넣음
-> worker가 나중에 처리

여기서 중요한 것은 둘 중 하나를 명확히 하는 것이다.

A. callback이 ownership을 worker에 넘기고 worker가 free
또는
B. callback이 필요한 데이터만 복사하고 즉시 free

둘 다 애매하면 이런 문제가 나온다.

즉 “누가 마지막에 free하는가”가 코드에 분명해야 한다.

pbuf pool 고갈은 어떻게 보이나

증상은 꼭 “메모리 누수”처럼 드러나지 않는다.

왜냐하면 PBUF_POOL은 UDP만의 자원이 아니라
RX 경로 전체에서 같이 쓰이는 경우가 많기 때문이다.

그래서 앱 하나의 해제 누락이
시스템 전체 네트워크 저하처럼 보일 수 있다.

구현 쪽에서 무난한 패턴

가장 단순한 구조는 아래 둘 중 하나다.

1. callback 안에서 필요한 만큼만 복사하고 즉시 free

udp recv callback
-> validate
-> copy payload to app buffer
-> pbuf_free(p)
-> signal worker

이 방식은 ownership이 단순해서 덜 헷갈린다.

2. pbuf 자체를 넘길 거면 ownership을 문서화

udp recv callback
-> queue pbuf pointer to worker
-> worker always frees after use
-> every error path frees too

이 방식은 복사 비용을 줄일 수 있지만,
queue full, parse fail, shutdown 경로까지 free 규칙이 필요하다.

로그를 이렇게 남기면 빨라진다

이 문제는 마지막 실패 한 줄만 보면 드라이버 이슈처럼 보인다.

나는 보통 아래 항목을 같이 본다.

  1. UDP recv callback 호출 횟수
  2. pbuf_free() 호출 횟수
  3. queue full 또는 parse fail 경로 진입 횟수
  4. PBUF_POOL 사용량 또는 alloc fail 로그
  5. worker가 실제로 free한 시각

이 다섯 줄이 있으면
“네트워크가 끊겼다”보다
“수신 버퍼가 회수되지 않았다”는 쪽이 먼저 보인다.

빠른 체크리스트

  1. UDP recv callback의 모든 return 경로에서 pbuf 해제가 보장되는지 확인
  2. payload 복사 후 원본 pbuf를 따로 해제하는지 확인
  3. callback과 worker 사이에서 ownership 주체를 주석이나 이름으로 명확히 했는지 확인
  4. queue full, parse error, shutdown 같은 예외 경로에도 free가 있는지 확인
  5. PBUF_POOL 고갈 로그와 UDP 수신 정지 시점을 같이 기록하는지 확인

한 줄 요약

lwIP RAW UDP callback에서 수신이 몇 분 뒤 멈춘다면 링크보다 먼저 pbuf_free() 누락을 의심하고, 모든 정상/예외 경로에서 누가 마지막으로 pbuf를 해제하는지 분명히 두는 편이 빠르다.

추천 키워드

lwIP, UDP, pbuf, PBUF_POOL, RAW API, embedded network


DevBJ | 오늘을살자, Log Today


Edit page
Share this post on:

Previous Post
lwIP TCP 버퍼 설정 읽는 법: TCP_SND_BUF, TCP_WND, MEM_SIZE, MEMP_NUM_TCP_SEG, PBUF_POOL_SIZE
Next Post
DoIP에서 Response Pending 중 Tester Present를 섞으면 꼬인다: 같은 세션 유지와 대기 흐름을 분리해서 봐야 한다