Nenhum comentário

Desenvolvendo com o Zephyr RTOS: Controlando o Kernel

Kernel do Zephyr
Este post faz parte da série Zephyr. Leia também os outros posts da série:

Olá caro leitor, como vai? Estou um pouco ausente da comunidade, com uma correria sem fim, mas aos poucos as coisas vão voltando à normalidade, e o que quero é ao menos tentar retomar as séries abertas aos poucos. E uma das dúvidas que mais recebi informalmente ou por e-mail é como desenvolver com o Zephyr RTOS. De fato temos alguns artigos aqui no Embarcados onde cobrimos o ambiente de desenvolvimento e debug com eclipse, mas ainda falta aquele dia a dia com o kernel, como utilizar, como desenvolver aplicações, essas coisas. Para isso vamos partir desses artigos e efetivamente experimentar o kernel. Tomarei a liberdade de utilizar diferentes alvos (todos focados em ARMv6 ou v7M, Cortex-M0 ou superior) para explorar as diferentes capacidades do kernel. E por falar em kernel, acho que neste artigo a melhor abordagem inicial é aprender como controlar o kernel. Para os leitores gostaria de deixar claro que espero um entendimento básico de programação multithread. Vamos lá?

Recobrando a organização do kernel

Um dos pontos do Zephyr kernel é sua organização, o sistema é bem modular, permitindo que o usuário utilize os recursos sob demanda. O mais interessante disso é que o build system do Zephyr kernel detecta os objetos alocados estaticamente e reserva exatamente a quantidade de memória RAM necessária para aquela aplicação. O Zephyr kernel ainda conta com uma separação bem definida entre os seus subsistemas. Por exemplo, no diretório /kernel vamos encontrar a implementação do micronúcleo de tempo real, a aplicação principal pode fazer o acesso das funções do kernel incluindo o arquivo zephyr.h em sua aplicação.

Diferentemente de RTOS como o popular FreeRTOS, o Zephyr kernel já entrega o controle para a aplicação do usuário com o RTOS inteiro inicializado (isso inclui até os device drivers utilizados), ou seja, conhecida função main() tida como entry point da aplicação já é uma thread por si só (possui prioridade mais alta por padrão).

Uma mudança que precisa ser considerada a partir da versão 1.7.x se situa na eliminação entre a divisão microkernel e nanokernel. Nas versões anteriores era possível configurar o RTOS para uma configuração com footprint ultra reduzido, chegando a consumir menos de 2KB de memória de programa, ao passo que a configuração em microkernel aplicava uma camada de firmware extra permitindo o uso de serviços de kernel mais complexos, por exemplo a preempção entre threads. Nas versões mais atuais, apenas existe o kernel, em que o usuário define o que vai ou não usar. A preempção é padrão do sistema, mas o usuário pode utilizar de forma emulada o serviço de fibers, que são threads de prioridades mais altas porém que não permitem a preempção. As vantagens dessa fusão resulta em um trabalho mais intuitivo por parte do desenvolvedor. Assim hoje o micronúcleo do Zephyr tem o aspecto da figura abaixo:

Kernel do Zephyr

Como todo bom RTOS e tal como fizemos com a série sobre o MBEDOS, outro RTOS com várias características interessantes, iremos começar pelo bloco mais primitivo de todos, as threads!

Threads com o Zephyr kernel

Como já dissemos em artigos anteriores, as threads e os semáforos são os objetos de kernel mais primitivos existentes. No caso das threads convém sempre lembrar que podem ser definidas como: Uma porção de código que tem por finalidade executar um determinado objetivo. Elas podem ser periódicas, aperiódicas ou esporádicas, podem ser run-to-completion, ou seja, toda vez que for invocada deve rodar até que sua ação seja completada. Podem ser bloqueantes, ou seja, aguardar um serviço externo fornecer informação necessária para o término de sua execução. No Zephyr isso não é diferente, o kernel utiliza como base o conhecido sched_FIFO como estratégia de agendamento de threads, o que se traduz na prática como a seguinte regra: A thread de maior prioridade e pronta para rodar assume o controle da CPU, porém, para manter a compatibilidade com o sistema de fibers, é possível criar threads com prioridades especiais, as quais sempre assumem o controle da CPU, mesmo que exista uma thread comum de altíssima prioridade pronta. Quando duas threads com característica de fibers estão prontas para rodar, o scheduler automaticamente entra em modo cooperativo, de forma que as threads só liberam a CPU em caso de notificação explícita através da função yield(), ou bloqueie enquanto aguarda por algum recurso, ou durma por algum período de tempo. Esquematizado abaixo:

Kernel do Zephyr - Scheduler

Resumindo, threads cuja a prioridade seja menor que zero, vão rodar em modo cooperativo. Esse mecanismo é indicado para threads que necessitem de rápida resposta, como device drivers. O subsistema de rede do zephyr utiliza exaustivamente esse recurso para cuidar da transmissão e recepção de pacotes para a PHY.

