• Nenhum resultado encontrado

Estudo Comparativo de Métodos de Verificação numa Urgência Hospitalar

N/A
N/A
Protected

Academic year: 2021

Share "Estudo Comparativo de Métodos de Verificação numa Urgência Hospitalar"

Copied!
66
0
0

Texto

(1)

Estudo Comparativo de Métodos de Verificação numa

Urgência Hospitalar

Miguel Ângelo Brázia Santos

Dissertação para obtenção do Grau de Mestre em

Engenharia Informática e de Computadores

Júri

Presidente: Prof. Pedro Sousa

Orientador:

Prof.ª Carla Ferreira

Vogais:

Prof. João Pereira

(2)
(3)

Agradecimentos

Gostaria de expressar o meu agradecimento à professora Carla Ferreira que me orientou ao longo deste trabalho e sem cujos conhecimentos na área em estudo ter-me-ia sido impossível levá-lo a cabo.

(4)

Resumo

O objectivo deste estudo é estabelecer uma comparação entre duas metodologias de verificação formal de software (o model checking e o theorem proving) utilizando um mesmo sistema como caso de estudo. O capítulo 2 apresenta as metodologias referidas para que o leitor possa perceber o que se está a fazer e como. Em seguida é descrito o caso de estudo que será definido nos capítulos 4 e 5 conforme a linguagem usada por cada metodologia. Nos capítulos 6 e 7 são demonstradas as verificações efectuadas, segundo o model checking e o theorem proving, respectivamente. Finalmente o capítulo 8 descreve as conclusões a que cheguei e as opções que tomaria se tivesse de verificar formalmente um sistema fora do meio académico.

Palavras-chave: verificação formal, theorem proving, model checking, propriedades, modelo

Abstract

The objective of this thesis is to establish a comparison between two formal methodologies for software verification (model checking and theorem proving) using the same system for both verifications. Chapter two presents those two methodologies so that the reader can understand what we are doing and how we are doing it. The case study is described in the second chapter and the models in PROMELA and AMN are presented in chapters four and five. Chapters six and seven show the verification of both models and chapter eight presents my conclusions.

(5)

Índice

Agradecimentos... I Resumo ... II Abstract... II Índice ... III Figuras... IV Tabelas... IV 1. Introdução ... 1

2. Apresentação das metodologias ... 2

2.1. Model Checking ... 2

2.2. Theorem Proving... 4

2.3. Verificação orientada à comunicação ... 5

2.3.1. Linguagem PROMELA ... 5 2.3.2. SPIN ... 14 2.4. Método B... 15 2.4.1. Notação AMN ... 16 2.4.2. Atelier B ... 23 2.5. Estado da Arte ... 24 3. Caso de Estudo ... 25 Propriedade a verificar ... 26 4. Modelo PROMELA ... 27 5. Modelo AMN ... 32

6. Verificação do modelo por Model Checking ... 37

7. Verificação do modelo por Theorem Proving ... 42

8. Conclusões ... 48

9. Bibliografia ... 53 ANEXOS

(6)

Figuras

Figura 1 - Diagrama de actividades do serviço de urgência ... 25

Figura 2 - Janela de interacção do Xspin... 38

Figura 3 - Simulação de um modelo no Xspin ... 39

Figura 4 - Resultado da verificação do modelo da urgência no Xspin... 41

Figura 5 - Janela de interacção do ProB com o modelo da urgência ... 43

Figura 6 - Interface gráfica Click'n Prove ... 44

Figura 7 – Relatório apresentado após a geração dos teoremas... 45

Figura 8 - Prova do teorema T1 ... 46

Figura 9 - Prova do teorema T3 ... 46

Figura 10 - Prova do teorema T4 ... 46

Figura 11 - BINGO, mensagem de sucesso da verificação do modelo ... 47

Tabelas

Tabela 1 - Operadores de LTL ... 15

Tabela 2 - Estados do processo doente... 28

Tabela 3 - Funções dos canais de comunicação... 30

(7)

1. Introdução

Num mundo moderno em que os sistemas de informação se encontram disseminados por toda a parte e em que é cada vez mais importante confirmar a correcção desses mesmos sistemas, os métodos formais para desenvolvimento e verificação de sistemas críticos assume um papel de crescente importância. No entanto, existem várias opções ou mesmo escolas de pensamento sobre como isto deverá ser feito. Os métodos formais diferem entre si de diversas formas, entre elas a perspectiva que enfatizam no sistema e as técnicas utilizadas para provar a correcção dos ditos sistemas.

A análise dos sistemas pode divergir em dois grandes ramos. Em primeiro lugar consideramos aqueles cuja complexidade se encontra relacionada na sua quase totalidade com a comunicação estabelecida entre os diversos elementos que constituem o sistema sendo que as manipulações de dados levadas a cabo são consideradas simples em comparação com as ditas interacções. Neste caso, a metodologia ideal consiste em modelar o sistema numa perspectiva orientada a processos que comunicam entre si, sendo a metodologia de verificação mais apropriada o model checking. O segundo ramo inclui os sistemas nos quais, pelo contrário, o importante não é a comunicação entre os elementos do sistema mas sim a informação que este mantém e a sua manipulação. Neste caso, a modelação deverá ser orientada aos dados e, por sua vez, a metodologia de verificação utilizada seria o theorem proving. Se em sistemas cuja orientação (aos processos ou aos dados) se encontra bem delineada é fácil decidir que paradigma seguir, o mesmo não acontece quando o mesmo sistema abarca propriedades das duas perspectivas. Neste caso as opiniões divergem sobre como abordar o assunto. Alguns defendem que os modelos deverão ser simplificados para possibilitar a utilização do model checking ou do theorem proving, enquanto outros defendem que ambas devem ser utilizadas e que deve ser feito um cruzamento dos dados obtidos através dos dois métodos.

O interesse em estabelecer uma comparação entre duas metodologias de verificação formal surgiu no seguimento de uma tese de doutoramento intitulada “Modelo Conceptual para a Auditoria Organizacional Contínua com Análise em Tempo Real” de Carlos Santos [SC07]. Esta tese não tem como objectivo a verificação formal, mas usa-a como um passo necessário no processo que analisa. Na referida tese, Carlos Santos aborda apenas o model checking. A presente tese abordará o model checking e também o theorem proving.

É de referir que o caso de estudo utilizado provém também da tese de doutoramento de Carlos Santos, embora numa versão mais simplificada.

(8)

2. Apresentação das metodologias

A verificação de Sistemas de Informação pode seguir vários paradigmas que diferem entre si em diversos aspectos, nomeadamente na modelação do sistema e propriamente nas metodologias abordadas na verificação em si. Sendo o objectivo desta tese estabelecer uma comparação entre diferentes metodologias de verificação de software, este capítulo apresenta os dois paradigmas que foram escolhidos para tal efeito, bem como as ferramentas que implementam os respectivos métodos.

As duas metodologias escolhidas foram o Model Checking e o Theorem Proving. Para poder representar um sistema de informação para que possa ser verificado segundo qualquer uma destas duas metodologias, é necessário submetê-lo a uma camada de abstracção. As abstracções do mesmo sistema para cada um dos métodos diferem. O Model Checking analisa o “comportamento” do sistema e permite uma verificação automática de sistemas finitos, enquanto que o Theorem Proving tem em conta os dados manipulados pelo sistema em causa e permite uma verificação parcialmente automática de sistemas infinitos. Estes conceitos serão esclarecidos mais à frente neste capítulo, durante a apresentação das metodologias e das ferramentas.

2.1. Model

Checking

O Model Checking é uma metodologia bem conhecida cujo objectivo é a verificação formal de sistemas segundo um paradigma comportamental e cujo algoritmo básico foi desenvolvido por E. Clarke, E. Emerson e A. Sistla [SCJ]. Tal como foi referido anteriormente, cada metodologia segue uma determinada orientação que se reflecte quer na forma de representar os sistemas que analisa quer na forma como é feita a verificação per se. A verificação pode ser feita sobre hardware ou software, através de um modelo criado a partir do sistema original (todo o hardware comercializado tem de ultrapassar com sucesso a fase em que o seu funcionamento é testado por um model checker). Este modelo envolve alguma abstracção relativamente ao sistema real, devendo reflectir a sua especificação.

