드디여 마지막 장인 동기화까지 왔다.

 

비선점형 멀티태스킹을 수행하는 나빌로스에서 커널 API Kernel_yield를 구현해서 

각 태스크가 다음 태스크에게 컨텍스트를 넘겨주었는데 싱글 코어는 괜찬지만 멀티 코어에서는 문제가 된다.

 

 

 

운영체제에서의 동기화와 아토믹 오퍼레이션

동기화 : 한 작업을 아토믹 오퍼레이션(atomic operation ?)으로 만드는 작업

아토믹 오퍼레이션 : 해당 작업이 끝날때까지 컨텍스트 스위칭이 발생하지 않음

-> 멀티코어 환경에선 아토믹 오퍼레이션이 수행될때 컨텍스트 스위칭 발생 안함.

= 다른작업이 못끼어든다

= 다른 코어도 방해하지 못함

=> 원자가 더이상 쪼개질수없는 단위처럼, 더이상 쪼갤수 없는 동작이란 의미로 이에 해당되는 작업이 크리티컬 섹션임

 

동기화

- 작업, 태스크가 크리티컬 섹션(다른 작업, 코어가 끼어들지 못함)인 경우, 아토믹 오퍼레이션으로 만듬

- 구현 알고리즘 : 세마포어, 뮤텍스, 스핀락

 

 

세마포어

아래는 세마포어 알고리즘의 슈도코드, 60년대 초에 나온 오래된 알고리즘

Test() : 크리티컬 섹션에 진입 가능한지 시험함 = 세마포어를 잠글수 있는지 확인

Release() : 크리티컬 섹션을 나갈 때 호출. 세마포어를 풀어줌

 

=> 크리티컬 섹션에 들어가면 잠금. 

=> 잠긴 동안 컨텍스트 스위칭이나 다른 코어 작업 불가

=> 크리티컬 섹션에서 나오면 잠금 해제

Test(S)
{
	while S <= 0; //대기
    S--;
}

Release(S)
{
	S++;
}

 

세마포어 구현하기

kernel/synch.h

#ifndef KERNEL_SYNCH_H_
#define KERNEL_SYNCH_H_

#include <stdint.h>

void	Kernel_sem_init(int32_t max);
bool	Kernel_sem_test(void);
void	Kernel_sem_release(void);

#endif

 

kernel/synch.c

sem test는 못잠글때 ( S<=0) false 반환 

sem release는 세마포어 변수 증가

 

sem init은 세마포어 초기화 함수 max 파라미터로 세마포어 최대값 받음. 8 못넘김

max가 1이면 크리티컬 섹션에 컨텍스트 한개만 진입 가능(바이너리 세마포어)

2면 2개, 3은 3까지 최대가 8이므로 8개 컨텍스트 진입 가능

0보다 작으면 8개 진입 가능

 

#define DEF_SEM_MAX 8

#include "synch.h"

static int32_t sSemMax;
static int32_t sSem;

void Kernel_sem_init(int32_t max)
{
	sSemMax = (max <= 0) ? DEF_SEM_MAX : max;
	sSemMax = (max >= DEF_SEM_MAX) ? DEF_SEM_MAX : max;

	sSem = sSemMax;
}

bool Kernel_sem_test(void)
{
	if (sSem <= 0)
	{
		return false;
	}
	
	sSem--;

	return true;
}

void Kernel_sem_release(void)
{
	sSem++;

	if(sSem >= sSemMax)
	{
		sSem = sSemMax;
	}
}

 

세마포어용 커널 API 추가

kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"
#include "event.h"

void Kernel_start(void);
void Kernel_yield(void);

void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);

void Kernel_lock_sem(void);
void Kernel_unlock_sem(void);

#endif

kernel.c

Kernel lock sem은 무한루프로 대기하는걸 Kernel_yield로 다음 태스크에게 전환하는걸로 구현

->  크리티컬 섹션 잠금을 가진 태스크의 컨텍스트까지가서 넘어가서야 세마포어 잠금을 풀수있도록 하기위함

 

#include <stdint.h>
#include <stdbool.h>

#include "memio.h"

#include "Kernel.h"
#include "event.h"
#include "msg.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

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;
}

bool Kernel_send_msg(KernelMsgQ_t Qname, void* data, uint32_t count)
{
	uint8_t* d = (uint8_t*)data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_enqueue(Qname, *d))
		{
			for (uint32_t j = 0; j < i; j++)
			{
				uint8_t rollback;
				Kernel_msgQ_dequeue(Qname, &rollback);
			}
			return false;
		}
		d++;
	}
	return true;
}

uint32_t Kernel_recv_msg(KernelMsgQ_t Qname, void* out_data, uint32_t count)
{
	uint8_t* d = (uint8_t*)out_data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_dequeue(Qname, d))
		{
			return i;
		}
		d++;
	}
	return count;
}

