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 기본 검증

  1. iofunc_write_verify(ctp, msg, ocb, NULL)
  • open 모드가 read-only인지 등 POSIX 검증을 처리
  • 실패면 그대로 return
  1. 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 기본 사용 패턴

  • 목적: “헤더 이후의 데이터”를 별도 버퍼로 완전하게 가져오기

절차(개념):

  1. 목적 버퍼 확보
  • 간단히 하려면 malloc(msg->i.nbytes)
  • 실무는 ring buffer / pre-allocated cache 등을 사용 가능
  1. resmgr_msgget(ctp, dst_buf, nbytes_to_get, offset) 호출
  • offset은 헤더 크기만큼 건너뛰기 위해 사용
    • 최소 offset = sizeof(io_write_t) (또는 해당 헤더 타입 크기)
  • nbytes_to_get은 보통 msg->i.nbytes
  1. 반환값으로 실제로 얻은 바이트 수를 확인
  • 요청보다 작으면: 실제 전송 크기/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 플래그를 세팅했는가