임베디드 OS 개발 프로젝트를 읽고 작성하였습니다.
시작하기 전에
이벤트 : 인터럽트와 태스크 간의 연결 매체
11장 이벤트
11.1 이벤트 플래그
- 이벤트는 개발자가 정한 어떤 값으로 전달됨.
이벤트를 비트맵(bitmap)으로 만들기
- 각각의 이벤트 값을 겹치지 않는 비트 위치에 할당.
- 이벤트 플래그(event flag) : 특정 비트 위치에 독립된 이벤트를 할당해서 이벤트가 있다 없다를 표시하는 방식.
kernel/event.h
이벤트 플래그를 처리하는 함수와 이벤트 플래그 자체를 선언
#ifndef KERNEL_EVENT_H_
#define KERNEL_EVENT_H_
typedef enum KernelEventFlag_t
{
KernelEventFlag_UartIn = 0x00000001, //UartIn 이벤트 선언
KernelEventFlag_Reserved01 = 0x00000002,
KernelEventFlag_Reserved02 = 0x00000004,
KernelEventFlag_Reserved03 = 0x00000008,
KernelEventFlag_Reserved04 = 0x00000010,
KernelEventFlag_Reserved05 = 0x00000020,
KernelEventFlag_Reserved06 = 0x00000040,
KernelEventFlag_Reserved07 = 0x00000080,
KernelEventFlag_Reserved08 = 0x00000100,
KernelEventFlag_Reserved09 = 0x00000200,
KernelEventFlag_Reserved10 = 0x00000400,
KernelEventFlag_Reserved11 = 0x00000800,
KernelEventFlag_Reserved12 = 0x00001000,
KernelEventFlag_Reserved13 = 0x00002000,
KernelEventFlag_Reserved14 = 0x00004000,
KernelEventFlag_Reserved15 = 0x00008000,
KernelEventFlag_Reserved16 = 0x00010000,
KernelEventFlag_Reserved17 = 0x00020000,
KernelEventFlag_Reserved18 = 0x00040000,
KernelEventFlag_Reserved19 = 0x00080000,
KernelEventFlag_Reserved20 = 0x00100000,
KernelEventFlag_Reserved21 = 0x00200000,
KernelEventFlag_Reserved22 = 0x00400000,
KernelEventFlag_Reserved23 = 0x00800000,
KernelEventFlag_Reserved24 = 0x01000000,
KernelEventFlag_Reserved25 = 0x02000000,
KernelEventFlag_Reserved26 = 0x04000000,
KernelEventFlag_Reserved27 = 0x08000000,
KernelEventFlag_Reserved28 = 0x10000000,
KernelEventFlag_Reserved29 = 0x20000000,
KernelEventFlag_Reserved30 = 0x40000000,
KernelEventFlag_Reserved31 = 0x80000000,
KernelEventFlag_Empty = 0x00000000,
} KernelEventFlag_t;
void Kernel_event_flag_set(KernelEventFlag_t event);
void Kernel_event_flag_clear(KernelEventFlag_t event);
bool Kernel_event_flag_check(KernelEventFlag_t event);
#endif /* KERNEL_EVENT_H_ */
코드 11.1 kernel/event.h
32비트 변수 한 개로는 이벤트 플래그 32개를 표시할 수 있으므로 이벤트 플래그 자리 32개를 예약해 놓음
kernel/event.c
이벤트 플래그 데이터를 처리하는 코드
#include "stdint.h"
#include "stdbool.h"
#include "stdio.h"
#include "event.h"
static uint32_t sEventFlag;
void Kernel_event_flag_init(void)
{
sEventFlag = 0;
}
void Kernel_event_flag_set(KernelEventFlag_t event)
{
sEventFlag |= (uint32_t)event;
}
void Kernel_event_flag_clear(KernelEventFlag_t event)
{
sEventFlag &= ~((uint32_t)event);
}
bool Kernel_event_flag_check(KernelEventFlag_t event)
{
if (sEventFlag & (uint32_t)event)
{
Kernel_event_flag_clear(event);
return true;
}
return false;
}
코드 11.2 kernel/event.c
코드 11.2 설명은 더보기 클릭!
7번째 줄 | static uint32_t sEventFlag; | 이벤트 플래그를 32개 기록하고 있으면서 태스크에 전달하는 역할을 하는 커널 자료 구조 |
9~12번째 줄 | void Kernel_event_flag_init(void) { sEventFlag = 0; } |
sEventFlag를 0으로 초기화하는 역할 |
14~17번째 줄 | void Kernel_event_flag_set(KernelEventFlag_t event) { sEventFlag |= (uint32_t)event; } |
sEventFlag에 특정 비트를 1로 바꿈 * event로 바꿀 비트가 전달됨 |
19~21번째 줄 | void Kernel_event_flag_clear(KernelEventFlag_t event) { sEventFlag &= ~((uint32_t)event); } |
sEventFlag에 특정 비트를 0으로 바꿈 * event로 바꿀 비트가 전달됨 |
24~32번째 줄 | bool Kernel_event_flag_check(KernelEventFlag_t event) { if (sEventFlag & (uint32_t)event) { Kernel_event_flag_clear(event); return true; } return false; } |
파라미터로 전달된 특정 이벤트가 sEventFlag에 1로 세팅되어 있는지 확인하는 함수 1로 세팅 -> true를 리턴해서 해당 이벤트가 대기 중임을 알리고 sEventFlag에서 해당 이벤트 플래그를 제거 0으로 세팅 -> false를 리턴 |
kernel/Kernel.c
태스크 관련 함수와 마찬가지로 태스크에서는 커널 API를 통해서 이벤트를 처리하게 하고 싶음
> Kernel_send_events()와 Kernel_wait_events() 함수 추가
#include "memio.h"
void Kernel_send_events(uint32_t event_list) //이벤트 전달 함수
{
for (uint32_t i = 0 ; i < 32 ; i++)
{
if ((event_list >> i) & 1)
{
KernelEventFlag_t sending_event = KernelEventFlag_Empty;
sending_event = (KernelEventFlag_t)SET_BIT(sending_event, i);
Kernel_event_flag_set(sending_event);
}
}
}
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list) //이벤트 대기 함수
{
for (uint32_t i = 0 ; i < 32 ; i++)
{
if ((waiting_list >> i) & 1)
{
KernelEventFlag_t waiting_event = KernelEventFlag_Empty;
waiting_event = (KernelEventFlag_t)SET_BIT(waiting_event, i);
if (Kernel_event_flag_check(waiting_event))
{
return waiting_event;
}
}
}
return KernelEventFlag_Empty;
}
코드 11.3 이벤트 관련 커널 API 추가 kernel/Kernel.c
Kernel_wait_events()
한번에 이벤트를 여러 개 보내고 받기 위해 파라미터로 uint32_t 사용.
EX) event1, event2, event3, event4를 한번에 보내면 이때 이벤트를 보내는 커널 API를 네 번 호출하는 것이 아니라 이벤트 자체가 비트맵이라는 것을 이용해서 다음과 같이 한 번만 호출해서 이벤트를 여러 개 보낼 수 있음.
Kernel_send_events(event1|event2|event3|event4)
서로 겹치는 값이 아니므로 비트 OR 연산으로 합쳐서 보내도 모든 이벤트를 구분해낼 수 있음.
마찬가지로 이제 이벤트를 받아서 처리하는 핸들러 측에서도 자신이 기다리는 이벤트의 전체 목록을 Kernel_wait_events()에 한번에 보내서 처리할 수 있음.
이벤트를 보내는 쪽과 받는 쪽이 서로 직접적인 관계가 없으므로 event1, event2, event3, event4를 여러 태스크에서 나눠서 처리할 수도 있음.
Task#1
Kernel_wait_events(event1|event3)
Task#2
Kernel_wait_events(event2)
Task#3
Kernel_wait_events(event4)
kernel/Kernel.h
#include "event.h"
void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);
11.2 인터럽트와 이벤트
이벤트는 인터럽트와 엮어서 사용하는 것이 일반적
-> QEMU라는 에뮬레이터 환경의 제약 때문에 UART사용
hal/rvpb/Uart.c
인터럽트 핸들러 수정
#include "stdbool.h"
#include "Kernel.h"
...
중략
...
static void interrupt_handler(void)
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
Kernel_send_events(KernelEventFlag_UartIn); //추가
}
코드 11.4 UART 인터럽트 핸들러 수정 Uart.c
kernel/event.h에 선언한 KernelEventFlag_UartIn 이벤트 플래그를 커널로 보냄
-> 인터럽트와 이벤트의 연결 끝!
boot/Main.c
태스크에서 이벤트를 받아 처리하는 코드 추가
static void Kernel_init(void)
{
uint32_t taskId;
Kernel_task_init();
Kernel_event_flag_init(); //플래그 초기화 함수 호출 추가
taskId = Kernel_task_create(User_task0);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task0 creation fail\n");
}
taskId = Kernel_task_create(User_task1);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task1 creation fail\n");
}
taskId = Kernel_task_create(User_task2);
if (NOT_ENOUGH_TASK_NUM == taskId)
{
putstr("Task2 creation fail\n");
}
Kernel_start();
}
...
중략
...
void User_task0(void) //이벤트 처리 함수 추가
{
uint32_t local = 0;
debug_printf("User Task #0 SP=0x%x\n", &local); //while 밖으로 이동 -> 태스크 결과 출력을 쉽게 보기 위함
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn); //Kernel_wait_events() 커널 API 사용
switch(handle_event)
{
case KernelEventFlag_UartIn:
debug_printf("\nEvent handled\n");
break;
}
Kernel_yield(); //기다리는 이벤트가 커널에 없으면 다른 태스크로 컨텍스트를 넘김
}
}
코드 11.5 Main.c 파일을 수정해서 이벤트 처리 테스트
QEMU를 실행하고 키보드를 입력하기 전까지는 아무런 반응이 나타나지 않음
키보드 자판을 누르면 해당 자판의 글자가 화면에 나오고 아래 줄에 "Event handled"라는 문장 출력 완료
11.3 사용자 정의 이벤트
이벤트는 꼭 인터럽트와 연관지을 필요x
-> 사용하지 않는 이벤트 플래그 하나에 이름을 주어서 태스크에서 태스크로 이벤트를 보낼 수 있음
=> 인터럽트와 이벤트의 차이
인터럽트 핸들러에서 인터럽트의 발생 소식을 태스크로 전달하기 위해 이벤트를 이용하는 것이지 이벤트가 반드시 인터럽트와 연결되어야만 하는 것은 아님
kernel/event.h
사용하지 않는 이벤트 플래그 하나에 이름을 붙여 주고
-> 이 이벤트 플래그를 Task0에서 보냄
-> Task1에서 이벤트 플래그를 받음
typedef enum KernelEventFlag_t
{
KernelEventFlag_UartIn = 0x00000001,
KernelEventFlag_CmdIn = 0x00000002,
KernelEventFlag_Reserved02 = 0x00000004,
KernelEventFlag_Reserved03 = 0x00000008,
...
중략
...
KernelEventFlag_Empty = 0x00000000,
} KernelEventFlag_t;
Reserved01로 예약되어 있던 안 쓰는 이벤트 플래그에 CmdIn이라는 이름을 붙여줌
boot/Main.c
Task0과 Task1 함수를 수정
void User_task0(void)
{
uint32_t local = 0;
debug_printf("User Task #0 SP=0x%x\n", &local);
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn);
switch(handle_event)
{
case KernelEventFlag_UartIn:
debug_printf("\nEvent handled by Task0\n");
Kernel_send_events(KernelEventFlag_CmdIn);
break;
}
Kernel_yield();
}
}
void User_task1(void)
{
uint32_t local = 0;
debug_printf("User Task #1 SP=0x%x\n", &local);
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
switch(handle_event)
{
case KernelEventFlag_CmdIn:
debug_printf("\nEvent handled by Task1\n");
break;
}
Kernel_yield();
}
}
코드 11.6 Task0과 Task1 함수 수정 Main.c
코드 11.6 설명은 더보기 클릭!
14번째 줄 | Kernel_send_events(KernelEventFlag_CmdIn); | Kernel_send_events() 커널 API는 Task0의 이벤트 처리 루틴 코드 안에서 호출됨. |
7~18번째 줄 | while(true) { KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn); #9번째 줄 switch(handle_event) { case KernelEventFlag_UartIn: debug_printf("\nEvent handled by Task0\n"); Kernel_send_events(KernelEventFlag_CmdIn); break; #12~15번째 줄 } Kernel_yield(); } |
UART 인터럽트 핸들러에서 KernelEventFlag_UartIn 이벤트를 보냄 => 스케줄러에 의해 Task0이 실행되면 9번째 줄에서 KernelEventFlag_UartIn 이벤트를 확인하고 받아옴. => 12번째 줄부터 15번째 줄 사이에 있는 이벤트 처리 코드가 실행됨. => 이벤트 처리 코드 안에 또 다시 Kernel_send_events() 커널 API를 호출해서 KernelEventFlag_CmdIn 이벤트를 보냄 => Kernel_yield()를 호출해서 스케줄링을 함 |
29~35번째 줄 | # Task1의 이벤트 처리 루틴 코드 KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn); # KernelEventFlag_CmdIn 이벤트를 기다림 switch(handle_event) { # 이벤트 처리 case KernelEventFlag_CmdIn: debug_printf("\nEvent handled by Task1\n"); break; } |
키보드를 눌러서 UART 인터럽트를 발생시키면 KernelEventFlag_UartIn 이벤트가 발생
-> 이 이벤트를 Task0이 받아서 처리함
-> 키보드로 입력한 글자와 Task0에서 출력하는 "Event handled by Task0"이라는 문장이 출력됨
-> Task0은 KernelEventFlag_CmdIn 이벤트를 보냄
-> 이 이벤트를 Task1이 받아서 처리함
-> "Event handled by Task1"이라는 문장을 바로 아래에 출력함
11.4 여러 이벤트 플래그를 동시에 보내고 처리하기
kernel/event.h
typedef enum KernelEventFlag_t
{
KernelEventFlag_UartIn = 0x00000001,
KernelEventFlag_CmdIn = 0x00000002,
KernelEventFlag_CmdOut = 0x00000004,
KernelEventFlag_Reserved03 = 0x00000008,
KernelEventFlag_Reserved04 = 0x00000010,
...
중략
...
KernelEventFlag_Empty = 0x00000000,
} KernelEventFlag_t;
Reserved02로 예약되어 있던 안 쓰는 이벤트 플래그에 CmdOut이라는 이름을 붙여줌
hal/rvpb/Uart.c
UART 인터럽트 핸들러 수정
static void interrupt_handler(void)
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
Kernel_send_events(KernelEventFlag_UartIn|KernelEventFlag_CmdIn);
if (ch == 'X')
{
Kernel_send_events(KernelEventFlag_CmdOut);
}
}
코드 11.7 동시에 이벤트를 여러 개 보내도록 UART 핸들러 수정하기
코드 11.7 설명은 더보기 클릭!
6번째 줄 | Kernel_send_events(KernelEventFlag_UartIn|KernelEventFlag_CmdIn); | 이벤트 플래그 두 개를 한 번에 보냄 -> 비트맵으로 설계되어 있으므로 논리 OR 연산자( | )로 원하는 이벤트 플래그를 쭉 이어서 사용할 수 있음 |
8~11번째 줄 | if (ch == 'X') { Kernel_send_events(KernelEventFlag_CmdOut); } |
대문자 X를 눌렀을 때만 KernelEventFlag_CmdOut 이벤트를 발생 |
boot/Main.c
Task0 함수 수정
void User_task0(void)
{
uint32_t local = 0;
debug_printf("User Task #0 SP=0x%x\n", &local);
while(true)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn|KernelEventFlag_CmdOut);
switch(handle_event)
{
case KernelEventFlag_UartIn:
debug_printf("\nEvent handled by Task0\n");
break;
case KernelEventFlag_CmdOut:
debug_printf("\nCmdOut Event by Task0\n");
break;
}
Kernel_yield();
}
}
코드 11.8 이벤트를 여러 개 받아 처리하는 Task0
코드 11.8 설명은 더보기 클릭!
9번째 줄 | KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn|KernelEventFlag_CmdOut); | KernelEventFlag_UartIn 이벤트와 KernelEventFlag_CmdOut 이벤트를 비트맵으로 설정 -> 하나가 커널에 대기 중일 때 해당 이벤트 값을 Kernel_wait_events() 함수가 리턴함 * 두 개를 동시에 처리하지 않음! |
15~17번째 줄 | case KernelEventFlag_CmdOut: debug_printf("\nCmdOut Event by Task0\n"); break; |
KernelEventFlag_CmdOut을 처리하는 코드 |
키보드를 눌러서 UART 인터럽트를 발생시키면
-> KernelEventFlag_UartIn 이벤트와 KernelEventFlag_CmdIn 이벤트를 UART 핸들러가 동시에 보냄
-> 각각 Task0과 Task1이 받아서 처리
-> "Event handled by Task0"는 Task0에서 출력하고 "Event handled by Task1"은 Task1에서 출력
대문자 'X'를 누르면
-> UART 핸들러는 KernelEventFlag_UartIn, KernelEventFlag_CmdIn, KernelEventFlag_CmdOut 이벤트를 모두 보냄
-> KernelEventFlag_UartIn 이벤트와 KernelEventFlag_CmdOut 이벤트는 Task0에서 처리하고 KernelEventFlag_CmdIn 이벤트는 Task1에서 처리
<결과>
X
Event handled by Task0 --> UartIn 이벤트 응답 (Task0)
Event handled by Task1 --> CmdIn 이벤트 응답 (Task1)
CmdOut handled by Task0 --> CmdOut 이벤트 응답 (Task0)
Task0에서 KernelEventFlag_UartIn 이벤트와 KernelEventFlag_CmdOut 이벤트를 처리하는데, 결과를 보면 한 번에 이벤트를 한 개씩만 처리함
-> while과 Kernel_wait_events() 및 Kernel_yield()의 호출 위치의 관계 때문
while 루프 한 번에 Kernel_wait_events() 커널 API가 한 번만 호출되고 바로 Kernel_yield()를 호출함
=> 한 번 스케줄링을 받을 때마다 이벤트를 한 개만 가져옴
해결 방법
해당 태스크가 처리할 이벤트가 없을 때까지 모든 이벤트를 다 처리하고 Kernel_yield()를 호출할 것
while(true)
{
bool pendingEvent = true; //처리할 이벤트가 남으면 true, 없으면 false
while(pendingEvent)
{
KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn|KernelEventFlag_CmdOut);
switch(handle_event)
{
case KernelEventFlag_UartIn:
debug_printf("\nEvent handled by Task0\n");
break;
case KernelEventFlag_CmdOut:
debug_printf("\nCmdOut Event by Task0\n");
break;
default:
pendingEvent = false;
break;
}
}
Kernel_yield();
}
Task0의 이벤트가 모두 처리되고, 다음에 Task1의 이벤트가 처리됨
11.5 요약
이벤트는 태스크 간 정보 전달뿐 아니라 인터럽트 핸들러에서 태스크로 정보를 전달할 때도 유용하게 쓸 수 있음!but 매우 단편적인 정보만 전달 가능
다량의 데이터를 전달할 수 있는 메시징 기능은 다음 장에..
'개발 > 임베디드 os 개발 프로젝트' 카테고리의 다른 글
13장 동기화 (1) | 2023.11.28 |
---|---|
12장 메시징 (0) | 2023.10.31 |
10장 컨텍스트 스위칭 (0) | 2023.10.12 |
9장 스케줄러 (0) | 2023.10.09 |
8장 태스크 (1) | 2023.10.09 |