Home Map Index Search News Archives Links About LF
[Top Bar]
[Bottom Bar]
[Foto do Autor]
Ismael Ripoll
Sobre o Autor: Doutorado pela Universidade Politécnica de Valencia em 1.996. Professor de Sistemas Operacionais no Departamento DISCA. Áreas de investigação de interese incluem tarefas em tempo-real e sistema operacionais. Usuário do Linux desde 1.994. Hobbies: Treking nos pirineus, esquiar e eletronica caseira.

Escreva ao autor

Indice:
KU Real Time Linux (KURT)
Para que serve oTempo Real?
Modulos carregáveis
O programa de Tempo Real
Comunicações entre tarefas
Conclusões
Bibliografia

KU Real Time Linux

[Ilustration]

Resumen: Neste segundo artigo dedicado ao RT-Linux, tratarei de oferecer uma visão mais prática do RT-Linux. Antes de entrar em detalhes, gostaria de dar uma rápida visão geral de um novo sistema operacional de tempo real sobre Linux KURT.




KU Real Time Linux (KURT)

No início deste ano (1998) nos foi apresentado um novo sistema operacional de tempo real baseado no LINUX. KURT é um sistema de tempo real brando (soft, ou firm), isto é, se tentar cumprir os prazos de execução, mas no caso de alguma tarefa ser finalizado um pouco atrasado, não acontece nenhum desastre. As tarefas do tempo real do KURT, podem fazer uso de todas as facilidades do Linux, ao contrário das tarefas do RT-Linux. As melhorias --modificações-- foram realizadas sobre o núcleo. são elas:

    Melhorar os picos do relógio do sistema. No Linux-i386, a frequencia de interrupção do relógio é de 10ms (cem vezes por segundo), e é com esta resolução de tempo, que se mede um controle de ações e mede-se o tempo. KURT utiliza o mesmo mecanismo de gerenciamento do tempo do RT-Linux. Programa o chip do relógio (8254) para que gere interrupções sob baixa demanda, no lugar de fazê-lo de forma períodica. Com isto, conseguimos uma resolução no relógio superior a um microsegundo.

    Foi modificado o planificador para incluir uma nova pol itica de planificação SCHE_KURT, além das que o Linux já tinham implementadas, que são as que POSIX define: SCHED_FIFO, SCHED_RR y SCHED_OTHER.

    Fora acrescentadas novas chamadas ao sistema para poder fazer uso das novas funcionalidades do tempo real.

As tarefas de tempo real são módulos de carga dinamica.

Um dos aspectos mais característicos do KURT é a política de planiificação que utiliza. Foi implementado um planificador cíclico. Este tipo de planificadores se basea no uso de uma tabela (chamada plan) na qual estejam anotadas todas as ações de planificação: instante de ativação, tarefa a executar, duração de ests, etc. A tabela é construida durante a fase de desenho do sistema e logo durante a execução, o trabalho do planificador consiste em ler sequencialmente a tabela e seguir suas indicações. Quando chega ao final da tabela, volta a repetir a execução desde o princípio da tabela, daí o nome de política de planificação. Este método de planificação tem muitas vantagens:

  • O planificador é muito fácil de implementar.

  • É muito eficiente.

  • Uma vez construído o plano, de imediato determinamos a planificabillidade do sistema (temos investigadores que defendem que este é o único método que pode garantir 100% de funcionamento de um STR) [XU93].

    O principal incoviniente está na dificuldade de construir o plano. E cada vez que alteramos alguns parâmetros das tarefas, temos que reconstruir o plano por completo. Por outra parte, o tamanho do plano, e portanto a quantidade de memória para armazená-lo, tem que ser muito grande.

Para que serve o Tempo Real?

