QNX RTOS: 10-4. Handling read() and write() - write()
2026. 1. 23. 18:25ㆍ운영체제/QNX
1) POSIX 관점에서 write()의 의미
write(fd, buf, nbytes)의 기대 동작:
- 성공: 1..nbytes 바이트를 “썼다”고 보고하고, 반환값은 실제로 쓴 바이트 수
- 에러: -1 반환 + errno 설정
- nbytes == 0: 권한이 있으면 0 반환(성공) 가능
- read()와 달리 “0이 EOF 의미가 아니다”
- 파일은 보통 커지거나, 디바이스 특성(프레임버퍼 등)상 “가득 참”을 에러로 처리할 수 있음

2) QNX에서 write()는 메시지 전송이며, header + data 형태
QNX write()는 내부적으로 MsgSendv()를 사용합니다(벡터 전송).
- 첫 iov: 헤더 struct _io_write
- 메시지 타입(_IO_WRITE), xtype, nbytes 등 포함
- 둘째 iov: 데이터(사용자가 넘긴 buf)
커널 관점에서 결국 서버(RM)가 받는 것은:
- “헤더 뒤에 데이터가 이어진 하나의 메시지”처럼 보입니다.
핸들러 시그니처는 io_write_t *msg를 받는데,
- io_write_t는 union이며
- msg->i.nbytes로 클라이언트가 쓰려는 바이트 수를 얻습니다.


3) Reply 규칙: write는 “데이터를 돌려줄 필요가 없다”
클라이언트는 write에서 데이터를 보내기만 하므로, RM의 reply는 보통:
- MsgReply(ctp->rcvid, status, NULL, 0);
여기서
- status = “성공적으로 처리한(쓴) 바이트 수”
- reply payload는 없음(NULL/0)
이 status가 MsgSendv() 반환값이 되고,
결국 클라이언트 write()의 반환값이 됩니다.
에러는 read()와 동일:
- 핸들러에서 return errno_value; 하면
- 프레임워크가 MsgError() 경로로 처리해서
- 클라이언트 write()는 -1, errno가 설정됩니다.
4) 예제 write 핸들러의 표준 골격(스크립트가 강조한 순서)
4.1 기본 검증
- iofunc_write_verify(ctp, msg, ocb, NULL)
- open 모드가 read-only인지 등 POSIX 검증을 처리
- 실패면 그대로 return
- xtype 체크
- 정상 기대값: _IO_XTYPE_NONE
- 아니면 return ENOSYS
4.2 데이터 접근(주의: “msg+1”이 항상 정답이 아님)
스크립트가 강조한 핵심 문제:
- 데이터는 논리적으로 “헤더 뒤”에 오므로 단순히 msg+1로 접근하고 싶지만,
- 수신 버퍼 크기 제한 때문에 payload 전체가 수신 버퍼에 다 복사되지 않을 수 있다.
IPC 규칙:
- 클라이언트가 3000바이트를 보냈어도 서버 수신 버퍼가 1000바이트면,
커널은 최소(sbytes, rbytes)만 복사해서 서버 버퍼에는 처음 1000바이트만 있다.
따라서 “msg+1로 바로 처리”는 부분 데이터만 처리하는 버그가 될 수 있습니다.

5) 해결책: resmgr_msgget()로 payload를 안전하게 수집
리소스 매니저에서는 편의 함수가 제공됩니다.
5.1 기본 사용 패턴
- 목적: “헤더 이후의 데이터”를 별도 버퍼로 완전하게 가져오기
절차(개념):
- 목적 버퍼 확보
- 간단히 하려면 malloc(msg->i.nbytes)
- 실무는 ring buffer / pre-allocated cache 등을 사용 가능
- resmgr_msgget(ctp, dst_buf, nbytes_to_get, offset) 호출
- offset은 헤더 크기만큼 건너뛰기 위해 사용
- 최소 offset = sizeof(io_write_t) (또는 해당 헤더 타입 크기)
- nbytes_to_get은 보통 msg->i.nbytes
- 반환값으로 실제로 얻은 바이트 수를 확인
- 요청보다 작으면: 실제 전송 크기/offset 조건 등을 재검토하거나 에러 처리
5.2 성능 포인트(스크립트 설명)
resmgr_msgget()은 최적화되어 있습니다.
- 먼저 로컬 수신 버퍼에 이미 들어온 부분을 memcpy로 복사
- 부족하면 그때만 MsgRead() 커널 콜로 나머지를 가져옴
즉, “가능하면 커널 콜을 줄인다”는 점이 장점입니다.
또한 벡터 버전 resmgr_msggetv()도 있어,
- 여러 목적 버퍼(예: 여러 캐시 블록, ring buffer 세그먼트)에 분산 저장 가능
6) 타임스탬프 처리: mtime/ctime 업데이트 플래그
write()는 보통 다음 시간들이 연관됩니다.
- modification time (mtime)
- change-of-status time (ctime)
매 write마다 커널 콜로 즉시 시간을 찍으면 정확하지만 오버헤드가 있으므로,
대부분 RM은:
- “업데이트 필요” 플래그만 세팅
- 클라이언트가 stat() 호출 시 기본 stat 핸들러가 플래그를 보고 그때 시간을 채움
또한 nbytes > 0일 때만 플래그를 세팅하고,
write(..., 0)이면 시간 업데이트를 하지 않는 패턴을 사용합니다.
7) 요약 체크리스트(구현 시 반드시 확인할 것)
- iofunc_write_verify()를 먼저 호출했는가
- msg->i.xtype == _IO_XTYPE_NONE을 확인했는가 (아니면 ENOSYS)
- “payload가 수신 버퍼에 다 있는가?”를 가정하지 않았는가
- 안전하게 하려면 resmgr_msgget()로 헤더 이후 데이터를 가져오는 구조로 작성
- MsgReply(ctp->rcvid, bytes_written, NULL, 0) 형태로 reply했는가
- reply 후 _RESMGR_NOREPLY를 반환했는가
- 필요하면 mtime/ctime 플래그를 세팅했는가
'운영체제 > QNX' 카테고리의 다른 글
| QNX RTOS: 10-3. Handling read() and write() - read() (0) | 2026.01.23 |
|---|---|
| QNX RTOS: 10-2. A Simple Resource Manager (0) | 2026.01.23 |
| QNX RTOS: 10-1. Overview of Resource Managers (0) | 2026.01.23 |
| QNX RTOS: 9-1. Images & Buildfiles (0) | 2026.01.02 |
| QNX RTOS: 8-6. Kernel Timeouts (0) | 2026.01.02 |