Existem várias linguagens que permitem a criação de modelos que possam ser interpretados por model checkers. Para este trabalho foi escolhida a linguagem PROMELA. O model checker utilizado será o SPIN. Será feita uma introdução tanto à linguagem como à ferramenta mais à frente, nas secções 2.3.2 e 2.3.3. No entanto, apresentam-se aqui algumas considerações gerais desta metodologia.

O modelo construído com base no sistema em análise é traduzido pela ferramenta para uma máquina de estados finitos (um autómato finito) que simula o comportamento do sistema. Um autómato é uma estrutura que pode ser representada por um grafo dirigido. Neste grafo, os vértices correspondem aos estados do autómato e as arestas correspondem às transições possíveis entre os vários estados. Por

(9)

sua vez, os estados do autómato finito correspondem aos estados em que o sistema inicial se poderá encontrar durante a sua execução.

Para além do modelo do sistema e do autómato gerado a partir daquele, terão de ser fornecidos ao model checker os pressupostos que o sistema supostamente deve manter. Estes pressupostos são especificados através de lógicas temporais.

Com base nestes pressupostos, o model checking consiste em verificar se, no modelo em causa, alguma transição pode levar o sistema a um estado inaceitável (ou seja, um estado que desrespeita os pressupostos indicados). Note-se que a verificação pode falhar também no caso de o autómato atingir um estado aceitável desde que o faça fora de um período de tempo para tal estipulado (a lógica temporal na qual os pressupostos são definidos permite o teste de limites temporais).

Após a construção do modelo e a especificação das propriedades a verificar (esta segunda tarefa pode ser complicada visto que as lógicas temporais podem ser difíceis de compreender para utilizadores menos experientes) o processo de verificação é bastante simples. Aliás, todo ele é efectuado pela ferramenta automaticamente sem necessitar de intervenção do utilizador. Este pormenor de usabilidade é de extrema importância na altura de escolher que metodologia utilizar.

Se o model checker acabar a verificação e não encontrar nenhum estado no modelo que seja não aceitável, a resposta dada pela ferramenta consiste em afirmar que o modelo se encontra de acordo com as propriedades especificadas. Se, pelo contrário, a verificação falhar, então a ferramenta apresenta ao utilizador um contra-exemplo. Ou seja, um exemplo de quando as propriedades não se verificam no modelo. Desta forma, conhecendo um exemplo do mau funcionamento do sistema é possível ao utilizador detectar o erro que esteve na sua origem e corrigi-lo.

Uma das aplicações directas possíveis para o model checking é a verificação de requisitos de um sistema em construção. Esta metodologia, o model checking, é a que apresenta uma maior taxa de sucesso neste tipo de tarefas [PG04].

Uma das desvantagens deste método prende-se com a representação do modelo do sistema em análise. Como já foi referido, a partir do modelo especificado é construído um autómato que representa o funcionamento indicado pelo modelo. No entanto, os estados deste autómato correspondem a todas as combinações possíveis de estados do sistema original, sejam estes aceitáveis ou não. Só desta forma o model checker poderá decidir se um determinado fluxo de execução leva o sistema a um estado aceitável ou não aceitável. É sabido que os sistemas que mais necessitam de verificação formal são aqueles que apresentam uma maior dimensão ou uma maior complexidade. Tentar construir um autómato como o referido anteriormente com base num tal sistema pode ser impossível (ocupa demasiada memória), e caso não seja, operar um autómato de tais dimensões é bastante árduo, mesmo para uma ferramenta automática. No entanto, é possível contornar este obstáculo. A maneira mais usual de fazê-lo consiste em abstrair o modelo que se vai verificar, pondo de parte tudo o que seja irrelevante para o estudo em causa. Assim é possível reduzir o tamanho da especificação e do autómato gerado. As outras duas maneiras conhecidas prendem-se mais com a ferramenta e menos com o utilizador. Nestes casos, são

(10)

fórmulas de lógica proposicional) ou são feitas reduções de ordem parcial na construção do autómato (i.e. se para a prova em causa a ordem pela qual os acontecimentos A e B se dão é irrelevante, então não é necessário considerar os dois casos, AB e BA).

As ferramentas de model checking foram criadas inicialmente com o objectivo de avaliar a correcção lógica de sistemas de estados discretos. No entanto, presentemente são também utilizadas para verificar o funcionamento de sistemas de tempo real (sistemas críticos) e de formas híbridas entre estes dois tipos [WP1].

2.2. Theorem

Proving

Outra metodologia amplamente utilizada na verificação de sistemas dá pelo nome de theorem proving. Nesta metodologia, tanto o sistema em análise como as propriedades que se pretende verificar são representados como fórmulas de uma lógica matemática. Existem várias ferramentas que podem ser usadas para verificação através de theorem proving, nomeadamente o Atelier-B, introduzido na secção 2.4.2. O modelo do sistema também pode ser especificado em várias linguagens de acordo com a ferramenta utilizada para a verificação. O Atelier-B, que é um theorem prover específico para B (existem outros genéricos) utiliza a linguagem AMN (Abstract Machine Notation) que será apresentada na secção 2.4.1.

A base do theorem proving é um conjunto de axiomas que corresponde ao modelo do sistema. A partir destes axiomas, o theorem prover tenta automaticamente derivar as propriedades do sistema que se quer provar. Seguindo esta abordagem, a verificação de software é apenas uma das aplicações desta metodologia. O theorem proving pode ser usado em provas matemáticas, desenvolvimento de hardware, entre outros. Um exemplo que nada tem a ver com a verificação de software é o seguinte:

“…um adolescente frustrado pode formular uma conjectura que consiste nas posições baralhadas das peças de um cubo de Rubik e, a partir de axiomas que descrevem movimentos legais que podem alterar a configuração do cubo, provar que o cubo pode ser rearranjado até atingir a solução.” [SG]

Reportando agora um exemplo mais significante, a NASA utiliza métodos automáticos de theorem proving para verificar propriedade de segurança do software das suas naves espaciais que foi gerado automaticamente a partir de especificações de alto nível [SG].

Embora tenha uma ampla área de aplicação, esta metodologia é cada vez mais utilizada na verificação automática de propriedades de sistemas críticos [CEWJ].

A prova produzida pelos sistemas que implementam esta metodologia justificam como e porquê as conjecturas são verificadas partindo dos axiomas, apresentando os passos que foram seguidos até alcançar a solução. Assim, o resultado da prova pode ser facilmente entendido pelos utilizadores e até mesmo por outros programas de computador. Para alem disto, a solução para o problema que é sugerida pelo prover pode vir a ser uma solução geral para aquele tipo de problemas.

(11)

Os sistemas de ATP (Automated Theorem Proving) são programas de computador extremamente poderosos capazes de resolver problemas bastante difíceis. Devido a esta enorme capacidade, por vezes o próprio programa chega a impasses na derivação de fórmulas a partir dos axiomas. Nesta altura é necessário que um perito em deduções matemáticas intervenha para resolver o impasse. Esta interacção entre o perito e o programa pode dar-se a diferentes níveis: pode ser a um nível muito detalhado em que o humano guia as inferências feitas pelo sistema ou a um nível superior em que o utilizador determina lemas imediatos que levam à prova da conjectura inicial [SG]. Por experiência própria, esta interacção pode mesmo levar a pequenas alterações no modelo como forma de resolver alguns dos problemas postos pelo theorem prover.

2.3. Verificação orientada à comunicação

Nesta secção será apresentada a linguagem de modelação PROMELA. Os modelos construídos com esta linguagem focam maioritariamente os aspectos dos sistemas relacionados com a comunicação e com a sincronização, deixando de parte pormenores relacionados com a informação criada e manipulada pelo sistema em causa.

Será apresentada nesta secção a ferramenta utilizada para fazer o Model Checking do modelo PROMELA, o Spin.

2.3.1. Linguagem PROMELA

Nesta secção iremos fazer uma revisão sobre o funcionamento das metodologias de verificação formal de sistemas que dão maior importância à comunicação e aos problemas de sincronização. Nomeadamente, construiremos o nosso exemplo com base na linguagem PROMELA, a qual irá ser apresentada, bem como a ferramenta de verificação SPIN.

O nome da linguagem PROMELA significa PROcess MEta LAnguage. Tal como o seu nome indica, o elemento básico desta linguagem é o processo. Os processos da linguagem PROMELA podem ser equiparados às funções da linguagem C.

