Implementação de Mecanismo de Sincronização entre Threads para a Plataforma FemtoJava Rita Kalile Almeida Andrade UFRGS - Universidade Federal do Rio Grande do Sul Instituto de Informática Porto Alegre RS {rkaandrade@inf.ufrgs.br} Resumo A exploração do paralelismo é uma das formas de aumentar o desempenho podendo-se até economizar energia. Uma das abordagens possíveis é a exploração do paralelismo ao nível de threads. Entretanto, o uso de threads pode inserir inconsistências, caso recursos compartilhados sejam acessados simultaneamente. Neste contexto se insere este trabalho, cujo objetivo está em alterar a arquitetura do femtojava, através da adição de uma nova instrução que permita o obter sincronização entre múltiplas threads. Palavras-chave: Sincronização, FemtoJava, Multi-threads, Escalonamento. 1 Introdução Uma das linhas de pesquisa do Laboratório de Sistemas Embarcados (LSE) situa-se em torno do micro-controlador FemtoJava, o qual é baseado numa máquina de pilha e que executa nativamente um subconjunto dos bytecodes Java. Diversos trabalhos têm sido realizados em torno deste micro-controlador, tais como a implementação de diferentes variações organizacionais e arquiteturais, como versões multiciclo, pipeline e VLIW; de um escalonador; e de um garbage collector. Estes trabalhos vêm permitindo o uso dos almejados recursos de orientação a objetos. Entretanto, à medida que a capacidade computacional do sistema aumenta a tendência é que ocorra também um crescimento da área do dispositivo e do consumo de energia. A exploração do paralelismo é uma das formas de aumentar o desempenho podendo-se até economizar energia. Atualmente, a granularidade do paralelismo é mais explorada ao nível de instruções (ILP). Porém, além de possuir uma capacidade finita, essa abordagem não se consegue extrair um grau desejável de paralelismo em determinados domínios de aplicações. Dessa forma, uma tendência é explorar paralelismo ao nível de threads (TLP).
Threads compartilham o mesmo espaço de endereçamento com o processo pai e com os demais threads, executam em um único processador sem troca de contexto e fazem um melhor uso de recursos. Entretanto, o uso de threads executando de forma concorrente pode inserir inconsistências, caso recursos compartilhados sejam acessados simultaneamente. Para evitar isso, o acesso a esses recursos deve ser sincronizado, de forma a garantir que determinadas partes do código, as seções críticas, sejam executadas por no máximo uma thread de cada vez. Neste contexto se insere este trabalho, cujo objetivo está em alterar a arquitetura do FemtoJava, através da adição de uma nova instrução, a synchronized, que permita obter sincronização entre múltiplas threads. Essa instrução consiste de sincronizar o acesso a uma determinada seção crítica compartilhada entre as threads executadas por um mesmo processador FemtoJava Multiciclo. Na Seção 2 são apresentados conceitos relacionados ao trabalho, uma breve descrição do FemtoJava e a especificação da instrução de sincronização em Java. Na seção 3 são discutidos detalhes da implementação realizada neste trabalho. E por fim, na seção 4 e 5 são discutidos os resultados, conclusões e trabalhos futuros. 2 Conceitos 2.1 O femtojava Multiciclo O processador FemtoJava [1] é um microcontrolador baseado em operações de pilha e que executa bytecodes Java. O Femtojava caracteriza-se por executar um conjunto reduzido de instruções Java e possuir uma arquitetura Harvard. Este processador foi projetado especificamente para a utilização em sistemas embarcados. Possui algumas características interessantes, tal como o tamanho da parte de controle ser diretamente proporcional ao número de instruções utilizadas. Ou seja, a partir de uma ferramenta disponível, o bytecode Java gerado é analisado e a parte de controle suporta apenas aquelas instruções que são utilizadas pela aplicação [6]. Vale ressaltar que, para a implementação de aplicações utilizando threads, aplicações alvo deste trabalho, foi necessário prever o uso da API de tempo-real [4]. Esta API permite a expressão de elementos comuns em sistemas de tempo-real como tarefas,
escalonadores, temporizadores, threads, dentre outros. A sua concepção foi baseada na Real-Time Specification for Java RTSJ ([8] e [9]). Para gerar aplicações a serem executadas pelo FemtoJava, foi utilizada a ferramenta SASHIMI (System as Software and Hardware in Microcontrollers) [6]. O SASHIMI é um ambiente para o projeto de aplicações embarcadas e geração de microcontroladores Java. Para este trabalho no qual há apenas simulação, foram utilizados apenas os arquivos correspondentes a RAM e a ROM do FemtoJava também gerados pela ferramenta. A simulação do FemtoJava foi realizada no ambiente CACO-PS (Cycle-Accurate Configurable Power Simulator) [3]. O simulador utiliza uma descrição arquitetural do microcontrolador, avaliando a potência consumida de cada bloco funcional que é ativado a cada ciclo de relógio durante a execução de todas as instruções de uma dada aplicação. 2.2 Sincronização A maior parte dos programas multi-threads necessitam que as threads se comuniquem de forma a sincronizar suas ações. Para isso, a linguagem Java provê locks. Os locks são objetos compartilhados, através do qual é possível coordenar o acesso simultâneo entre threads a um determinado recurso, chamada seção crítica, impedindo assim que sejam inseridas inconsistências durante a execução da aplicação. Para isso, cada thread precisa adquirir um lock antes de usar o recurso e liberá-lo após executar a seção crítica. A isso dá-se o nome de exclusão mútua. Mesmo quando a thread que possui o lock for interrompida, nenhuma outra thread poderá adquiri-lo até que a primeira volte a utilizar o processador, termine a tarefa e libere o lock para outra thread. Java explicita operações de exclusão mútua em bytecodes. Os bytecodes monitorenter e monitorexit são utilizados para esta tarefa, assim um trecho de código fonte como o do ilustrado na Figura 1 após a compilação é transformado nos bytecodes ilustrado na Figura 2.... synchronized (lockx) { //instruções java; } Figura 1 - Código fonte Java
Figura 2 Bytecodes Java 3 O Mecanismo de Sincronização no FemtoJava Esse trabalho consistiu em adicionar a instrução synchronized ao conjunto de instruções do micro-controlador femtojava. Para isso, foi necessária a implementação de um componente, o MonitorLock, o qual ficou responsável pelo gerenciamento do acesso das threads à seção crítica. A implementação deste mecanismo deu-se conforme ilustrado na Figura 3. Um buffer circular de dimensões fixas mantém uma lista dos objetos de lock que estão sendo utilizados num dado instante e as respectivas threads que estão disputando aquele determinado lock para acessar a seção crítica. As referências destacadas simbolizam o endereço das threads que estão liberadas. As demais referências correspondem a threads que estão aguardando a liberação do lock. MonitorLock Obj de Lock Ref lockx Ref locky Ref lockz Threads que disputam o Lock Ref ThreadA Ref ThreadB Ref ThreadC Ref ThreadY Ref ThreadK Ref ThreadD Ref ThreadC Código da ThreadC... synchronized (lockx) { //instruções java; } Figura 3 Coube a este componente, ainda, manter um mecanismo de notificação ao escalonador, informando-lhe quando uma determinada thread deveria ser bloqueada à medida que as mesmas tentassem acessar a seção crítica, porém o lock pertencesse à outra
thread ou desbloqueadas quando uma thread que detém o lock terminasse de executar a seção crítica. Quando uma thread tiver de ser bloqueada, o componente emite um sinal ao timer, notificando-o de que o escalonador deve interromper a thread que está sendo executada. Deve-se deixar claro que a interrupção ocorrerá somente após o término da instrução que está sendo executada, no caso, monitorenter, garantindo que não haverá inconsistências nos estados das threads. Adicionalmente, a thread passa para o estado bloqueada, o que lhe impede de ser escalonada. De forma análoga ocorre quando uma thread libera o lock, ou seja, executa um monitorexit. Neste caso não é necessário emitir interrupção, visto que a thread poderá continuar sua execução até que finalize seu time-slice (fatia de tempo para execução). Porém, faz-se necessário que a thread seguinte da fila seja desbloqueada, possibilitando que o escalonador saiba que aquela thread poderá ser executada. Esse mecanismo está de acordo com a especificação da API de tempo-real, a qual introduz um espaço de memória das threads destinado a informar quando a mesma está bloqueada ou não. Esse espaço de memória é destinado ao escalonador, que só escalona threads não bloqueadas. Outra abordagem cogitada para o tratamento da sincronização previa a inclusão apenas a thread detentora do lock no buffer. Com isso haveria um ganho quanto a área. Porém, essa implementação implicaria em threads desbloqueadas, executando uma espera ocupada, utilizando o processador para realizar comparações e chamadas para verificar a liberação do lock. Devido a esse problema, essa abordagem foi descartada. Vale lembrar que, devido ao FemtoJava ser uma máquina de pilha, os valores referentes aos endereços da thread e do lock são obtidos a partir do topo da pilha. Contudo, segundo a especificação Java, o valor do endereço da thread executada não está no topo da pilha. Por isso foi necessário introduzir o bytecode aload_0 antes dos bytecodes monitorenter e monitorexit do código da ROM gerada a partir da aplicação Java, a fim de que o valor referente ao endereço da thread na memória fosse empilhado e posteriormente consumido durante a execução das intruções. Esta alteração foi realizada na especificação do ambiente Sashimi. A interface do componente implementado ficou conforme mostrado na Figura 4. Os sinais de entrada sig_a_out e sig_b_out correspondem aos valores contidos no topo da
pilha, ou seja, a referência para o lock e para a thread que está sendo executada. Os demais sinais de entrada foram inseridos para permitir a manutenção do contexto quando não houvesse necessidade de emitir nenhuma interrupção (sig_tf0) ou escrita (sig_ram_rw = 1) do conteúdo sig_b_in na memória no endereço sig_mar_out. Sig_a_out Sig_b_out sig_tf0_t sig_mar_out_t sig_ram_rw_t sig_b_in_ MonitorLock 2 word_ctrl sig_tf0 sig_mar_out sig_ram_rw sig_b_in Figura 4 O desenvolvimento deste componente seguiu as seguintes etapas: implementação do componente na linguagem C; implementação de aplicações teste com a ferramenta Sashimi; adição do componente e simulação do femtojava na ferramenta CACO-PS. 4 Resultados Com a adição deste componente ao micro-controlador femtojava, obteve-se um mecanismo de sincronização entre threads, garantindo resultados consistentes após a execução das mesmas. O componente foi validado por meio da simulação utilizando uma aplicação Produtor x Consumidor, implementada utilizando as classes e as interfaces da API de tempo-real. A instrução adicionada teve uma latência de 9 ciclos de clock. Esse número é justificado pelos ciclos necessários para efetuar o incremento do PC, o acesso à memória para atualizar o estado das threads e pela atualização da pilha.
5 Conclusões e trabalhos futuros Neste trabalho foi descrito o mecanismo implementado para obter sincronização entre threads na plataforma FemtoJava. Esse mecanismo permitirá a exploração de paralelismo no nível de threads nas aplicações desenvolvidas para esta plataforma. Vale ressaltar que todas as instruções executadas no FemtoJava Multiciclo são atômica. Com isso, garante-se que nenhuma das instruções será interrompida pelo durante sua execução, o que poderia ocasionar em inconsistências. Como possíveis trabalhos futuros, propõe-se a implementação do componente em VHDL e extração de mais resultados. Sugere-se ainda a implementação de um mecanismo que torne a inserção de timers parametrizável. Isso se faz necessário devido ao fato de as aplicações que utilizam a API de tempo-real apresentarem um dos timers exclusivo para o escalonador, restando apenas mais um timer para a aplicação. Referências bibliográficas [1]. Making Java Work for Microcontroller Applications, Sérgio Akira Ito, Luigi Carro, Ricardo Pezzuol Jacobi. [2]. S. A. Ito, L. Carro, R. P. Jacobi. Making Java Work for Microcontroller Applications, IEEE Design & Test of Computers, vol. 18, n. 5, 2001, pp. 100-110. [3] A.C.Beck Filho, F.R.Wagner, L.Carro. CACO-PS: A General Purpose Cycle-Accurate Configurable Power Simulator. In: SBCCI'03 16th Symposium on Integrated Circuits and Systems Design. São Paulo, Brazil, September 2003. [4]. Optimizing Real-time Embebed Systems Development Using a RTSJ-based API, D. Mulchandani, Java for Embedded Systems, IEEE Internet Computing, vol. 2, no. 3, May/June 1998, pp. 30-39. [5]. Sérgio Akira Ito, Luigi Carro, Ricardo Pezzuol Jacobi. System Design Based on Single Language and Single-Chip Java ASIP Microcontroller.Design Automation and Test in Europe. Proceedings, Paris, France: IEEE Computer Society Press, 2000. p.703-707. [6]. Manual do Sashimi, versão janeiro 2006. [7]. Manual do CACO-PS, versão janeiro 2006. [8] G.Bollella, J. Gosling, B. Brosgol. The Real-Time Specification for Java. 2001. Disponível em: http://www.rtj.org/rtsj-v1.0.pdf.
[9] Sun Microsystems. The Real-Time Java Platform. 2004. Disponível em: http://research.sun.com/projects/mackinac/mackinac_whitepaper.pdf.