QNX RTOS: 5-9. Event Delivery

2025. 12. 15. 11:03운영체제/QNX

1. Event란 무엇인가?

  • Event(이벤트) = “어떤 일이 일어났음을 알려주는 알림을 담는 컨테이너”
  • 누가 누구에게 보낼 수 있나?
    • 스레드 ↔ 스레드 (같은 프로세스 또는 다른 프로세스)
    • 커널 → 스레드 (하드웨어 인터럽트, 타이머 만료 등)

이벤트 형태는 크게 네 가지로 볼 수 있음:

  1. Pulse(펄스)
    • 경량 알림, MsgReceive()로 깨움
  2. Signal(시그널)
    • POSIX 시그널 형태로 알림
  3. Interrupt(인터럽트 기반 이벤트)
    • 하드웨어 인터럽트가 발생하면 커널이 등록된 이벤트를 전달
    • 스레드는 InterruptWait()로 블록되어 있다가 깨움
  4. Timer(타이머 기반 이벤트)
    • 타이머 만료 시 커널이 이벤트 전달

요약하자면:

“어떤 조건(인터럽트, 타이머, 서버 내부 조건 등)이 발생했을 때
미리 등록해 둔 방식(pulse, signal 등)으로 깨워 주는 메커니즘”


2. 이벤트를 표현하는 구조체: struct sigevent

모든 이벤트는 결국 struct sigevent에 담김.

  • 이벤트의 타입 (pulse, signal, interrupt 등)
  • 어느 스레드/채널로 보낼지
  • 우선순위, 코드(code), value 등 부가 데이터

보통 클라이언트가 이 구조체를 초기화해서 서버나 커널에 넘기게 됨:

 
struct t_sigevent sigevent;

초기화 방법은 두 가지:

  1. 직접 필드 하나씩 채우기 (수동 초기화)
  2. 매크로를 사용해서 깔끔하게 초기화

자주 쓰는 매크로들:

  • SIGEV_INTR_INIT(&sigevent)
    • 인터럽트용 이벤트
    • 스레드는 InterruptWait()로 블록 → 인터럽트 발생 시 깨움
  • SIGEV_PULSE_INIT(&sigevent, self_coid, MyPriority, our_code, value)
    • Pulse 형태 이벤트
    • MsgReceive()로 pulse를 받는 구조
    • self_coid: 어느 채널/연결로 pulse를 보낼지
    • MyPriority: pulse 보낼 때 사용할 우선순위
    • our_code, value: 서버가 클라이언트에게 전달하고 싶은 작은 데이터
  • SIGEV_SIGNAL_INIT(&sigevent, signo)
    • 시그널 기반 이벤트

이외에도 여러 SIGEV_..._INIT 매크로들이 있고, 라이브러리 레퍼런스에 정리되어 있음.


3. Thread ↔ Thread / Process ↔ Process 이벤트 전달 (Pulse 중심)

3.1 기본 흐름

  1. 클라이언트 준비
    • 클라이언트는 자기 채널을 만들어 두고(ChannelCreate()),
      그 채널로 연결(ConnectAttach())을 해서 자기 자신에게 pulse를 받을 수 있는 coid를 만든다 (self coid).
    • 그 coid와 함께 SIGEV_PULSE_INIT()로 sigevent를 초기화한다.
    • 이 sigevent를 일반 메시지(MsgSend) 안에 넣어서 서버로 전송한다.
    • “나중에 어떤 일이 생기면 이 이벤트 방식(이 pulse)으로 나를 깨워 주세요.”
  2. 서버 측 처리 (등록)
    서버는 MsgReceive()로 메시지를 받으면:
    • 메시지 안에 들어 있는 sigevent를 꺼낸다.
    • 필요하면 MsgVerifyEvent()로 “이 이벤트 구조가 말이 되는지(잘 만들어졌는지)” 검증한다.
    • 검증 OK라면:
      • sigevent를 내부에 저장
      • 같이 온 rcvid(client의 receive ID)도 저장
      • MsgReply()로 클라이언트에게 “이벤트 잘 등록했다. 나중에 깨워 줄게요.”라고 응답하고 클라이언트를 unblock
  3. 이벤트 발생 시점 (나중에)
    • 서버 내부 로직 또는 하드웨어/타이머에 의해 “이 클라이언트를 깨워야 할 타이밍”이 오면,
    • MsgDeliverEvent(rcvid, &event) 를 호출
      • 앞에서 저장해 둔 rcvid, sigevent를 사용
    • 이때 sigevent가 pulse 타입이므로 실제로는 pulse가 클라이언트 채널로 날아감
  4. 클라이언트에서 수신
    • 클라이언트는 MsgReceive()로 자기 채널을 듣고 있다가
    • pulse를 받으면 rcvid == 0 으로 구분하고,
    • struct _pulse 안의 code, value를 보고 “어떤 이벤트인지” 처리

