View
214
Download
0
Category
Preview:
Citation preview
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.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
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
pilha.
23
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
^
^
24
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.
25
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.
Do modelo inicial [SC07] consta mais um tipo de doente, o doente emergente. Este tipo refere-se
aos doentes que, devido ao seu estado agravado de saúde devem ser atendidos de imediato, deixando
26
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.
27
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:
28
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
29
deste modelo não são as cadeiras mas a comunicação entre os vários intervenientes. Assim, existe
apenas um valor inteiro que indica a quantidade de cadeiras disponíveis.
Processo Atendimento O processo de atendimento simula o atendimento do doente por pessoal interno (médicos,
enfermeiros…). O atendimento é feito respeitando a ordem pela qual os doentes são colocados na fila
para atendimento. Todos os procedimentos relativos ao atendimento são abstraídos deste modelo.
Assim, este processo retira os doentes da fila para atendimento, liberta os meios que possam estar
ocupados (cadeiras) e dá alta ao doente.
CANAIS
Como foi descrito no capítulo de introdução à linguagem PROMELA, a forma utilizada para
estabelecer comunicação entre os vários processos do modelo é a utilização de canais de comunicação.
Estes canais funcionam como filas que neste caso irão simular as filas que se formam para o
atendimento no serviço de urgência. De seguida serão enumerados os canais usados no modelo e serão
explicadas as funções de cada um.
Canal Função
fila_admin
Este canal corresponde precisamente à fila que se forma junto do assistente
administrativo com a finalidade de preencher a FSU. No modelo, quem pode escrever
neste canal é o próprio doente (no caso deste ser independente) ou o acompanhante
do doente dependente. A leitura é feita pelo assistente administrativo pela ordem pela
qual as escritas foram feitas (política FIFO – First In, First Out).
fila_aux
Este canal, por sua vez, simboliza a fila na qual o doente dependente é colocado em
espera pela cadeira de rodas. A escrita é efectuada pelo doente e a leitura é feita pelo
auxiliar.
fila_fsu
Neste canal, o assistente administrativo assinala o facto de que a FSU relativa a
determinado doente já foi preenchida, permitindo que este seja posteriormente atendido
na urgência. O responsável pela escrita no canal é, portanto, o assistente
administrativo e o leitor será o próprio doente.
fila_atende
Após o preenchimento da FSU e o doente ter uma cadeira (caso seja do tipo
dependente) este é colocado na fila para atendimento/tratamento. É o próprio doente
que se insere na fila e a leitura é efectuada pelo pessoal responsável pelo tratamento
(neste caso, é o processo atende o responsável).
30
Canal Função
fila_transporte
Este canal é onde o auxiliar assinala que o doente dependente já dispõe da cadeira de
rodas para poder deslocar-se na urgência. É lido pelo doente, possibilitando assim a
progressão para o estado em que será levado a cabo o atendimento/tratamento.
fila_alta
Este canal é onde o pessoal do atendimento/tratamento sinaliza que o doente já foi
tratado e está pronto para abandonar o serviço de urgência. Ao ler deste canal, o
doente em seguida sai da urgência.
Tabela 3 - Funções dos canais de comunicação
Na execução do modelo, são criados tantos doentes quantos os indicados numa constante do
modelo: limiteDoentes. Para diferenciá-los, é atribuído a cada doente um identificador (um inteiro
estritamente crescente iniciado em zero). São estes identificadores que identificam os doentes nas
comunicações entre os processos. Assim, o doente com o id = 1 insere este identificador num canal e
o leitor desse canal sabe a que doente se refere.
Para evoluir o seu estado interno, o doente tem várias vezes de efectuar leituras em certos canais.
Por exemplo, apenas quanto a sua FSU tiver sido preenchida, ele pode ser tratado. No entanto, ao fazer
uma leitura do canal fila_fsu, nada garante que a mensagem lida seja a sua (o doente pode ler uma
mensagem com um identificador diferente do seu). Desta forma, não há coordenação no atendimento e
este pormenor pode levar a erros na simulação.
Para resolver este problema, optou-se por criar canais cujos valores fossem compostos por dois
inteiros. Aquando da escrita nos canais, ao invés de inserir apenas o identificador do doente, este
identificador é também repetido. Ou seja, os dois inteiros que compõem cada valor do canal são
idênticos. Quando o doente faz a leitura dos canais, lê o primeiro inteiro para uma variável, e coloca no
segundo uma constante que é o seu próprio identificador. Pela definição dos canais, se na leitura se
colocar uma constate em vez de uma variável, a leitura só será realmente efectuada se o valor dessa
constante for igual ao valor contido no canal. Desta forma garante-se que cada doente apenas lê
mensagens que lhe sejam destinadas.
Aquando da construção do modelo, convencionou-se que os canais teriam uma capacidade de
armazenamento de cinco valores.
CONTANTES DO MODELO
Existem quatro constantes neste modelo. Duas delas (dependente e independente) visam
apenas fornecer um meio de diferenciar os tipos de doentes. As constantes limiteDoentes e
limiteAdmin servem para indicar respectivamente quantos doentes e quantos assistentes
administrativos devem ser criados na execução do modelo.
31
VECTORES No modelo, há determinada informação que é estritamente necessária ao correcto funcionamento
do mesmo. Nomeadamente informação relativa ao estado interno do doente. Desta forma, foram criados
três vectores nos quais se armazena essa informação.
O vector tipo tem dimensão igual a limiteDoentes. Neste vector é armazenado o tipo de cada
doente (dependente ou independente).
O vector administrativo tem a mesma dimensão que o anterior. O seu objectivo é guardar a
informação relativa a que assistente administrativo atendeu que doente. Depois de construído o modelo,
é usado apenas para facilitar a leitura da execução.
Finalmente o canal estado, que tem a mesma dimensão que os canais anteriores, onde é
guardado o estado interno dos doentes ao longo da sua execução. Todas as posições deste vector são
inicializadas a zero e, supondo o correcto funcionamento do modelo, terão o valor quatro no final da
execução.
EXECUÇÂO DO MODELO
A linguagem PROMELA exige a existência de um processo especial (o processo init) que
corresponde à execução do modelo. Desta forma, é neste processo que são criados todos os outros
processos que constituem o modelo. São criados limiteAdmin processos Administrativo, um
Auxiliar, limiteDoentes Doentes e um processo Atende.
OBJECTIVO DA VERIFICAÇÃO O objectivo desta verificação é comprovar que, uma vez que o doente entre no serviço de urgência,
obrigatoriamente sai dele da forma correcta. Neste modelo, a única forma de podermos verificar isto é
comprovar que todos os doentes atingem o estado 4. Desta forma, um simples assert no processo
doente verifica isto. assert(estado[id] == 4)
32
5. Modelo AMN
No capítulo anterior foi apresentado o modelo da urgência na linguagem PROMELA construído a
partir do modelo UML da tese de doutoramento do Eng. Carlos Santos [SC07]. O objectivo de tal modelo
é servir de base para a verificação por um model checker. Neste capítulo será apresentado um modelo
em AMN com base no mesmo modelo UML. Este por sua vez será a base para uma verificação segundo
a metodologia de theorem proving.
Tal como foi indicado no capítulo de apresentação desta linguagem, esta é baseada em máquinas
abstractas e a definição destas é feita recorrendo à teoria dos conjuntos, dando especial ênfase à
informação manipulada.
Desta forma, neste capítulo será apresentada a máquina que foi criada para este efeito e serão
explicadas as opções que foram tomadas durante esse processo.
Uma vez que a máquina abstracta é a “unidade” desta linguagem e cada máquina tem uma série
de propriedades internas e operações, optou-se por criar uma máquina que seria a própria urgência e
cujas operações reflectiriam as várias acções que o doente pode realizar dentro do serviço de urgência
(como chegar, ser transportado, ser atendido…).
CONJUNTOS Em primeiro lugar, apresentam-se os conjuntos que fazem parte desta máquina. Alguns destes
conjuntos são definidos juntamente com os seus elementos, enquanto que outros são definidos sem os
elementos que os constituem. Aqueles cujos elementos são definidos, são-no porque é importante para o
modelo saber quantos e quais são esses elementos, como acontece com o conjunto ESTADO. Pelo
contrário, nos outros conjuntos não é importante saber quais são os elementos constituintes do conjunto.
Existe apenas dois conjuntos cujos elementos não são definidos: CADEIRAS e PESSOA. O serviço
de urgência atende doentes. Obviamente, doentes devem ser pessoas. O conjunto PESSOAS serve
apenas para garantir este formalismo e não deve ter qualquer tipo de limite. Neste modelo, as cadeiras
são representadas como um conjunto (CADEIRAS) sem no entanto limitar o número de cadeiras
existentes.
Os conjuntos cujos elementos têm de ser definidos para o bom funcionamento da máquina são os
conjuntos ESTADO e TIPO_DOENTE. O conjunto ESTADO é composto pelos elementos entrada,
transporte, cuidados e alta. Mais à frente serão explicados os significados destes estados. Os
elementos que compõem o conjunto TIPO_DOENTE são dependente e independente. Tal como no
modelo em PROMELA, deixamos de parte os doentes emergentes.
Estes conjuntos são definidos na cláusula SETS do modelo AMN.
33
CONSTANTES O tempo máximo que cada doente pode esperar para ser atendido (i.e. o tempo que espera desde
que lhe foi aplicada uma operação até que lhe seja aplicada outra) está definido numa constante do
modelo que é inicialização da máquina, escolhida pelo utilizador. Na cláusula PROPERTIES do modelo é
definido o tipo desta constante.
VARIÁVEIS
Na cláusula VARIABLES são declaradas as variáveis necessárias ao modelo. A informação sobre o
tipo destas variáveis encontra-se na cláusula INVARIANT e a sua inicialização é levada a cabo na
cláusula INITIALISATION.
Iremos apresentar as diversas variáveis utilizadas juntamente com o seu tipo e inicialização, bem
como o motivo que levou à sua criação.
Variável doente
Esta variável é definida como sendo um subconjunto do conjunto PESSOA e é inicializada como um
conjunto vazio. Quando uma pessoa se dirige ao hospital não é considerada ainda doente. Só após
chegar à urgência é considerada um doente. Isto reflecte-se no facto de a operação responsável por
processar a chegada do doente (que recebe uma pessoa como argumento) adiciona a pessoa que
acabou de chegar à variável doente.
Variável estado
A variável estado representa uma relação total entre doente e ESTADO e é inicializada como um
conjunto vazio. Sempre que uma pessoa passa a ser um doente, passa também a ter um estado. Esta
variável armazena os estados de todos os doentes. A relação é necessária para saber a que doente se
refere cada estado.
Variável cadeiras_ocupadas
Esta variável é um subconjunto do conjunto CADEIRAS e é inicializada como um conjunto vazio. Tal
como o nome indica, todas as cadeiras que estejam a ser usadas estão neste subconjunto.
Variável tipo
Esta variável representa uma relação total entre doente e TIPO_DOENTE e é inicializada como um
conjunto vazio. Nesta variável são armazenados os tipos de todos os doentes que entram na urgência
sob a forma desta relação.
34
Variável tempo
Esta variável é um número natural inicializado a 0 (zero) que é constantemente incrementada pela
operação IncrementaTempo, simulando assim o passar do tempo.
Variável tempo_espera
A variável tempo_espera é uma relação entre doentes e tempos. O valor atribuído ao
tempo_espera de cada doente indica o tempo que o doente está à espera desde a última operação que
foi executada respeitante a ele.
DEFINIÇÕES Neste modelo, não pode passar mais do que um intervalo de tempo definido entre cada operação
aplicada a um doente. Este facto é garantido na operação IncrementaTempo, recorrendo a uma
definição criada na própria máquina: espera_aceitavel. Esta definição devolve verdadeiro se o tempo
efectivo que o doente está à espera para lhe ser aplicada a próxima operação for menor que
MAX_TEMPO. A definição encontra-se na cláusula DEFINITIONS da máquina.
OPERAÇÕES Em seguida serão apresentadas as operações que podem ser executadas neste máquina. É de
salientar que todas elas serão executadas pelo próprio doente.
Operação Chegar Esta operação é a primeira e única que a pessoa pode executar quando chega à urgência (pessoa
porque, como referido anteriormente, só passa a ser considerado doente depois de chegar). Recebe
como argumento uma pessoa (um elemento de PESSOA) e um tipo de doente (um elemento de
TIPO_DOENTE). Estas restrições dos argumentos constam das pré-condições da operação, bem como a
condição que a pessoa passada como argumento não pertencer ao sub-conjunto doente. Várias coisas
são feitas nesta operação. Em primeiro lugar, a pessoa é adicionada à variável doente e é atribuído ao
seu tempo de espera o valor do tempo actual. Em seguida, conforme o tipo de doente presente no
argumento, é definido o tipo de doente na variável que existe para esse efeito. Se o doente for
dependente, o seu estado passará a ser transporte. Caso contrário (o doente é independente) o seu
estado será entrada.
Operação Transportar
Esta operação não recebe qualquer argumento. A sua execução faz com que o doente dependente
que está à espera há mais tempo seja transportado (i.e. lhe seja atribuída uma cadeira de rodas). Após
isto o seu estado passa a entrada e o seu tempo de espera assume o valor actual do tempo.
35
Operação Registar
A operação Registar não recebe qualquer argumento e é executada sobre o doente cujo
estado seja entrada e que esteja à espera há mais tempo. Após a execução desta operação o estado
do doente assume o valor de cuidados. O tempo de espera do doente assume o valor do tempo actual.
Operação CuidadosSaude
Esta operação não recebe qualquer argumento. A sua execução representa a prestação de
cuidados médicos aos doentes. Aplica-se ao doente que esteja no estado cuidados e esteja há mais
tempo à espera para ser atendido. Como resultado, o doente pode permanecer no mesmo estado (se
necessitar de mais cuidados) ou passar ao estado alta. A cada execução desta operação, o tempo de
espera do doente assume o valor do tempo actual..
Operação DoenteSaiHospital
Esta operação não recebe qualquer argumento. A sua execução resulta em limpar os registos do
doente que esteja no estado alta e que esteja à espera há mais tempo. Para além disso, e caso o
doente seja dependente, é libertada a cadeira de rodas que este ocupava.
Evolução do Tempo
A evolução do tempo é simulada pela operação IncrementaTempo. Esta operação incrementa o
tempo se o tempo actual mais um consistir numa espera aceitável para todos os doentes. Isto é, o tempo
é incrementado se o incremento não violar o tempo máximo de espera a que cada doente está sujeito.
Prioritização do atendimento Cada uma das operações que podem ser executadas são aplicadas ao doente que está em
condições de executá-la (no que toca ao seu estado) e que se encontra há mais tempo à espera.
ESTADOS DO DOENTE
Como pode notar-se pelos elementos do conjunto ESTADO quer pela descrição das operações, o
doente pode ser caracterizado pelo estado em que se encontra. Melhor, este estado do doente não
caracteriza o doente em si, mas sim em que passo do atendimento da urgência ele se encontra.
Antes de chegar à urgência, a pessoa não é é considerada doente, pelo que não tem nenhum
estado associado. Quando entra no serviço é-lhe atribuído um tipo, e conforme esse tipo é-lhe atribuído
também um estado. Se o doente for independente, o próximo passo será registar-se no serviço de
urgência (preencher a FSU). O seu estado será entrada. Por outro lado, se for dependente, o seu
estado será transporte que indica que está à espera que lhe seja atribuída uma cadeira de rodas para
36
que possa deslocar-se no dentro da urgência. Neste caso, apenas após a atribuição da cadeira (na
operação Transportar) é que o doente passa ao estado entrada.
Quando se encontra no estado entrada, o doente efectua o registo na urgência e passa ao estado
cuidados. Embora esta acção possa ser na realidade levada a cabo pelo acompanhante, esse cenário
não é tido em conta para não complicar o modelo e porque tal facto não é importante para o objectivo da
verificação.
Estando registado, o doente é atendido e passa para o estado alta. O presente modelo não pretende
avaliar a forma como é feito esse atendimento, assumindo apenas que é feito e terminado.
Estado Significado
entrada O doente chegou ao hospital. Se for dependente, já dispõe de uma cadeira de rodas.
transporte Doente dependente espera que lhe seja atribuída uma cadeira de rodas ou maca da
qual necessita para se deslocar.
cuidados O doente já está registado e está em condições de receber o tratamento médico.
alta O doente já foi tratado e está em condições de abandonar a urgência.
<sem estado> O doente não está na urgência, quer porque ainda não se dirigiu ao serviço ou porque
já foi atendido e já o abandonou.
Tabela 4 - Estados do doente
OBJECTIVO DA VERIFICAÇÃO
No modelo PROMELA, pretendia-se verificar que o doente efectivamente saía da urgência. Neste
modelo queremos provar precisamente o mesmo. Para tal, foi definido um invariante que diz que um
doente tem de sair da urgência. A forma utilizada para representar esse facto é garantir que entre cada
operação, cada doente não espera mais do que um determinado tempo máximo (MAX_TEMPO).
O invariante que define este objectivo é o seguinte:
(E1)
37
6. Verificação do modelo por Model Checking
No capítulo 2 desta tese foram apresentadas as linguagens de modelação utilizadas em cada uma
das metodologias que serão abordadas neste estudo. Os capítulos 4 e 5 apresentam respectivamente os
modelos em PROMELA e AMN que foram construídos com base no modelo UML do serviço de urgência
constante da tese de doutoramento do Mestre Carlos Santos [SC07]. O presente capítulo e o próximo
visam apresentar a verificação destes modelos segundo os diferentes paradigmas. Neste capítulo será
utilizado o modelo descrito no capítulo 4 com o objectivo de fazer a sua prova formal utilizando um model
checker.
O model checker utilizado nesta fase do trabalho foi já apresentado no capítulo de apresentação
das metodologias, como parte do artigo escrito para a cadeira de Introdução à Investigação. Assim,
iremos agora explorar o tentar validar o modelo utilizando o SPIN.
Requisitos
A ferramenta utilizada para a verificação encontra-se disponível na Internet no endereço
http://spinroot.com/ e é compatível com diversas plataformas como sistemas Unix, Windows PC e
MacOS.
Para poder executar a aplicação é necessário instalar algum software adicional como seja um
compilador de C. Para facilitar a interacção com a ferramenta, encontra-se disponível no site referido
anteriormente uma interface gráfica cuja utilização requer a instalação de um interpretador de Tcl (Tool
Command Language).
A plataforma utilizada neste estudo foi WindowsXP. A instalação resume-se a descarregar os
ficheiros do site e colocá-los numa pasta. Para utilizar a interface gráfica basta descarregar o ficheiro
executável Tcl disponibilizado no mesmo site. Quanto ao compilador de C, foi instalada uma versão do
Cygwin. Esta aplicação encontra-se disponível em http://www.cygwin.com/ e é descrita como sendo “a
Linux-like environment for Windows” e contém um compilador de C bem como outras ferramentas
disponibilizadas por um sistema Linux.
Procedimento
Todo o processo de simulação e prova do modelo foi feito recorrendo à interface gráfica
disponibilizada pela ferramenta. Esta apresenta uma janela conforme a da figura 3.
38
Figura 2 - Janela de interacção do Xspin
O modelo que se pretende verificar deve ser introduzido nesta janela. O modelo pode ser
encontrado na sua totalidade no anexo A1. A ferramenta inclui uma funcionalidade que faz a correcção
sintáctica do modelo. Este pormenor é bastante importante, ainda mais quando o utilizador é uma pessoa
que não domina a linguagem de modelação utilizada. No entanto, o PROMELA é uma linguagem de fácil
compreensão.
Ao construir um modelo, frequentemente o modelador sente necessidade de executá-lo para
verificar se ele realmente funciona como pretendido. Na maioria das vezes os modelos são construídos
apenas em papel e a execução do modelo é impossível. No entanto, uma vez que esta ferramenta se
destina à verificação automático do modelo, dispõe também de um simulador. Este simulador oferece
uma gama de opções que vão desde a informação que se pretende visualizar durante a simulação até à
opção de executar uma simulação aleatória ou com base numa que seja mais interessante. Ao iniciar a
simulação, é-nos dada a opção de realizá-la passo a passo ou toda de seguida, podendo ser
interrompida quando necessário. As opções predefinidas mostram o estado das variáveis do modelo e a
sua evolução, bem como um diagrama das mensagens enviadas entre os processos. Esta foi
nomeadamente uma das formas que usei para testar se o modelo estava a executar exactamente como
era pretendido.
39
Figura 3 - Simulação de um modelo no Xspin
Se a simulação não indicar nenhum erro no modelo (ou depois de corrigidos todos os indicados)
passamos então à verificação propriamente dita. É nesta fase que, embora transparentemente para o
utilizador, o SPIN cria os autómatos finitos que foram referidos na secção 2.1. Também o verificador
apresenta uma série de opções que vão desde o que se pretende verificar até técnicas de compressão
para facilitar o processo. É também nesta fase que são introduzidas (se for o caso) as propriedades em
lógica temporal contra as quais será testada a correcção do modelo.
A verificação que levámos a cabo tinham como objectivo comprovar propriedades de correcção do
modelo. Ou seja, pretendiam verificar se o modelo não chegava a estados inválidos bem como se as
asserções presente no código não eram violadas. Mais, a verificação deveria indicar se existia alguma
porção de código inatingível no modelo.
Pela forma como o model checker funciona, a memória do computador utilizado deve ser um factor
fundamental a ter em conta. No caso particular deste estudo, o computador usado tinha uma memória
RAM disponível de cerca de 480 MB. O modelo a verificar executa 5 processos doente, 2 processos
admin, 1 processo auxiliar e 1 processo atende. Mantendo uma margem de segurança, foi permitido
que a verificação utilizasse até 300 MB (este valor pode ser definido nas propriedades). Com esta
configuração, apenas foi possível obter resultados na opção ‘Bitstate’. Após 16 minutos a verificação
automática termina sem encontrar nenhum erro no modelo. Experimentou-se fazer a mesma verificação
nos outros dois modos de procura disponíveis (Exhaustive e Hash-compact), sendo que nenhum destes
40
apresentou resultados conclusivos – a procura nunca foi concluída por falta de memória. No entanto, o
checker apresentava como resultado o facto de não ter encontrado qualquer erro até ao ponto onde a
memória existente permitiu que a verificação chegasse.
Resultados O model checking foi executado num computador com tecnologia Intel, num Sistema operativo
WindowsXP e com cerca de 300MB de memória RAM disponibilizados para a prova.
Da análise dos resultados da simulação do modelo surgiram algumas correcções ao modelo. Estas
deveram-se a incorrecções do modelo relativamente ao comportamento que era esperado (e que se
encontrava de acordo com o que acontece na realidade). O processo de simulação foi repetido até
chegar a um modelo que não apresentasse deficiências aparentes. Após atingir esse modelo, passou-se
à verificação formal.
41
Figura 4 - Resultado da verificação do modelo da urgência no Xspin
Devido às restrições de memória apresentadas pelo método em questão, foi necessário utilizar a
opção da ferramenta que obriga ao menor consumo de memória (supertrace/bitstate). Neste modo, ao
fim de aproximadamente 14 minutos a ferramenta termina a verificação do modelo e indica que não
foram encontrados quaisquer erros. No entanto, antes de chegar a este resultado, várias foram as vezes
em que o model checker encontrou erros no modelo. Nesta situação, era apresentado um guião de
simulação com o contra-exemplo, possibilitando assim ao utilizador a compreensão e correcção do erro
detectado. É de referir que a ferramenta necessitou apenas de 17MB para completar a prova.
Nos outros modos disponibilizados pela ferramenta para verificar o modelo (exhaustive e hash-
compact) a memória da máquina utilizada era insuficiente para completar o processo. Ao atingir o limite
de memória disponível, o checker indicava que até onde lhe foi possível chegar, não tinha sido detectado
nenhum erro mas seria necessária mais memória para poder concluir a verificação.
42
7. Verificação do modelo por Theorem Proving Neste capítulo vai ser demonstrada a verificação do modelo do nosso caso de estudo segundo a
metodologia de theorem proving. O modelo que serve de base a esta verificação encontra-se descrito no
capítulo 5 e está na sua totalidade no anexo A2.
Requisitos Tal como referido no capítulo anterior, a simulação é uma fase importante na construção de um
modelo. Uma vez que, para fazer a verificação segundo o theorem proving é necessário construir outro
modelo, torna-se necessário dispor de um simulador para a linguagem utilizada. Desta forma, foi utilizada
para esse fim uma ferramenta académica, o ProB, que se encontra disponível para download em
http://users.ecs.soton.ac.uk/mal/systems/ProB_Download/.
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 ->
fila_transporte?id,eval(nid); ::else -> skip fi; estado[id] = 2; ::estado[id] == 2 -> fila_atende!id, nid; estado[id] = 3; ::estado[id] == 3 -> fila_alta?id, eval(nid); estado[id] = 4; ::estado[id] == 4 -> break; od; assert(estado[id] == 4) }
2
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() }
4
A2. Modelo AMN MACHINE Urgencia CONSTANTS MAX_TEMPO PROPERTIES MAX_TEMPO : NAT1 DEFINITIONS espera_aceitavel(t) == !dd.(dd:doente =>
(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
Recommended