Os sistemas actuais, nomeadamente os sistemas distribuídos (aqueles onde se dão situações de concorrência) e os sistemas críticos são artefactos de grandes dimensões, comportando uma enorme quantidade de informação e sendo compostos por inúmeros elementos interligados. Grande parte da informação existente neste tipo de sistemas é perfeitamente supérflua no que respeita à verificação formal. O que realmente interessa neste tipo de análise é a informação relacionada directamente com a comunicação e com a concorrência de acesso aos recursos. Assim, a primeira preocupação a ter em conta quando se pretende verificar formalmente um tal sistema consiste em construir um modelo abstracto do respectivo sistema. Esta abstracção permite pôr de parte todos os pormenores que não são

(12)

necessários para a análise do sistema. Este constitui um passo fundamental para o correcto funcionamento do algoritmo de verificação utilizado pela ferramenta SPIN.

Tal como foi já referido, o modelo de um sistema em PROMELA é construído com base em processos que interagem entre si ou que concorrem aos mesmos recursos. A comunicação entre os processos é feita através de canais de comunicação. Os canais são objectos fornecidos directamente pela linguagem e serão explicados oportunamente.

Os recursos pelos quais os processos concorrem podem ser variáveis do sistema ou canais de comunicação. As próximas secções têm como objectivo apresentar a linguagem de modelação PROMELA.

Exequibilidade

das

expressões

Na linguagem PROMELA não existe diferença entre as condições e as restantes expressões. Nesta linguagem, expressões booleanas isoladas podem ser usadas como expressões normais e a execução das expressões está condicionada pela sua exequibilidade. Dependendo dos valores das variáveis do sistema ou dos conteúdos das mensagens existentes nos canais, qualquer expressão pode ser exequível ou ficar bloqueada. A exequibilidade é o meio básico de sincronização utilizado pelo PROMELA. Um processo pode estar à espera da ocorrência de um evento enquanto espera que uma expressão se torne exequível. Por exemplo, em vez de ficar bloqueado num ciclo de espera activa

while(a != b) skip /* espera que a seja igual a b */

um processo em PROMELA pode atingir o mesmo objectivo através da expressão (a == b)

uma vez que a expressão é executável apenas se a condição se verificar. Caso contrário, a execução do processo fica bloqueada na expressão anterior até que os valores das duas variáveis sejam iguais. O comando skip pode ser entendido como o “comando vazio”. Ou seja, a sua execução é sempre permitida e não produz nenhuma alteração.

Variáveis e tipos de dados

Na linguagem PROMELA, as variáveis podem armazenar informação global do sistema (que poderá ser usada por todos os processos em execução) ou informação específica de cada processo, consoante o local onde a declaração da variável é feita. As variáveis podem ser de um dos seis seguintes tipos:

• bit • bool • byte • short

(13)

• int • chan

Os primeiros cinco tipos correspondem aos tipos básicos da linguagem. As variáveis destes tipos podem armazenar apenas um valor de cada vez. O último tipo corresponde aos canais de comunicação. Este objecto pode armazenar um número predefinido de valores agrupados em estruturas definidas pelo utilizador. A utilização dos canais será aprofundada mais à frente.

As variáveis são consideradas globais se forem declaradas fora da declaração de qualquer processo e locais caso contrário. As declarações são feitas escrevendo primeiro o tipo a atribuir à variável seguido do nome pelo qual esta vai ser conhecida. Por exemplo, as expressões:

bool flag int estado byte mensagem

declaram três variáveis, uma do tipo booleano, uma do tipo inteiro e outra do tipo byte.

Na linguagem PROMELA, as variáveis podem também ser usadas como vectores (arrays). Por exemplo, a expressão

byte estado[N]

declara um vector de N bytes. Os valores guardados nos vectores podem ser acedidos de forma semelhante ao que acontece na linguagem de programação C. Desta forma, para aceder ao valor guardado na quarta posição do vector estado basta usar a expressão

estado[3]

Tal como em C, a indexação dos vectores começa em zero. Assim, para aceder ao quarto elemento do vector deve utilizar-se o índice 3.

As declarações de variáveis são sempre executáveis, bem como as atribuições. A atribuição consiste em atribuir a uma variável um determinado valor. Como exemplo de uma atribuição temos a seguinte expressão segundo a qual se atribui à variável quantidade o valor 3:

quantidade = 3

Declaração de tipos de processos

Para poder executar processos é necessário defini-los e dar-lhe nomes de forma que estes possam ser invocados. A forma de fazê-lo em PROMELA é recorrendo à palavra-chave proctype. A expressão seguinte exemplifica a definição de um processo simples.

proctype A( ) { byte estado; estado = 3 }

O processo definido tem o nome A. O corpo do processo, definido dentro de chavetas, pode ser composto por declarações de variáveis e de canais de comunicação e de expressões para manipular tais

(14)

artefactos. No caso do exemplo anterior o corpo do processo A consiste na declaração de uma variável do tipo byte e na atribuição do valor 3 a essa variável.

Ao contrário do que acontece em C, nesta linguagem o ponto e vírgula (;) funciona como um separador de expressões e não como terminador das mesmas. Como alternativa ao ponto e vírgula pode usar-se o símbolo →, embora este costume ser usado quando existe uma relação causa/efeito entre as expressões. Ambos os separadores são equivalentes.

Processo inicial

A expressão proctype serve apenas para declarar processos e não para executá-los. Apenas um processo é executado no início e a esse processo dá-se o nome de processo inicial. Este processo é referido numa especificação em PROMELA como init e é comparável à função main da linguagem de programação C. A mais pequena especificação possível em PROMELA é:

init { skip }

A forma utilizada para executar processos definidos com a palavra-chave proctype é através da expressão run. Considere-se uma especificação que contém uma expressão que declara um tipo de processo A, conforme foi demonstrado anteriormente. Em tal especificação, supondo que queremos apenas executar o processo A, o processo init ficaria como se demonstra.

init { run A() }

O operador run é um operador unário que instancia uma cópia de um dado tipo de processo que não espera pela terminação do processo. O operador é executável apenas se o processo puder efectivamente ser instanciado e retorna um valor positivo nesse caso. Caso contrário, o operador devolve o valor zero e o processo não é instanciado. Tal situação pode ocorrer se demasiados processos se encontrarem a correr, uma vez que o PROMELA limita o número de processos em execução como forma de prevenir a explosão de estados. O valor deste limite não depende da linguagem mas sim do hardware. O valor devolvido pelo operador run é um identificador do processo instanciado. Pelo facto de o operador devolver um valor numérico, este pode ser utilizado em expressões como por exemplo:

i = run A( ) && ( run B( ) || run C ( ) )

No entanto, esta expressão pode não ser muito útil. O próprio valor devolvido pelo operador run é de pouco uso uma vez que a comunicação entre processos é estabelecida através de canais de comunicação.

O operador run pode passar valores de argumentos aos processos que cria. Para tal é necessário que tais argumentos sejam definidos aquando da declaração do processo. Um exemplo de tal situação é dado em seguida. O processo A é declarado como recebendo dois argumentos, um byte e um short que lhe são passados na invocação.

(15)

proctype A(byte estado, short valor) { (estado == 1) -> estado = valor }

Init { run A(1, 3) }

Os argumentos dos processos são passados por valor. As únicas excepções são os canais de comunicação. Estes, quando passados como argumento de um processo, são passados por referência.

O operador run pode ser usado para criar um novo processo não só no corpo do processo init mas no corpo de qualquer outro processo. Um processo em execução desaparece quando este termina (quando é executada a última linha da sua declaração). No entanto, a terminação de um processo está dependente da terminação de todos os processos que o primeiro tenha criado.

Concorrência e sequências atómicas

O operador run pode ser usado para instanciar mais do que uma cópia do mesmo processo simultaneamente. Este facto acarreta um problema quando são instanciados vários processos que executam operações de leitura e de escrita sobre os mesmos recursos. Quando isto acontece torna-se impossível saber o resultado da execução dos processos. Como exemplo, vejamos este caso em que dois processos concorrentes manipulam a mesma variável.

byte estado = 1;

proctype A( ) { (estado == 1) -> estado = estado + 1 } proctype B( ) { (estado == 1) -> estado = estado – 1 } init { run A( ); run B( ) }

Neste caso específico, se um dos processos terminar a sua execução antes que o seu concorrente inicie, o segundo processo ficará eternamente bloqueado na condição inicial. Se o primeiro processo passar a condição e for bloqueado, o segundo processo passará também a condição e ambos alterarão a variável sem que seja possível determinar se o valor final será 0, 1 ou 2.