void Kernel_lock_sem(void)
{
	while(false == Kernel_sem_test())
	{
		Kernel_yield();
	}
}

void Kernel_unlock_sem(void)
{
	Kernel_sem_release();
}

 

 

리얼뷰 피비는 싱글코어 에뮬레이터이며, 비선점형 스케줄러라 직접 Kernel_yield 호출 필요 동작가능한 코드 구현하면

 

Uart.c x키 눌럿을떄 CmdOut 이벤트 발생하도록 수정시키고

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.h"
#include "msg.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();

	if (ch != 'x')
	{
		Hal_uart_put_char(ch);
		Kernel_send_msg(KernelMsgQ_Task0, &ch, 1);
		Kernel_send_events(KernelEventFlag_UartIn);
	}
	else
	{
		Kernel_send_events(KernelEventFlag_CmdOut);
	}
}

 

Test_critical_section()

크리티컬 섹션을 만들기위해 여러 코어가 같이 쓰는 공유 자원 필요

첫 인자  uint32_t p는 공유 자원 값을 바꾸기위한 값

다음은 호출한 태스크 번호

어느태스크가 어떤 값을 보냈는지 출력

공유 변수 값을 바꾼후 다음 테스크로 스캐줄링

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"
#include "msg.h"
#include "synch.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

static void Test_critical_section(uint32_t p, uint32_t taskId);

void User_task0(void);
void User_task1(void);
void User_task2(void);

static uint32_t shared_value;

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 2 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

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);

	uint8_t		cmdBuf[16];
	uint32_t	cmdBufIdx = 0;
	uint8_t		uartch = 0;

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				Kernel_recv_msg(KernelMsgQ_Task0, &uartch, 1);
				if (uartch == '\r')
				{
					cmdBuf[cmdBufIdx] = '\0';

					Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1);
					Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx);
					Kernel_send_events(KernelEventFlag_CmdIn);
					/*
					while(true)
					{
						Kernel_send_events(KernelEventFlag_CmdIn);
						if (false == Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1))
						{
							Kernel_yield();
						}
						else if (false == Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx))
						{
							uint8_t rollback;
							Kernel_recv_msg(KernelMsgQ_Task1, &rollback, 1);
							Kernel_yield();
						}
					}
					*/
					cmdBufIdx = 0;
				}
				else
				{
					cmdBuf[cmdBufIdx] = uartch;
					cmdBufIdx++;
					cmdBufIdx %= 16;
				}
				break;
			case KernelEventFlag_CmdOut:
				Test_critical_section(5, 0);
				break;
		}
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User Task #1 SP = 0x%x\n", &local);

	uint8_t cmdlen = 0;
	uint8_t cmd[16] = {0};

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
		switch(handle_event)
		{
			case KernelEventFlag_CmdIn:
				memclr(cmd, 16);
				Kernel_recv_msg(KernelMsgQ_Task1, &cmdlen, 1);
				Kernel_recv_msg(KernelMsgQ_Task1, cmd, cmdlen);
				debug_printf("\nRecv Cmd : %s\n", cmd);
				break;
		}
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Kernel_yield();
	}
}

static void Test_critical_section(uint32_t p, uint32_t taskId)
{
	debug_printf("User Task #%u Send=%u\n", taskId, p);
	shared_value = p;
	Kernel_yield();
	delay(1000);
	debug_printf("User Task #%u Shared Value = %u\n", taskId, shared_value);
}

 

테스트 크리티컬 섹션 함수은

유아트 컨트롤러에서 x 입력시 cmdout 이벤트를 발생시켜

태스크 1이 받아 호출된다.

여기서 인자를 5, 0(태스크 번호)를 주어

공유된 변수 값을 덮어쓰고 5가 출력되어 정상적으로 동작했다.

 

 

 

하지만 동시에

태스크 2에서 공유 변수값을 3으로 바꾸도록 하면

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Test_critical_section(3, 2);
		Kernel_yield();
	}
}

 

 

태스크2의 차례때 테스트 크리티칼 섹션 함수가 계속 수행되어

3을 보내고 3을 받는 식이 되지만

중간에 태스크 0이 끼어들어

정작 태스크0이 보낸건 5지만, 커널 양보 호출후 공유된 변수 값을 출력할때는 3이 나오고만다.

이런식으로 같은 자원을 여러 태스크끼리 공유할때 문제가 발생

 

 

거의 다왔는데 메시지 부터 꼬인걸 도저히 해결이 안된다.

일단 세마포어와 뮤텍스가 뭐하는건지 봣으니 여기까지 그냥 마무리

 

 

 

+ Recent posts