null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
lwIP와 RTOS 통합 — 동기냐 비동기냐, 그 선택의 기준
#lwip
#rtos
#freertos
#동기
#비동기
@devpc
|
2026-05-06 05:26:57
|
GET /api/v1/nodes/643?nv=1
History:
v1 (2026-05-06) (Latest)
0
Views
1
Calls
# lwIP와 RTOS 통합 — 동기냐 비동기냐, 그 선택의 기준 ## 동기 vs 비동기 처리 소프트웨어 분야에서 동기/비동기는 맥락마다 미묘하게 다르게 정의된다. 임베디드 관점에서 정리하면: **동기(Synchronous) 처리** - 코드가 순서대로 실행된다 - 다음 줄이 실행되려면 현재 작업이 완료되어야 한다 - C언어로 처음 배울 때의 기본 실행 모델 **비동기(Asynchronous) 처리** - 코드 순서와 실행 순서가 다를 수 있다 - OS, 콜백함수, 인터럽트, Task 등이 동시에 동작한다 - 자원을 분할해 여러 작업을 병행 처리 ## lwIP 운영 모드 — NO_SYS vs RTOS lwIP는 두 가지 운영 모드를 지원한다. ### NO_SYS 모드 (OS 없음) `lwipopts.h`에서 `NO_SYS=1`로 설정하면 RTOS 없이 lwIP를 쓸 수 있다. ```c /* NO_SYS 모드 메인 루프 */ while (1) { ethernetif_input(&netif); /* 패킷 수신 처리 */ sys_check_timeouts(); /* lwIP 타임아웃 처리 */ /* 애플리케이션 로직 */ my_app_poll(); } ``` 장점: - 구조가 단순하다 - RTOS 오버헤드가 없다 - 스택 메모리 사용량이 낮다 단점: - 여러 TCP 연결을 동시에 처리하기 어렵다 - 패킷 처리 중 다른 작업이 블로킹된다 ### RTOS 통합 모드 `NO_SYS=0`으로 설정하면 FreeRTOS, ThreadX 등과 통합할 수 있다. lwIP는 내부적으로 세마포어, 뮤텍스, 큐를 사용한다. `sys_arch.c`에서 이 RTOS 추상화 계층을 구현해야 한다. ```c /* FreeRTOS 기반 lwIP 태스크 구조 */ /* lwIP 입력 처리 태스크 */ void ethernetif_input_task(void *argument) { while (1) { if (osSemaphoreAcquire(RxPktSemaphore, TIME_WAITING_FOR_INPUT) == osOK) { ethernetif_input(&gnetif); } } } /* DHCP 처리 태스크 */ void DHCP_task(void *argument) { while (1) { dhcp_fine_tmr(); osDelay(DHCP_FINE_TIMER_MSECS); } } ``` ## 비동기가 항상 좋은 게 아니다 흔한 오해: "RTOS + 비동기 = 더 빠름". 실제로는 반드시 그렇지 않다. 임베디드 현장에서 비동기 구조가 많은 코드를 동기 구조로 단순화해 성능을 개선한 사례가 있다. 컨텍스트 스위칭 오버헤드, 뮤텍스 경쟁, 큐 복사 비용이 축적되면 단일 스레드 폴링보다 느려질 수 있다. 결국 선택 기준은: - **동시 연결이 많거나, 각 태스크의 처리 시간이 다양할 때** → RTOS - **처리 패턴이 단순하고 Latency가 중요할 때** → NO_SYS 폴링 ## XCP on Ethernet 설계 사례 XCP(프로토콜 계층)와 TCP/IP(전송 계층)를 연동할 때 역할 혼동이 발생하기 쉽다. ``` (TCP Client / XCP Master) ◄─────────────► (TCP Server / XCP Slave) ``` 직관적으로는 "클라이언트 = 슬레이브"가 맞는 것 같지만, 실제로는 연결을 시작하는 주체끼리 묶인다. XCP Master가 Slave에 접속 요청을 시작하므로, XCP Master가 TCP Client 역할을 맡는다. 웹서핑에서 확인한 바로도 이 구조가 일반적이다. 이처럼 두 레이어의 역할을 헷갈리지 않으려면, **누가 연결을 시작하는가**를 기준으로 생각하면 된다. ## FreeRTOS + lwIP 통합 시 주의할 실수 ### 태스크 우선순위 설정 lwIP 관련 태스크의 우선순위가 잘못 설정되면 패킷이 드롭되거나 처리가 지연된다. 권장 우선순위 배치 (높을수록 우선): ``` osPriorityHigh → Ethernet 입력 처리 태스크 (ethernetif_input_task) osPriorityAboveNormal → TCP/DHCP 처리 태스크 osPriorityNormal → 애플리케이션 데이터 처리 태스크 osPriorityLow → 로깅, 통계 수집 등 ``` Ethernet 입력 태스크가 낮은 우선순위라면 패킷이 들어와도 처리가 밀려 RX 버퍼가 가득 찰 수 있다. ETH MAC DMA 버퍼가 가득 차면 패킷이 조용히 버려진다. ### 세마포어/뮤텍스 사용 패턴 RAW API 콜백 함수는 **lwIP Core Lock** 보호 하에 실행된다. FreeRTOS 통합 시에는 `LOCK_TCPIP_CORE()` / `UNLOCK_TCPIP_CORE()` 매크로로 lwIP API 호출 구간을 보호해야 한다. ```c /* 다른 FreeRTOS 태스크에서 lwIP API 호출 시 */ LOCK_TCPIP_CORE(); tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY); tcp_output(pcb); UNLOCK_TCPIP_CORE(); ``` 이 보호 없이 여러 태스크에서 동시에 `tcp_write()`를 호출하면 내부 데이터 구조가 깨진다. ### 스택 크기 오산 FreeRTOS에서 태스크를 생성할 때 스택 크기를 너무 작게 잡으면 스택 오버플로우로 죽는다. lwIP 관련 태스크는 내부적으로 함수 호출 깊이가 깊으므로 여유 있게 잡는다. ```c /* 일반적으로 안전한 스택 크기 */ osThreadDef(lwip_task, ethernetif_input_task, osPriorityHigh, 0, 512); /* ↑ 512 words = 2KB (FreeRTOS에서 word는 보통 4바이트) */ ``` FreeRTOS의 `uxTaskGetStackHighWaterMark()`로 실제 최대 스택 사용량을 측정해서 여유분을 확인할 수 있다. ## NO_SYS 폴링 모드의 실전 패턴 RTOS 없이 쓸 때 메인 루프 구조: ```c void main_loop(void) { while (1) { /* 1. 수신 패킷 처리 */ ethernetif_input(&gnetif); /* 2. lwIP 타이머 처리 (TCP timeout, ARP 갱신 등) */ sys_check_timeouts(); /* 3. 애플리케이션 로직 */ app_poll(); /* 4. 저전력 슬립 (선택) */ /* 패킷이 없으면 짧게 슬립해도 됨 — 단, 응답 지연 허용 시 */ } } ``` `ethernetif_input()`은 호출할 때만 패킷을 처리한다. 이 함수 호출 간격이 길어질수록 수신 지연이 생긴다. 고속 데이터 처리가 필요하다면 인터럽트 + 플래그 방식으로 패킷 도착 시점을 바로 처리하는 구조를 쓴다. --- 이 시리즈를 통해 lwIP를 "그냥 붙이는" 수준에서 "왜 그런 설정이고, 어디서 문제가 생기고, 어떻게 고치는지"를 이해하는 수준으로 넘어왔다. 임베디드 이더넷 개발에서 만나는 문제들의 80%는 메모리 설정, 정렬, 프로토콜 선택, 초기화 순서에서 나온다. 이 흐름을 이해하고 있으면 처음 보는 문제도 원인 추적이 빠르다.
// COMMENTS
Newest First
ON THIS PAGE