Existem várias soluções para este tipo de problemas. Uma delas passa por ceder ou não acesso à informação por parte de um processo através de testes a variáveis de controlo. No entanto, a linguagem PROMELA possui uma primitiva que permite evitar os erros gerados por erros de leitura como o que pode acontecer no exemplo anterior. Se uma porção de código for inserida dentro dos limites da palavra-chave atomic, esta porção de código tem de ser toda executada ou nenhum desse código será executado. Essa porção de código funciona efectivamente como se fosse uma única instrução atómica. Um exemplo de utilização da palavra atomic é dado agora.

(16)

proctype nr(short pid, a, b) { int res;

atomic { /* código crítico */ }

}

Canais de mensagens síncronos e assíncronos

Os canais de comunicação na linguagem PROMELA servem para modelar as transferências de informação entre os vários processos. Estes podem ser declarados globalmente ou localmente aos processos, tal como as variáveis. A declaração dos canais é feita recorrendo à palavra chan como exemplificado em seguida. O variável c está associada a um vector de 3 canais.

chan a, b; chan c[3]

chan d = [16] of { short } chan e[3] = [4] of { byte }

chan f = [16] of { byte, int, chan, byte }

O canal d consiste num único canal com capacidade para armazenar até 16 mensagens do tipo short. O canal e é uma mistura dos dois anteriores, sendo que é um vector de 3 canais em que cada um dos canais tem capacidade para 4 bytes. Por fim, o canal f tem capacidade para 16 mensagens. No entanto cada uma das mensagens é composta por quatro campos cujos tipos se encontram declarados dentro de chavetas.

As expressões seguintes são as utilizadas em PROMELA para escrever e ler (por esta ordem) valores em canais.

canalE!expressão canalR?variavel

Desta forma, o valor resultante da avaliação da expressão será enviado para o canalE e, por sua vez, o valor lido do canalR será armazenado na variável. Se a mensagem for composta por vários parâmetros, a expressão de escrita ou leitura deve indicar todos os valores ou variáveis pela ordem correspondente à indicada na declaração do canal.

canal!expr1,expr2,expr3 canal?var1,var2,var3

Se a mensagem tiver vários parâmetros mas na escrita no canal forem indicados mais parâmetros para além dos necessários, os valores a mais perdem-se. Se forem indicados menos valores do que os necessários, os respectivos campos da mensagem terão um valor indefinido. No caso da leitura, se se tentar ler mais parâmetros do que os devidos, os que se encontram em excesso assumem valores

(17)

indefinidos enquanto que se se lerem campos em defeito, os valores dos campos não lidos serão perdidos.

Convencionou-se que, quando existem diferentes tipos de mensagens, o primeiro campo de cada mensagem deve ser usado para especificar o tipo de mensagem. Assim, nas expressões de leitura e escrita do canal, a primeira variável deverá conter o tipo de mensagem a lista com os restantes parâmetros deve encontrar-se dentro de parêntesis a seguir ao tipo de mensagem:

canal!tipomsg(arg1,arg2,…,argn)

As expressões de escrita em canais são executáveis sempre que o canal em questão não se encontre cheio. Por sua vez, a leitura será possível sempre que o canal não esteja vazio. Opcionalmente, um dos argumentos da expressão de leitura pode ser uma constante. Neste caso, a leitura do valor armazenado no canal fica condicionado pelo tipo da constante. A leitura dá-se apenas se o valor do campo da mensagem que corresponda a uma constante na expressão for igual ao da constante.

As leituras e escritas nos canais não podem ser usadas como testes. Ou seja, a expressão seguinte executa um teste mas remove um valor do canal.

(canal?var == 0)

No entanto, o PROMELA possui uma forma de executar testes nos canais sem efeitos secundários. canal?[ack,var]

A expressão anterior verifica se a primeira mensagem do canal é uma mensagem de acknowledge sem no entanto remover a mensagem do canal.

Até agora temos falado apenas de comunicação assíncrona. Neste tipo de comunicação os processos enviam mensagens para os canais de comunicação e prosseguem a sua execução sem esperar que a mensagem seja lida. Por sua vez, outro processo há-de ler aquela mensagem do canal. No entanto, a linguagem PROMELA permite também simular comunicação síncrona entre processos. Na comunicação síncrona, o processo emissor bloqueia o seu funcionamento até que outro processo leia a mensagem que o primeiro enviou (rendezvous communication). Tal comunicação é simulada através de canais com capacidade para zero mensagens. Ou seja, estes canais apenas passam as mensagens em vez de armazená-las. É também possível construir vectores de canais síncronos, da mesma forma que se faz com os canais assíncronos. Este tipo de mecanismo permite manter a sincronia apenas entre dois processos (emissor e receptor).

Controlo do fluxo da execução

Ao longo desta apresentação do PROMELA já foram apresentadas três maneiras de controlar a execução de processos: concatenação de comandos dentro de um processo, execução paralela de processos e sequências atómicas. No entanto existe outras três formas de controlar o fluxo da execução e que serão apresentadas nesta secção:

(18)

• repetição

• saltos incondicionais

O mecanismo mais fácil de compreender é o da selecção condicional que se baseia meramente no conceito de causalidade. Se determinada condição se verificar, executa-se uma expressão. O mecanismo de selecção do PROMELA concede uma multiplicidade a este conceito. Várias condições são especificadas juntamente com as respectivas expressões a executar. Se alguma das condições for avaliada como verdadeira, a expressão correspondente é executada e apenas uma expressão é executada. Se as condições especificadas não apresentarem exclusão mútua (ou seja, várias possam ser avaliadas verdadeiras em simultâneo) apenas uma das expressões correspondentes é escolhida para ser executa e esta selecção é aleatória. A estrutura que permite tal controlo de fluxo é o if e a sua sintaxe é:

if

:: condicao1 -> opcao1

:: condicao2 -> opcao2

fi

Se nenhuma das condições for avaliada verdadeira, o processo fica bloqueado até que alguma se torna executável.

O mecanismo de repetição obtém-se através de uma pequena alteração ao mecanismo de escolha. A selecção de qual das expressões a executar é igual à seguida pelo if. A diferença é que essa selecção é sempre repetida. A única forma de interromper o ciclo é através da execução do comando break. No exemplo seguinte, o ciclo executa-se enquanto a variável contador assumir um valor diferente de zero e é interrompido quando a variável assume esse valor. A execução consiste em decrementar o valor do contador.

do

:: (contador != 0) -> contador = contador – 1 :: (contador == 0) -> break

od

Para terminar analisamos o comando de salto incondicional. Embora este mecanismo seja cada vez menos usado na computação actual pelos erros que pode causar, o PROMELA implementa-o. Assim, através da colocação de etiquetas e do comando goto é possível implementar saltos incondicionais na execução.

Exemplo PROMELA

Depois de apresentar o funcionamento da linguagem PROMELA, apresentamos um exemplo muito simples que tem como objectivo mostrar como é possível implementar nesta linguagem um modelo de um sistema com o qual muitas vezes nos deparamos na informática.

(19)

O exemplo que iremos utilizar é uma pilha. Uma pilha é um objecto computacional que serve para armazenar informação temporariamente. Este método de armazenamento pode também ser usado como meio de comunicação. A especificidade de uma pilha consiste na forma como a informação armazenada é ordenada. Este objecto computacional segue uma política que é chamada na gíria informática de LIFO (last in first out). Ou seja, tal como numa pilha de tabuleiros, o último tabuleiro a ser colocado na pilha terá de ser o primeiro a ser retirado. Da mesma maneira, o último artefacto de informação a ser colocado na pilha será o primeiro a ser retirado desta e não existe outra maneira de manipular os artefactos contidos na pilha. A operação de colocar um elemento na pilha chama-se push e a operação contrária chama-se pop. Assim, apresenta-se em seguida a implementação de um modelo deste objecto em PROMELA.

#define tamanho 5 int stack[tamanho]; int pos = 0, read;

chan canal = [1] of { int };

proctype push(int value) {

canal?read;

(pos < tamanho) -> stack[pos] = value;

pos = pos + 1; canal!1 } proctype pop() { canal?read;

(pos > 0) -> pos = pos - 1;

printf("pop: %d\n", stack[pos]); canal!1 } init { canal!1; run push(1); run push(2); run push(3); run push(4); run push(5); run push(6); run pop(); run pop(); }

(20)

