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 Outubro de 2007
66
Embed
Estudo Comparativo de Métodos de Verificação numa Urgência ... · A verificação de Sistemas de Informação pode seguir vários paradigmas que diferem entre si em diversos aspectos,
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
I
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
Outubro de 2007
II
I
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.
II
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.
Keywords: formal verification, theorem proving, model checking, properties, model
III
Í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.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
IV
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 Tabela 4 - Estados do doente ..................................................................................................................... 36
1
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.
2
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
3
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
usados algoritmos que evitam a escrita do autómato (recorrendo a uma representação através de
4
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.
5
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
6
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
7
• 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
8
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.
9
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.
10
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
11
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:
• selecção condicional
12
• 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.
13
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(); }
14
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.
15
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 ( ϕFG )
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
16
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.
Variáveis
17
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
18
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
^
19
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
20
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
21
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
22
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 pré-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
Para poder executar esta ferramenta torna-se necessário instalar algum software adicional,
nomeadamente um interpretador de Tcl (Tool Command Language).
A ferramenta ProB dispões de um model checker mas não dispõe de um theorem prover. Este facto
obrigou à utilização de uma outra ferramenta para verificar o modelo. Essa ferramenta dá pelo nome de
B4free e encontra-se disponível para download em http://www.b4free.com/. A instalação deste programa
em WindowsXP obrigava à instalação de uma imagem de um sistema Linux, no qual o B4free seria
executado. Por considerar que seria mais fácil instalar uma distribuição de Linux do que configurar uma
imagem de Linux em Windows, instalou-se o Linux da distribuição Ubuntu.
A instalação da ferramenta em Linux é bem menos assustadora. Após concluídos todos os passos
da instalação, é necessário instalar uma interface gráfica: Click’n’Prove. No site do B4free encontra-se
um link através do qual se acede aos ficheiros de instalação da interface. Tal como no caso do B4free,
basta seguir os passos indicados nas instruções de instalação para que tudo funcione correctamente.
Para que a interface funcione é apenas necessário dispor do programa Xemacs instalado.
Procedimento Tal como o Xspin, o ProB recebe o modelo a verificar (desta feita na linguagem AMN). Durante a
simulação, estão disponíveis para o utilizador os valores de todas as variáveis da máquina, todas as
operações executáveis a cada instante e um historial das operações que foram executadas ao longo da
simulação. Sempre que a execução de uma operação violar o invariante, esse acontecimento é indicado.
43
Figura 5 - Janela de interacção do ProB com o modelo da urgência
A ferramenta ProB dispõe também de um model checker para verificar o modelo AMN. No entanto,
este processo obriga à criação de restrições nas variáveis do modelo para evitar o fenómeno de state
space explosion. O theorem proving não enfrenta este problema, visto que a metodologia utilizada
permite verificar sistemas com estados infinitos. Desta forma, a opção de model checking da ferramenta
foi ignorada.
Tal como no caso do modelo em PROMELA, muitas foram as simulações do modelo AMN até
atingir uma máquina que efectivamente apresentasse o comportamento desejado.
O passo seguinte no nosso procedimento consiste em executar a prova do modelo com a
ferramenta B4free e com a interface Click’n Prove. Ao executar a interface, são-nos apresentadas duas
janelas, uma com os comandos e outra que apresenta os resultados e os erros. Para poder proceder a
qualquer verificação é necessário criar um projecto que deverá ter o mesmo nome que a máquina a
verificar (fará mais lógica que seja a máquina a ter o mesmo nome que o projecto). Após criado o
projecto, o ficheiro que contém a máquina AMN deve ser colocado na pasta source do projecto. Depois
deste passo é necessário, na aplicação, adicionar uma máquina ao projecto e seleccionar o ficheiro que
colocámos na pasta.
44
Figura 6 - Interface gráfica Click'n Prove
O passo seguinte consiste em verificar (automaticamente) os tipos do modelo. Desta forma a
ferramenta verifica se há algum erro a este nível no modelo. Em seguida segue-se a criação das proof
obligations. As proof obligations são os teoremas que têm de ser verificadas para considerar o modelo
correcto. No caso concreto do modelo da urgência, são criadas 66 proof obligations das quais 60 são
consideradas óbvias.
Para que se perceba o conceito de proof obligation, apresenta-se um exemplo:
MACHINE Exemplo Proof Obligations:
INVARIANT I [A] I
INITIALIZATION A I ^ P => [S] I
OPERATION
Op =
PRE P
THEN S
END
END
45
As proof obligations significam que em primeiro lugar, a inicialização da máquina tem de respeitar o
invariante ([A] I). Em segundo lugar, na execução da operação op num estado em que o invariante se
verifica (I ^ P), a execução de S deve manter i invariante ([S] I). É segundo este esquema que são
geradas as proof obligations para uma máquina AMN.
Figura 7 – Relatório apresentado após a geração dos teoremas
A ferramenta dispõe de dois motores de prova com “forças” diferentes. O primeiro é o p0 e funciona
com um conjunto reduzido de hipóteses enquanto que o segundo, p1, tem em conta um conjunto mais
alargado de hipóteses [AJR03].
O primeiro passo no processo de prova dos teoremas consiste em correr o motor de prova mais
fraco, p0. Como resultado, dos 60 teoremas restam apenas 10 que não foram provados pelo motor. Em
seguida, deve executar-se o motor mais forte. No caso concreto deste modelo, p1 não consegui provar
nenhum dos 10 teoremas que p0 também não provou. Desta forma, a única maneira de provar a
correcção do modelo é através de uma prova interactiva. Algumas destas provas podem ser triviais, mas
outras podem ser bastante complexas.
Os 10 teoremas resultantes podem ser agrupados visto que a forma de resolução é idêntica.
Basicamente resume-se ao mesmo problema mas em secções distintas do modelo. Assim, será
apresentado apenas o primeiro caso de cada um destes grupos e a respectiva solução.
O primeiro teorema é o seguinte e é relativo à operação Chegar:
(T1)
O teorema diz que se adicionarmos à variável estado o valor { }araTransportdd → , a variável
estado pertence à relação ESTADOdoente →/ . O valor actual de doente é { }dddoente ∪ . Embora
o teorema pareça óbvio mediante uma análise do modelo AMN, o motor de prova não conseguiu prová-
lo. A resolução deste problema consiste em detectar no modelo uma condição ou propriedade que
confirme o teorema. Consultando o modelo (anexo A.2), consta do invariante uma expressão que
comprova exactamente que a variável estado é do tipo indicado no teorema. Assim, deve fazer-se uma
procura no modelo a expressões relacionadas com estado . Ao encontrar a expressão pretendida,
46
adiciona-se essa expressão à prova e executa-se p0. O resultado deste processo é a prova do teorema
assinalada pela palavra SUCCESS.
Figura 8 - Prova do teorema T1
Existe mais um teorema semelhante a este para provar. Outros dois teoremas há cujo método de
resolução é igual. A única diferença é o facto de não ser referir à variável estado mas sim à variável
tipo . Apresenta-se em seguida o teorema cuja interpretação e solução podem ser derivadas de T1.
(T2)
O segundo tipo de teoremas está relacionado com a manipulação do tempo e é o seguinte:
(T3)
A prova deste modelo não exige qualquer exercício de dedução, sendo apenas necessário
executar o motor de prove a p0. O resultado apresentado é o seguinte:
Figura 9 - Prova do teorema T3
O terceiro tipo de prova, que consiste num único teorema, relaciona-se directamente com o
invariante. Apresenta-se o teorema em T4.
(T4)
Tal como no caso anterior, a prova deste teorema faz-se apenas recorrendo ao motor de prova p0.
Figura 10 - Prova do teorema T4
47
Após estas dez provas interactivas, o modelo está provado e a ferramenta apresenta uma
mensagem que é significativa do alívio do utilizador ao provar o modelo na totalidade: BINGO!
Figura 11 - BINGO, mensagem de sucesso da verificação do modelo
48
8. Conclusões Depois de aplicar as duas metodologias apresentadas no capítulo 2 ao modelo do serviço de
urgência (capítulo 3), o próximo passo consiste em estabelecer uma comparação entre ambas. Diversos
factores podem ser comparados no processo de verificação formal de um sistema, desde a construção
do modelo à verificação final, passando pela simulação do modelo e correcção dos erros encontrados.
Modelação Neste estudo em particular, foram utilizadas duas ferramentas que seguem diferentes paradigmas.
Como tal, os modelos construídos para cada uma delas foram especificados em duas linguagens de
modelação diferentes, sendo que uma aborda aspectos particulares que a outra descura e vice-versa.
Não conhecia nenhuma das linguagens, no entanto não tive dificuldade em adaptar-me a elas visto que
em ambos os casos, já tinha lidado com alguma tecnologia semelhante. Relativamente ao PROMELA,
trata-se de uma linguagem bastante semelhante ao C. Embora o C não use o conceito de processo mas
sim de função, estes dois conceitos são bastante semelhantes. A existência dos canais de comunicação
foi uma novidade à qual facilmente me adaptei. Por outro lado, a notação AMN tem algumas
semelhanças com a programação orientada a objectos. Uma máquina AMN assemelha-se a um objecto
que tem estado e tem operações que podem alterar o estado e pode também herdar de outras máquinas.
Por outro lado, não há nenhum mecanismo que permita a criação dinâmica de máquinas e o número de
instâncias existentes é definido à partida.
Se o conceito AMN é facilmente compreensível, já a forma usada para codificar o modelo pode não
ser muito intuitiva. Todo o modelo é escrito numa mistura entre código tipo C (if then else, e outras
instruções semelhantes) e teoria de conjuntos. A princípio, é um pouco confuso idealizar um sistema
especificado em teoria de conjuntos. Num modelo como o utilizado neste estudo, o processo não é
demasiado complexo. No entanto, há que ter em conta que em sistemas cuja complexidade seja superior
(em outro tipo de sistemas, poderá ser mesmo bastante superior) pode ser necessário recorrer a ajuda
especializada. Isto porque, embora a teoria dos conjuntos seja suficientemente forte para representar um
sistema complexo, a forma de fazê-lo pode tornar-se quase tão complexa como o próprio sistema. Assim,
será necessária alguma formação inicial neste campo para que um engenheiro possa modelar um
sistema com alguma complexidade em AMN.
Ainda relativamente à modelação, no model checking é necessário não só simplificar o modelo mas
mesmo limitar o âmbito das variáveis para que a verificação possa ser realizada. Por exemplo, não
permitir que um inteiro assuma valores acima de um limite, normalmente baixo. O mesmo não acontece
no theorem proving que consegue provar máquinas com estados infinitos.
49
Especificação de propriedades a verificar Se a modelação do sistema é relativamente fácil, já a especificação das propriedades a verificar
pode não ser tão simples, uma vez que depende da complexidade do modelo e do que se pretende
verificar. No caso do model checking (tal como foi indicado no capítulo correspondente) as propriedades
a verificar são escritas numa lógica temporal (Linear Temporal Logic). O modelo utilizado neste estudo é
bastante simples. Assim, não foi necessário recorrer à LTL para verificar a sua correcção. Bastou a
inserção de uma instrução assert no próprio modelo. No entanto, ao contrário deste exemplo meramente
académico, a verificação formal é utilizada em sistemas reais e muito mais complexos, nomeadamente
os sistemas considerados críticos. Neste tipo de sistemas, as propriedades a verificar têm tendência a
ser bastante complexas. Se descrever algumas propriedades em LTL (como as exemplificadas no
capítulo em que esta lógica é apresentada) pode ser simples, a crescente complexidade do sistema em
análise implica um igualmente crescente complexidade das propriedades a verificar.
Simulação O passo a seguir à especificação do modelo é a sua simulação. Embora o objectivo seja verificar
formalmente a correcção do modelo, uma simulação ajuda a compreender o seu funcionamento e a
detectar alguns erros. Assim, o SPIN dispõe de um simulador de modelos e no caso do AMN recorremos
a uma ferramenta concebida para o efeito.
O SPIN é uma ferramenta destinada a fazer model checking de modelos de sistemas concorrentes.
Desta forma, a simulação do modelo executa instruções por uma ordem aleatória. Ou seja, se num
determinado momento se encontram em execução 3 processos (por exemplo), qualquer instrução desses
3 processos pode ser executada. Este facto dificulta o acompanhamento da simulação por parte do
utilizador, pelo menos num período inicial. No entanto é este mecanismo que permite simular a
concorrência no modelo. Por outro lado, a informação apresentada durante a simulação facilita e ajuda a
compreensão da linha de execução seguida na simulação. Essa informação consiste nos valores das
variáveis do modelo bem como um gráfico das mensagens trocadas entre os processos. Em resumo, a
simulação é fácil de fazer e de compreender.
A ferramenta utilizada para simular o modelo AMN, o ProB, apresenta também os valores das
variáveis do modelo. Desta feita, não são executadas instruções mas sim operações (que podem ser
compostas por várias instruções). Assim, a ferramenta apresenta, a cada momento, uma lista com as
operações que podem ser executadas (conforme as pré-condições definidas em cada uma), tal como um
historial de todas as operações já realizadas.
Ambas as ferramentas de simulação são fáceis de usar e de compreender. A única vantagem que
vejo no ProB relativamente ao SPIN é o facto de poder escolher que operação executar.
50
Verificação dos modelos Finalmente, a última fase do processo é precisamente a prova dos modelos. No que toca ao model
checking, a verificação é extremamente simples. Basta correr o motor de verificação e esperar que o
modelo seja provado com sucesso ou que seja detectado algum erro. Se for esse o caso, é apresentado
um guião de simulação para exemplificar o erro. O único problema deste método é a memória
necessária. Como foi indicado no capítulo 6, verificar o modelo usando uma procura exaustiva não foi
possível com um computador com 300 MB de RAM disponíveis para a prova. Tendo em conta que este
modelo é bastante simples e reflecte também um sistema simples, este facto pode ser um problema para
a utilização deste método em situações de projectos reais. Embora o modelador abstraia do modelo tudo
o que não for relevante, ainda assim o modelo pode ser demasiado complexo para que seja possível
realizar uma busca exaustiva. No entanto, as alternativas apresentadas pela ferramenta obtêm um
resultado igualmente satisfatório sem realizar a busca completa. Foi assim que se realizou a prova neste
estudo. [HG98] refere que a técnica de bitstate hashing apresenta uma alta cobertura em verificações
usando memória que pode ser algumas ordens de magnitude inferior à requerida para realizar uma
verificação exaustiva.
O método de theorem proving é mais complexo do que o model checking. A ferramenta analisa o
modelo, define os teoremas que têm de ser provados para verificar o modelo e prova alguns
automaticamente. Em primeiro lugar, o elevado número de teoremas gerado neste caso simples da
urgência faz pensar que num sistema complexo, serão gerados muitos mais teoremas. No entanto, o
problema consiste nos teoremas que o motor de prova não consegue provar. Estes podem até parecer
triviais para o utilizador, mas se a ferramenta não consegue prová-los, é necessário intervir
manualmente. Para levar a cabo a prova interactiva de teoremas é necessário ter não só uma grande
experiência de teoria de conjuntos mas também um enorme experiência na ferramenta utilizada (neste
caso o Click’n Prove [AJR03]). Na minha opinião, aprender a manejar a ferramenta não é uma tarefa
muito árdua. Já fazer as provas necessárias é bastante mais complicado. Em todas as provas feitas
neste estudo num estado inicial do modelo necessitei de ajuda experiente. Relativamente ao estado final,
provar os teoremas não provados pela ferramenta foi relativamente fácil, tendo em conta a simplicidade
dos teoremas e a experiência que eu já detinha.
Tempo dispendido Um factor que pode ser importante na escolha de uma metodologia de verificação formal é o tempo
dispendido com cada uma. Tal como referido em [BD] (trata-se igualmente de uma comparação entre
theorem proving e model checking) o processo de theorem proving é mais moroso do que o model
checking. No entanto, pode ser útil analisar onde cada uma das metodologias “perde” mais tempo.
51
Enquanto que no model checking, a maior parte do tempo é passada a simplificar o modelo para facilitar
(ou mesmo possibilitar) a verificação, o maior percentagem de tempo empregue no theorem proving é
relativa à prova dos teoremas.
Experiência necessária Como referi nos parágrafos anteriores, qualquer dos dois métodos requer a intervenção de pessoas
experientes no campo, embora em partes diferentes do processo. No entanto, é necessário que esta
pessoa experiente tenha algum conhecimento do modelo para poder pôr em prática os seus
conhecimentos. A experiência no campo da matemática de nada serve para especificar propriedades em
LTL se a pessoa não conhecer o modelo, da mesma forma que se a pessoa não conhecer o modelo
AMN, dificilmente conseguirá provar os teoremas do theorem proving.
Comparação com estudos anteriores
A experiência que levei a cabo ao longo deste trabalho foi de encontro às conclusões atingidas em
[BD]. Embora o modelo final em AMN tenha sindo facilmente provado pelo método de theorem proving,
isto foi resultado de uma simplificação considerável do modelo que antes era mais complexo e mais difícil
de provar. O domínio quer da ferramenta quer da linguagem de modelação e de teorias matemáticas é
fundamental para o theorem proving.
Considerações finais De todos estes aspectos, os que me parecem ser mais importantes na escolha de uma
metodologia para verificar um modelo são a simplicidade do processo de verificação, o tipo de sistema a
verificar e a sua dimensão. No que toca à simplicidade, parece-me óbvio que será preferível trabalhar
com a ferramenta/metodologia que for menos complexa. Neste caso, o model checking. No entanto, é
necessário ter em conta o tipo de sistema em causa. Se considerarmos um semelhante ao usado neste
estudo em que, embora a manipulação de informação seja importante mas foi propositadamente
descurada, o model checking, para além de simples, adequa-se e oferece resultados satisfatórios. Se,
pelo contrário, a informação for mais importante, o model checking pode não ser suficientemente
poderoso para verificar o modelo. Assim, neste tipo de situações, se possível, dever-se-á sacrificar a
simplicidade de uma ferramenta pela competência da outra no campo em estudo.
Frente à necessidade de verificar formalmente um modelo e de escolher uma metodologia para
esse efeito, eu faria uma opção conforme descrevi no último parágrafo, tendo em conta os vários
aspectos que considero importantes na escolha e recorrendo até aos que considero menos importantes.
52
Deste estudo concluo que a metodologia de model checking é mais fácil e mais acessível do que o
theorem proving.
Devo referir que julgo ser importante a realização de estudos neste ramo dos métodos formais,
visto que estes ocupam (ou deviam ocupar) um papel importante no desenvolvimento de software. Este
tipo de metodologias permite descobrir e corrigir no início do processo de desenvolvimento erros que de
outra maneira seriam descobertos muito mais tarde (na fase de experimentação do cliente, por exemplo),
diminuindo assim o possível custo do projecto. Embora a verificação formal não possa garantir que o
software é perfeito, é uma potente ferramenta para melhorá-lo.
53
9. Bibliografia [AJR03] Abrial, J.-R., Cansell, D. : Clinck’n Prove Interactive Proofs Within Set Theory [BD] Basin, David et al, Verifying a Signature Architecture – A Comparative Case Study, under
consideration for publication in Formal Aspects of Computing [BF01] Butler, M., Falampin, J. : An Approach to Modeling and Refining Timing Properties in B, 2001 [CEWJ] Clarke, Edmund M., Wing, Jeannette M.: Formal Methods: State of the Art and Future
Directions, CMU Computer Science Technical Report CMU-CS-96-178, August 1996. Published in: ACM Computing Surveys (http://vl.fmnet.info/pubs/)
[FC06] Ferreira, Carla.: Acetatos da cadeira de Desenvolvimento Formal de Software (IST – Tagus
Park, 2006) [GM06] Guimarães, Mário Luís: The Multimedia Library: formal software using B (2006) [HG91] Holzmann, Gerard J.: Design and validation of computer protocols, Prentice Hall, New Jersey
(1991) [HG97] Holzmann, Gerald J.: The Model Checker SPIN, IEEE Transactions on Software Engineering,
Vol. 23, No. 5 (1997) [HG98] Holzmann, Gerard J.: An Analysis of Bitstate Hashing (USA 1998) [PG04] Palshikar, Girish Keshav: An introduction do model checking, Embedded System Design,
2004 (http://www.embedded.com) [SC07] Santos, Carlos Alberto Lourenço dos : Modelo Conceptual para Auditoria Organizacional
Contínua com Análise em Tempo Real, Tese de Doutoramento, Instituto Superior Técnico, 2007
[SCJ] Seger, Carl-Johan: An Introduction to Formal Hardware Verification, Department of Computer
Science, University of British Columbia, Vancouver [SG] Sutcliffe, Geoff: Automated Theorem Proving, An Overview of Automated Theorem Proving,
Department of Computer Science, University of Miami (http://www.cs.miami.edu/~tptp/OverviewOfATP.html)
[SP1] SPIN, General Tool Description (http://www.spinroot.com) [SS01] Schneider, Steve: The b-method, Palgrave Macmillan, New York (2001) [WP1] Wikipedia, Model Checking (http://en.wikipedia.org/wiki/Model_checking) [WP2] Wikipedia, Automated Theorem Proving (http://en.wikipedia.org/wiki/Theorem_proving) [WP3] Wikipedia, Linear time logic (http://en.wikipedia.org/wiki/Linear_temporal_logic)
1
ANEXOS
1
A1. Modelo PROMELA #define independente 21 #define dependente 22 #define limiteDoentes 5 #define limiteAdmin 2 int tipo[limiteDoentes]; int administrativo[limiteDoentes]; int estado[limiteDoentes]; int cadeiras = 2; chan fila_admin = [5] of { int, int } chan fila_aux = [5] of { int, int } chan fila_fsu = [5] of { int, int } chan fila_atende = [5] of { int, int } chan fila_transporte = [5] of { int, int } chan fila_alta = [5] of { int, int } proctype doente(int id) { int nid = id; estado[id] = 0; do ::tipo[id] = independente; break ::tipo[id] = dependente; break od; do ::estado[id] == 0 -> if ::tipo[id] == independente -> fila_admin!id, nid; ::tipo[id] == dependente -> fila_aux!id, nid; run acompanhante(id); fi; estado[id] = 1; ::estado[id] == 1 -> fila_fsu?id, eval(nid); if ::tipo[id] == dependente ->
proctype admin(int id) { int id_doente; int nid; end: do ::fila_admin?id_doente, nid -> administrativo[id_doente] = id;
fila_fsu!id_doente, nid; od } proctype acompanhante(int id) { int nid = id; fila_admin!id, nid; } proctype auxiliar() { int id_doente; int nid; end: do ::fila_aux?id_doente, nid -> xxx: if ::cadeiras > 0 -> cadeiras--; fila_transporte!id_doente, nid ::cadeiras == 0 -> goto xxx; fi; od } proctype atende() { int id_doente; int nid; end: do ::fila_atende?id_doente, nid -> if ::tipo[id_doente] == dependente -> cadeiras++; ::else -> skip fi; fila_alta!id_doente, nid; od }
3
init { int i; i = 0; do ::(i<limiteAdmin) -> run admin(i+1); i = i +1; ::(i >= limiteAdmin) -> break; od; run auxiliar(); i = 0; do ::(i < limiteDoentes) -> run doente(i); i = i +1 ::(i >= limiteDoentes) -> break; od; run atende() }
(t - tempo_espera(dd)) <= MAX_TEMPO) SETS PESSOA; ESTADO = {entrada, transporte, cuidados, alta}; TIPO = {independente, dependente}; CADEIRAS VARIABLES doente, estado, tipo, tempo_espera, tempo, cadeiras_ocupadas INVARIANT doente <: PESSOA & estado : doente --> ESTADO & tipo : doente --> TIPO & tempo_espera : doente --> NAT & tempo : NAT & cadeiras_ocupadas <: CADEIRAS & /* Todos os doentes esperam menos de max_tempo em cada etapa */ !d.(d:doente => tempo - tempo_espera(d) <= MAX_TEMPO) & !d.(d:doente => tempo_espera(d) <= tempo) INITIALISATION doente := {} || estado := {} || tipo := {} || tempo_espera := {} || tempo := 0 || cadeiras_ocupadas := {} OPERATIONS Chegar(dd, tt) = PRE dd:PESSOA & tt:TIPO & dd /: doente THEN doente:=doente \/ {dd} || tempo_espera(dd) := tempo || IF tt = dependente THEN tipo(dd) := dependente ||
5
estado(dd) := transporte ELSE tipo(dd) := independente || estado(dd) := entrada END END; /*Apenas os doentes dependentes são transportados*/ Transportar = ANY dd WHERE dd:doente & min(ran(tempo_espera)) = tempo_espera(dd) & tempo_espera(dd) < tempo & estado(dd) = transporte THEN ANY novaCadeira WHERE novaCadeira : CADEIRAS - cadeiras_ocupadas THEN cadeiras_ocupadas := cadeiras_ocupadas \/ {novaCadeira} END || estado(dd) := entrada || tempo_espera(dd) := tempo END; Registar = ANY dd WHERE dd:doente & min(ran(tempo_espera)) = tempo_espera(dd) & tempo_espera(dd) < tempo & estado(dd) = entrada THEN estado(dd) := cuidados || tempo_espera(dd) := tempo END; CuidadosSaude = ANY dd WHERE dd:doente & min(ran(tempo_espera)) = tempo_espera(dd) & tempo_espera(dd) < tempo & estado(dd) = cuidados THEN CHOICE estado(dd) := cuidados || tempo_espera(dd) := tempo OR estado(dd) := alta || tempo_espera(dd) := tempo END END; DoenteSaiHospital = ANY dd WHERE dd:doente & min(ran(tempo_espera)) = tempo_espera(dd) & tempo_espera(dd) < tempo & estado(dd) = alta
6
THEN IF tipo(dd) = dependente THEN ANY novaCadeira WHERE novaCadeira : cadeiras_ocupadas THEN cadeiras_ocupadas := cadeiras_ocupadas - {novaCadeira} END END || doente := doente - {dd} || estado := {dd} <<| estado || tipo := {dd} <<| tipo || tempo_espera := {dd} <<| tempo_espera END; IncrementaTempo = ANY tt WHERE tt : NAT & tt = tempo + 1 & espera_aceitavel(tt) THEN tempo := tempo + 1 END END