Talvéz muitos achem que as técnicas de tempo real só são utilizados na NASA, ou em mísseis ou coisas do estilo. Bem que isto poderia estar certo alguns anos atrás, hoje em dia, a situação mudou muito -- e há de mudar muito mais--, devido a progressiva incorporação da informática e a eletronica na vida cotidiana. O âmbito da vida cotidiana em que o tempo real mais está presente, é no campo das telecomunicações e nas aplicações multimídia. Por exemplo, se queremos que nosso computador seje campaz de reproduzir um arquivo de som gravado no disco, o programa terá que continuamente (melhor dizendo, periodicamente) ler, descomprimir e enviar a placa de som os sons. Se no momento que estamos escutando a gravação, estamos trabalhando com um editor de textos ou simplesmente estamos compilando o núcleo do Linux, é facil percebermos os silencios devido a que o processador estar ocupado realizando outras tarefas. Se no lugar do som, o que estamos reproduzindo é vídeo, então produziremos paradas ou cortes na imagem. Este tipo de sistemas são denominados sistema de tempo brando (o não cumprimento de um prazo de execução não produz um desastre grava, mas sim, uma degradação das prestações).

As aplicações no RT-Linux vão mais além das aplicações estritamente de tempo real. Com RT-Linux podemos tomar o controle total do PC (digo PC e não o computador porque agora, não há nenhuma implementação do RT-Linux para outra arquitetura) tal como podemos fazer no MSDOS. Em uma tarefa de tempo real, podemos acessar a todas as portas do PC, instalar manejadores de interrupções, desabilitar temporariamente as interrupções, ou seja, que podemos deixar a máquina em suspenso, como se tratasse de um Windows. Esta possibilidade é muito atrativa para todos aqueles que gostam de conectar pequenos "gadgets" eletrônicos ao computador.


Módulos carregáveis

Para entender e poder utilizar RT-Linux é necessário conhecer os módulos de carga dinâmicos do Linux. Matt welsh escrever um artigo completo em que explica com profundidade todos os aspectos dos módulos.

O que são?

Na maioria das implementações UNIX, a única forma de acessar o hardware (portas, memória, interrupções, etc.) é mediante os arquivos especiais, tendo previamente instalado o manejador de dispositivos (device driver). Apesar de que existem bons livros que explicam como construir manejadores de dispositivos, parece-nos ser um trabalho aborrecido e comprido, pois temos que implementar muitas funções para poder juntá-lo com o sistema operacional.

Os módulos são "troços do sistema operacional" que podemos inserir e extratir em tempo de execução. Quando compilamos um programa composto por vários arquivos fonte, primeiro compilamos em separado cada arquivo para produzir o o arquivo objeto ".o" e logo depois unimos todos eles, resolvendo todas as referencias, para criar um único executável. Suponhamos que o arquivo objeto que contém a função main, pudessemos pô-lo em ejecução, e que o sistema operacional fosse capaz de carregar na memória e juntar todo o restante dos arquivos só quando fosse necessários. Bem, pois o núcleo do Linux é capaz de fazer isto com o próprio núcleo. Quando Linux inicia, só carrega na memória o executável vmlinuz, que contém as partes indispensáveis, e logo em tempo de execução, pode carregar e descarregar seletivamente os módulos que esteje necessitando em cada momento.

Os módulos são uma característica opcional do Linux, escolhidas quando compilamos o núcleo. Os núcleos de todas distribuições que conheço, foram compilados com a opção de módulos ativada.

Podemos inclusive criar novos módulos e carregá-los sem necessidade de recompilar nem reinicializar o computador.

Uma vez carregado um módulo, passa a formar parte do próprio sistema operacional, pelo que:

  • Pode fazer uso de todas as funções e acessar todas as variáveis e estruturas de dados do núcleo.

  • O código do módulo é executado com o máximo nível de previlégio do processador. Na arquitetura i386 executamos no nível 0 (ring level 0), com que podemos fazer qualquer tipo de acesso de entrada/saída e executar instruções previlegiadas.

  • A memória, tanto de programa como de dados, está mapeada diretamente na memória física, sobre ela não podemos fazer "paging", ou como incorretamente através de um "swapping". Com o que numca poderemos produzir uma falha de página durante sua execução.

Como podemos ver, um módulo de carga dinâmico em sí mesmo, já possui algumas características de tempo real; evita os atrasos por falha de página e tem acesso a todos os recursos do hardware.

Como criamos e utilizamos?