Neste exemplo modela-se uma pilha de dimensão máxima 5. A pilha é implementada recorrendo a um vector de inteiros. Os dois processos declarados correspondem às várias acções que se podem tomar relativamente à pilha. Consideremos que a pilha já se encontra inicializada, ou seja, todas as posições do vector estão a zero e a variável pos, que indicará a próxima posição da pilha a preencher, está também com o valor zero. Os dois processos correspondem às acções push e pop. Nestes processos são utilizados vectores (atribuição de valores, indexação), passagem de parâmetros e expressões regulares como condições de controlo de fluxo. No processo pop é também usado o comando printf que, embora não apresentado neste texto, funciona de maneira idêntica ao comando homónimo da linguagem de programação C.

O canal existente na especificação tem como objectivo sincronizar o acesso à pilha funcionando como um semáforo de exclusão mútua. Desta forma apenas um processo pode manipular a pilha de cada vez.

2.3.2. SPIN

No capítulo 2 foi apresentada uma metodologia de verificação (model checking) que segue um paradigma comportamental. Isto significa que o objecto da análise é o comportamento do sistema ao longo da sua execução. O SPIN é uma ferramenta de verificação centrada em model checking e é uma das mais usadas neste campo encontrando-se disponível publicamente desde 1991 [SP1]. Pretende verificar se o sistema em causa tem propensão para gerar erros e, caso tal aconteça, possibilitar a sua correcção. O modelo do sistema original que é analisado pelo SPIN é especificado na linguagem PROMELA que foi apresentada ao longo da secção 2.3.2.

Tanto o funcionamento do PROMELA como de um model checker já foram apresentados neste artigo. No entanto é de salientar uma particularidade do SPIN. As premissas que se pretende verificar são especificadas numa lógica temporal denominada de LTL (linear time logic). A partir destas premissas, é construído um autómato que representa não as premissas mas o seu contrário. Por exemplo, se queremos verificar se um sistema não contém ciclos infinitos, define-se uma expressão LTL com esse significado. A partir de tal expressão, o SPIN constrói um autómato cujo funcionamento reflecte a existência de ciclos infinitos. A verificação consiste então numa operação realizada sobre os dois autómatos que vai gerar um novo autómato. Se a linguagem aceite por este terceiro autómato for vazia, significa que a intersecção dos dois autómatos é também vazia o que quer dizer que não há nenhum estado do modelo que verifique a existência de ciclos infinitos. Caso contrário, os estados do autómato gerado indicam precisamente os estados do autómato inicial nos quais se verificam ciclos infinitos e os caminhos que levam a esse estado. Desta forma, o utilizador pode modificar o modelo de forma a corrigir tal situação.

(21)

Pequena introdução à LTL

A lógica temporal linear é uma lógica composta por proposições, as conectivas lógicas conhecidas (negação, conjunção, disjunção e implicação) e um conjunto de operadores modais temporais. É através destes operadores que se torna possível definir proposições que tenham em conta o factor tempo.

As operações mais utilizadas são apresentadas de seguida [WP3].

Textual Simbólico Explicação Diagrama

X

ϕ

No estado seguinte, a proposição

ϕ

tem de verificar-se

G

ϕ

A proposição

ϕ

tem de verificar-se

globalmente: em todos os estados do caminho F

ϕ

A proposição

ϕ

tem de verificar-se eventualmente: em algum estado futuro no caminho

ψ

U

ϕ

A proposição

ψ

tem de verificar-se até que se verifique

ϕ

.

Tabela 1 - Operadores de LTL

Existem duas propriedades muito importantes que podem ser expressas em LTL (serão usados os termos em inglês por haver dificuldade em corresponder termos em português).

• Safety – propriedade segundo a qual algo mau nunca acontece (

G

¬

ϕ

)

• Liveness – propriedade segundo a qual algo bom continua sempre a acontecer (

G

F

ϕ

)

2.4. Método

B

No capítulo anterior foi apresentada a linguagem de modelação PROMELA. Os modelos construídos com esta linguagem focam maioritariamente os aspectos dos sistemas relacionados com a comunicação e com a sincronização. No entanto, existe outra metodologia de modelação que foca os aspectos relacionados não com a comunicação mas com a informação manipulada pelo sistema e com a sua estrutura. Nesta secção será apresentada uma tal metodologia conhecida como Método B.

A metodologia utilizada pelo método B (em diante apenas B) conta com avanços provenientes de investigações sobre métodos formais que foram realizadas ao longo dos últimos trinta anos. Também

(22)

neste método é necessário construir um modelo do sistema que se pretende verificar formalmente. A notação utilizada dá pelo nome de AMN (Abstract Machine Notation). Esta notação contém mecanismos estruturantes que suportam a modularidade e uma abstracção semelhante aos objectos. A construção de sistemas é baseada num desenvolvimento por camadas no qual os artefactos de grandes dimensões são compostos por colecções de artefactos mais pequenos e mais simples.

A notação do B dá ênfase à simplicidade, excluindo deliberadamente alguns pormenores de programação que possam tornar o sistema demasiado complexo. A grande dificuldade presente na engenharia de grandes sistemas de software prende-se com a estrutura, a gestão e o controlo de grandes volumes de detalhes. Os artefactos individuais que compõem o sistema devem ser de fácil compreensão de forma que a verificação da sua combinação e das suas relações seja exequível com razoavelmente pouco esforço.

2.4.1. Notação AMN

Tal como indicado pelo nome da notação, os modelos construídos em AMN são compostos por máquinas. Estas máquinas funcionam de maneira semelhante aos objectos das linguagens orientadas aos objectos, podendo também ser comparadas com o conceito de classe. As máquinas têm um nome para poderem ser nomeadas posteriormente, têm estado interno, têm funções que podem alterar o estado interno e/ou processar dados fornecidos à máquina e funciona como uma caixa negra. Para o utilizador, é indiferente a forma como os dados são processados dentro da máquina. O importante é o resultado obtido. Assim, esta notação tem várias etiquetas para definir as diferentes propriedades da máquina.

Nomear uma máquina

É indiscutível a necessidade de atribuir um nome a um objecto para que este seja conhecido, diferenciado de outros objectos e para que possa ser invocado quando for necessário. Na notação AMN, a maneira de nomear uma máquina é através da etiqueta MACHINE. A primeira linha da especificação de cada máquina deverá conter esta etiqueta seguida do nome a atribuir, conforme o exemplo apresentado:

MACHINE nome_da_maquina

Nesta notação, o estado interno das máquinas é guardado em variáveis. As variáveis podem ter diferentes tipos. O tipo de cada variável deve estar de acordo com o valor que essa variável representa no sistema uma vez que esta modelação deve ser feita com a intenção de perceber o sistema e não a forma como este é implementado.

(23)

As variáveis podem armazenar valores, conjuntos, relações, funções, sequências, entre outros tipos. A declaração das variáveis é feita recorrendo à etiqueta VARIABLES. Os nomes das variáveis a declarar devem ser indicado em seguida.

VARIABLES actual, proximo

Na linha de código anterior, são declaradas duas variáveis, nomeadamente a variável actual e a variável proximo. No entanto, a declaração das variáveis não especifica o seu tipo (ao contrário do que acontece no PROMELA). O tipo de uma variável é um dado constante ao longo da execução de um programa (ou, neste caso, de uma máquina). Assim, o tipo que corresponde a cada variável é indicado noutra etiqueta, a etiqueta INVARIANT. Tal como o seu nome indica, esta etiqueta serve para definir invariantes, ou seja, expressões que definem propriedades que não se devem alterar durante toda a execução da máquina. Esta cláusula pode indicar também outras restrições relativas às variáveis como valores que elas possam assumir ou mesmo relações existentes entre várias variáveis.

Invariantes

Uma máquina nunca pode atingir um estado no qual não se verifique uma das expressões expressas na cláusula de invariantes. Esta etiqueta como que define o correcto funcionamento da máquina. Como exemplo da utilização desta etiqueta, apresentamos a seguinte linha de código:

INVARIANT actual

N

proximo

N

actual

proximo

Aqui definem-se ambas as variáveis como sendo do tipo natural e estabelece-se a relação segundo a qual o valor assumido pela variável actual tem de ser menor ou igual ao assumido pela variável proximo.

Operações

