QNX RTOS: 5-7. Server Designs
2025. 12. 2. 22:53ㆍ운영체제/QNX
1. 싱글 스레드 서버: 가장 단순한 기본형
- 가장 기본적인 서버 구조는:
for (;;) { rcvid = MsgReceive(chid, &msg, sizeof(msg), &info); // msg 처리 MsgReply(rcvid, EOK, &reply, sizeof(reply)); }
- 여러 클라이언트가 있더라도:
- Client1 → 처리 → Reply
- 그 다음 Client2 → 처리 → Reply
- … 이런 식으로 순차 처리만 해도,
- 각 요청을 처리하는 시간이 짧고, 전체 부하가 크지 않으면
→ 한 스레드로도 충분함.
장점:
- 코드가 단순함 (동기 처리, 공유 데이터 race 걱정 적음)
- Mutex, lock, 복잡한 동기화 로직이 거의 필요 없음
단점:
- 어떤 요청 하나가 오래 걸리면 (예: 큰 연산, 장시간 I/O):
- 그 동안 다른 클라이언트는 MsgReceive까지 도달 못 해서 기다림
- 특히 마감시간(Deadline)이 짧은 고우선순위 클라이언트가 와도,
- 서버가 이전 요청 처리에 묶여 있으면 제때 못 처리할 수 있음

즉, “응답 시간이 긴 요청이 섞여 있으면 싱글 스레드로는 위험”.
2. 멀티 스레드 서버: 워커 스레드 풀 구조
그래서 나오는 전형적인 구조:
“서버 안에 여러 개의 워커 스레드를 두고, 전부 같은 채널에서 MsgReceive()로 대기시킨다.”
2.1 구조 그림

- 서버 프로세스 안에:
- Thread 1: MsgReceive(chid, ...)에서 block
- Thread 2: MsgReceive(chid, ...)에서 block
- Thread 3: MsgReceive(chid, ...)에서 block
- …
- 클라이언트가 메시지를 보내면:
- 커널이 대기 중인 서버 스레드 중 하나를 골라서 깨움
- 그 스레드에게 클라이언트의 priority를 상속 (priority inheritance) 시켜줌
- 그 스레드가 해당 클라이언트 요청만 처리한 뒤 MsgReply()를 호출하면 끝
여기서 중요한 포인트:
- 여러 스레드가 있어도 모두 같은 코드 라인에서 MsgReceive()를 호출하고 있음
→ 서로 완전히 동일한 “워커 스레드”라고 보면 됨 - 어떤 스레드가 선택될지는 커널이 알아서 고름 (문서화된 보장은 없음)
2.2 장점
- 응답 지연(latency) 감소
- Client1의 긴 작업을 Thread3가 처리 중이어도
- Client3(짧은 deadline, 높은 priority)의 요청이 오면
→ Thread1 같은 다른 스레드가 바로 깨어나서 작업 수행 - 필요하면 Thread1이 Thread3를 preempt해서 먼저 실행될 수도 있음
- 멀티코어 활용
- 코어가 4개라면, 서로 다른 스레드들이 서로 다른 코어에서 동시에 실행 가능
- Throughput + deadline 만족률 모두 향상
- Priority inheritance 자동 처리
- 메시지 패싱 자체가 priority inheritance를 해주기 때문에
- 서버 내부에서 “어떤 클라이언트 priority였는지”를 따로 전달·관리할 필요가 없음
2.3 다른 구조와 비교
- 다른 설계도 가능은 함:
- 예: 수신 전담 스레드 하나가 MsgReceive()만 하고,
내부 queue에 넣어두고, 워커 스레드에게 분배하는 구조
- 예: 수신 전담 스레드 하나가 MsgReceive()만 하고,
- 하지만 이 방식은:
- 서버 내부에서 다시 “메시지 → 스레드” 디스패칭을 해야 해서 오버헤드 증가
- priority inheritance도 직접 신경 써야 할 수 있음
그래서 “실용적인 QNX 서버”는 대부분 MsgReceive()를 여러 스레드가 같이 들고 있는 구조를 많이 씀.
3. 즉시 Reply하지 않는 서버 (지연 Reply 패턴)
두 번째 중요한 포인트는:
“서버는 항상 MsgReceive 직후에 바로 MsgReply할 필요가 없다.”
즉, 클라이언트 메시지를 받은 뒤, 나중에 조건이 만족되었을 때 Reply 할 수 있음.

3.1 큐/대기열 예시
시나리오:
- Client A: “큐 3에서 데이터 하나 주세요. 기다릴게요” 라는 메시지 전송
- 서버가 내부 상태를 보니:
- 큐 3에 아직 데이터가 없음
이때 서버는 두 가지 선택을 할 수 있음:
- 바로 에러 리턴: “지금은 데이터 없음, 나중에 다시 시도해”
- 클라이언트를 REPLY blocked 상태로 유지하고, 나중에 데이터가 생겼을 때 알려주기

2번 선택:
- 서버는 해당 요청의 rcvid, 요청한 큐 번호, 바이트 수 등을 내부 client list에 저장
- 그리고 MsgReply를 아직 하지 않고 다시 MsgReceive()로 돌아감 → 다른 요청/이벤트 처리
- 나중에 다른 클라이언트 or 하드웨어 인터럽트가 큐 3에 데이터를 넣었을 때:
- “아, 큐 3 기다리던 애 있다!” 라는 걸 client list에서 보고
- 그때 비로소 해당 rcvid에 대해 MsgReply() 실행 → “여기 네가 기다리던 데이터”
이 구조가 굉장히 많이 쓰임:
- 메시지 큐 서버
- 장치 드라이버 (하드웨어에서 데이터 들어올 때까지 기다리는 클라이언트들)
- 파일 시스템의 read() 구현 등
3.2 /devc-pty + shell 예시

- shell 이 /devc-pty (pseudo-terminal 드라이버)에:
- “사용자가 키보드로 타이핑한 input 주세요. 기다릴게요” 메시지 전송
- 아직 사용자가 아무것도 안 쳤다면:
- 드라이버는 “지금 줄 데이터 없음”
- 하지만 shell은 “데이터 생길 때까지 기다릴게” 라고 했으므로
- shell은 REPLY blocked 상태
- 드라이버 프로세스는 Receive blocked 상태로 입력을 기다릴 수 있음
- 사용자가 키보드를 치면:
- 인터럽트 → 드라이버가 입력 버퍼 채우고,
- 그제서야 MsgReply()로 shell에게 “타이핑된 문자열”을 넘겨줌
- shell은 reply를 받고 나면, 다시:
- “또 입력 주세요, 기다릴게요” 메시지를 보냄
이게 매우 전형적인 “입력 주도형 (input-driven)” 구조임.
4. 정리
강의에서 말한 포인트를 한 줄씩 요약하면:
- 싱글 스레드 서버
- 단순하고 안전하지만, 오래 걸리는 작업이 섞이면
다른 클라이언트가 제때 처리되지 못할 수 있음.
- 단순하고 안전하지만, 오래 걸리는 작업이 섞이면
- 멀티 스레드 서버 (워커 풀 + 공용 채널)
- 여러 스레드가 동일 채널에서 MsgReceive()로 대기
- 클라이언트가 메시지 보내면 커널이 스레드 하나 골라서 깨우고
해당 클라이언트 priority를 상속해서 실행 → latency, deadline, throughput 향상
- 지연 Reply(Delayed Reply) 패턴
- MsgReceive()로 메시지 받아도 바로 MsgReply() 하지 않고
rcvid를 저장해뒀다가 이벤트(데이터 도착 등)가 생겼을 때 reply하는 구조 - 큐 서버, 디바이스 드라이버, 터미널 드라이버 등에서 매우 흔하게 쓰임
- 클라이언트는 그동안 REPLY blocked 상태로 “데이터를 기다리는 중”
- MsgReceive()로 메시지 받아도 바로 MsgReply() 하지 않고
'운영체제 > QNX' 카테고리의 다른 글
| QNX RTOS: 5-9. Event Delivery (0) | 2025.12.15 |
|---|---|
| QNX RTOS: 5-8. Deadlock Avoidance (0) | 2025.12.03 |
| QNX RTOS: 5-6. Issues Related to Priorities (0) | 2025.12.02 |
| QNX RTOS: 5-5. Multi-Part Messages (0) | 2025.12.02 |
| QNX RTOS: 5-4. How a Client Finds a Server (0) | 2025.12.02 |