Um módulo é criado a partir de uma fonte em "C". Continuando temos um módulo mínimo (para poder fazer a maioria das seguintes ordens é necessário ser super-usuário, root) :

================ exemplo1 ================

#define MODULE
#include <linux/module.h>
#include <linux/cons.h>

static int output=1;

int init_module(void) {
printk("Output= %d\n",output);
return 0;
}
void cleanup_module(void) {
printk("Adios, By, Chao, Ovuar, \n");
}

Para compilá-lo utilizaremos os seguintes parâmetros:

# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -c exemplo1.c

A opção "-c" indica ao gcc que deve parar quando tiver criado o arquivo objeto, eliminando a fase ligação (link). O resultado é o arquivo objeto exemplo1.o.

O núcleo não dispõe de "saída standard", pelo que não poderemos utilizar a função printf()... em troca, o núcleo oferece uma versão desta chamada printk(), que funciona quase igual, só que o resultado é impresso no buffer circular de mensagens (kernel ring buffer). Neste buffer é onde escrevemos todas as mensagens do núcleo, são as mensagens que vemos quando iniciamos o Linux. Em qualquer momento podemos ver o conteúdo digitando o comando dmesg ou diretamente consultando o arquivo /proc/kmsg.

Como podemos ver, a função main() não existe e em seu lugar chamamos a função init_module() sem nenhum parâmetro. Antes de descarregar o módulo, chamamos cleanup_module(). Para carregar o módulo, fazemos com o seguinte comando: "insmod: .

# insmod exemplo1.o

Acabamos de instalar exemplo1 e executar sua função init_module(). Se quisermos ver os resultados chamamos:

# dmesg | tail -1
Output= 1

O comando lsmod nos permite listar todos os módulos que estão em execução ou carregados no momento:


# lsmod
Module Pages Used by
exemplo1 1 0
sb 6 1
uart401 2 [sb] 1
sound 16 [sb uart401] 0 (autoclean)

E finalmente com rmmod descarregamos um módulo:


# rmmod exemplo1
# dmesg | tail -2
Output= 1
Adios, By, Chao, Orvua,

a saída do dmesg nos mostra que executamos a função cleanup_module().

Só nos falta saber como podemos passar parâmetros a um módulo. Não há nada mais fácil e surpreendente. Podemos atribuir valores às variáveis globais, como sómente escrever a atribuição como parâmetro de insmod. Por exemplo :

# insmod exemplo1.o output=4
# dmesg | tail -3
Output= 1
Adios, By, Chao, Orvua,
Output= 4

agora já sabemos todo o necessário sobre métodos, voltemos ao RT-Linux.

O programa de Tempo Real

Lembre,o-nos que para poder utilizar RT-Linux, primeiro tivemos que preparar o núcleo do Linux para suportar os módulos de tempo real, operação que explicamos no primeiro número da série [referência a ele].

Podemos utilizar RT-Linux de duas formas distintas :

  1. Como um sistema classico de tempo real com um planificador baseado em prioridades fixas.

  2. Como um PC simples [bare], algo parecido com o que podemos fazer no DOS: capturar as interrupções e ter o controle total do computador.

Neste artigo só explicarei como utilizar RT-Linux como um sistema com prioridades fixas. O exemplo que vamos ver não faz nada "útil", tão sómente põe em marcha (inicia) uma tarefa de tempo real, que é um simples caracol :

================== exemplo2 ==================

#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/rt_sched.h>
RT_TASK task;
void fun(int computo) {
int bucle,x,limite;
limite = 10;
while(1){
for (bucle=0; bucle<computo; bucle++)
for (x=1; x<limite; x++);
rt_task_wait();
}
}
int init_module(void) {
RTIME now = rt_get_time();

rt_task_init(&task, fun, 50 , 3000, 1);
rt_task_make_periodic(&task, now+(RTIME)(RT_TICKS_PER_SEC*4000)/1000000,
(RTIME)(RT_TICKS_PER_SEC * 100)/1000000);

return 0;
}


void cleanup_module(void){
rt_task_delete(&task);
}



Para compilá-lo, utilizaremos o seguinte comando :

# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c exemplo2.c

Já que este programa é um módulo, o ponto de entrada é a função init_module(). O que ele faz primeiro é ler o instante de tempo atual e o guarda em uma variável local; a função rt_get_time() devolve o número de RT_TICKS_PER_SEC transcorridos desde o momento que se inicializou o computador (que na atual implementação RT_TICKS_PER_SEC vale 1.193.180, o que dá uma resolução de 0.838 micro-segundos). com rt_task_init() inicializamos a estrutura task sem por em marcha a tarefa. O programa principal da tarefa recém criada é é fun(), o segundo parâmetro é o valor do dado que se passou a nova tarefa quando começar sua execução, observe que fun() espera um parâmetro do tipo int. O parâmetro seguinte é o tamanho da pilha da tarefa, já que cada tarefa tem uma fila de execução própria, necessita uma fila própria. O último parâmetro é a prioridade neste caso, como uma só tarefa no sistema, e podemos por qualquer valor de prioridade.

rt_task_make_periodic() converte a tarefa task em uma tarefa periódica. O primeiro tempo é o instante de tempo absoluto em que se ativará pela primeira vez e o seguinte parâmetro indica o período entre ativações sucessivas a partir da primeira.

A tarefa de tempo real (função fun()), é um loop infinito, dentro do qual existem duas ações: um loop que só serve para gastar o tempo, e logo chama rt_task_wait(). rt_task_wait(), é uma função que suspende a execução da tarefa que a chama até a próxima ativação, momento e que ele continuará a execução com a seguinte instrução, depois de rt_task_wait(). Observe que uma tarefa periódica não é executada desde o princípio de cada ativação, sim que a própria tarefa tem que paralizar-se (quando finalizou seu trabalho) e esperar a próxima ativação. Desta forma podemos programar uma tarefa para que sómente na primeira ativação realize certos trabalhos de inicialização.

Para poder por o exemplo2 em execução, temos que instalar primeiro o módulo rt_prio_sched, pois necessitamos das funções rt_task_make_periodic(), rt_task_delete() e rt_task_init(). A função rt_get_time() não está dentro do núcleo, e portanto não temos que instalar nada para poder utilizá-lo.

# modprobe rt_prio_sched
# insmod ./exemplo2.o

