Meu Kernel – Minha Vida

Este post faz parte da série Meu Kernel, Minha Vida

Olá caro leitor, tudo bem? Neste artigo gostaria de compartilhar o resultado dos meus estudos, relacionado ao desenvolvimento de kernel. A inspiração para o estudo surgiu após ver o conteúdo da palestra do professor Rodrigo Almeida fez para a ESC Embedded Systems Conference 2017 Silicon Valley e pelo Felipe Neves, que vem desenvolvendo o seu próprio sistema operacional de tempo real (RTOS), o uLipeRTOS.

Para os meus estudos utilizei o material que o professor Rodrigo Almeida disponibilizada aqui no Embarcados e na sua página pessoal. Outra fonte que consultei foi o livro The Definitive Guide to the ARM Cortex-M0 do autor Joseph Yiu, além de estudar o código fonte de diferentes RTOS’s.

Para este artigo vou apresentar um kernel multi-tarefas cooperativo, bem simples, com níveis de prioridade na execução das tarefas. As principais características de um sistema com núcleo cooperativo são:

  • o código fonte pode ser todo escrito em linguagem C, não necessitando de nenhuma característica especial do hardware;
  • necessita que todas tarefas/processos terminem, para possibilitar que outra tarefa possa ser executada;
  • rotina com loop infinito podem travar o sistema (para o kernel aqui apresentado, loop infinito vai travar  a aplicação em um determinado processo).

Para mais informações sobre kernel e sistemas operacionais recomendo a leitura dos seguintes artigos:

Justificativa

Para quem já está familiarizado com o desenvolvimento de firmware com RTOS, sabe como é cômodo estruturar a sua aplicação, dividindo em diversas tarefas, deixando a cargo do RTOS o escalonamento delas.

Como é de conhecimento de muitos desenvolvedores, existem uma quantidade enorme de aplicações que não necessitam das características de um sistema de tempo real. E os RTOS’s são aplicados mesmo assim, só pelas facilidades em estruturar o firmware.

O ponto negativo dessa abordagem é que os RTOS’s sempre ocupam uma boa fatia de memória RAM e memória ROM, e a maiorias dos microcontroladores possui uma quantidade bem reduzida desses recursos. Com a proposta do kernel aqui apresentado, é possível ter as mesmas características do escalonador existente em um RTOS, assim facilitando a estruturação do firmware.

O kernel

O kernel desenvolvido conta com escalonador de tarefas e diversas APIs (funções) para manipular os processos. Então, basicamente o kernel se resume em apenas no escalonador de tarefas.

Projeto

O projeto de demonstração utiliza a Freedom Board KE06Z, que possui um microcontrolador com núcleo ARM Cortex-M0. E uma característica interessante dessa arquitetura é o temporizador “SysTick“. Trata-se de uma interrupção periódica, própria do núcleo. É através dela que o kernel obtém a sua base de tempo.

Nota: Para arquiteturas diferentes, é necessário reservar um “Timer”, para fornecer a base de tempo para o kernel.

Basicamente o projeto conta com vetor, de estrutura previamente definida, onde são inseridas as tarefas e suas configurações. E num segundo momento o escalonador assume o controle e passa a gerenciar este vetor para decidir qual tarefa deve ser executada.

A seguir é apresentado o código fonte do kernel desenvolvido, para a melhor compreensão do projeto (kernel.c e kernel.h).

kernel.c

/***********************************************************************************************
 * kernel.c										       *
 *											       *
 *  Created on: 02/01/2018								       *
 *      Author: Evandro Teixeira							       *
 **********************************************************************************************/
#include "kernel.h"
/***********************************************************************************************
 * 									         	       *
 **********************************************************************************************/