As operações são a computação que a máquina pode executar. A declaração das operações é feita utilizando a etiqueta OPERATIONS. Uma máquina pode conter mais do que uma operação, sendo que cada uma é declarada individualmente mas todas se encontram sob a etiqueta OPERATIONS. A especificação das operações tem de conter informações como o nome da operação, os parâmetros de entrada e de saída (caso existam), restrições sobre os parâmetros ou os estados nos quais a operação pode ser executada, as variáveis que são modificadas e os efeitos da execução da operação para a máquina.

var_saida

nome_operacao (vars_entrada)

Esta linha de código exemplifica o cabeçalho de uma operação em AMN. O cabeçalho é onde se declara o nome da operação, neste caso nome_operacao. Também no cabeçalho podem definir-se as

(24)

variáveis de entrada (argumentos, inputs) e as variáveis de saída (outputs). No entanto estas variáveis são opcionais visto que não é obrigatório que as operações recebam argumentos e devolvam resultados.

A especificação de uma operação pode ser constituída por uma pré-condição e pelas instruções que a operação concretamente executa. A pré-condição deve ter restrições sobre os tipos de todas as variáveis e podem ter também restrições aos argumentos e ao estado da máquina. Esta cláusula existe para garantir que a operação é executada apenas quando se reúnem as condições para isso necessárias. A etiqueta reservada para as pré-condições é PRE. Após os pré-requisitos especificados, define-se o corpo da função. Para tal existe a etiqueta THEN logo a seguir à PRE. É nesta parte da operação que se definem as instruções que serão executadas quando a operação for chamada. O fim da operação é marcado pela etiqueta END.

A título de exemplo, apresenta-se uma operação que pertence a uma máquina dispensadora de senhas para atendimento. O nome da operação é serve_next. serve e next são variáveis de estado que representam o número da senha da pessoa a ser atendida e o número da próxima senha a dispensar (respectivamente). A pré-condição refere que serve < next, ou seja, nunca se pode atender uma pessoa com um número de senha que ainda não tenha sido emitido pela máquina. ss é o output. No corpo da operação é atribuído o valor de serve + 1 a ss e a serve.

ss

serve_next =

PRE serve < next

THEN ss, serve := serve + 1, serve + 1

END

Note-se que podem atribuir-se listas de valores a listas de variáveis, tal como no exemplo anterior. A lista de variáveis deve aparecer antes do sinal := com as variáveis separadas por vírgulas, acontecendo o mesmo no lado direito do sinal := com os valores a atribuir. Os valores e as variáveis devem aparecer pela mesma ordem.

Inicialização

A notação AMN permite que o programador escolha em que estado cada máquina inicia a sua execução. Ou seja, é possível inicializar as variáveis com valores escolhidos pelo programador simulando o estado a partir do qual a máquina irá começar a trabalhar. Para esta finalidade existe em AMN a etiqueta INITIALISATION. Todas as inicializações necessárias devem ser feitas neste campo. É mostrado um exemplo em seguida. Nesta caso as variáveis var1 e var2 são ambas inicializadas com o valor zero.

INITIALISATION var1, var2 := 0, 0

(25)

Skip

Tal como no PROMELA, a notação AMN tem um “comando vazio” que não produz qualquer alteração na máquina. Embora possa parecer de pouca utilidade, podem surgir situações em que este comando possa ser empregue. O comando em causa é o comando skip (tal como no PROMELA).

Controlo de fluxo de execução

O AMN também tem expressões que controlam o fluxo de execução do código das máquinas. A expressão mais conhecida e que é comum a todas as linguagens imperativas é a expressão IF. Como sempre, a execução deste comando depende de uma expressão que é avaliada. Se resultar que a avaliação da expressão é verdadeira, segue-se um fluxo de execução. Caso contrário, outro fluxo será seguido.

IF E THEN S ELSE T END

E é a expressão a ser avaliada e S e T expressões AMN.

Embora existam outras formas de controlar o fluxo de execução numa operação AMN, a única apresentada é o IF porque esta foi a única utilizada no modelo construído para este estudo.

Escolha não determinista

O AMN dispõe de uma instrução que permite seleccionar determinados de uma forma não determinista. Fala-se da expressão ANY, cujo funcionamento se exemplifica.

ANY x WHERE x є A THEN … END

Nesta expressão é criada uma variável interna da operação (x) e a essa variável é atribuído um qualquer elemento do conjunto A. Entre as palavras THEN e END deverão estar instruções que manipulem a variável x. As atribuições podem ser mais complexas, tanto quanto a teoria de conjuntos permite.

Argumentos das máquinas e suas restrições

Já vimos que as operações definidas nas máquinas podem receber argumentos. O mesmo acontece com as próprias máquinas. Assim, torna-se possível a criação de máquinas genéricas e reutilizáveis no futuro. Já vimos também que os nomes das máquinas são definidos com a palavra MACHINE. É também aqui que são definidos os argumentos recebidos pela máquina. Estes argumentos

(26)

podem ser de dois tipos: escalares ou conjuntos. Os argumentos que representam conjuntos devem ter os nomes escritos em letras maiúsculas enquanto que os que representam valores escalares devem ter os nomes escritos em letras minúsculas.

MACHINE nome_da_maquina(CONJUNTO, escalar)

Por vezes pode ser necessário definir algumas restrições relativamente aos argumentos da máquina. O local indicado para colocar estas restrições é na etiqueta CONSTRAINTS.

CONSTRAINTS capacidade ∈N

capacidade

4096

Este exemplo é retirado de uma máquina que modela um clube que restringe o número dos seus membros a 4096 [SS01].

Definição de conjuntos, constantes e propriedades

Para além dos conjuntos já existentes e de outros conjuntos que possam ser passados como argumentos à máquina, dentro desta podem também ser criados novos conjuntos que possam ser considerados necessários ao funcionamento da máquina. Para este efeito existe a etiqueta SETS. Aqui serão declarados os conjuntos e definidos os seus elementos. Como exemplo de um tal conjunto pode considerar-se o conjunto das direcções magnéticas:

SETS DIRECCOES = { norte, sul, este, oeste }; CONJUNTO2

Neste exemplo, CONJUNTO2 simboliza a declaração de outro conjunto, ou seja, demonstra-se a declaração em simultâneo de dois conjuntos. Um inicializado e outro não.

Todas as constantes utilizadas na definição de uma máquina devem ser declaradas na etiqueta CONSTANTS. O tipo destas constantes e algumas restrições às mesmas devem ser indicadas na etiqueta PROPERTIES. Esta etiqueta serve também para definir restrições aos conjuntos definidos em SETS bem como aos parâmetros da máquina.

Composição de máquinas

Em sistemas complexos é importante poder estruturar os sistemas de forma a separar responsabilidades. Ou seja, responsabilizar diferentes partes do sistema por diferentes pedaços de informação ou por diferentes computações. Tal estruturação facilita não só a posterior análise do sistema mais, mais importante, facilita consideravelmente a própria construção do sistema uma vez que cada módulo (cada responsabilidade) é construído individualmente. Estes módulos serão depois integrados formando assim o sistema completo.

A forma de estruturação presente no método B é a composição de máquinas. Diversas máquinas são elaboradas sendo-lhes atribuídas diferentes responsabilidades. Depois, é construída uma máquina

(27)

que incorpora (de várias maneiras possíveis) as máquinas específicas e engloba todas as funcionalidades.

Uma das maneiras de por em prática esta estruturação é através da inclusão. Diz-se que uma máquina M2 inclui uma máquina M1 se na descrição de M2 se encontrar a linha de código

INCLUDES M1

Uma máquina pode incluir qualquer número de outras máquinas, incluindo máquinas que incluem outras máquinas. Os conjuntos e as constantes definidas em M1 são visíveis para M2 tal como se fossem definidas nas cláusulas SETS e CONSTANTS da própria M2. A cláusula PROPERTIES de M2 pode definir restrições aos conjuntos e constantes de M1, bem como definir relações destas com os conjuntos e constantes de M2.

O estado da máquina incluída (M1) passa a fazer parte da máquina M2. Os invariantes de M2 podem definir restrições sobre as variáveis de estado de M1 e relações destas com as de M2. O estado de M1 é directamente visível por M2, ou seja, as operações de M2 podem aceder às variáveis de estado de M1 para leitura. Os invariantes de M2 incluem os invariantes de M1. Uma vez que os invariantes de M2 têm em conta a máquina M1, as operações desta última estão disponíveis apenas para M2 e nenhuma outra máquina pode incluir M1. No entanto, para manter a consistência interna da máquina incluída, M2 não pode executar atribuições directas sobre as variáveis de M1, assegurando assim que se verificam os seus próprios invariantes. Assim, a única forma existente para M2 alterar o estado de M1 é executando as operações da última.