Já que rt_prio_sched é um módulo do sistema, o criamos quando compilamos o núcleo do Linux, e foi copiado para o diretório /var/modules/2.0.33/, podemos utilizar o comando modprobe, que é simplesmente uma forma mais fácil de carregar módulos (é capaz de procurar o módulo pedido em vários diretórios. Ver modprobe(1).

Se tudo foi bem, com o comando lsmod podemos ver que os dois módulos foram carregados corretamente.

Bem, pois já temos uma tarefa de tempo real em andamento; Notas algo? Se o processador está um pouco lento, possivelmente notará que Linux está um pouco diferente do normal. Podes aumentar o número de interações do loop de fun(), variando o terceiro parâmetro de rt_task_init(). Uma forma de apreciar que Linux utiliza menos tempo de processador com o programa ico, pois o tempo empregado para as tarefas de rempo real é, sobre todos os efeitos, como se o processador funcionasse mais limpo. Linux imagina que todos os seus processos precisam de mais tempo para fazer o mesmo. Se o tempo de computação (tempo necessário para executar todas as interações do loop) da tarefa é superior a 100 microsegundos, então Linux suspenderá suas atividades, pois a tarefa em background, e a tarefa de tempo real comsomem 100% do tempo. Realmente, Linux NÃO suspendeu suas atividades, simplesmente não tem tempo de processador.

Comunicações entre tarefas

No RT-Linux, só há uma forma de comunicações: Real-Time FIFO. O funcionamento é muito similar ao que acontece em PIPE's do Unix, a comunicação é por fluxo de dados sem estrutura. Uma FIFO é um buffer de bytes de tamanho fixo, que podemos fazer operações de leitura e escrita.

Com os FIFOS, podemos comunicar tanto tarefas em tempo real entre sí, como tarefas em tempo real com processos Linux normais.

# for i in 0 1 2 3; do mknod /dev/rtf$i c 63 $i; done

Desde o ponto de vista de um processo Linux normal uma FIFO é um arquivo especial de caracteres. Normalmente estaré no diretório /dev/rtf0, dev/rtf1, etc. Estes arquivos não existem no Linux, portanto temos que criá-los. Podemos fazer da seguinte forma:


Se precisamos de mais FIFOs, só temos que criá-los rtf4, rtf5 etc. Os arquivos especiais são a interface de acesso a um manobrador do sistema operacional, mas se o manipulador não existe, então de nada serve o arquivo especial, isto feito, tentar abrir um arquivo especial do qual o sistema operacional não tem o manipulador associado, falhará.

DrawObject

Os FIFOs são utilizados como fossem arquivos normais (open read/write close). Para poder utilizá-los, tem que existir, isto é, uma tarefa de tempo real tem que ter criado o FIFO antes que um processo normal do Linux possa fazer um open sobre ele.



A partir do ponto de vista de uma tarefa em tempo real, os FIFOs são utilizados mediante funções específicas:

  • rt_create(unsigned int fifo, int size): cria um FIFO com um buffer de tamanho size. A partir deste momento, e até que se destrua, o dispositivo acessado desde /dev/rtf[fifo] existe e pode ser utilizado.

  • rt_destroy(unsifned int fifo): destroi o FIFO e libera memória.

  • rt_fifo_put(fifo, char *buf, int count): tenta escrever count bytes do buffer buf. Se não há bastante espaço no buffer do FIFO, retorna -1.

  • rt_fifo_get(fifo, char *buf, count): tenta ler count bytes a partir do FIFO, se não há bastante dados disponíveis retorna -1.



Vejamos agora um exemplo de sistema que faz uso desta funções. Este exemplo é uma pequena modificação de um dos exemplos que existe na distribuição do RT-Linux (sound):

================== exemplo3 ==================

#define MODULE
#include <linux/module.h>
#include <linux/rt_sched.h>
#include <linux/rtf.h>
#include <asm/io.h>


RT_TASK task;

static int filter(int x){
static int oldx;
int ret;

if (x & 0x80) {
x = 382 - x;
}
ret = x > oldx;
oldx = x;
return ret;
}

void fun(int dummy) {
char data;
char temp;
while (1) {
if (rtf_get(0, &data, 1) > 0) {
data = filter(data);
temp = inb(0x61);
temp &= 0xfd;
temp |= (data & 1) << 1;
outb(temp,0x61);
}
rt_task_wait();
}
}


int init_module(void){
rtf_create(0, 4000);
/* enable counter 2 */
outb_p(inb_p(0x61)|3, 0x61);
/* this is just to ensure that the output of the counter is 1 */
outb_p(0xb0, 0x43);
outb_p(3, 0x42);
outb_p(00, 0x42);
rt_task_init(&task, fun, 0 , 3000, 1);
rt_task_make_periodic(&task, rt_get_time()+(RTIME)1000LL,
(RTIME)(RT_TICKS_PER_SEC/ 8192LL));

return 0;
}

void cleanup_module(void){
rt_task_delete(&task);
rtf_destroy(0);

}

Exatamente como no exemplo2, necessitamos dos serviços do módulo rt_prio_sched, mas agora também precisamos dos sevicos do módulo rt_fifo_new para poder utilizar os FIFOs.

Se criarmos uma tarefa de tempo real periódica, com uma frequencia de 8192Hz. Esta tarefa lê bytes da FIFO zero e se encontra alguma coisa o envia a porta do altofalante do PC. Se copiamos um arquivo de som no formato ".au" no diretório /dev/rtf0, poderemos ouvi=lo. Não é necessário dizer que a qualidade de som é péssima, pois o hardware do PC s;o permite utilizar um bit para modular sinal. No diretório testing/sound da distribuição, podemos encontrar o arquivo Linux.au para fazer provas.

Para compilá-lo e xecutálo:

# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -D__RT__ -c ejemplo3.c
# modprobe rt_fifo_new; modprobe rt_prio_sched; insmod ejemplo3.o
# cat linux.au > /dev/rtf0

Observe como o programa cat pode utilizar para escrever sobre qualquer arquivo, incluidos os arquivos especiais. Também poderíamos utilizar o comando cp.

Para poder ver como afeta o tempo real a qualidade de reprodução, só temos que escrever um programa que faça o mesmo, mas desde um processo de usuário:

================== exemplo4 ==================

#include <unistd.h>
#include <asm/io.h>
#include <time.h>

static int filter(int x){
static int oldx;
int ret;

if (x & 0x80)
x = 382 - x;

ret = x > oldx;

oldx = x;
return ret;
}
espera(int x){
int v;
for (v=0; v<x; v++);
}
void fun() {
char data;
char temp;

while (1) {
if (read(0, &data, 1) > 0) {
data = filter(data);
temp = inb(0x61);
temp &= 0xfd;
temp |= (data & 1) << 1;
outb(temp,0x61);
}
espera(3000);
}
}

int main(void){
unsigned char dummy,x;
ioperm(0x42, 0x3,1);
ioperm(0x61, 0x1,1);

/* enable counter 2 */
dummy= inb(0x61);
espera(10);
outb(dummy|3, 0x61);

/* this is just to ensure that the output of the counter is 1 */
outb(0xb0, 0x43);
espera(10);


outb(3, 0x42);
espera(10);
outb(00, 0x42);

fun();
}

Este programa é compilado como um programa normal:

# gcc -O2 ejemplo4.c -o exemplo4

e para executá-lo:

# cat linux.au | exemplo4



Para poder acessar as portas hardware do computador a partir de um programa normal de Linux, temos que pedir permissão ao sistema operacional. Isto é uma medida de proteção básica e necessária para evitar que um programa possa acessar por exemplo so disco rígido diretamente. a chamada ioperm(0 serve para indicar ao sistema operacional que queremos acessar a um determinado nível de endereços de entrada/saída. Só será concedido o acesso aos programas sendo executados com permissão de super-usuário. Outro detalhe a destacar é como se gera a frequencia de 8192Hz, para se emitir o som. Se bem que existe a chamada nanodelay(), esta só tem uma resolução do milisegundo, pelo que temos que fazer uso da temporização por loop de espera. O loop está ajustado para que mais ou menos funcione sobre um Pentium 100Mhz.

Agora prova e executa o exemplo4 junto com o programa ico. O que se ouve? Te parece agora que o tempo real serve para alguma coisa?

Conclusão

Este segundo artigo teve como ponto central os detalhes de programação de tarefas de tempo real. Os exemplos apresentados são bastante simples e necessitam de utilidades, no segundo número, apresentarei uma aplicação mais útil. Poderemos controlar a televisão a partir do Linux, ou mais surpreendente, controlar teu Linux à distancia (controle remoto)!!





Bibliografía:

"Online KURT" B. Srinivasan. http://hegel.ittc.ukans.edu/projects/kurt
"A Linux-based Real-Time Operating System" por Michael Barabanov. Master of Science, New Mexico Institute of Mioning and Technology.
"Linux as an Embedded Operating System" by Jerry Epplin http://www.espmag.com/97/fe39710.htm
"Implementing Loadable Kernel Modules for Linux" por Matt Welsh http://www.ddj.com/ddj/1995/1995.05/welsh.html
"Linux Kernel Internals" por M. Beck, H. Böhme, M. Dziadzka, U. Kunitz, R. Magnus, D. Verworner. Ed: Addison-Wesley.
"Programação Linux 2.0, API do sistema e funcionamento do núcleo" pors Rémy Card, Eric Dumas y Franck Mével. (Escrito originalmente em frances e traduzido para o espanhol). Ed: Eyrolles, Ediciones Gestión 2000.
"On Satisfying Timing Constrains in Hard-Real-Time Systems" por J. Xu & L. Parmas. IEEE Trans. on Software Engineering. Jan 1993

Esta website é mantida por by Miguel Angel Sepulveda
© Ismael Ripoll 1998
LinuxFocus 1998