//static strFlag vFlag;
static strKernel vKernel;
static kernel_tick ticks = 0;
extern uint32_t SystemCoreClock;
static kernel_tick kernel_ticks_get(void) ;
static void kernel_setup_systick(void);
static void kernel_task_idle(void);
static void (*IdleTask)(void);
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void SysTick_Handler(void)
{
	ticks++;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
static kernel_tick kernel_ticks_get(void)
{
	return ticks;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
static void kernel_setup_systick(void)
{
	uint32_t ticks = SystemCoreClock/1000;
	SysTick->LOAD  = ticks - 1;
	SysTick->VAL   = 0;
	SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

	NVIC_EnableIRQ(SysTick_IRQn);
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
kernel_status kernel_init(void)
{
	strTask task_ilde = {kernel_task_idle,Task_Ready,Priority_Idle,0,kernel_task_waiting};
	vKernel.task_vetor[ID_IDLE] = task_ilde;
	vKernel.position_index = 0;
	vKernel.counter_task   = 0;
	vKernel.index_high     = 1;
	vKernel.index_medium   = 1;
	vKernel.index_low      = 1;
	kernel_setup_systick();
	return kernel_ok;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
kernel_status kernel_add_task(ptrTask task,task_priority priority,task_state state, id_Task *id)
{
	if((vKernel.counter_task < NUMBER_TASK) && (task != NULL))
	{
		// Nota: task idle na posicao zero vetor
		vKernel.counter_task++;
		vKernel.task_vetor[vKernel.counter_task].task = task;
		vKernel.task_vetor[vKernel.counter_task].priority = priority;
		vKernel.task_vetor[vKernel.counter_task].state = state;
		vKernel.task_vetor[vKernel.counter_task].pausedtime = 0;
		vKernel.task_vetor[vKernel.counter_task].kernel_task_state = kernel_task_waiting;

		*id = vKernel.counter_task;

		return kernel_ok;
	}
	else return kernel_fail;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void kernel_run(void)
{
	uint8_t i = 0;
	
	while(1)
	{
		//
		// Checa se tem alguma em pausa
		//
		for(i=1;i<(vKernel.counter_task + 1);i++)
		{
			if(vKernel.task_vetor[i].state == Task_Paused)
			{
				if(kernel_ticks_get() > vKernel.task_vetor[i].pausedtime)
				{
					vKernel.task_vetor[i].state = Task_Ready;
					vKernel.task_vetor[i].pausedtime = 0;
				}
			}
		}

		//
		// busca tarefa de alta prioridade pronta para ser executada
		//
		for(i=vKernel.index_high;i<=(vKernel.counter_task + 1);i++)
		{
			if( (vKernel.task_vetor[i].priority == Priority_High) &&
				(vKernel.task_vetor[i].state == Task_Ready) &&
				(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
			{
				vKernel.index_high = i;
				vKernel.index_high++;
				if(vKernel.index_high>=(vKernel.counter_task + 1))
					vKernel.index_high = 1;

				break;
			}
		}
		if(i > (vKernel.counter_task + 1))
		{
			// busca tarefa de media prioridade para ser executada
			for(i=vKernel.index_medium;i<=(vKernel.counter_task + 1);i++)
			{
				if( (vKernel.task_vetor[i].priority == Priority_Medium) &&
					(vKernel.task_vetor[i].state == Task_Ready) &&
					(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
				{
					vKernel.index_medium = i;
					vKernel.index_medium++;
					if(vKernel.index_medium>=(vKernel.counter_task + 1))
						vKernel.index_medium = 1;

					break;
				}
			}
			if(i > (vKernel.counter_task + 1))
			{
				// busca tarefa de baixa prioridade para ser executada
				for(i=vKernel.index_low;i<=(vKernel.counter_task + 1);i++)
				{
					if( (vKernel.task_vetor[i].priority == Priority_Low) &&
						(vKernel.task_vetor[i].state == Task_Ready) &&
						(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
					{
						vKernel.index_low = i;
						vKernel.index_low++;
						if(vKernel.index_low>=(vKernel.counter_task + 1))
							vKernel.index_low = 1;

						break;
					}
				}
				if(i > (vKernel.counter_task + 1))
				{
					vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
					vKernel.position_index = ID_IDLE;	// Executa tarefa ilde
				}
				else
				{
					vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
					vKernel.position_index = i;
				}
			}
			else
			{
				vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
				vKernel.position_index = i;
			}
		}
		else
		{
			vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
			vKernel.position_index = i;
		}

		// muda estado da tarefa
		vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_running;

		// executa tarefa
		(*vKernel.task_vetor[vKernel.position_index].task)();
	}
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
static void kernel_task_idle(void)
{
	IdleTask();
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void kernel_task_delay(kernel_tick time)
{
	time += kernel_ticks_get();
	vKernel.task_vetor[vKernel.position_index].pausedtime = time;
	vKernel.task_vetor[vKernel.position_index].state = Task_Paused;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void kernel_task_set_priority(task_priority priority)
{
	if((priority > ID_IDLE) && (priority <= Priority_High))
		vKernel.task_vetor[vKernel.position_index].priority = priority;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
kernel_status kernel_add_task_idle(void (*task)(void))
{
	if(task != NULL)
	{
		IdleTask = task;
		return kernel_ok;
	}
	else return kernel_fail;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void kernel_task_delete(void)
{
	vKernel.task_vetor[vKernel.position_index].state = Task_Deleted;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
kernel_status kernel_task_blocks(id_Task id)
{
	if(id < vKernel.counter_task)
	{
		vKernel.task_vetor[id].state = Task_Blocked;

		return kernel_ok;
	}
	else return kernel_fail;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
kernel_status kernel_task_unlock(id_Task id)
{
	if(id < vKernel.counter_task)
	{
		vKernel.task_vetor[id].state = Task_Ready;

		return kernel_ok;
	}
	else return kernel_fail;
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/
void kernel_error(void)
{
	while(1)
	{
		__asm ("NOP");
	}
}
/***********************************************************************************************
 * 											       *
 **********************************************************************************************/

kernel.h

/***********************************************************************************************
 * kernel.h										      *
 *											      *
 *  Created on: 02/01/2018								      *
 *      Author: Evandro Teixeira							      *
 **********************************************************************************************/

#ifndef SOURCES_KERNEL_KERNEL_H_
#define SOURCES_KERNEL_KERNEL_H_

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

#include "extern.h"

#define NUMBER_TASK		8
#define ID_IDLE			0

#ifndef kernel_tick
typedef uint32_t kernel_tick;
#endif

#ifndef id_Tack
typedef uint8_t id_Task;
#endif

#ifndef id_Flag
typedef uint16_t id_Flag;
#endif

#ifndef ptrTask
typedef void(*ptrTask)(void);
#endif

typedef enum
{
	Flag_False = false,
	Flag_True = true,
}Kernel_Flag;

typedef enum
{
	kernel_fail = false,
	kernel_ok = true,
}kernel_status;

typedef enum
{
	kernel_task_running = true,
	kernel_task_waiting = false,
}kernel_task;

typedef enum
{
	//Task_Running = 0,//
	Task_Ready = 0,
	Task_Blocked,
	Task_Paused,
	Task_Deleted,
}task_state;

typedef enum
{
	Priority_Idle = 0,
	Priority_Low,
	Priority_Medium,
	Priority_High,
}task_priority;

typedef struct
{
	ptrTask task;
	uint8_t state;
	uint8_t priority;
	kernel_tick pausedtime;
	kernel_task kernel_task_state;
}strTask;

typedef struct
{
	strTask task_vetor[NUMBER_TASK];
	uint8_t position_index;
	uint8_t counter_task;
	uint8_t index_high;
	uint8_t index_medium;
	uint8_t index_low;
}strKernel;

typedef struct
{
	id_Flag flag;
	id_Flag flag_index;
	id_Task number_task;
	bool flag_status;
}strFlag;

kernel_status kernel_init(void);

kernel_status kernel_add_task(ptrTask task,task_priority priority,task_state state, id_Task *id);

void kernel_task_delay(kernel_tick time);

void kernel_task_set_priority(task_priority priority);

void kernel_run(void);

kernel_status kernel_add_task_idle(void (*task)(void));

void kernel_task_delete(void);

kernel_status kernel_task_blocks(id_Task id);

kernel_status kernel_task_unlock(id_Task id);

void kernel_error(void);

#endif /* SOURCES_KERNEL_KERNEL_H_ */

Tarefas

As tarefas no kernel possuem 4 estados, que são:

  • pronta (Task_Ready);
  • em pausa (Task_Paused);
  • bloqueada (Task_Blocked);
  • deletada (Task_Deleted).

As tarefas também são agrupadas em prioridades. O núcleo do sistema disponibiliza 3 níveis de prioridades: baixa (Priority_Low), média (Priority_Medium) e alta (Priority_High). Além das prioridades citadas, o kernel possui a prioridade dedicada à tarefa ociosa (Task_Idle com prioridade Priority_Idle). Trata-se da tarefa que é executada quando não houver nenhuma tarefa no estado pronta. A mesma pode e deve ser utilizada pela aplicação, sendo que o kernel não executa nenhum processo nesta tarefa.

O kernel, entre aspa, acaba criando listas de tarefas para cada nível de prioridade. E assim, o escalonador de tarefas seleciona qual tarefa deve ser executada pela CPU. Outra característica do núcleo é, o escalonamento entras as tarefas de mesma prioridade, que trabalham no modo FIFO (First In, First Out – quer dizer que a primeira tarefa da fila será a primeira a ser executada). A imagem a seguir demonstra com maiores detalhes o agrupamento de tarefas feito pelo o escalonador.

Organização das tarefas feito escalonador
Figura 1 – Organização das tarefas feito escalonador

API’s do Sistema

Principais Funções:

Como dito o kernel possui algumas API’s para manipular os processos. Como não poderia faltar, tem a API “kernel_init”, é a primeira função do sistema que deve ser executada. É nesta função que serão inicializadas as variáveis de controle do sistema.

A segunda função a ser executada é a “kernel_add_task”, através da qual são adicionadas as tarefas da aplicação ao kernel. E, além de inserir as tarefas ao sistema, é por ela que são configurados os parâmetros das tarefas (prioridade e estado inicial).

E por fim temos a API “kernel_run”, onde se encontra o escalonador das tarefas do sistema.

Funções Adicionais: 

Como dito, o kernel aqui apresentado é cooperativo, e é necessário que cada processo termine para que outra tarefa seja executada. O mesmo provê algumas API’s para manipular os parâmetros das tarefas. Assim possibilitando que as tarefas possam ser; pausadas, bloqueadas, desbloqueadas e deletadas.

A primeira API é “kernel_task_delay”, através da qual é possível pausar a execução da tarefa por um determinado tempo. Ao final desse período, o kernel se encarrega de alterar o seu estado, para o status pronta (Task_Ready).

Outra API para manipular o estado da tarefa é a função “kernel_task_delete”, que faz com que a tarefa não seja mais executada pelo kernel.

Nota: Quando é executada essa API, o kernel não exclui a tarefa do vetor. Portanto, não libera espaço em memória.

A funções “kernel_task_blocks” e “kernel_task_unlock” servem para bloquear e desbloquear a execução da tarefa pelo sistema.

O kernel também possui API para a troca de prioridade das tarefas, a função “kernel_task_set_priority”. 

E como foi dito anteriormente, o kernel possui a tarefa ociosa (task_Idle), one o usuário poder estar adicionando um processo. Isso é feito através da função “kernel_add_task_idle”.

Projeto de Demonstração

O Projeto que escolhi para demonstração, é bem simples. O algoritmo consiste em verificar os Push Buttons (SW2, SW3) e acionar o LED RGB que estão presente na Freedom Board KE06Z.

A seguir é apresentado o código fonte da aplicação (main.c, app.c e app.h).

main.c

/**********************************************************************************************
 * main.c										      *
 *											      *
 *  Created on: 02/01/2018								      *
 *      Author: Evandro Teixeira							      *
 **********************************************************************************************/
#include "MKE06Z4.h"
#include "extern.h"

extern id_Task id_task_led_red;
extern id_Task id_task_led_blue;
extern id_Task id_task_sw2;
extern id_Task id_task_sw3;

int main(void)
{
	app_init();

	printf("\n\rAplicacao de Demonstracao do Kernel");

	if(kernel_init() == kernel_fail )
	{
		printf("\n\rFalha em iniciar o kernel");
		kernel_error();
	}

	if(kernel_add_task(app_task_led_red,Priority_Low,Task_Ready,&id_task_led_red) == kernel_fail)
	{
		printf("\n\rFalha em add tarefa ao kernel");
		kernel_error();
	}

	if(kernel_add_task(app_task_led_blue,Priority_Low,Task_Ready,&id_task_led_blue) == kernel_fail)
	{
		printf("\n\rFalha em add tarefa ao kernel");
		kernel_error();
	}

	if(kernel_add_task(app_task_sw2,Priority_High,Task_Ready,&id_task_sw2) == kernel_fail)
	{
		printf("\n\rFalha em add tarefa ao kernel");
		kernel_error();
	}

	if(kernel_add_task(app_task_sw3,Priority_High,Task_Ready,&id_task_sw3) == kernel_fail)
	{
		printf("\n\rFalha em add tarefa ao kernel");
		kernel_error();
	}

	if(kernel_add_task_idle(app_task_led_green) == kernel_fail)
	{
		printf("\n\rFalha em add tarefa ao kernel");
		kernel_error();
	}

	kernel_run();

    for (;;)
    {
    	printf("\n\rFalha em iniciar o kernel");
    	kernel_error();
    }
    /* Never leave main */
    return 0;
}

app.c

/************************************************************************************
 * app.c									    *
 *										    *
 *  Created on: 02/01/2018							    *
 *      Author: Evandro Teixeira						    *
 ************************************************************************************/
#include "app.h"
/************************************************************************************
 * 										    *
 ************************************************************************************/
id_Task id_task_led_red;
id_Task id_task_led_blue;
id_Task id_task_led_green;
id_Task id_task_sw2;
id_Task id_task_sw3;
extern uint32_t SystemCoreClock ;
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_init(void)
{
	UART_ConfigType sConfig;

	SystemCoreClockUpdate();

	sConfig.u32SysClkHz = SystemCoreClock;
    sConfig.u32Baudrate = 9600;
	UART_Init(UART1,&sConfig);

	GPIO_PinInit(BUTTOM_SW2);
	GPIO_PinInit(BUTTOM_SW3);

	RED_Init();
	BLUE_Init();
	GREEN_Init();
}
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_task_led_red(void)
{
	RED_Toggle();
	kernel_task_blocks(id_task_led_red);
}
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_task_led_blue(void)
{
	BLUE_Toggle();
	kernel_task_blocks(id_task_led_blue);
}
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_task_led_green(void)
{
	GREEN_Toggle();
}
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_task_sw2(void)
{
	if( (GPIO_Read(BUTTOM) & READ_BUTTOM_SW2) == 0)
		kernel_task_unlock(id_task_led_red);
	else
		RED_Set();

	kernel_task_delay(100);
}
/************************************************************************************
 * 										    *
 ************************************************************************************/
void app_task_sw3(void)
{
	if( (GPIO_Read(BUTTOM) & READ_BUTTOM_SW3) == 0)
		kernel_task_unlock(id_task_led_blue);
	else
		BLUE_Set();

	kernel_task_delay(100);
}
/************************************************************************************
 * 										    *
 ************************************************************************************/

app.h

/************************************************************************************
 * app.h
 *
 *  Created on: 02/01/2018
 *      Author: Evandro Teixeira
 *************************************************************************************/

#ifndef SOURCES_APP_APP_H_
#define SOURCES_APP_APP_H_


#include "extern.h"

#define	 BUTTOM_SW3 		GPIO_PTH3,GPIO_PinInput
#define	 BUTTOM_SW2 		GPIO_PTH4,GPIO_PinInput

#define  READ_BUTTOM_SW2  	GPIO_PTH4_MASK
#define  READ_BUTTOM_SW3  	GPIO_PTH3_MASK

#define  BUTTOM				GPIOB

void app_init(void);

void app_task_led_red(void);

void app_task_led_blue(void);

void app_task_led_green(void);

void app_task_sw2(void);

void app_task_sw3(void);


#endif /* SOURCES_APP_APP_H_ */

Resultado

O resultado da aplicação desenvolvida é: temos o LED verde piscando em alta frequência, pois o sistema gasta pouco tempo, para verificar os Push Buttons e a CPU do microcontrolador passa grande parte do tempo executando a tarefa ociosa (IdleTask). Então temos a sensação que o LED verde está o tempo todo acionado.

Quando pressionado o Push Button SW2, o LED vermelho começa a piscar. O mesmo vale para o Push Button SW3, que faz com que o LED azul pisque. A seguir temos figura 2, demonstrando o resultado da aplicação.

Resultado da Aplicação
Figura 2 – Resultado da Aplicação

Conclusão

O kernel aqui apresentado é fruto dos meus estudos sobre o assunto, não tem como objetivo se tornar uma alternativa para substituir sistemas operacionais já existentes no mercado. Mas sim, demonstrar ao caro leitor, que é possível desenvolver soluções simples para estruturar firmware, não se limitando apenas ao uso de um RTOS ou do Super Loop.

Próximos passos… É estudar sobre a troca de contexto e as técnicas existentes para o mesmo. E assim conseguir desenvolver kernel preemptivo. Assim que for evoluindo nos meus estudos, prometo trazer mais artigos relacionados ao assunto.

O código fonte do projeto com aplicação e kernel deixei disponível no meu Github. E fica aqui o meu convite a você caro leitor, que se interessou pelo assunto, a contribuir com o projeto, testando e aperfeiçoando o mesmo.

Referências 

The Definitive Guide to the ARM Cortex-M0
Desenvolvendo um RTOS: Introdução
Desenvolvendo um RTOS: processos e tarefas
Desenvolvendo um RTOS: Utilizando buffer circular como gestor de processos

Meu Kernel, Minha Vida

Meu Kernel Minha Vida – Round-Robin
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Meu Kernel – Minha Vida

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: