lwIP로 bring-up 하고 나면 종종 이런 증상이 나온다.
- 평소엔 멀쩡한데 가끔만 죽는다
- 부하가 올라가면 확률이 올라간다
- 크래시 위치가 매번 다르다(
ip4_input,udp_input,tcp_input등) memp/pbuf통계를 보면 어느 순간 바닥난다
이럴 때 제일 먼저 의심하는 게 난 이거다.
pbuf 수명주기(ownership)가 애매하다
특히 “성능 최적화”로 PBUF_REF(zero-copy)를 넣기 시작하면
버퍼 재사용 타이밍이 한 번만 어긋나도 랜덤 이슈가 된다.
pbuf를 한 줄로 요약하면
pbuf는 lwIP가 패킷 데이터를 들고 다니는 컨테이너다.
중요한 건 payload가 “어디 메모리냐”와 “누가 언제 free 하냐”다.
실무에서 자주 쓰는 타입만 단순화하면 이렇다.
- PBUF_POOL: lwIP 풀에서 나온 버퍼(보통 RX에 안전).
pbuf_free()면 풀로 반환 - PBUF_RAM: heap에서 새로 만들어진 버퍼(복사 기반).
pbuf_free()면 해제 - PBUF_REF: “외부 메모리”를 참조만 함(복사 없음).
pbuf_free()는 참조만 내려감(원본 버퍼는 네가 관리)
즉,
PBUF_REF는 빠르지만 외부 버퍼의 수명은 lwIP가 책임져주지 않는다.
가장 흔한 크래시 원인 4개
1) 스택/임시 버퍼를 PBUF_REF로 감싼다
예를 들면 이런 형태다.
tmp(스택/임시 버퍼)에 데이터를 채움
→ PBUF_REF로 tmp를 payload로 참조
→ udp_send()/tcp_write() 호출
→ 함수 리턴과 함께 tmp는 재사용/파기될 수 있음
함정은 간단하다.
udp_send()가 “그 순간” 바로 다 보내는 게 아닐 수 있다- 함수가 리턴하면
tmp는 이미 다른 값으로 덮일 수 있다
그래서 “가끔만 깨지는” 형태가 된다.
2) DMA RX 버퍼를 너무 빨리 재사용한다
드라이버에서 RX DMA 링을 돌리다 보면
받은 프레임을 lwIP에 넘긴 직후에 버퍼를 다시 링에 붙여버리는 실수가 나온다.
RX DMA 버퍼 재사용 시점
vs
lwIP가 pbuf를 다 소비하고 pbuf_free()되는 시점
이 둘이 같지 않다.
zero-copy를 하려면 결국:
- “버퍼를 언제 다시 써도 되는지”를 결정할 기준이 필요하고
- 그 기준은 대개
pbuf_free()(참조 카운트 0) 쪽이다
3) free 주체가 두 군데다(더블 프리/이중 반환)
실무에서 많이 꼬이는 패턴:
- 드라이버도 버퍼를 풀로 반환
- lwIP 쪽에서도
pbuf_free()로 반환(또는 그 반대)
구조가 흔들리면 증상이 랜덤해진다.
이 문제는 “성능”이 아니라 “소유권” 문제라서,
팀 내 규칙을 딱 하나로 고정하는 게 제일 편하다.
버퍼 소유권은 한 군데만 가진다
4) 드롭 경로에서 pbuf_free를 빼먹는다
부하가 올라가면 드롭 경로가 자주 탄다.
여기서 pbuf_free()가 빠지면,
몇 분/몇 시간 뒤에 PBUF_POOL이 고갈되고 결국 멈춘다.
증상은 보통:
- 처음엔 UDP가 가끔 끊기다가
- 어느 순간부터는 “아예 통신이 안 됨”
으로 간다.
실무에서 무난한 선택지(안전 우선)
1) RX는 PBUF_POOL + 복사로 먼저 안정화
bring-up 단계에서는 보통 이게 제일 덜 아프다.
- 드라이버는 DMA 버퍼를 마음대로 재사용 가능
- lwIP는 자기 풀만 관리하면 됨
성능은 손해지만, 랜덤 크래시를 먼저 없애기 좋다.
2) zero-copy는 “free 콜백이 있는 구조”로
PBUF_REF를 쓰더라도,
결국 핵심은 이거다.
lwIP가 pbuf를 놓는 시점(pbuf_free)
→ 그때 외부 버퍼를 풀로 반환
그래서 외부 버퍼 풀을 만들고,
pbuf가 해제될 때 그 풀로 반환되게 구조를 잡으면 안정성이 올라간다.
프로젝트마다 구현은 다르지만, 방향은 동일하다.
- 외부 버퍼는 “참조 카운트 0”이 될 때만 재사용
- ISR/드라이버/네트워크 태스크가 모두 같은 규칙을 따름
빠른 디버깅 체크리스트
PBUF_POOL고갈 여부부터 확인(LWIP_STATS,memp,pbuf)- 드롭 경로에서
pbuf_free()가 빠진 곳이 없는지 grep PBUF_REF를 쓰는 코드가 있으면 “원본 버퍼 수명”을 표로 정리- DMA RX/TX 버퍼 재사용 시점을 로그로 찍어서, pbuf 해제보다 빠른지 확인
- “가끔만”이면 대부분 ownership/수명주기 문제다(락보다 먼저 체크)
결론
lwIP에서 pbuf는 “메모리 소유권 계약서”다. PBUF_REF/zero-copy를 쓰면 성능은 얻지만, 버퍼 수명주기를 정확히 맞추지 않으면 랜덤 크래시로 돌아온다. 먼저 복사 기반으로 안정화하고, zero-copy는 free 시점까지 버퍼를 묶어두는 구조로 천천히 옮기는 게 실무적으로 덜 아프다.
DevBJ | No Bio, Just Log 기술 삽질로그