Skip to content
Go DevBJ
Go back

lwIP pbuf가 가끔 터진다: PBUF_REF/POOL/RAM 수명주기와 zero-copy 함정

Edit page

lwIP로 bring-up 하고 나면 종종 이런 증상이 나온다.

이럴 때 제일 먼저 의심하는 게 난 이거다.

pbuf 수명주기(ownership)가 애매하다

특히 “성능 최적화”로 PBUF_REF(zero-copy)를 넣기 시작하면
버퍼 재사용 타이밍이 한 번만 어긋나도 랜덤 이슈가 된다.

pbuf를 한 줄로 요약하면

pbuf는 lwIP가 패킷 데이터를 들고 다니는 컨테이너다.
중요한 건 payload가 “어디 메모리냐”와 “누가 언제 free 하냐”다.

실무에서 자주 쓰는 타입만 단순화하면 이렇다.

즉,
PBUF_REF는 빠르지만 외부 버퍼의 수명은 lwIP가 책임져주지 않는다.

가장 흔한 크래시 원인 4개

1) 스택/임시 버퍼를 PBUF_REF로 감싼다

예를 들면 이런 형태다.

tmp(스택/임시 버퍼)에 데이터를 채움
→ PBUF_REF로 tmp를 payload로 참조
→ udp_send()/tcp_write() 호출
→ 함수 리턴과 함께 tmp는 재사용/파기될 수 있음

함정은 간단하다.

그래서 “가끔만 깨지는” 형태가 된다.

2) DMA RX 버퍼를 너무 빨리 재사용한다

드라이버에서 RX DMA 링을 돌리다 보면
받은 프레임을 lwIP에 넘긴 직후에 버퍼를 다시 링에 붙여버리는 실수가 나온다.

RX DMA 버퍼 재사용 시점
vs
lwIP가 pbuf를 다 소비하고 pbuf_free()되는 시점

이 둘이 같지 않다.

zero-copy를 하려면 결국:

3) free 주체가 두 군데다(더블 프리/이중 반환)

실무에서 많이 꼬이는 패턴:

구조가 흔들리면 증상이 랜덤해진다.

이 문제는 “성능”이 아니라 “소유권” 문제라서,
팀 내 규칙을 딱 하나로 고정하는 게 제일 편하다.

버퍼 소유권은 한 군데만 가진다

4) 드롭 경로에서 pbuf_free를 빼먹는다

부하가 올라가면 드롭 경로가 자주 탄다.
여기서 pbuf_free()가 빠지면,
몇 분/몇 시간 뒤에 PBUF_POOL이 고갈되고 결국 멈춘다.

증상은 보통:

으로 간다.

실무에서 무난한 선택지(안전 우선)

1) RX는 PBUF_POOL + 복사로 먼저 안정화

bring-up 단계에서는 보통 이게 제일 덜 아프다.

성능은 손해지만, 랜덤 크래시를 먼저 없애기 좋다.

2) zero-copy는 “free 콜백이 있는 구조”로

PBUF_REF를 쓰더라도,
결국 핵심은 이거다.

lwIP가 pbuf를 놓는 시점(pbuf_free)
→ 그때 외부 버퍼를 풀로 반환

그래서 외부 버퍼 풀을 만들고,
pbuf가 해제될 때 그 풀로 반환되게 구조를 잡으면 안정성이 올라간다.

프로젝트마다 구현은 다르지만, 방향은 동일하다.

빠른 디버깅 체크리스트

  1. PBUF_POOL 고갈 여부부터 확인(LWIP_STATS, memp, pbuf)
  2. 드롭 경로에서 pbuf_free()가 빠진 곳이 없는지 grep
  3. PBUF_REF를 쓰는 코드가 있으면 “원본 버퍼 수명”을 표로 정리
  4. DMA RX/TX 버퍼 재사용 시점을 로그로 찍어서, pbuf 해제보다 빠른지 확인
  5. “가끔만”이면 대부분 ownership/수명주기 문제다(락보다 먼저 체크)

결론

lwIP에서 pbuf는 “메모리 소유권 계약서”다. PBUF_REF/zero-copy를 쓰면 성능은 얻지만, 버퍼 수명주기를 정확히 맞추지 않으면 랜덤 크래시로 돌아온다. 먼저 복사 기반으로 안정화하고, zero-copy는 free 시점까지 버퍼를 묶어두는 구조로 천천히 옮기는 게 실무적으로 덜 아프다.


DevBJ | No Bio, Just Log 기술 삽질로그


Edit page
Share this post on:

Next Post
DoIP에서 ACK를 받았는데 UDS 응답이 없다: Diagnostic ACK/NACK를 제대로 쓰는 법