Quando M2 inicia a sua execução, primeiro são inicializadas todas as máquinas incluídas e só depois é executada a cláusula INITIALISATION de M2.

A máquina M1 é totalmente possuída por M2. As operações de M1 estão acessíveis a M2 para possibilitar a alteração do seu estado, mas não estão disponíveis no ambiente em que se encontra M2. Ou seja, fora de M2 não é possível aceder directamente às operações de M1. Desta forma, a única maneira de alterar o estado de M1 é através das operações de M2 que por sua vez irão invocar as operações de M1. no entanto, existe uma forma de promover as operações de M1 de forma que estas sejam vistas como se fossem operações da própria M2. Para tal, M2 deve conter uma cláusula na qual especifica que operações de M1 devem ser promovidas:

PROMOTES op1

Se se quiser que todas as operações de M1 sejam promovidas em M2, então M2 é considerada uma extensão de M1, sendo isto indicado pelo uso da cláusula EXTENDS em M2 em vez de PROMOTES.

EXTENDS M1

Existem ainda duas outras maneiras de ligar máquinas hierarquicamente de forma a construir um sistema de grandes dimensões. Uma delas consiste na utilização da cláusula SEES. Se a máquina M2 tiver nas suas cláusulas SEES M1 significa que M2 tem acesso de leitura ao estado de M1. Esta opção pode ser usada se várias máquinas necessitarem de informação contida por outra máquina. Uma vez que

(28)

uma máquina M1 só pode ser incluída por uma única máquina, a cláusula SEES permite que várias máquinas tenham acesso de leitura à mesma máquina.

Por outro lado, existe outra etiqueta cujo efeito é semelhante ao da etiqueta SEES. A etiqueta em questão é USES. Uma máquina que use outra tem acesso de leitura às variáveis desta, bem como pode estabelecer restrições a estas variáveis na sua cláusula de invariantes. Desta forma é possível simular o facto de uma máquina (o seu estado) depender de uma outra máquina que não é controlada pela primeira. Assim sendo, não é possível à primeira máquina garantir que o funcionamento da máquina usada não vai infringir os seus invariantes. Para tal, ambas as máquinas devem ser incluídas numa outra cuja função será, entre outras, garantir a manutenção dos invariantes de ambas as máquinas.

Execução de operações em paralelo

Quando uma máquina inclui várias outras máquinas, pode ser necessário alterar o estado de mais do que uma máquina como resultado de uma operação da máquina inclusiva. Isto pode ser feito através do comando paralelo op1 || op2 em que op1 e op2 são operações de duas máquinas incluídas diferentes. Neste caso, a condição para a execução da operação paralela será a conjunção das pré-condições das diferentes operações e o corpo da operação será a execução paralela dos corpos das várias operações.

PRE P1 THEN S1 END || PRE P2 THEN S2 END = PRE P1

P2 THEN S1 || S2 END

Exemplo AMN

No capítulo anterior, no qual foi apresentada uma metodologia orientada à comunicação e uma linguagem em conformidade com a metodologia foi apresentado um exemplo na linguagem PROMELA. Agora, para exemplificar o funcionamento da notação AMN que tem sido explicado neste capítulo, apresenta-se o mesmo exemplo mas na notação das máquinas. A especificação apresentada contém uma máquina que mantém a informação da pilha e que apresenta duas operações para manipular a pilha.

(29)

MACHINE stack (ELEM, cap)

CONSTRAINTS cap ∈ N VARIABLES contents

INVARIANT contents ∈ seq(ELEM)

size(contents)

cap

INITIALIZATION contents = []

OPERATIONS

push (ee) =

PRE ee ∈ ELEM

size(contents) < cap THEN contents := contents

ee END;

ee

pop =

PRE size(contents) > 0

THEN ee := last(contents) ||

contents = contents – last(contents) END;

END

O tipo da informação armazenada pela pilha é definido pelo conjunto ELEM que é fornecido à máquina aquando da sua criação. O invariante diz que o limite de elementos armazenados pela pilha não pode ser excedido. Cada uma das operações insere ou retira um elemento da pilha, garantindo sempre que não se insere um elemento numa pilha cheia nem se retira elementos de uma pilha vazia. Como foi indicado anteriormente, todas as expressões da especificação são expressões pertencentes à lógica de conjuntos.

2.4.2. Atelier B

Na secção 2.2 desta tese foi apresentada uma metodologia de verificação formal que foca principalmente aspectos estruturais do sistema. O Atelier-B é uma ferramenta que implementa precisamente este tipo de metodologia. A linguagem utilizada para especificar o modelo do sistema em análise é o AMN (Abstract Machine Notation) e foi apresentada na secção 2.4.1.

Ao contrário do que acontece no SPIN, as propriedades que se pretende verificar no sistema em análise não são especificadas à parte mas sim no próprio modelo do sistema sob a forma de invariantes. Tal como referido, a notação AMN é baseada na teoria dos conjuntos e lógica de predicados. Desta forma, as máquinas que simulam o modelo são traduzidas para axiomas matemáticos (à base de conjuntos). O mecanismo de prova usa estes axiomas para derivar e provar os teoremas, executando assim a verificação formal do sistema em análise.

Embora não seja necessário demasiado conhecimento sobre teoria dos conjuntos para escrever a especificação do sistema, podem ser necessários conhecimentos de dedução de teoremas no caso de o prover chegar a um impasse no processo de theorem proving. Neste caso torna-se necessária a

^

(30)

interacção humana para resolver o impasse e permitir que o algoritmo continue a executar-se normalmente.

2.5. Estado da Arte