4. 이벤트 내용 업데이트: SIGEV_FLAG_UPDATEABLE

기본적으로:

  • 클라이언트가 sigevent를 만들 때 넣은 값이 그대로 다시 돌아옴
    (예: 처음에 value = 10 이면, 나중에 서버가 이벤트를 보낼 때도 10)

하지만 어떤 경우에는:

“이벤트를 보낼 시점에 서버가 value를 수정해서 보내고 싶을 때

예: 클라이언트는 “나중에 현재 온도값을 pulse로 보내 주세요”라고 등록만 하고,
세부 값(온도)는 이벤트 발생 시점에 서버가 채워서 보내기.

이때 필요한 것이:

  • 클라이언트 측에서 미리 허용 플래그를 세팅해야 함
    • SIGEV_FLAG_UPDATEABLE
    • 또는 SIGEV_MAKE_UPDATEABLE(&ev) 매크로 사용

이 플래그가 의미하는 것:

“서버가 이벤트를 전달할 때, 이 sigevent 안의 value 같은 필드를 수정해도 괜찮다.”

서버 입장에서는:

  1. 이벤트를 보낼 때 이 플래그를 확인
  2. 세팅되어 있으면, value 등을 현재 값으로 덮어쓰고 MsgDeliverEvent() 실행
  3. 세팅되어 있지 않다면, 원래 클라이언트가 등록한 값 그대로 사용

5. Interprocess Event일 때 필요한 것: MsgRegisterEvent()

프로세스 ↔ 프로세스 사이에서 이벤트를 쓸 때는 한 가지가 더 필요합니다:

“이 이벤트는 어느 서버가 보내는 것만 허용할지를 미리 등록해야 한다”

그게 바로 MsgRegisterEvent() 호출임.

5.1 기본 형태

클라이언트는:

  1. sigevent를 만든다 (예: pulse event)
  2. MsgRegisterEvent(server_coid 또는 특수 값, &event) 호출
    • server_coid : “이 이벤트를 나중에 전달할 수 있는 권한을 가진 서버의 connection ID”
    • 이 호출을 통해 event가 “registered event”가 된다
  3. 이제 메시지에 sigevent를 넣어서 서버에 보냄
  4. 서버는 나중에 MsgDeliverEvent()로 이 registered event를 사용할 수 있음

옵션 값:

  • 특정 서버에게만 허용: server_coid (일반적인 케이스)
  • 어떤 서버든 OK: _NTO_REGEVENT_ALLOW_ANY_SERVER
    • 보안적으로는 덜 안전
  • Process Manager(프로세스 매니저)에게서만 받고 싶을 때: SYSMGR_COID

중요 포인트:

  • Interrupt나 Timer 기반 이벤트는 커널이 직접 보내므로,
    • 이 경우에는 MsgRegisterEvent()가 필수는 아님
  • 하지만 thread ↔ thread / process ↔ process 이벤트(pulse, signal)를 사용할 때는
    “누가 보낼 수 있는지”를 제한하기 위해 MsgRegisterEvent()를 쓰는 것이 일반적

