lwIP로 bring-up 할 때, 이상하게 “가끔만” 문제 생기는 케이스가 있다.
- DHCP가 어떤 환경에서는 잘 받고, 어떤 환경에서는 영원히 못 받는다
- ARP가 가끔 갱신이 안 돼서, 통신이 끊긴 것처럼 보인다
- TCP가 가끔 타임아웃/재전송이 꼬이고, 연결이 좀비처럼 남는다
캡처를 떠도 애매하다.
패킷은 오가는데, 내부 상태가 이상하게 굳어있다.
이럴 때 제일 먼저 확인하는 게 난 이거다.
타이머 루프가 실제로 돌고 있나?
왜 NO_SYS에서 더 자주 터지나
lwIP는 내부에서 여러 타이머 이벤트를 돌린다.
- ARP aging
- DHCP retransmit / renew
- TCP retransmission / TIME-WAIT 정리
- (옵션) IGMP, DNS 등
RTOS + tcpip_thread 모델이면,
이 타이머는 보통 스레드가 알아서 돈다.
그런데 NO_SYS(bare-metal)로 쓰면 얘기가 달라진다.
네가 주기적으로 타이머 함수를 불러줘야
lwIP가 앞으로 간다
그 역할을 하는 게 sys_check_timeouts()다.
sys_check_timeouts()가 빠졌을 때 전형적인 증상
현장에서 자주 보이는 패턴은 이렇다.
1) DHCP가 “랜덤하게” 안 된다
어떤 네트워크에서는 되는데,
어떤 네트워크에서는 초기 Discover/Request 이후에 진행이 멈춘다.
“DHCP 서버가 이상한가?”로 넘어가기 전에,
타이머 처리가 실제로 돌아가서 재전송/백오프가 수행되는지부터 봐야 한다.
2) ARP가 오래된 엔트리를 계속 믿는다
링크를 뺐다 꼈거나,
상대가 바뀌었는데도,
장비가 이전 MAC으로 계속 때리는 것처럼 보일 때가 있다.
이때도 aging/refresh 쪽 타이머가 멈춰 있으면 상태가 쉽게 꼬인다.
3) TCP가 “죽었는데 안 죽은 것처럼” 남는다
- ACK가 안 오는데도 연결이 정리되지 않는다
- 재전송 정책이 이상해 보인다
- TIME-WAIT 소켓이 계속 쌓인다
타이머가 안 돌면 “재전송/정리”가 멈춘다.
그러면 부하가 올라갈수록 더 이상한 형태가 된다.
어디서 호출하는 게 제일 덜 아픈가
원칙은 단순하다.
NO_SYS 환경에서는
주기적으로 sys_check_timeouts()를 호출
호출 위치는 프로젝트 구조에 따라 다르지만,
실무에서 덜 아픈 패턴은 보통 둘 중 하나다.
1) main loop에서 주기적으로 호출
for (;;) {
ethernet_poll(); // RX/TX 처리(보드 구현에 따라 다름)
sys_check_timeouts(); // lwIP 타이머
app_poll();
}
이 구조의 장점은 단순하다.
- 타이머가 절대 안 굶는다
- bring-up 단계에서 디버깅하기 쉽다
2) 1ms/10ms tick에서 호출 (주의: ISR에서 오래 돌리지 말기)
tick 기반으로 돌리는 것도 가능하지만,
ISR에서 오래 잡고 있지 않게만 주의하면 된다.
“얼마나 자주”가 적당하냐
정답은 프로젝트마다 다르지만,
bring-up 단계에서 가장 안전한 기준은 이거다.
- 최소 10ms 단위로는 한 번씩 돌린다
- 여유가 있으면 1ms~5ms 수준으로 더 촘촘하게 돌려도 된다
여기서 포인트는 “정확한 주기”보다,
굶지 않게 돌리는 거다.
특히 main loop가 가끔 길어지면(플래시/센서/로그),
타이머가 순간적으로 굶을 수 있다.
그때부터 “가끔만” 문제가 시작된다.
빠른 디버깅 체크리스트
NO_SYS설정을 다시 확인(내가 RTOS 모델인지, bare-metal인지)sys_check_timeouts()가 호출되는지 grep- 호출 주기가 가끔 100ms 이상 늘어나는 구간이 없는지(로그/플래시/연산)
- DHCP/ARP/TCP 문제가 섞여 나오면 타이머 굶김부터 의심
- RTOS 모델이라면 반대로 “두 군데에서 동시에 돌리는 실수”가 없는지 확인
결론
NO_SYS에서 lwIP는 “네가 시간을 흘려줘야” 앞으로 간다. DHCP/ARP/TCP가 가끔만 이상해지면, sys_check_timeouts() 호출 여부와 호출 주기부터 잡는 게 가장 싸고 빠르다.
DevBJ | No Bio, Just Log 기술 삽질로그