QNX RTOS: 10-2. A Simple Resource Manager

2026. 1. 23. 17:41운영체제/QNX

목표(Behavior)

클라이언트 관점에서 이 예제 리소스 매니저(Resource Manager)는 다음처럼 동작합니다.

  • read() : 항상 0바이트 반환 (EOF처럼 동작, 실제 하드웨어 입력 없음)
  • write() : 어떤 크기든 성공 처리 (데이터를 “받아주기만” 하는 형태)
  • 그 외 동작(open/close/stat 등)은 프레임워크 기본(default) 동작을 따름

즉, “가짜 디바이스(dummy device)”를 만들어 /dev/example 같은 엔트리를 노출시키고, 최소한의 read/write만 커스터마이즈하는 전형적인 연습 구조입니다.


초기화 전체 흐름(One Big Picture)

초기화 단계는 크게 두 덩어리입니다.

  1. 구조체/테이블 준비(정적 설정)
  2. pathname space 등록 + 메시지 수신 루프(동작 시작)

스크립트 기준으로 단계는 다음 순서로 정리됩니다.

  1. Dispatch 구조체 생성(Dispatch structure)
  2. Connect 함수 테이블 설정(Connect function table)
  3. I/O 함수 테이블 설정(I/O function table)
  4. 디바이스 속성 설정(Device attributes, iofunc_attr_t)
  5. 리소스 매니저 속성/attach(attach to pathname space, secpol_resmgr_attach)
  6. Dispatch context 할당(Dispatch context, ctp)
  7. Receive loop: dispatch_block() + dispatch_handler() 반복

Step 1) Dispatch 생성: dispatch_create_channel()

하는 일

  • 리소스 매니저가 메시지를 받기 위한 채널(Channel) 을 생성하고,
  • 프레임워크가 사용할 dispatch 핸들(dpp) 을 확보합니다.

왜 필요한가

QNX의 RM은 “메시지 서버”입니다. 따라서 수신 엔드포인트(채널) 없이는 connect/read/write 메시지를 받을 수 없습니다.

파라미터 포인트

  • channel ID를 -1로 주는 패턴: “새로 만들어 달라”는 의미로 많이 사용
  • DISPATCH_FLAG_NOLOCK: 불필요한 락을 비활성화(스크립트 설명).
    다만 멀티스레드/동시성 요구가 있으면 이 플래그 선택은 신중해야 합니다(이 강의는 단순 예제 전제).

Step 2~3) Connect/I-O 함수 테이블 설정

리소스 매니저 프레임워크는 “어떤 메시지 타입을 어떤 핸들러로 처리할지”를 테이블로 받습니다.

2) Connect 테이블(Connect messages)

  • pathname 기반 호출이 들어올 때 처리
  • 예: open(), (필요 시) unlink(), rename() 등

대부분의 단순 디바이스 RM은 보통 open만 의미 있게 다루고, unlink/rename은 파일시스템 성격이 있을 때 비중이 커집니다.

3) I/O 테이블(I/O messages)

  • file descriptor 기반 호출 처리
  • 예: _IO_READ, _IO_WRITE, _IO_CLOSE, _IO_DEVCTL 등

이 예제는 “read=0 bytes”, “write=always ok”가 목표이므로, 통상 read/write 핸들러만 override 하고 나머지는 default로 둡니다.

테이블 초기화 패턴 요지

스크립트에서는 “초기화 함수 호출 시 1번째/3번째 인자를 상수로 두고, connect 및 I/O 함수 포인터를 넣는다”는 식으로 설명합니다.
핵심은: 프레임워크 기본 핸들러를 깔고, 필요한 항목만 교체하는 구성입니다.


Step 4) Device attributes 설정: iofunc_attr_init()

하는 일

  • 디바이스의 기본 속성(권한/타입/UID/GID 등)을 담는 attribute 구조체(iofunc_attr_t) 를 초기화합니다.

왜 필요한가

  • attach 시에 “이 엔트리가 어떤 타입이며 권한이 무엇인지”를 RM 프레임워크가 알아야 합니다.
  • 보통 stat(), 권한 체크, open 모드 체크 등이 이 정보에 의존합니다.