Interessante, não? Mas creio que o usuário queira ver como utilizar as threads no Zephyr, correto? Primeiramente, onde encontrar as coisas? A interface para uso dos recursos de kernel do Zephyr pode ser encontrada no arquivo /include/kernel.h. Assim, primeiramente vamos instanciar uma task:

Para quem está acostumado ao uso de RTOS, pouco temos de novidade aqui. Para alocar uma thread basicamente precisamos reservar seu TCB (o conhecido bloco de controle da thread), a área de pilha e o ponto de entrada, que nada mais do que o código da thread propriamente dito. De posse dessas informações, basta chamar a API do k_thread_create(), a documentação em kernel.h dessa API encontra-se na linha  444 e inclui a descrição dos parâmetros extras de cada thread. Agora que já sabemos como criar uma thread, vamos controlá-la. O exemplo clássico é colocando a thread para dormir por um período de tempo e depois acordando-a. Para isso vamos utilizar os samples fornecidos, navegue até samples/basic/blink_red/src e abra o arquivo main, você deve dar de cara com isso:

Ignore as demais linhas, vamos diretamente para a linha 34, em k_sleep(), essa função é a equivalente ao conhecido Task Delay presente em diversos outros RTOS. Basicamente a thread dorme por um período especificado em ticks (no Zephyr kernel o valor padrão é 1 tick para 1 milissegundo, ou seja, o valor passado como argumento vai corresponder diretamente o tempo em milissegundos que a thread vai dormir). Umas sugestão interessante, ao rodar esse exemplo, coloque o osciloscópio diretamente no I/O onde está conectado o LED. O usuário vai notar que a thread realmente vai executar a cada 1 segundo, com pouco ou nenhum jitter, o que denota como característica chave de um RTOS, a execução em tempo real.

Para rodar o exemplo acima, estou utilizando a conhecida Freedom Board K64F da NXP, observada abaixo:

A escolha se dá pelo fato de ser uma das placas nativamente suportadas pelo Zephyr, mas o usuário pode adicionar seu hardware personalizado. Falaremos como portar uma nova placa nos próximos artigos.

Para rodar o exemplo, prepare o ambiente do Zephyr (modifique seu bash_profile se necessário, ou ajuste as variáveis de ambiente do Eclipse, como já cobrimos nos artigos anteriores):

Utilizando o comando make, o firmware será compilado gerando os artefatos executáveis em outdir/frdm_k64f  com o nome padrão de zephyr./elf/bin/hex. Com isso você poderá debugar o kernel ou simplesmente fazer o download da imagem.

Conta mais sobre o controle de Threads!

O exemplo acima é o conhecido blink led, mas as funções de controle de thread do Zephyr vão além disso:

A conhecida função yield é utilizada para liberar a CPU da thread corrente quando outra de mesma prioridade encontra-se pronta para rodar. Essa função basicamente reordena a runqueue interna do kernel e joga a thread corrente pro fim da fila, e puxa a próxima thread no topo da runqueue. Caso não haja tarefa de mesma prioridade pronta para rodar, essa rotina retorna imediatamente. Yield é a função chave para usar as fibers do kernel, permitindo que todas elas rodem liberando a CPU quando seu objetivo for atingido.

Todo bom RTOS deve contar com a função Get Current, e no Zephyr não é diferente. Essa função retorna o id da task corrente, muito útil quando a aplicação não memoriza o id de determinada task durante sua criação.

Priority Getters / Setters são as funções chaves quando a aplicação necessita de alocação de prioridade dinâmica. O que basicamente ela faz é retornar o valor da prioridade da thread especificada e, respectivamente, modificar seu valor. Um cuidado deve ser tomado com seu uso, pois ao passar um valor de prioridade igual ou menor que -1 a thread em questão irá automaticamente para o modo fiber!

Suspend/Resume encerram nossa descrição do thread management principal. k_thread_suspend faz a thread dormir (ou seja, liberando outra thread ou a IDLE para execução) até que alguém invoque k_thread_resume. Lembrando que como todo RTOS, uma vez que qualquer função do thread management seja chamada e a execução seja efetuada com sucesso, o scheduler será invocado em seguida, o que pode culminar na preempção da thread corrente. Portanto, atenção a esse ponto durante do desenvolvimento da sua aplicação.

Conclusão

Espero que esse artigo tenha despertado o interesse do leitor em conhecer mais sobre o Zephyr kernel. Cobrimos os primeiros passos teóricos para uso do thread manager e algumas de suas funções. A ideia de retomar o Zephyr está basicamente no meu slot de tempo que aumentou um pouquinho (risos) e no recente boom de contribuição que o kernel está recebendo, inclusive dos fabricantes de semicondutores. Ficou curioso sobre como anda o kernel? Manda sua dúvida aqui pra gente! Nó próximo artigo teremos um curto texto recheado com alguns casos de uso práticos na K64F. Então é isso, até a próxima!

Referências

Outros artigos da série

<< Debug do kernel do Zephyr com o Eclipse IDE
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Desenvolvendo com o Zephyr RTOS: Controlando o Kernel
Comentários:
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments
Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 

Soluções inteligentes para acionamento de MOSFETs/IGBTs com família STDRIVE

Data: 08/10 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link