3
Introdução
Neste trabalho pretendeu-se criar uma aplicação para servir como subsistema de
gestão de recursos em bibliotecas.
O objectivo do projecto será a construção de um simulador para os problemas de um
silo para estacionamento automóvel. O silo irá ter vários andares que comunicam entre
si e também várias entradas e saídas.
O projecto será realizado em várias fases, ao longo das quais se irão introduzir
requisitos funcionais mais elaborados para a cuja resolução teremos de usar os
recursos do Sistema Operativo (vamos continuar a usar o Linux).
Procuramos na nossa implementação utilizar os conceitos aprendidos nas aulas
teóricas e tirar partido dos mecanismos da linguagem C para reutilização e optimização
de código.
4
1 – Simulador de Entrada de Viaturas em Massa
Construir um pequeno programa multithreaded para enviar veículos para as quatro entradas
do silo, em simultâneo. O programa deverá enviar rajadas de matrículas, distintas,
sequenciais, em número a definir como parâmetro na linha de comando. Os veículos saídos
do silo deverão ser registados, com o respectivo tempo de saída, ou alternativamente, o
tempo em que permaneceram no silo. O objectivo é verificar que os veículos entrados são
consistentes com os que saem. Se optou por efectuar as saídas ao fim de um tempo aletório,
poderá ser conveniente modificar o output do simulador para emitir eese tempo de saída de
cada veículo, juntamente com a respectiva matrícula.
Para realizarmos este exercício efectuamos uma aplicação que através da linha de comandos
recebe um número que corresponde à quantidade de matrículas a gerar.
A quantidade de matrículas é limitada entre A e Z, sendo essa condição testada no programa.
Para a geração da quantidade de matrículas foi criado um algoritmo que gera sequencialmente
uma matrícula. A sequência tem um inicio, e via incrementando de forma circular.
As matrículas geradas são colocadas num Fifo que corresponde a uma das entradas do silo
(entrada é designada na linha de comando).
O envio é efectuado através de Threads, pelo que foi necessário recorrer a um mutex por
forma a controlar a concorrência sobre as variáveis de posição de letras / dígitos da matrícula.
O código do simulador de entradas corresponde ao ficheiro Gerador_Matriculas.c
5
2 – Segurança de dados
Planear e implementar os mecanismos de sincronização necessários para oferecer as
garantias a) e b) referidas na introdução:
a) garantir que nenhum veículo desaparece
b) garantir que a marca de tempo quando um veículo entra não é corrompida ou alterada
por outro veiculo
Para realizarmos este exercício, planeamos efectuar alterações no core da aplicação de forma
a podermos garantir a sincronização de escritas e leituras em zonas comuns aos vários threads
através da utilização de um mutex.
Desta forma é possível reservar o tempo de actuação sobre uma secção crítica em que os
restantes processos aguardam pelo fim do processo que está em utilização.
Logo nenhum veículo desaparece por não haver escritas simultâneas.
Também quando um veículo entra no silo, a sua hora não é alterada porque faz parte da
estrutura do tipo de dados que é armazenada.
Os dados são por isso geridos através uma estrutura de lugar que tem um apontador para o
veículo armazenado. Esta estrutura é manipulada pelos threads que com base no princípio
anterior não modificam simultaneamente o conteúdo de um lugar.
6
3 – Sincronização de acesso e permanência
Planear e implementar os mecanismos de sincronização necessários para oferecer as
garantias c) d) e e) referidas na introdução. Estes são requisitos avançados.
c) garantir que não entre nenhum veiculo quando o parque está cheio
d) garantir que nenhum veículo fica indefinidamente no parque
e) garantir que, se decidirmos encerrar o parque nenhum veículo em trânsito fica lá dentro.
O planeamento destes mecanismos passou por implementar como no exercício anterior mutex
que sinalizam a reserva de acções sobre secções críticas. Também recorremos a variáveis
binárias e a métodos específicos para gerirem os eventos.
Para garantirmos que nenhum veículo entra quando o parque se encontra cheio, no momento
de guardarVeiculo procurar o primeiro lugar vazio, consideramos que esta parte da operação
seja uma secção crítica, controlada com um mutex, e como tal não é possível haver
concorrência nesse momento com outros threads nomeadamente os que colocam veículos na
fila de saída.
Para garantirmos que nenhum veículo fica indeterminadamente no silo, cada vez que um
veículo é guardado é associado um thread com temporizador de saída removerVeiculo. Com
estes threads activos, a aplicação Main não encerra sem que seja feito o respectivo join de
todos eles. Assim, nenhum veículo ficará perdido.
Para garantirmos que nenhum veículo fica no silo, acrescentámos uma variável binária
“fechado” a qual é utilizada pela gestão de entradas para aceitar ou não veículos durante a
transição de encerramento para fecho finalizado.
7
4 – Código da aplicação
Aplicação para gerar e enviar através de um Fifo matrículas para uma entrada:
Gerador_Matriculas.c
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_BUF_SIZE 255
#define PIPE_IN "/tmp/SiloEntrada%d"
int matriculas;
int max_matriculas;
pthread_mutex_t mutex;
// Caracteres iniciais para as matrículas
// Vamos gerar algo do tipo LetraLetra-Numero-Numero
// Geração é sequêncial
char p1 = 'A', p2 = 'A', p3 = '0', p4 = '0', p5 = '0', p6 = '0';
void verificaDigitoMatricula(char *digit, char *hdigit, char limit, char reset) {
if (*digit > limit) {
*digit = reset;
*hdigit = *hdigit + 1;
}
}
char calculaMatricula(char *matricula) {
pthread_mutex_lock(&mutex); // entra na secção crítica
p6++; // nota p6 é variável global e está por isso numa secção crítica
verificaDigitoMatricula(&p6, &p5, '9', '0');
verificaDigitoMatricula(&p5, &p4, '9', '0');
verificaDigitoMatricula(&p4, &p3, '9', '0');
verificaDigitoMatricula(&p3, &p2, '9', '0');
verificaDigitoMatricula(&p2, &p1, 'Z', 'A');
if (p1 > 'Z') {// se a posição da Letra da matricula ultrapassa o limite Z termina geração
printf("Limite de matriculas possíveis foi atingidon");
exit(-1);
}
// atribui os valores de cada posição da matricula na memória
matricula[0] = p1;
matricula[1] = p2;
matricula[2] = '-';
matricula[3] = p3;
matricula[4] = p4;
matricula[5] = '-';
matricula[6] = p5;
matricula[7] = p6;
pthread_mutex_unlock(&mutex); // sai da secção crítica
8
}
void *criarFifo(void *threadid) {
char fifo[13];
char matricula[9];
int taskid, ret_val, wrfd;
taskid = (*(int *) threadid);
sleep(1); // necessário para sincronizar os threads
snprintf(fifo, sizeof (fifo), PIPE_IN, taskid + 1);
ret_val = mkfifo(fifo, 0666);
if ((ret_val == -1) && (errno != EEXIST)) {
perror("Error creating named pipen");
exit(-1);
}
wrfd = open(fifo, O_WRONLY);
printf("fifo %s criadon", fifo);
for(;;) {
if (matriculas++ > max_matriculas) break; // Verifica se atingimos o limite de geração de matrículas
calculaMatricula( &matricula ); // Obtem uma matricula
printf("Thread %d: %s (%d/%d)n", taskid, matricula, matriculas, max_matriculas);
write(wrfd, matricula, 9);
}
pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
int numThreads = 4; // Lança 4 Threads - 1 para cada piso
pthread_t threads[numThreads];
int *taskids[numThreads];
int thr;
if (argc != 2) {
printf("Passe os seguintes parâmetros: %s <Nº de Matriculas a gerar> n", argv[0]);
exit(-1);
}
max_matriculas = atoi(argv[1]); // Nº de matrículas a gerar
for (thr = 0; thr < numThreads; thr++) { // Para cada piso cria um novo thread
taskids[thr] = (int *) malloc(sizeof (int));
*taskids[thr] = thr;
printf("Thread %dn", thr);
int res = pthread_create(&threads[thr], NULL, criarFifo, (void *) taskids[thr]);
if (res) {
printf("Erro a criar thread - code: %dn", res);
exit(-1);
}
}
pthread_exit(NULL);
}
9
Aplicação Core do Silo:
Core.c
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/ipc.h>
#include <pthread.h>
#define MAX_BUF_SIZE 9
#define PIPE_IN "/tmp/SiloEntrada%d"
#define PIPE_OUT "/tmp/SiloSaida"
/*
* Definição de Chave para o segmento de memória
*
*/
#define KEY (66661)
/*
* Definição de limites globais para utilização na aplicação
*/
#define MAX_PISOS 4
#define MAX_POSICOES 10
/*
* Estrutura do silo para armazenamento de veículos
*
* O silo tem vários andares
* Tem em cada andar várias posições
*
* Cada matrícula de veículo corresponde a uma identidade
*
*/
typedef struct Lugar {
char matricula[MAX_BUF_SIZE]; // Texto da matrícula do veículo
time_t hora; // Hora de entrada de veículo
} lugar_t;
typedef struct threadedLugar {
lugar_t* lugar;
pthread_t tid;
} tlugar_t;
/*
* Variavel global correspondente à estrutura do silo
* Silo com 4 andares e 10 lugares cada um
*/
static lugar_t* plugares;
/*
* Variavel global buffer de entrada por piso
*/
char buf[MAX_PISOS][MAX_BUF_SIZE];
10
tlugar_t tlugares[MAX_PISOS][MAX_POSICOES];
pthread_mutex_t mutex;
char fechado = 0;
void initLugares() {
int i, j;
lugar_t *l;
l = plugares;
for (i = 0; i < MAX_PISOS; i++) // Limpa todas as posições
for (j = 0; j < MAX_POSICOES; j++, l++) {
l->hora = 0;
l->matricula[0] = '0';
}
}
void outLugares() {
int i, j;
lugar_t *l;
l = plugares;
for (i = 0; i < MAX_PISOS; i++)
for (j = 0; j < MAX_POSICOES; j++, l++) {
printf("%d,%d Hora: %d Matricula: %sn", i, j, l->hora, l->matricula);
}
}
/*
* generates a psuedo-random integer between 0 and max
*/
int randint(int max) {
return (int) (rand() % max);
}
void *removerVeiculo(void* tlugarArg) {
int seconds;
int fd, ret_val;
tlugar_t *tlugar = (tlugar_t*) tlugarArg;
seconds = (randint(10) + 1);
printf("A aguardar %d segundos para remover a matricula %s tid %dn", seconds, tlugar->lugar->matricula, tlugar-
>tid);
sleep(seconds); // Aguarda para remover automaticamente a matricula do silo
ret_val = mkfifo(PIPE_OUT, 0666);
if ((ret_val == -1) && (errno != EEXIST)) {
perror("Error creating named pipen");
exit(-1);
}
fd = open(PIPE_OUT, O_WRONLY); // Envia a matricula pelo Pipe de saída
write(fd, tlugar->lugar->matricula, MAX_BUF_SIZE);
printf("Matricula %s removida. tid(%d)n", tlugar->lugar->matricula, tlugar->tid);
tlugar->lugar->hora = 0; // Limpa o lugar no silo
tlugar->lugar->matricula[0] = NULL;
pthread_exit((void*) tlugar->tid);
11
}
void encerrarSilo() {
int i, j, fd;
lugar_t *l = plugares;
pthread_mutex_lock(&mutex); // Escreve na memória
for (i = 0; i < MAX_PISOS; i++) { // Limpa todos os veículos no silo quando este encerra
for (j = 0; j < MAX_POSICOES; j++, l++) {
if (l->hora > 0) {
/* Abre o Pipe para escrita */
fd = open(PIPE_OUT, O_WRONLY);
/* Escreve a matrícula para o pipe */
write(fd, l->matricula, MAX_BUF_SIZE);
l->matricula[0] = NULL;
l->hora = 0;
}
}
}
pthread_mutex_unlock(&mutex);
}
int guardaVeiculo(char *matricula) {
pthread_t thread;
int piso = 0, posicao = 0;
lugar_t *l = plugares;
tlugar_t *ptlugar;
time_t curtime;
curtime = time(NULL);
struct tm *loctime;
loctime = localtime(&curtime);
pthread_mutex_lock(&mutex); // Inicio da secção crítica em que se mexe com a memória
int i, j, encontrou = 0;
for (i = 0; i < MAX_PISOS && encontrou == 0; i++) { // procura o primeiro lugar vazio
for (j = 0; j < MAX_POSICOES && encontrou == 0; j++, l++) {
if (l->hora == 0) {
piso = i;
posicao = j;
encontrou = 1;
}
}
}
pthread_mutex_unlock(&mutex); // Fim da secção crítica em que se mexeu com a memória
if (i == MAX_PISOS && j == MAX_POSICOES) // Se atingiu o máximo de posições termina
{
printf("Nao existem lugares livres para a matricula: %sn", matricula);
return -1;
}
strncpy(l->matricula, matricula, MAX_BUF_SIZE); // Guarda matrícula
l->hora = curtime;
12
ptlugar = &tlugares[i][j];
ptlugar->lugar = l;
ptlugar->tid = i * MAX_POSICOES + j + MAX_PISOS + 1;
printf("Matricula %s guardada: piso %d posicao %d tid %dn", matricula, piso, posicao, ptlugar->tid);
pthread_create(&thread, NULL, removerVeiculo, (void *) ptlugar); // associa a cada veículo guardado uma thread
para remover veículo
return 1;
}
reservarMem() {
int shmid;
key_t key;
int shmz = MAX_PISOS * MAX_POSICOES * sizeof (lugar_t);
key = KEY;
if ((shmid = shmget(key, shmz, IPC_CREAT | 0666)) < 0) {
perror("shmget");
exit(1);
}
if ((plugares = shmat(shmid, NULL, 0)) == (char *) - 1) {
perror("shmat");
exit(1);
}
}
void *gereEntradaPiso(void *thread_id) {
char *pipename;
int tid, fd, numread;
tid = (int) thread_id;
pipename = malloc(strlen(PIPE_IN) + 2);
sprintf(pipename, PIPE_IN, tid + 1);
fd = open(pipename, O_RDONLY);
for(;;) {
numread = 0;
numread = read(fd, buf[tid], MAX_BUF_SIZE);
buf[tid][numread] = '0';
if (numread != 0 && strcmp(buf[tid], "x") == 0) // se for passado x termina ciclo
{
fechado = 1;
encerrarSilo(); // Encerra o silo cada vez que receber a ordem pelo Fifo
break;
}
if (fechado == 0 && numread != 0) {
guardaVeiculo(buf[tid]);
}
}
free(pipename);
pthread_exit((void*) thread_id);
13
}
int main(int argc, char *argv[]) {
int t, rc;
void *status;
pthread_attr_t attr;
srand(time(NULL)); // Cria aleatóriamente
pthread_t threads[MAX_PISOS];
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
reservarMem(); // Trata da memória
initLugares(); // Trata dos lugares
for (t = 0; t < MAX_PISOS; t++) {
rc = pthread_create(&threads[t], &attr, gereEntradaPiso, (void *) t);
if (rc) {
printf("Erro a criar thread - code: %dn", rc);
exit(-1);
}
}
pthread_attr_destroy(&attr); // Aguarda que todos os threads encerrem
for (t = 0; t < MAX_PISOS; t++) {
rc = pthread_join(threads[t], &status);
if (rc) {
printf("Erro a criar thread - code: %dn", rc);
exit(-1);
}
printf("Thread %ld terminou - status %ld n", t, (long) status);
}
printf("Encerrar aplicação.n");
pthread_exit(NULL);
}
Aplicação para mostrar a Fila de saída do Silo:
Output.c
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define PIPE_OUT "/tmp/SiloSaida"
int main(int argc, char *argv[]) {
int fd, readbytes;
14
char buffer[255];
for(;;) {
// Lê do fifo num ciclo infinito as matriculas que saem do silo
fd = open(PIPE_OUT, O_RDONLY);
readbytes = read(fd, buffer, 255);
buffer[readbytes] = '0';
if (readbytes != 0) {
printf("Matricula Saída: %sn", buffer);
}
}
}