이벤트가 더 이상 필요 없으면:

  • MsgUnregisterEvent() 로 등록 해제

6. 이벤트 검증: MsgVerifyEvent() – “클라이언트를 클라이언트로부터 보호”

서버 입장에서,

  • 클라이언트가 sigevent를 보냈을 때
  • 나중에 이벤트를 전달하는 시점에는 다시 클라이언트와 상호작용할 기회가 없음
    (그때 오류가 나면 이미 늦음)

그래서 서버는 처음 등록 단계에서:

  • MsgVerifyEvent(&event) 를 호출해서:
    • 이벤트 구조가 유효한지
    • 서버 입장에서 처리 가능한 타입인지
    • 권한 및 포맷이 맞는지 등을 확인
  • 만약 잘못된 이벤트라면:
    • 그 시점에 바로 에러를 리턴하고 등록을 거부할 수 있음

요약:

“나중에 문제가 터지지 않도록,
이벤트를 받았을 때 바로 검증하고 안 맞으면 거절하라.


7. 전체 흐름 한 번에 정리

  1. 클라이언트 측
    • struct sigevent ev;
    • SIGEV_PULSE_INIT() 등으로 초기화
    • 필요하면 SIGEV_MAKE_UPDATEABLE(&ev)로 updateable 허용
    • MsgRegisterEvent(server_coid, &ev)로 event 등록
    • 그 후 MsgSend()로 서버에 “이 이벤트를 써서 나를 나중에 깨워 달라”는 요청 전송
  2. 서버 측 (등록 단계)
    • MsgReceive()로 요청 수신
    • MsgVerifyEvent()로 이벤트 구조 검증
    • OK면:
      • ev를 저장
      • rcvid도 저장
      • MsgReply()로 “등록 완료, 나중에 notify 해줄게요” 응답
  3. 서버 측 (이벤트 발생 시)
    • 상태 변화 / 하드웨어 입력 / 내부 로직에 의해 “이제 이벤트를 보내야 할 때”가 되면
    • 저장해 둔 ev + rcvid를 사용해서 MsgDeliverEvent() 호출
    • 커널이 적절한 방식(여기선 pulse)으로 클라이언트를 깨움
  4. 클라이언트 측 (수신)
    • 자기 채널을 MsgReceive()로 듣고 있다가
    • pulse 수신 시, rcvid == 0, code, value를 확인하고 처리
  5. Clean up
    • 클라이언트가 disconnect되면, 서버는 disconnect pulse를 받아
      • 클라이언트 관련 rcvid, event, coid 등 정리
      • 필요한 경우 ConnectDetach() 호출
    • 클라이언트가 이벤트가 더 이상 필요 없을 경우 MsgUnregisterEvent() 호출

8. SDV/자율주행 관점에서 어떻게 생각하면 편하냐면…

지금까지 본 내용을 자율주행/SDV 시스템에 비유하면:

  • 이벤트(event) = “상태 변화 알림 패킷”
    • lane detection 완료
    • sensor timeout
    • TSN 게이트에서 특정 priority queue 비워짐
    • DDS topic에서 특정 QoS violation 발생, 등
  • 상위 모듈(플래너, 상태 머신)은
    • 주기적으로 polling하기보다
      이런 event를 등록해 두고 pulse 기반으로 깨워지는 구조를 사용하면
    • CPU 낭비 줄이고, 구조를 깔끔하게 유지할 수 있

'운영체제 > QNX' 카테고리의 다른 글

QNX RTOS: 6-1. IPC Methods  (0) 2025.12.15
QNX RTOS: 5-10. Shared Memory  (0) 2025.12.15
QNX RTOS: 5-8. Deadlock Avoidance  (0) 2025.12.03
QNX RTOS: 5-7. Server Designs  (0) 2025.12.02
QNX RTOS: 5-6. Issues Related to Priorities  (0) 2025.12.02