2025. 12. 15. 11:03ㆍ운영체제/QNX
1. Event란 무엇인가?
- Event(이벤트) = “어떤 일이 일어났음을 알려주는 알림을 담는 컨테이너”
- 누가 누구에게 보낼 수 있나?
- 스레드 ↔ 스레드 (같은 프로세스 또는 다른 프로세스)
- 커널 → 스레드 (하드웨어 인터럽트, 타이머 만료 등)

이벤트 형태는 크게 네 가지로 볼 수 있음:
- Pulse(펄스)
- 경량 알림, MsgReceive()로 깨움
- Signal(시그널)
- POSIX 시그널 형태로 알림
- Interrupt(인터럽트 기반 이벤트)
- 하드웨어 인터럽트가 발생하면 커널이 등록된 이벤트를 전달
- 스레드는 InterruptWait()로 블록되어 있다가 깨움
- Timer(타이머 기반 이벤트)
- 타이머 만료 시 커널이 이벤트 전달
요약하자면:
“어떤 조건(인터럽트, 타이머, 서버 내부 조건 등)이 발생했을 때
미리 등록해 둔 방식(pulse, signal 등)으로 깨워 주는 메커니즘”
2. 이벤트를 표현하는 구조체: struct sigevent
모든 이벤트는 결국 struct sigevent에 담김.
- 이벤트의 타입 (pulse, signal, interrupt 등)
- 어느 스레드/채널로 보낼지
- 우선순위, 코드(code), value 등 부가 데이터
보통 클라이언트가 이 구조체를 초기화해서 서버나 커널에 넘기게 됨:
초기화 방법은 두 가지:
- 직접 필드 하나씩 채우기 (수동 초기화)
- 매크로를 사용해서 깔끔하게 초기화
자주 쓰는 매크로들:
- 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 기본 흐름

- 클라이언트 준비
- 클라이언트는 자기 채널을 만들어 두고(ChannelCreate()),
그 채널로 연결(ConnectAttach())을 해서 자기 자신에게 pulse를 받을 수 있는 coid를 만든다 (self coid). - 그 coid와 함께 SIGEV_PULSE_INIT()로 sigevent를 초기화한다.
- 이 sigevent를 일반 메시지(MsgSend) 안에 넣어서 서버로 전송한다.
- “나중에 어떤 일이 생기면 이 이벤트 방식(이 pulse)으로 나를 깨워 주세요.”
- 클라이언트는 자기 채널을 만들어 두고(ChannelCreate()),
- 서버 측 처리 (등록)
서버는 MsgReceive()로 메시지를 받으면:- 메시지 안에 들어 있는 sigevent를 꺼낸다.
- 필요하면 MsgVerifyEvent()로 “이 이벤트 구조가 말이 되는지(잘 만들어졌는지)” 검증한다.
- 검증 OK라면:
- sigevent를 내부에 저장
- 같이 온 rcvid(client의 receive ID)도 저장
- MsgReply()로 클라이언트에게 “이벤트 잘 등록했다. 나중에 깨워 줄게요.”라고 응답하고 클라이언트를 unblock
- 이벤트 발생 시점 (나중에)
- 서버 내부 로직 또는 하드웨어/타이머에 의해 “이 클라이언트를 깨워야 할 타이밍”이 오면,
- MsgDeliverEvent(rcvid, &event) 를 호출
- 앞에서 저장해 둔 rcvid, sigevent를 사용
- 이때 sigevent가 pulse 타입이므로 실제로는 pulse가 클라이언트 채널로 날아감
- 클라이언트에서 수신
- 클라이언트는 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 같은 필드를 수정해도 괜찮다.”
서버 입장에서는:
- 이벤트를 보낼 때 이 플래그를 확인
- 세팅되어 있으면, value 등을 현재 값으로 덮어쓰고 MsgDeliverEvent() 실행
- 세팅되어 있지 않다면, 원래 클라이언트가 등록한 값 그대로 사용
5. Interprocess Event일 때 필요한 것: MsgRegisterEvent()
프로세스 ↔ 프로세스 사이에서 이벤트를 쓸 때는 한 가지가 더 필요합니다:
“이 이벤트는 어느 서버가 보내는 것만 허용할지를 미리 등록해야 한다”
그게 바로 MsgRegisterEvent() 호출임.

5.1 기본 형태
클라이언트는:
- sigevent를 만든다 (예: pulse event)
- MsgRegisterEvent(server_coid 또는 특수 값, &event) 호출
- server_coid : “이 이벤트를 나중에 전달할 수 있는 권한을 가진 서버의 connection ID”
- 이 호출을 통해 event가 “registered event”가 된다
- 이제 메시지에 sigevent를 넣어서 서버에 보냄
- 서버는 나중에 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. 전체 흐름 한 번에 정리

- 클라이언트 측
- struct sigevent ev;
- SIGEV_PULSE_INIT() 등으로 초기화
- 필요하면 SIGEV_MAKE_UPDATEABLE(&ev)로 updateable 허용
- MsgRegisterEvent(server_coid, &ev)로 event 등록
- 그 후 MsgSend()로 서버에 “이 이벤트를 써서 나를 나중에 깨워 달라”는 요청 전송
- 서버 측 (등록 단계)
- MsgReceive()로 요청 수신
- MsgVerifyEvent()로 이벤트 구조 검증
- OK면:
- ev를 저장
- rcvid도 저장
- MsgReply()로 “등록 완료, 나중에 notify 해줄게요” 응답
- 서버 측 (이벤트 발생 시)
- 상태 변화 / 하드웨어 입력 / 내부 로직에 의해 “이제 이벤트를 보내야 할 때”가 되면
- 저장해 둔 ev + rcvid를 사용해서 MsgDeliverEvent() 호출
- 커널이 적절한 방식(여기선 pulse)으로 클라이언트를 깨움
- 클라이언트 측 (수신)
- 자기 채널을 MsgReceive()로 듣고 있다가
- pulse 수신 시, rcvid == 0, code, value를 확인하고 처리
- Clean up
- 클라이언트가 disconnect되면, 서버는 disconnect pulse를 받아
- 클라이언트 관련 rcvid, event, coid 등 정리
- 필요한 경우 ConnectDetach() 호출
- 클라이언트가 이벤트가 더 이상 필요 없을 경우 MsgUnregisterEvent() 호출
- 클라이언트가 disconnect되면, 서버는 disconnect pulse를 받아
8. SDV/자율주행 관점에서 어떻게 생각하면 편하냐면…
지금까지 본 내용을 자율주행/SDV 시스템에 비유하면:
- 이벤트(event) = “상태 변화 알림 패킷”
- lane detection 완료
- sensor timeout
- TSN 게이트에서 특정 priority queue 비워짐
- DDS topic에서 특정 QoS violation 발생, 등
- 상위 모듈(플래너, 상태 머신)은
- 주기적으로 polling하기보다
이런 event를 등록해 두고 pulse 기반으로 깨워지는 구조를 사용하면 - CPU 낭비 줄이고, 구조를 깔끔하게 유지할 수 있
- 주기적으로 polling하기보다
'운영체제 > 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 |