스크립트 포인트

  • 권한을 옥탈(octal) 표기(예: 0666, 0644 등)로 넣는 전형적 패턴
  • 예제에서는 device-specific 추가 데이터가 없어서 기본값 위주로 사용
  • 필요하면 이 구조체를 확장(embedding)해서 사용자 데이터를 같이 들고 다니는 설계도 가능(실무에서는 흔함)

Step 5) Pathname space 등록: secpol_resmgr_attach()

하는 일

  • /dev/example 같은 이름을 pathname space에 등록하여,
  • 클라이언트가 open("/dev/example")로 접근할 수 있도록 “OS의 일부처럼 보이게” 만듭니다.

왜 secpol_* 인가

  • 과거에는 resmgr_attach()를 썼고,
  • 보안 정책(Security Policy)을 반영하기 위해 확장된 것이 secpol_resmgr_attach()라는 설명입니다.

주요 인자(스크립트 기준 의미)

  • secpol_handle: 보통 NULL
  • dpp: Step 1에서 받은 dispatch 포인터
  • attr: RM attach용 추가 속성(대개 NULL)
  • path: 등록할 경로(/dev/example)
  • file_type: 보통 FILE_TYPE_ANY 같은 기본값
  • flags: 0 또는 제어 플래그
  • connect funcs, io funcs: Step 2~3에서 만든 테이블
  • ioattr: Step 4의 디바이스 attribute 포인터
  • perms_set: 보통 NULL이지만 “정책에 의해 속성이 변경되었는지” 알려주는 용도로 활용 가능

매우 중요한 실무 포인트(스크립트의 “piece of advice”)

  • attach 전에 하드웨어 탐지(HW detect), 버퍼 할당(buffer allocation), 초기 설정(configuration)을 끝내라.
  • 이유: attach 이후에는 클라이언트가 보게 되고(open 시도 등), “준비가 덜 된 상태의 RM”은 즉시 오류/레이스/불완전 상태를 노출할 수 있습니다.

구조체 라이프타임(lifetime) 주의

  • secpol_resmgr_attach()는 connect/io/attr 구조체를 복사하지 않는다는 설명이 있습니다.
  • 따라서 해당 구조체들은 RM이 살아있는 동안 계속 유효해야 합니다.
    • 전역(global)로 두거나
    • malloc() 등으로 할당해 “프로세스 생명주기 전체”를 보장해야 합니다.

권한(Privilege) 요구

  • pathname space에 이름을 등록하는 행위는 특권이 필요할 수 있으며,
  • 스크립트에서는 PROCMGR_AID_PATHSPACE 능력(Ability)이 필요하다고 언급합니다.

Step 6) Dispatch context 할당: dispatch_context_alloc()

하는 일

  • 메시지 수신 루프에서 사용할 컨텍스트(ctp) 를 할당합니다.
  • 이 컨텍스트는 수신 버퍼(receive buffer), rcvid 등 “현재 수신한 메시지 상태”를 들고 있으며,
  • connect/I-O 핸들러로 전달되는 핵심 파라미터입니다.

즉, 핸들러 입장에서는 ctp를 통해:

  • 어떤 요청인지(메시지 헤더/타입)
  • 누가 보냈는지(rcvid)
  • 수신 버퍼가 어디인지
    등을 접근하게 됩니다.

Step 7) Receive loop: dispatch_block() + dispatch_handler()

역할 분담

  • dispatch_block(ctp)
    • 블로킹(blocking) 으로 메시지 도착을 기다림
  • dispatch_handler(ctp)
    • 도착한 메시지를 해석하여
    • connect면 connect handler,
    • I/O면 I/O handler로 디스패치(dispatch)

이 루프가 RM의 “서버로서의 생명”입니다.


전체를 구현 관점으로 축약한 체크리스트

  1. dpp = dispatch_create_channel(-1, DISPATCH_FLAG_NOLOCK)
  2. connect 테이블 초기화(최소 open)
  3. io 테이블 초기화(최소 read/write)
  4. iofunc_attr_init(&ioattr, mode, NULL, NULL)
  5. id = secpol_resmgr_attach(NULL, dpp, NULL, "/dev/example", FILE_TYPE_ANY, 0, &connect_funcs, &io_funcs, &ioattr, &perms_set)
  6. ctp = dispatch_context_alloc(dpp)
  7. while( (ctp = dispatch_block(ctp)) ) dispatch_handler(ctp);