Da crescente importância dada às metodologias de verificação formal surge também a necessidade de conhecer o universo de ferramentas/metodologias existentes e de estabelecer uma comparação entre elas. No entanto, não existem muitos estudos realizados neste sentido. Esta secção refere um estudo comparativo precisamente entre as duas metodologias aqui abordadas (o Model Checking e o Theorem Proving. [BD] No caso do primeiro, e linguagem utilizada é a mesma (o PROMELA) mas no segundo é usado o Z. O caso de estudo abordado é uma arquitectura para administração de chaves digitais.

Os principais aspectos considerados para a comparação foram o tamanho do modelo gerado, o tempo dispendido no processo e a experiência necessária por parte dos intervenientes.

No que diz respeito ao tamanho dos modelos, o modelo PROMELA tem uma extensão de 647 linhas enquanto que o modelo em Z tem 550 linhas.

No que toca ao tempo dispendido, o processo de Theorem Proving foi mais demorado do que o Model Checking. No entanto, salienta-se que enquanto o tempo dispendido no Theorem Proving foi passado a provar o modelo, o tempo gasto no Model Checking foi maioritariamente passado a abstrair o modelo para possibilitar que o Model Checker efectivamente terminasse a prova.

Relativamente à experiência necessária, é referido que um engenheiro sem conhecimentos aprofundados de ambas as tecnologias consegue construir um primeiro modelo sem dificuldade. No entanto, o modelo em Z necessitou de ser revisto e reestruturado por alguém mais experiente. Por outro lado, a escrita de expressões em LTL demonstrou ser mais complexa do que a sua equivalente em Z.

É de salientar que a inexistência de mais estudos nesta área faz com que não haja uma referência concreta, baseada na experiência, para clarificar os aspectos relevantes de ambas as tecnologias.

(31)

3. Caso de Estudo

Neste capítulo será apresentado sucintamente o caso de estudo que servirá de base a todo o trabalho. Este caso de estudo foi retirado da tese de doutoramento de Carlos Santos [SC07], embora tenha sido simplificado. Tal escolha deve-se ao facto de o modelo se encontrar demasiado pormenorizado na referida tese. Para este estudo, não é necessário um modelo tão detalhado. Desta forma, mantivemos os actores e os processos principais.

Figura 1 - Diagrama de actividades do serviço de urgência

O serviço de urgência atende doentes que, pelo seu estado de saúde urgente, necessitam ser vistos por um médico o mais depressa possível. No entanto, o hospital tem de manter um historial dos doentes que a ele recorrem quer para fins administrativos quer para manter actualizada a ficha de cada doente. Assim, com excepção de casos em que o estado de saúde do doente inspira cuidados imediatos, o primeiro passo a tomar no serviço de urgência é registar o doente. Este passo administrativo é expressamente necessário, nomeadamente para permitir o acompanhamento do doente por vários especialistas dentro do mesmo serviço de urgências. Este modelo considera que há dois tipos de doentes: os independentes e os dependentes. Este escalonamento é feito conforme o estado do doente. Se o doente chega ao serviço de urgências em condições de se registar a si mesmo, dirige-se ao assistente administrativo (ou para a fila de atendimento deste) e fornece ao mesmo os dados necessários ao processo. Caso o doente não esteja em condições de fazê-lo, trará consigo um acompanhante que faça o registo por ele. Deste registo resulta uma ficha denominada ficha de socorro urgente – FSU – que contém os dados do doente, principalmente os relativos ao episódio clínico que levou o doente à urgência.

Também, se o doente for dependente, o serviço de urgência dispõe de uma determinada quantidade de cadeiras de rodas ou macas rodadas para acomodar e transportar estes doentes. Este processo desenrola-se em paralelo com o registo por parte do acompanhante e o seu responsável é um auxiliar do serviço.

(32)

os processos burocráticos para depois. Considerou-se que não seria necessário integrar este tipo de doente no modelo pois iria complicá-lo excessivamente, tendo em conta o objectivo deste trabalho.

Após concluído o passo administrativo, resta ao doente esperar pela sua vez de ser atendido. Quando é chamado para ser atendido, o doente desloca-se ao local destinado ao atendimento/tratamento (ou é transportado pelo auxiliar). Aqui, médicos, enfermeiros, técnicos e auxiliares tratam o doente com todos os meios disponíveis. Este passo do percurso do doente pela urgência será abstraído, considerando-se apenas que é executado com sucesso por todos os doentes admitidos ao serviço.

Após todo este processo, o doente abandona o serviço de urgência.

Propriedade a verificar

O caso de estudo, tal como descrito nas linhas anteriores, é suficiente e bastante para ser verificado. Agora é necessário definir que propriedades se pretende verificar.

Sempre que uma pessoa entra num serviço de urgência de qualquer hospital, espera-se que a pessoa venha a sair. Esta saída pode dar-se de múltiplas formas (alta, óbito, fuga, internamento noutro serviço…). Para o nosso estudo é apenas necessário garantir que o doente abandona o serviço por motivos previstos, ou seja, excluindo a fuga. Desta forma e para simplificar o modelo, foi considerada apenas a saída com alta. O fundamental aqui é que não é suposto um doente perder-se num serviço de urgências, ainda menos ad eternum.

Considerando que o modelo se encontra bem definido, se todos os intervenientes no caso original desempenharem bem os seus papéis, todos os doentes percorrerão o percurso normal desde a chegada à urgência até à saída. A propriedade que se vai verificar neste modelo é precisamente essa: todo e qualquer doente que entra no serviço de urgência acaba por abandoná-lo.

(33)

4. Modelo PROMELA

Tal como referido anteriormente, a linguagem de modelação PROMELA tem por base processos e a comunicação entre esses processos. Como tal, o modelo da urgência do hospital será composto por vários processos que interajam entre sim. Desta maneira, optou-se por fazer com que cada um destes processos representasse um dos intervenientes no atendimento dos doentes na urgência (doente, assistente administrativo, auxiliar…)

Com base no modelo UML da urgência do hospital construído por Carlos Santos na sua tese de Doutoramento [SC07] e tal como referido no capítulo anterior, considerou-se que haveria três tipos de doente: doente independente, dependente e emergente.

• Doente independente: é aquele que se dirige à urgência do hospital sozinho por não necessitar de ajuda para fazê-lo. O seu estado de saúde permite que ele execute todos os passos administrativos (preenchimento da FSU – Ficha de Socorro Urgente) que levam à sua admissão no serviço de urgência.

• Doente dependente: é aquele que, pelo seu estado de saúde, necessita de um acompanhante (normalmente um familiar) que execute por ele os passos administrativos que levam à admissão ao serviço de urgência. Também devido ao seu estado de saúde, este doente necessita de uma cadeira de rodas (ou maca) para poder deslocar-se dentro do serviço de urgência.

• Doente emergente: é aquele que, sendo transportado para o hospital de ambulância (ou não), devido ao seu estado de saúde agravado, é admitido à urgência e é tratado de imediato, deixando os passos administrativos para mais tarde.

Uma vez que o objectivo deste trabalho é estabelecer uma comparação entre duas metodologias de verificação e não analisar o caso de estudo profundamente, optou-se por não considerar os doentes emergentes pela complexidade que este apresenta (como referido anteriormente). Assim, o modelo do serviço de urgências terá em conta apenas os doentes dependentes e independentes.

PROCESSOS

Em seguida enumeram-se e descrevem-se os vários processos que constituem o modelo.

Processo Doente

Este processo pretende representar o indivíduo doente que se dirige ao serviço de urgência. O processo doente tem um estado interno que corresponde à fase do atendimento em urgência na qual o doente se encontra. Esses estados representam o seguinte:

(34)

Estado Significado

0 Estado atribuído a qualquer doente que chegue à urgência cujo significado é “o doente chegou à urgência”.

1

Este estado pode ter duas interpretações conforme o tipo de doente a que se refere. Se considerarmos um doente independente, este estado significa que o doente está na fila para ser atendido pelo assistente administrativo a fim de este preencher a FSU. Por outro lado, se o doente for dependente, significa que o doente está numa fila para que lhe seja atribuída uma cadeira de rodas (ou maca) e que o seu acompanhante está na fila do funcionário administrativo para preencher a FSU.

2

Quando se encontra neste estado, o doente já foi atendido pelo assistente administrativo e já foi criada a FSU com os seus dados e que relata o episódio clínico que o levou a dirigir-se ao serviço. Mais, se o doente for dependente, significa que, para além da FSU já ter sido preenchida (não pelo doente mas sim pelo acompanhante), o doente já ocupa uma cadeira de rodas (ou maca) para poder deslocar-se dentro do serviço de urgências.

3 Neste estado, o doente já entrou para a urgência e está em espera para ser atendido pelo pessoal interno da urgência (médicos, enfermeiros…).

4 Neste estado, o doente já foi atendido. Este estado significa que o doente já percorreu o “percurso da urgência” e que já tem alta, estando pronto para sair.

Tabela 2 - Estados do processo doente Processo Administrativo

Este processo visa simular o assistente administrativo responsável por receber os dados do doente (directamente do mesmo ou através do seu acompanhante) e preencher a FSU. Dado que este modelo pretende analisar a comunicação entre os processos e não o tratamento de informação dentro da urgência, o preenchimento concreto da FSU foi deixado de parte. Não obstante, este processo assinala (veremos como mais à frente) o preenchimento da FSU, permitindo assim ao doente evoluir o seu estado interno.

Processo Acompanhante

O único objectivo deste processo é ir para a fila do assistente administrativo no lugar do doente que acompanha a fim de facultar ao assistente os dados necessários ao preenchimento da FSU.

Processo Auxiliar

Este processo representa o auxiliar que disponibiliza as cadeiras de rodas para os doentes dependentes, caso haja cadeiras disponíveis. Tal como acontece relativamente à FSU, o importante

Referências

Documentos relacionados

O primeiro item, “Mercado de Call Center: histórico, seus desafios, tendências para futuro” traz um recorte sobre o segmento de Call Center e suas principais

Figura A53 - Produção e consumo de resinas termoplásticas 2000 - 2009 Fonte: Perfil da Indústria de Transformação de Material Plástico - Edição de 2009.. A Figura A54 exibe

Com base nos resultados da pesquisa referente à questão sobre a internacionalização de processos de negócios habilitados pela TI com o apoio do BPM para a geração de ganhos para

“O aumento da eficiência e o plano de produção fizeram com que a disponibilidade das células de fabricação aumentasse, diminuindo o impacto de problemas quando do

Este trabalho traz uma contribuição conceitual sobre a utilização do sistema de gestão de produtividade que poderá motivar futuras pesquisas sobre o tema, bem

A fundamentação teórica proporciona a compreensão da mobilidade internacional no contexto da internacionalização, iniciativas de internacionalização implantadas nas

c.4) Não ocorrerá o cancelamento do contrato de seguro cujo prêmio tenha sido pago a vista, mediante financiamento obtido junto a instituições financeiras, no

Os autores relatam a primeira ocorrência de Lymnaea columella (Say, 1817) no Estado de Goiás, ressaltando a importância da espécie como hospedeiro intermediário de vários parasitos