Skip to content
Go DevBJ
Go back

lwIP에서 ISR에서 바로 보내면 가끔 터진다: tcpip_thread로 넘기는 패턴 정리

Edit page

lwIP를 RTOS 위에 올리고 나면 꼭 한 번은 이런 형태를 겪는다.

그리고 로그를 보면 이런 코드가 슬쩍 보인다.

결론부터 말하면,
ISR에서 lwIP 코어를 “정면으로” 건드리면 랜덤 이슈가 나기 쉽다.

왜 ISR 호출이 위험한가 (실무 관점)

lwIP는 크게 두 가지 모드로 쓰인다.

RTOS + tcpip_thread 구성에서는
lwIP 내부 상태가 사실상 tcpip_thread 중심으로 돌아간다고 보면 된다.

이 상태에서 ISR이 중간에 치고 들어오면 문제는 보통 이 두 가지다.

  1. 락 없이 코어 구조체를 만짐 (race)
  2. ISR에서 블로킹/메모리 할당/큐 포화가 발생 (타이밍 꼬임)

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

목표: ISR에서는 최소만, 나머지는 tcpip_thread로

ISR에서 할 일은 진짜 최소로 잡는 게 편하다.

그리고 lwIP API 호출, pbuf 생성/체인, 프로토콜 처리는
tcpip_thread 컨텍스트로 넘겨서 처리한다.

가장 흔한 구조: ISR → queue → net task → tcpip_thread

구조는 대략 이렇다.

ETH IRQ
  → (frame pointer/len만) RX queue push
    → net task가 queue pop
      → tcpip_callback()로 lwIP 컨텍스트에 작업 전달

이 패턴의 장점은 단순하다.

코드 스케치: tcpip_callback으로 넘기기

아래는 “개념”만 잡는 용도다.
프로젝트마다 드라이버/메모리 모델이 다르니 그대로 붙여넣기용은 아니다.

struct rx_item {
  void *buf;
  uint16_t len;
};

static void lwip_rx_in_tcpip_thread(void *arg) {
  struct rx_item *item = (struct rx_item *)arg;

  // 여기서 pbuf를 만들고(혹은 REF로 감싸고) netif->input으로 넘긴다.
  // pbuf 생명주기(REF 사용 시 free 콜백)가 핵심 포인트.

  // struct pbuf *p = pbuf_alloc(PBUF_RAW, item->len, PBUF_POOL);
  // memcpy(p->payload, item->buf, item->len);
  // if (netif->input(p, &gnetif) != ERR_OK) pbuf_free(p);
}

void ethernet_rx_task(void *arg) {
  for (;;) {
    struct rx_item item;
    // queue_pop(&item);  // IRQ가 넣어둔 프레임
    tcpip_callback(lwip_rx_in_tcpip_thread, &item);
  }
}

여기서 포인트는 두 가지다.

자주 깨지는 지점 4개

1) tcpip_callback에 넘긴 인자가 스택 변수다

위 스케치처럼 &item을 넘기면,
콜백이 실행되기 전에 item이 바뀌거나 사라질 수 있다.

실무에서는 보통 이렇게 한다.

2) ISR에서 pbuf_alloc()를 한다

이건 생각보다 많이 본다.
보드/힙/락 구현에 따라 랜덤하게 죽기 딱 좋다.

가능하면 pbuf_alloc()은 tcpip_thread 쪽에서 하자.

3) LWIP_TCPIP_CORE_LOCKING을 켜놓고 규칙을 섞어쓴다

core locking을 켰다고 해서 “아무데서나 호출”이 되는 게 아니다.

프로젝트에서는 한 가지 모델로 정리하는 게 덜 아프다.

4) TCPIP_MBOX_SIZE가 작아서 콜백이 드롭된다

콜백/메시지는 결국 mbox로 쌓인다.
부하가 올라가면 “가끔만” 콜백이 누락될 수 있다.

이때 증상은 보통 이렇다.

이런 경우에는 mbox/큐의 수용량과
net task 우선순위를 같이 봐야 한다.

빠른 디버깅 체크리스트

  1. lwIP API가 ISR/드라이버 컨텍스트에서 호출되는지부터 grep
  2. LWIP_ASSERT / LWIP_STATS를 켜서 “어디가 막히는지” 숫자로 보기
  3. tcpip_thread 우선순위/스택, TCPIP_MBOX_SIZE를 같이 확인
  4. pbuf 누수/고갈이면 memp/pbuf 통계부터 본다
  5. “가끔만”이면 대부분 컨텍스트/락/큐 포화다 (확률 이슈)

결론

lwIP는 성능도 좋고 유연한데, RTOS 환경에서는 “호출 컨텍스트 규칙”을 어기면 랜덤 이슈가 나온다. ISR에서는 최소만 하고, tcpip_thread로 넘기는 구조로 정리하면 재현 어려운 네트워크 버그가 크게 줄어든다.


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


Edit page
Share this post on:

Previous Post
DoIP Functional Address는 왜 응답이 이상하게 보일까
Next Post
DoIP 통신이 가끔 끊긴다: Alive Check / TCP Keepalive / Tester Present를 분리해서 보자