Skip to content

CarlossOliveira/SO

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 

Repository files navigation

Cábula de SO

Índice

  1. Processos Filho (Child Processes with Fork)
  2. Threads (Tasks)
  3. Semáforos POSIX (POSIX Semaphores)
  4. Memória Partilhada (Shared Memory)
  5. Sinais (Signals)
  6. Pipes
  7. Filas de Mensagens (Message Queues)
  8. Ficheiros Mapeados na Memória (Memory Mapped Files)
  9. Tabela de Resumo

Processos Filho (Child Processes with Fork)

#include <unistd.h> // include para fork(), getpid(), getppid()
#include <sys/wait.h> // include para wait(), waitpid()
#include <stdlib.h> // include para exit()

Criar Processos Filhos (Dar fork do processo pai)

pid_t fork(void); // Cria um novo processo filho que é uma cópia do processo pai. Retorna o PID do processo filho para o processo pai e 0 para o processo filho. Se houver um erro retorna -1.

pid_t wait(int *wstatus); // Aguarda até que um processo filho termine. Retorna o PID do processo filho terminado ou -1 se houver um erro. O argumento wstatus é usado para retornar o status do processo filho.
pid_t waitpid(pid_t pid, int *wstatus, int options); // Aguardar por um processo filho específico ou por um conjunto de processos filhos. Retorna o PID do processo filho terminado ou -1 se houver um erro. "pid" pode ser um PID específico, -1 para qualquer processo filho,... "wstatus" é usado para retornar o status do processo filho. "options" permite especificar opções adicionais como WNOHANG, WUNTRACED,...

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf

#define NUMERO_DE_CHILD_PROCESSES 10

pid_t lista_de_child_processes[NUMERO_DE_CHILD_PROCESSES];

int main() {
    // Criar os processos filhos
    for (int child = 0; child < NUMERO_DE_CHILD_PROCESSES; child++) {
        if ((lista_de_child_processes[child] = fork()) == 0) /* Após criar o processo filho, a execução continua nele */ {
            printf("Eu sou a criança número %i, o meu PID é %d e o PID do meu pai é %d.\n", child, getpid(), getppid());
            exit(0); // Termina a execução e "mata-se"
        }
    }

    // Espera pelos Child Processes antes de terminar
    for (int child = 0; child < NUMERO_DE_CHILD_PROCESSES; child++) {
        wait(NULL);
    }

    return 0;
}

Remover Processos Filhos

Não existe nenhum comando para matar explicitamente child processes. O child process só é efetivamente removido uma vez que execute um exit(0) para terminar o processo ou uma vez que receba um SIGTERM.


Threads (Tasks)

#include <pthread.h> // include para pthread_create(), pthread_join(), pthread_mutex_init(), pthread_mutex_lock(), pthread_mutex_unlock(), pthread_mutex_destroy(), pthread_cond_init(), pthread_cond_wait(), pthread_cond_signal(), pthread_cond_broadcast(), pthread_cond_destroy()

Spawnar Threads

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // Cria uma thread. "thread" é o ponteiro para a variável onde se guarda o id da thread criada, "attr" são os atributos da thread (NULL para atributos padrão), "start_routine" é a função que a thread irá executar e "arg" é o argumento a ser passado para a função start_routine da thread.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar perror
#include <errno.h> // Importar errno.h para usar perror

#define NUMERO_DE_THREADS 5

pthread_t lista_de_threads[NUMERO_DE_THREADS];

void* tarefa(void* argumento) {
    if (argumento != NULL) {
        int *contador = (int*)argumento; // Cast do argumento para o tipo correto
        while(*contador <= 100) {
            (*contador)++;
        }
    }

    return NULL;
}

int main() {
    int incrementador = 1;
    void* ponteiro_para_incrementador = &incrementador; // Criar um ponteiro do tipo void a apontar para o incrementador para o poder passar por parâmetro
    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        if ((pthread_create(&lista_de_threads[thread], NULL, tarefa, ponteiro_para_incrementador /*Este argumento é onde se passam os parâmetros para a função a ser executada pela thread*/)) != 0) /* Verifica se houve erro no spawn da thread*/ {
            perror("Erro ao criar uma thread!");
        }
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        pthread_join(lista_de_threads[thread], NULL); // Espera que todas as threads retornem do processo de execução
    }

    return 0;
}

Remover Threads

Não existe nenhum comando para remover explicitamente threads dado que uma thread é apenas uma "linha de execução" de um processo, ou seja, uma thread só é efetivamente removida quando o processo que spawnou a thread termina ou quando o trabalho da thread é concluído e ela retorna.

Mutex

Criar e Iniciar Mutex

int pthread_mutex_lock(pthread_mutex_t *mutex); // Bloqueia o mutex se não tiver bloqueado, senão, espera até ele desbloquear. "mutex" é o ponteiro para o mutex a ser bloqueado.
int pthread_mutex_unlock(pthread_mutex_t *mutex); // Desbloqueia o mutex. "mutex" é o ponteiro para o mutex a ser desbloqueado.

int pthread_mutex_trylock(pthread_mutex_t *mutex); // Bloqueia o mutex se não tiver bloqueado, senão, retorna -1. "mutex" é o ponteiro para o mutex a ser bloqueado.

Criação Estática

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Cria uma variável global para guardar o mutex e inicia o mutex de forma estática e predefinida.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar os printf e perror
#include <errno.h> // Importar errno.h para usar perror

#define NUMERO_DE_THREADS 5

pthread_t lista_de_threads[NUMERO_DE_THREADS];

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Cria uma variável global para guardar o mutex e inicia o mutex

void* tarefa(void* argumento) {
    if (argumento != NULL) {
        int *incrementador = argumento; // Cast do argumento para o tipo correto
        while(*incrementador <= 100) {
            pthread_mutex_lock(&mutex); // Bloqueia o mutex
            printf("Valor no incrementador: %i\n", *incrementador);
            (*incrementador)++;
            pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
        }
    }

    return NULL;
}

int main() {
    int incrementador = 1;
    void* ponteiro_para_incrementador = &incrementador;
    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        if ((pthread_create(&lista_de_threads[thread], NULL, tarefa, ponteiro_para_incrementador /*Este argumento é onde se passam os parâmetros para a função a ser executada pela thread*/)) != 0) /*Verifica se houve erro no spawn da thread*/ {
            perror("Erro ao criar uma thread!");
        }
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        pthread_join(lista_de_threads[thread], NULL); // Espera que todas as threads retornem do processo de execução
    }

    (...)
}

Criação Dinâmica

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); // Inicializa o mutex. "mutex" é o ponteiro para o mutex a ser inicializado e "attr" são os atributos do mutex (NULL para atributos padrão).

Exemplo:

#include <stdio.h> // Importar stdio.h para usar os printf e perror
#include <errno.h> // Importar errno.h para usar perror

#define NUMERO_DE_THREADS 5

pthread_t lista_de_threads[NUMERO_DE_THREADS];

void* tarefa(void* argumento) {
    if (argumento != NULL) {
        int *incrementador = argumento;

        while(*incrementador <= 100) {
            pthread_mutex_lock(&mutex); // Bloqueia o mutex
            printf("Valor no incrementador: %i\n", *incrementador);
            (*incrementador)++;
            pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
        }
    }

    return NULL;
}

int main() {
    // Criar os mutexs
    pthread_mutex_t mutex; // Inicializa uma variável para guardar o mutex
    pthread_mutex_init(&mutex, NULL);  // Inicializa o mutex com os atributos a NULL

    int incrementador = 1;
    void* ponteiro_para_incrementador = &incrementador;
    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        if ((pthread_create(&lista_de_threads[thread], NULL, tarefa, ponteiro_para_incrementador /*Este argumento é onde se passam os parâmetros para a função a ser executada pela thread*/)) != 0) /*Verifica se houve erro no spawn da thread*/ {
            perror("Erro ao criar uma thread!");
        }
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        pthread_join(lista_de_threads[thread], NULL); // Espera que todas as threads retornem do processo de execução
    }

    (...)
}

Remover Mutex

int pthread_mutex_destroy(pthread_mutex_t *mutex); // Destroi o mutex . "mutex" é o ponteiro para o mutex a ser destruído.

Exemplo:

int main() {
    (...)

    pthread_mutex_destroy(&mutex);

    return 0;
}

Mutex com Condição

Criar e Iniciar Mutex com Condição

int pthread_cond_signal(pthread_cond_t *cond); // Sinaliza a condição (desbloqueia uma thread que esteja à espera da condição). "cond" é o ponteiro para a variável de condição a ser sinalizada. Usada em cenários que apenas uma thread ouvem sinais numa condição.
int pthread_cond_broadcast(pthread_cond_t *cond); // Sinaliza a condição (desbloqueia todas as threads que estejam à espera da condição). "cond" é o ponteiro para a variável de condição a ser sinalizada. Usada em cenários que uma ou mais threads ouvem sinais numa condição.
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // Espera pelo sinal na condição (desbloqueia o mutex e bloqueia a thread até que a condição seja sinalizada, uma vez que a condição é sinalizada o mutex volta a ser bloqueado e a thread desbloqueia assim evitando deadlock). "cond" é o ponteiro para a variável de condição e "mutex" é o ponteiro para o mutex.

Criação Estática

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // Cria uma variável global para guardar uma condição e inicia-la de forma estática e predefinida.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar perror
#include <errno.h> // Importar errno.h para usar perror

#define NUMERO_DE_THREADS_PRODUTORAS 5
#define NUMERO_DE_THREADS_CONSUMIDORAS 1

pthread_t lista_de_threads_produtoras[NUMERO_DE_THREADS_PRODUTORAS];
pthread_t lista_de_threads_consumidoras[NUMERO_DE_THREADS_CONSUMIDORAS];

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* produtor(void* argumento) /* Incrementa um contador até 100, uma vez chegando a 100 termina e sinaliza a condição */ {
    if (argumento != NULL) {
        int *contador = (int*)argumento;

        while(*contador <= 100) {
            pthread_mutex_lock(&mutex); // Bloqueia o mutex
            printf("Valor no incrementador: %i\n", (*contador));
            (*contador)++;
            pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
        }
        pthread_cond_signal(&cond); // Sinaliza a condição (desbloqueia a thread que esteja à espera da condição)
    }

    return NULL;
}

void* consumidor(void* argumento) /* Lê o valor do incrementador e termina */ {
    if (argumento != NULL) {
        int *contador = (int*)argumento;

        pthread_mutex_lock(&mutex); // Bloqueia o mutex
        pthread_cond_wait(&cond, &mutex); // Espera pelo sinal na condição (desbloqueia o mutex e bloqueia a thread até que a condição seja sinalizada, uma vez que a condição é sinalizada o mutex volta a ser bloqueado e a thread desbloqueia assim evitando deadlock)
        printf("Consumidor a processar o valor: %i\n", (*contador));
        pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
    }

    return NULL;
}

int main() {
    // Criar incrementador e um ponteiro do tipo void a apontar para ele para o poder passar por parâmetro
    int incrementador = 1;
    void* ponteiro_para_incrementador = &incrementador;

    // Criar as threads produtoras
    for (int thread = 0; thread < NUMERO_DE_THREADS_PRODUTORAS; thread++) {
        if (pthread_create(&lista_de_threads_produtoras[thread], NULL, produtor, ponteiro_para_incrementador) != 0) /*Verifica se houve erro no spawn da thread*/ {
            perror("Erro ao criar uma thread!");
        }
    }

    // Criar as threads consumidoras
    for (int thread = 0; thread < NUMERO_DE_THREADS_CONSUMIDORAS; thread++) {
        if (pthread_create(&lista_de_threads_consumidoras[thread], NULL, consumidor, ponteiro_para_incrementador) != 0) {
            perror("Erro ao criar uma thread!");
        }
    }

    // Esperar que todas as threads produtoras terminem
    for (int thread = 0; thread < NUMERO_DE_THREADS_PRODUTORAS; thread++) {
        pthread_join(lista_de_threads_produtoras[thread], NULL);
    }

    // Esperar que todas as threads consumidoras terminem
    for (int thread = 0; thread < NUMERO_DE_THREADS_CONSUMIDORAS; thread++) {
        pthread_join(lista_de_threads_consumidoras[thread], NULL);
    }

    (...)
}

Criação Dinâmica

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); // Inicializa a variável de condição. "cond" é o ponteiro para a variável de condição a ser inicializada e "attr" são os atributos da condição (NULL para atributos padrão).

Exemplo:

#include <stdio.h> // Importar stdio.h para usar perror
#include <errno.h> // Importar errno.h para usar perror

#define NUMERO_DE_THREADS_PRODUTORAS 5
#define NUMERO_DE_THREADS_CONSUMIDORAS 3

pthread_t lista_de_threads_produtoras[NUMERO_DE_THREADS_PRODUTORAS];
pthread_t lista_de_threads_consumidoras[NUMERO_DE_THREADS_CONSUMIDORAS];

pthread_mutex_t mutex; // Cria uma variável global para guardar o mutex
pthread_cond_t cond; // Cria uma variável global para guardar a condição a ser sinalizada

void* produtor(void* argumento) {
    if (argumento != NULL) {
        int *contador = (int*)argumento;

        while(*contador <= 100) {
            if (pthread_mutex_trylock(&mutex) == 0) /* Tenta bloquear o mutex, caso este não esteja bloqueado */ {
                printf("Valor no incrementador: %i\n", (*contador));
                (*contador)++;
                pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
            }
            printf("Produtor a aguardar...\n"); // Enquanto o mutex está bloqueado, o produtor imprime esta mensagem
        }
        pthread_cond_broadcast(&cond); // Sinaliza a condição (desbloqueia as threads que estejam à espera da condição)
    }

    return NULL;
}

void* consumidor(void* argumento) /* Lê o valor do incrementador e termina */ {
    if (argumento != NULL) {
        int *contador = (int*)argumento;

        pthread_mutex_lock(&mutex); // Bloqueia o mutex
        pthread_cond_wait(&cond, &mutex); // Espera pelo sinal na condição (desbloqueia o mutex e bloqueia a thread até que a condição seja sinalizada, uma vez que a condição é sinalizada o mutex volta a ser bloqueado e a thread desbloqueia assim evitando deadlock)
        printf("Consumidor a processar o valor: %i\n", (*contador));
        pthread_mutex_unlock(&mutex); // Desbloqueia o Mutex
    }

    return NULL;
}

int main() {
    // Inicializar o mutex
    pthread_mutex_init(&mutex, NULL);  // Inicializa o mutex com os atributos a NULL

    // Inicializar a condição
    pthread_cond_init(&cond, NULL); // Inicializa a condição com os atributos a NULL

    // Criar incrementador e um ponteiro do tipo void a apontar para ele para o poder passar por parâmetro
    int incrementador = 1;
    void* ponteiro_para_incrementador = &incrementador;

    // Criar as threads produtoras
    for (int thread = 0; thread < NUMERO_DE_THREADS_PRODUTORAS; thread++) {
        if (pthread_create(&lista_de_threads_produtoras[thread], NULL, produtor, ponteiro_para_incrementador) != 0) /*Verifica se houve erro no spawn da thread*/ {
            perror("Erro ao criar uma thread!");
        }
    }

    // Criar as threads consumidoras
    for (int thread = 0; thread < NUMERO_DE_THREADS_CONSUMIDORAS; thread++) {
        if (pthread_create(&lista_de_threads_consumidoras[thread], NULL, consumidor, ponteiro_para_incrementador) != 0) {
            perror("Erro ao criar uma thread!");
        }
    }

    // Esperar que todas as threads produtoras terminem
    for (int thread = 0; thread < NUMERO_DE_THREADS_PRODUTORAS; thread++) {
        pthread_join(lista_de_threads_produtoras[thread], NULL);
    }

    // Esperar que todas as threads consumidoras terminem
    for (int thread = 0; thread < NUMERO_DE_THREADS_CONSUMIDORAS; thread++) {
        pthread_join(lista_de_threads_consumidoras[thread], NULL);
    }

    (...)
}

Remover Mutex com Condição

int pthread_cond_destroy(pthread_cond_t *cond); // Destroi a condição. "cond" é o ponteiro para a condição a ser destruída.

Exemplo:

int main() {
    (...)

    pthread_cond_destroy(&cond); // Destroi a condição
    pthread_mutex_destroy(&mutex); // Destroi o mutex

    return 0;
}

Semáforos POSIX (POSIX Semaphores)

#include <semaphore.h> // include para sem_init(), sem_wait(), sem_post(), sem_destroy(), sem_open(), sem_close(), sem_unlink()
#include <fcntl.h> // include para O_CREAT, O_EXCL

Semáforos Não Nomeados (Unnamed Semaphores)

Criar e dar attach em Semáforos Não Nomeados

int sem_init(sem_t *sem, int pshared, unsigned int value); // Inicia o semáforo não nomeado. "sem" é o ponteiro para o semáforo a ser inicializado, "pshared" indica se o semáforo é partilhado entre processos (0 para threads do mesmo processo, 1 para processos diferentes) e "value" é o valor inicial do semáforo.
int sem_wait(sem_t *sem); // Decrementa o semáforo se ele não estiver a 0, senão, espera que ele fique maior que 0. "sem" é o ponteiro para o semáforo retornado pela função sem_init.
int sem_post(sem_t *sem); // Incrementa o semáforo. "sem" é o ponteiro para o semáforo retornado pela função sem_init.

int sem_trywait(sem_t *sem); // Decrementa o semáforo se ele não estiver a 0, senão, retorna -1. "sem" é o ponteiro para o semáforo retornado pela função sem_init.
int sem_getvalue(sem_t *sem, int *sval); // Obtém o valor atual do semáforo. "sem" é o ponteiro para o semáforo retornado pela função sem_init e "sval" é o ponteiro para a variável onde se guarda o valor do semáforo.
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // Decrementa o semáforo se ele não estiver a 0, senão, espera até ao tempo especificado em abs_timeout. "sem" é o ponteiro para o semáforo retornado pela função sem_init.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf e perror
#include <errno.h> // Importar errno.h para usar perror
#include <pthread.h> // Importar pthread.h para a criação de threads

#define NUMERO_DE_THREADS 10

sem_t semaforo; // Declarar globalmente uma variável para guardar o ponteiro para o semáforo
pthread_t lista_de_threads[NUMERO_DE_THREADS];

void* tarefa(void* argumentos) /* Decrementa o semáforo e faz um sleep por 10 segundos antes de incrementar o semáforo */ {
    for (int tarefa = 0; tarefa < 3; tarefa++) {
    sem_wait(&semaforo); // Decrementa o semáforo
    sleep(10);
    sem_post(&semaforo); // Incrementa o semáforo
    }

    return NULL;
}

int main() {
    if ((sem_init(&semaforo, 0, 5)) == -1) /*Inicia um semáforo com "5 espaços". Verifica também se existiu algum erro na criação do semáforo*/ {
        perror("Erro a iniciar o semáforo!");
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        if ((pthread_create(&lista_de_threads[thread], NULL, tarefa, NULL)) != 0) {
            perror("Error ao criar uma thread!");
        }
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        pthread_join(lista_de_threads[thread], NULL);
    }

    (...)
}

Remover e/ou dar detach Semáforos Não Nomeados

int sem_destroy(sem_t *sem); // "sem" é o ponteiro para o semáforo a ser destruído

Exemplo:

int main() {
    (...)

    sem_destroy(&semaforo); // Destroi o semáforo associado ao ponteiro passado por argumento

    return 0;
}

Semáforos Nomeados (Named Semaphores)

Criar e dar attach Semáforos Nomeados

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); // Cria o semáforo nomeado. "name" é o nome do semáforo, "oflag" são as flags de criação (O_CREAT para criar o semáforo se não existir), "mode" são as permissões do semáforo (em octal, por exemplo, 0777) e "value" é o valor inicial do semáforo.
int sem_wait(sem_t *sem); // Decrementa o semáforo. "sem" é o ponteiro para o semáforo retornado pela função sem_open.
int sem_post(sem_t *sem); // Incrementa o semáforo. "sem" é o ponteiro para o semáforo retornado pela função sem_open.

Exemplo:

#include <fcntl.h> // Importar fcntl.h para a flag O_CREAT
#include <stdio.h> // Importar stdio.h para usar printf e perror
#include <errno.h> // Importar errno.h para usar perror
#include <pthread.h> // Importar pthread.h para a criação de threads

#define NUMERO_DE_THREADS 10

sem_t* semaforo_nomeado; // Declarar globalmente uma variável para guardar o ponteiro para o semáforo
pthread_t lista_de_threads[NUMERO_DE_THREADS];

void* tarefa(void* argumentos) {
    for (int tarefa = 0; tarefa < 3; tarefa++) {
        sem_wait(semaforo_nomeado); // Decrementa o semáforo
        sleep(10);
        sem_post(semaforo_nomeado); // Incrementa o semáforo
    }

    return NULL;
}

int main() {
    sem_unlink("GUSTAVO");
    if ((semaforo_nomeado = sem_open("GUSTAVO", O_CREAT, 0777, 5)) == SEM_FAILED) /*Inicia um semáforo com "5 espaços". Verifica também se existiu algum erro na criação do semáforo*/ {
        perror("Erro a iniciar o semáforo!");
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        if ((pthread_create(&lista_de_threads[thread], NULL, tarefa, NULL)) != 0) {
            perror("Error ao criar uma thread!");
        }
    }

    for (int thread = 0; thread < NUMERO_DE_THREADS; thread++) {
        pthread_join(lista_de_threads[thread], NULL);
    }

    (...)
}

Remover e/ou dar detach Semáforos Nomeados

int sem_close(sem_t *sem); // Sai do semáforo nomeado. "sem" é o ponteiro para o semáforo retornado pela função sem_open.
int sem_unlink(const char *name); // Apaga o semáforo nomeado do sistema. "name" é o nome do semáforo a ser removido.

Exemplo:

int main() {
    (...)

    sem_close(semaforo_nomeado);
    sem_unlink("GUSTAVO");

    return 0;
}

Memória Partilhada (Shared Memory)

#include <sys/shm.h> // include para shmget(), shmat(), shmdt(), shmctl()
#include <sys/ipc.h> // include para IPC keys (ftok) e estruturas de IPC

Criar e dar attach em blocos de Memória Partilhada

int shmget(key_t key, size_t size, int shmflg); // Cria um id para a criação e/ou entrada num segmento de memória partilhada. "key" é a chave única para identificar o segmento de memória partilhada, "size" é o tamanho do segmento em bytes e "shmflg" são as flags de criação e permissões (IPC_CREAT para criar o segmento se não existir e as permissões em octal, por exemplo, 0777).
void *shmat(int shmid, const void *shmaddr, int shmflg); // Entra e/ou cria no segmento de memória partilhada referente ao id passado por parâmetro. "shmid" é o id do segmento de memória partilhada retornado pela função shmget, "shmaddr" é o endereço onde se deseja anexar o segmento (NULL para deixar o sistema escolher) e "shmflg" são as flags de anexação (0 para anexação padrão).

Exemplo:

int main(){
    int shm_id = shmget(2006, sizeof(int), IPC_CREAT | 0777); // Cria um pedido de criação de um espaço de memória partilhada entre processos com as seguintes características: key = 2006 (normalmente key = IPC_PRIVATE), tamanho = sizeof(int), flag de criação = IPC_CREAT e flag de permissões = 777. Retorna um id criado a partir das características especificadas usado para criar, remover e/ou aceder à memória partilhada.
    if (shm_id == -1) /*Verifica se existiu algum erro na criação da memória partilhada*/ {
        perror("shmget failed");
        exit(1);
    }

    int *shared_memory = (int *)shmat(shm_id, NULL, 0); // Cria o espaço de memória partilhada associada ao id, shm_id. Retorna um ponteiro de acesso à memória partilhada.
    *shared_memory = 0; // Guarda o valor 0 na memória partilhada.

    (...)
}

Apagar e/ou sair de um bloco de Memória Partilhada

int shmdt(const void *shmaddr); // Sai do espaço de memória partilhada. "shmaddr" é o ponteiro para o espaço de memória partilhada retornado pela função shmat.
int shmctl(int shmid, int cmd, struct shmid_ds *buf); // Apaga o espaço de memória partilhada. "shmid" é o id do espaço de memória partilhada retornado pela função shmget, "cmd" é a operação a ser realizada (IPC_RMID para apagar o espaço de memória partilhada) e "buf" é um ponteiro para uma estrutura de dados usada para obter ou definir informações sobre o segmento de memória partilhada (pode ser NULL se não for necessário).

Exemplo:

int main(){
    (...)

    shmdt(shared_memory); // Sai do espaço de memória partilhado.

    shmctl(shm_id, IPC_RMID, NULL); // Apaga o espaço de memória partilhado associado ao id passado no primeiro parâmetro.

    return 0;
}

Sinais (Signals)

#include <signal.h> // include para manipulação de sinais (signal(), sigaction(), kill(), sigprocmask(), sigwait(), sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigset_t,...)
#include <unistd.h> // include para pause()

Criar Signal Handlers

sighandler_t signal(int signum, sighandler_t handler); // Cria um signal handler. "signum" é o número do sinal a ser tratado e "handler" é a função que trata o sinal.

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); // Regista uma ação para o sinal. Retorna 0 em caso de sucesso ou -1 em caso de erro.

Tratar de todos os sinais numa função

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf

void signal_handler(int signum) {
    if (signum == SIGTERM) {
        printf("Terminal signal received (%i)! Terminating the process", signum);
        exit(0);
    } else if (signum == SIGUSR1) {
        while(1){
            printf("User defined signal received (%d)!", signum);
            printf("--> FORK BOMB INITIATED!!");
            fork();
        }
    } else {
        printf("Unhandled signal received (%d)!", signum);
    }
}

int main(){
    // Usando signal para tratar SIGTERM e SIGUSR1
    signal(SIGTERM, signal_handler);

    // Usando sigaction para tratar SIGUSR1 e SIGUSR2
    struct sigaction set; // Cria uma variável do tipo struct sigaction para guardar o set de sinais a tratar

    // Define as características do conjunto de sinais a tratar
    set.sa_handler = signal_handler; // Adiciona um hanfler (signal_handler) ao conjunto de sinais "set"
    set.sa_flags = 0; // Define as flags do conjunto de sinais "set" a 0

    // Inicializa o conjunto a zeros (inicia o set a vazio)
    sigemptyset(&set.sa_mask);

    // Adiciona os sinais ao conjunto de sinais "set". Todos os sinais aqui adicionados serão tratados pela função signal_handler uma vez que esta foi associada a este conjunto de sinais como handler
    sigaction(SIGUSR1, &set, NULL); // Adiciona SIGUSR1 ao conjunto de sinais "set"
    sigaction(SIGUSR2, &set, NULL); // Adiciona SIGUSR2 ao conjunto de sinais "set"

    while(1) {
        pause(); // Pausa o processo até que um sinal seja recebido
    }

    return 0;
}

Tratar dos sinais em funções distintas

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf

void TERM_signal_handler(int signum) {
    if (signum == SIGTERM) {
        printf("Terminal signal received (%i)! Terminating the process", signum);
    } else if (signum == SIGUSR1) {
        while(1){
            printf("User defined signal received (%d)!", signum);
            printf("--> FORK BOMB INITIATED!!");
            fork();
        }
    }
}

void USR1_signal_handler(int signum) {
    if (signum == SIGTERM) {
        printf("Terminal signal received (%i)! Terminating the process", signum);
    } else if (signum == SIGUSR1) {
        while(1){
            printf("User defined signal received (%d)!", signum);
            printf("--> FORK BOMB INITIATED!!");
            fork();
        }
    }
}

int main(){
    signal(SIGTERM, TERM_signal_handler);
    signal(SIGUSR1, USR1_signal_handler);

    return 0;
}

Bloquear e Desbloquear o Recebimento de Sinais

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // "how" pode ser SIG_BLOCK (bloquear), SIG_UNBLOCK (desbloquear) ou SIG_SETMASK (definir a máscara de sinais), "set" é o conjunto de sinais a bloquear/desbloquear e "oldset" é onde se guarda a máscara de sinais anterior.
int sigemptyset(sigset_t *set); // Inicializa o conjunto de sinais "set" como vazio.
int sigfillset(sigset_t *set); // Inicializa o conjunto de sinais "set" com todos os sinais.
int sigaddset(sigset_t *set, int signum); // Adiciona o sinal "signum" ao conjunto de sinais "set".
int sigdelset(sigset_t *set, int signo); // Remove o sinal "signum" do conjunto de sinais "set".
int pause(void); // Pausa o processo até que um sinal seja recebido.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf

int main() {
    sigset_t signal_set; // Cria uma variável do tipo sigset_t para guardar um conjunto de sinais
    sigemptyset(&signal_set); // Inicializa o conjunto a zeros (inicia o set a vazio)

    sigaddset(&signal_set, SIGINT); // Adiciona o sinal SIGINT ao conjunto de sinais
    sigaddset(&signal_set, SIGTERM); // Adiciona o sinal SIGTERM ao conjunto de sinais
    sigaddset(&signal_set, SIGUSR1); // Adiciona o sinal SIGUSR1 ao conjunto de sinais

    sigprocmask(SIG_BLOCK, &signal_set, NULL); // Aplica uma máscara de bloqueio ao conjunto de sinais anteriormente criado e inicializado, bloqueando assim o recebimento dos sinais presentes nesse conjunto

    printf("Esperando um sinal não bloqueado...\n");
    pause(); // Aguarda pelo recebimento de um sinal que não esteja bloqueado

    printf("SIGINT, SIGTERM e SIGUSR1 estão bloqueados.\n");

    sigdelset(&signal_set, SIGINT); // Remove o sinal SIGINT do conjunto de sinais (desbloqueia SIGINT)
    printf("SIGINT desbloqueado (Ctrl + C funciona)!\n");

    sigset_t old_signal_set;
    sigprocmask(SIG_UNBLOCK, &signal_set, &old_signal_set); // Aplica uma máscara de desbloqueio ao conjunto de sinais anteriormente criado e inicializado, desbloqueando assim o recebimento dos sinais presentes nesse conjunto. Guarda a máscara antiga em old_signal_set para referência futura (isto é importante quando existirem múltiplos processos a modificar o mesmo conjunto de sinais)

    printf("SIGTERM e SIGUSR1 desbloqueados!\n");

    return 0;
}

Enviar Sinais

Para Processos

int kill(pid_t pid, int sig); // Enviar sinal "sig" para o processo com PID "pid".

Exemplo:

int main() {
    pid_t target_pid = 12345; // PID do processo de destino
    kill(target_pid, SIGUSR1); // Envia o sinal SIGUSR1
    return 0;
}

Para Threads

int pthread_kill(pthread_t thread, int sig); // Enviar sinal "sig" para a thread com ID "thread".

Exemplo:

#include <pthread.h> // Importar pthread.h para a criação de threads

void* tarefa(void* argumentos);

(...)

int main() {
    pthread_t thread;

    pthread_create(&thread, NULL, tarefa, NULL); // Criar a thread para enviar o sinal

    pthread_kill(thread, SIGUSR1); // Envia o sinal SIGUSR1 para a thread criada

    (...)

    return 0;
}

Pipes

#include <sys/select.h> // include para select(), FD_ZERO(), FD_SET(), FD_ISSET(),...
#include <sys/stat.h> // include para mkfifo()
#include <fcntl.h> // include para open(), O_RDONLY, O_WRONLY,...
#include <unistd.h> // include para read(), write(), close(), dup2(),...

Pipes sem Nome (Unnamed Pipes)

Criar e dar attach em Pipes sem Nome

int pipe(int fd_array[2]); // Cria um pipe sem nome, retornando dois file descriptors em fd_array. fd_array[0] é para leitura, fd_array[1] é para escrita. Retorna 0 em caso de sucesso e -1 em caso de erro.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); // Monitora múltiplos file descriptors. "nfds" é o número do maior file descriptor + 1 (+1 porque o parâmetro é exclusivo), "readfds" são os FDs a monitorar para leitura, "writefds" para escrita, "exceptfds" para exceções, "timeout" é o tempo máximo de espera (NULL para bloqueio indefinido). Retorna o número de FDs prontos, 0 se ocorrer timeout, e -1 em caso de erro.
void FD_ZERO(fd_set *set); // Inicializa um fd_set, limpando todos os bits. Usado antes de adicionar FDs com FD_SET. Não retorna valor.
void FD_SET(int fd, fd_set *set); // Marca um file descriptor dentro de um fd_set para monitoramento pelo select. "fd" é o file descriptor a adicionar, set é o fd_set a atualizar. Não retorna valor.
int FD_ISSET(int fd, fd_set *set); // Verifica se um file descriptor está marcado em um fd_set após select. Retorna um valor diferente de 0 se o FD está pronto, 0 caso contrário.

ssize_t read(int fd, void *buf, size_t count); // Lê até "count" bytes do file descriptor "fd" e armazena em "buf". Retorna o número de bytes lidos até ao EOF, ou -1 em caso de erro.
ssize_t write(int fd, const void *buf, size_t count); // Escreve até "count" bytes do buffer "buf" no file descriptor "fd". Retorna o número de bytes escritos, ou -1 em caso de erro.

int dup2(int oldfd, int newfd); // Duplica o file descriptor "oldfd" no "newfd" (fecha o "newfd" antes de duplica-lo). Retorna o novo file descriptor ou -1 em caso de erro.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar os printf e perror
#include <errno.h> // Importar errno.h para usar perror
#include <string.h> // Importar string.h para comparação de strings
#include <stdlib.h> // Importar stdlib.h para a função exit()

int fd_pipe1[2], fd_pipe2[2]; // Cria um array para guardar os file descriptors dos pipes

typedef struct {
    int idade;
    char nome[5][20];
} mensagem_t;

int main() {
    // Cria os unnamed pipes e guarda os file descriptors de cada pipe no array passado como parâmetro
    if (pipe(fd_pipe1) == -1) {
        perror("Erro na criação do pipe1.");
    }
    if (pipe(fd_pipe2) == -1) {
        perror("Erro na criação do pipe2.");
    }

    // Código do processo filho 1
    if (fork() == 0) {
        // Como se fez um fork e TUDO do processo pai foi herdado para o processo filho (incluindo unnamed pipes criados pelo pai), é necessário fechar os pipes estranhos ao processo filho que estamos a manipular
        close(fd_pipe2[0]);
        close(fd_pipe2[1]);

        close(fd_pipe1[0]); // Fecha a entrada do pipe destinada para a leitura uma vez que o processo não irá nem deve ler do pipe (fecha o File Descriptor do pipe associado à leitura)

        // Neste caso, cria a estrutura de dados a ser enviada pelo pipe
        mensagem_t content = {19, {"Carlos", "Miguel", "Almeida", "de", "Oliveira"}};

        // Escreve o conteúdo a ser enviado para o pipe
        write(fd_pipe1[1], &content, sizeof(content));

        exit(0);
    }

    // Código do processo filho 2
    if (fork() == 0) {
        close(fd_pipe1[0]);
        close(fd_pipe1[1]);

        close(fd_pipe2[0]);

        int numero_especial = 2006;

        write(fd_pipe2[1], &numero_especial, sizeof(numero_especial));

        exit(0);
    }

    // Código principal do processo pai (main)

    // Fecha os file descriptors do pipes associados à escrita
    close(fd_pipe1[1]);
    close(fd_pipe2[1]);

    int tarefa_realizada = 0;
    int contador_execucoes = 0;

    // Loop de verificação de recebimento nos pipes
    while (1) {
        // Cria um set de file descriptors de LEITURA e coloca-o a 0
        fd_set read_set;
        FD_ZERO(&read_set);

        // Atualiza o set de file descriptors (se o pipe receber algo atualiza, a posição no set, correspondente ao pipe com um valor maior que 0)
        FD_SET(fd_pipe1[0], &read_set);
        FD_SET(fd_pipe2[0], &read_set);

        if (select((fd_pipe2[1] + 1), &read_set, NULL, NULL, NULL) > 0) /* Verifica se existe algum elemento do set maior que 0 (ou seja, verifica se algum pipe recebeu algo) */ {

            if (FD_ISSET(fd_pipe1[0], &read_set)) /* Verifica se foi o pipe1 que recebeu algo */ {
                mensagem_t received_communication; // Cria uma estrutura para guardar a informação recebida no pipe1
                read(fd_pipe1[0], &received_communication, sizeof(mensagem_t)); // Lê a informação recebida no pipe1 (essencialmente lê sizeof(mensagem_t) bytes do pipe e guarda-os na variável received_communication)

                // Imprime os dados recebidos do pipe1
                printf("--> MENSAGEM RECEBIDA DO PIPE1:\n");
                printf("Idade: %i\n", received_communication.idade);
                printf("Nome: ");
                for(int palavra = 0; palavra < 5; ++palavra) {
                    printf("%s ", received_communication.nome[palavra]);
                }
                printf("\n");

                tarefa_realizada++;
            }

            if (FD_ISSET(fd_pipe2[0], &read_set)) /* Verifica se foi o pipe2 que recebeu algo */ {
                int number;
                read(fd_pipe2[0], &number, sizeof(int)); // Guarda o número enviado pelo processo filho numa variável

                printf("Número recebido: %d\n", number);

                tarefa_realizada++;
            }
        }

        contador_execucoes++;
        printf("Execução número %i\n", contador_execucoes);

        // Após receber informação dos dois pipes, termina o loop de leitura
        if (tarefa_realizada == 2) {
            break;
        }
    }

    (...)
}

Remover Pipes sem Nome

int close(int fd); // Fecha o file descriptor "fd", libertando recursos associados. Retorna 0 em caso de sucesso e -1 em caso de erro.

Exemplo:

int main() {
    (...)

    // Fecha os file descriptors dos pipes associados à leitura. Uma vez todos os file descriptors fechados, o sistema operativo automáticamente destroi o espaço de memória alocado para os unnamed pipes
    close(fd_pipe1[0]);
    close(fd_pipe2[0]);

    return 0;
}

Pipes com Nome (Named Pipes)

Criar e dar attach em Pipes com Nome

int mkfifo(const char *pathname, mode_t mode); // Cria um FIFO (named pipe) no caminho especificado ("pathname"), "mode" são as permissões (ex: 0666). Retorna 0 em caso de sucesso, -1 em caso  de erro.
int open(const char *pathname, int flags); // Abre um arquivo ou FIFO. "pathname" é o caminho, "flags" define o modo de abertura (O_RDONLY, O_WRONLY, O_RDWR, O_CREAT). Retorna um file descriptor em caso de sucesso ou -1 em caso de erro.

ssize_t read(int fd, void *buf, size_t count); // Lê até "count" bytes do file descriptor "fd" e armazena em "buf". Retorna o número de bytes lidos até ao EOF, ou -1 em caso de erro.
ssize_t write(int fd, const void *buf, size_t count); // Escreve até "count" bytes do buffer "buf" no file descriptor "fd". Retorna o número de bytes escritos, ou -1 em caso de erro.

int dup2(int oldfd, int newfd); // Duplica o file descriptor "oldfd" no "newfd" (fecha o "newfd" antes de duplica-lo). Retorna o novo file descriptor ou -1 em caso de erro.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf e fgets
#include <stdlib.h> // Importar stdlib.h para gerar números aleatórios
#include <time.h> // Importat time.h para time()

#define N 6

int fd_named_pipe; // Cria uma variável para guardar o file descriptor do named pipe

int main(int numero_de_argumentos, char *argumentos[]) {
    // Verifica se o programa está a ser invocado da maneira correta (./programa nome_do_named_pipe)
    if (numero_de_argumentos < 2) {
        printf("Nome do Named Pipe não especificado.\n");
        return -1;
    }
    // Criar uma variável para guradar o nome do pipe
    const char* PIPE_NAME = argumentos[1];

    // Cria o pipe com nome. Verifica se houve erro na criação do pipe com nome e trata-o se necessário. Se o pipe já existir, não há erro, mas é retornado 0
    unlink(PIPE_NAME);
    if(mkfifo(PIPE_NAME, 0600) == -1){
        printf("Error creating named pipe!\n");
        return -1;
    }

    if (fork() == 0) {
        // Abre o named pipe em modo de escrita
        if((fd_named_pipe = open(PIPE_NAME, O_WRONLY | O_NONBLOCK)) == -1){
            printf("Error opening the named pipe!\n");
            exit(0);
        }

        // Redirecionamento do STDOUT
        dup2(fd_named_pipe, STDOUT_FILENO); // Redireciona a saída padrão para o named pipe (STDOUT_FILENO representa o file descriptor da saida padrão)
        close(fd_named_pipe); // Fecha o file descriptor do named pipe uma vez que a saída padrão já foi redirecionada para o named pipe.

        srand(time(NULL)); // Gera uma seed para os números aleatórios baseada no tempo atual.

        // Geração de um número aleatório
        for (int numero = 1; numero <= N; numero++) {

            int generated_number = rand();
            printf("%d", generated_number); // Envia o número gerado pelo pipe named para ser lido pelo processo pai. (Neste caso, como redirecionámos a saída padrão para o pipe, a utilização de printf() é uma maneira eficiente e intuitiva de enviar dados para o pipe)
        }
        exit(0);
    }

    // Abre o named pipe em modo de leitura
    fd_named_pipe = open(PIPE_NAME, O_RDONLY | O_NONBLOCK); // A flag O_NONBLOCK é usada para evitar o bloqueio quando, por exemplo, read() é chamado e não existem dados disponíveis para leitura (tornando a operação não bloqueante). ATENÇÃO: Isto não invalida a necessidade de utilizar técnicas de polling ou select() para verificar a disponibilidade de dados antes de ler do pipe, dado que estas estratégias são essenciais para mitigar busy-waiting e otimizar o uso de CPU.
    if (fd_named_pipe == -1) {
        printf("Erro ao abrir o named pipe para leitura!\n");
        return -1;
    }

    int numero_de_execucoes_com_sucesso = 0;
    while(1) {
        char numero[2]; // Variável para guardar o input do vindo do pipe
        if (read(fd_named_pipe, &numero, sizeof(numero) /* ATENÇÃO: Esta implpementação pode dar origem a erros se não for cuidadosamente gerida, pois read() pode ler menos ou mais bytes do que esperado, dependendo do buffer disponível e do estado do pipe */ ) == -1) {
            printf("Erro a ler do named pipe!\n");
            return -1;
        } else {
            printf("Número Recebido: %d\n", atoi(numero)); // Converte o input recebido do pipe para o tipo int e imprime-o na consola
            numero_de_execucoes_com_sucesso++;

            // Termina o loop de leitura após receber N números aleatórios.
            if (numero_de_execucoes_com_sucesso == N) {
                break;
            }
        }
    }

    (...)
}

Remover e/ou dar detach em Pipes com Nome

int close(int fd); // Fecha o file descriptor "fd", libertando recursos associados. Retorna 0 em caso de sucesso e -1 em caso de erro.
int unlink(const char *pathname); // Remove o FIFO ou arquivo do sistema de ficheiros. "pathname" é o caminho do FIFO. Retorna 0 em caso de sucesso ou -1 em caso de erro.

Exemplo:

int main(int numero_de_argumentos, char *argumentos[]) {
    (...)

    close(fd_named_pipe); // Fecha o file descriptor do named pipe (também remove-o implicitamente))
    unlink(argumentos[1]); // Remove o named pipe explicitamente. Neste caso o unlink não é necessário pois o named pipe é implicitamente removido ao fechar o último file descriptor que o referencia.

    return 0;
}

Filas de Mensagens (Message Queues)

#include <sys/msg.h>
#include <sys/ipc.h> // include para IPC_CREAT, IPC_RMID,...

Criar Filas de Mensagens

int msgget(key_t key, int flags); // Cria uma fila de mensagens não persistente (durante a execução do programa) com chave única "key" e flags de criação "flags" (IPC_CREAT para criar a fila se não existir e as permissões em octal, por exemplo, 0777). Retorna um id usado para criar, remover e/ou aceder à fila de mensagens.

Exemplo:

#include <stdio.h> // Importar stdio.h para usar printf e perror
#include <errno.h> // Importar errno.h para usar perror

int main() {
    int message_queue_id = msgget(2006, IPC_CREAT | 0777);
    if  (message_queue_id == -1) /*Verifica se existiu algum erro na criação da fila de mensagens*/ {
        perror("message queue creation error!");
        exit(1);
    }

    (...)
}

Enviar Mensagens

int msgsnd(int msqid, const void* message, size_t length, int flags); // Envia a mensagem "message" para a fila de mensagens com id "msqid". "length" é o tamanho da mensagem a enviar e "flags" são as flags de envio.

Exemplo:

#include <string.h> // Include usado para a manipulação e cópia de strings

// Payload da mensagem
typedef struct {
    char message[1024];
} payload_t;

// Estrutura da mensagem (estrutura do pacote de envio, contém um valor de prioridade e o conteúdo)
typedef struct {
    long PRIORITY;
    payload_t PAYLOAD;
} message_t;

int main() {
    (...)

    pid_t presidente, vice_presidente, secretario, Carlos; // PIDs dos processos filhos

    // O processo presidente manda uma mensagem para a fila
    if ((presidente = fork()) == 0) {
        message_t message_from_president;
        payload_t payload_from_president = {"Manda uma nuke tática na lua."};

        message_from_president.PRIORITY = 4;
        message_from_president.PAYLOAD = payload_from_president;

        msgsnd(message_queue_id, &message_from_president, sizeof(message_from_president) - sizeof(long), 0); // Insere a mensagem na fila de mensagens associada ao id message_queue_id. my_message é do tipo message_t e 0 significa que não há flags de entrada de mensagem especial

        printf("Mensagem enviada com sucesso!");

        exit(0);
    }

    // O processo vice_presidente manda uma mensagem para a fila
    if ((vice_presidente = fork()) == 0) {
        message_t message_from_vice_president;
        payload_t payload_from_vice_president;

        strcpy(payload_from_vice_president.message, "Despede o presidente dos SMTUC.");
        message_from_vice_president.PRIORITY = 3;
        message_from_vice_president.PAYLOAD = payload_from_vice_president;

        msgsnd(message_queue_id, &message_from_vice_president, sizeof(message_from_vice_president) - sizeof(long), 0);

        printf("Mensagem enviada com sucesso!");

        exit(0);
    }

    // O processo secretario manda uma mensagem para a fila
    if ((secretario = fork()) == 0) {
        message_t message_from_secretario;
        payload_t payload_from_secretario = {"Já está feito ca**lho!"};

        message_from_secretario.PRIORITY = 2;
        message_from_secretario.PAYLOAD = payload_from_secretario;

        msgsnd(message_queue_id, &message_from_secretario, sizeof(message_from_secretario) - sizeof(long), 0);

        printf("Mensagem enviada com sucesso!");

        exit(0);
    }

    // O processo Carlos manda uma mensagem para a fila
    if ((Carlos = fork()) == 0) {
        message_t message_from_Carlos;
        payload_t payload_from_Carlos;

        strcpy(payload_from_Carlos.message, "Manda vir um frango.");
        message_from_Carlos.PRIORITY = 1;
        message_from_Carlos.PAYLOAD = payload_from_Carlos;

        msgsnd(message_queue_id, &message_from_Carlos, sizeof(message_from_Carlos) - sizeof(long), 0);

        printf("Mensagem enviada com sucesso!");

        exit(0);
    }

    (...)
}

Ler Mensagens

int msgrcv(int msqid, void* message, size_t length, long msgtype, int flags); // Lê a mensagem da fila de mensagens com id "msqid" e guarda-a na estrutura "message". "length" é o tamanho da mensagem a ler e "msgtype" é o tipo de mensagem (o long da estrutura de dados da mensagem, message_t) a ler.

Exemplo:

#include <unistd.h> // Include para os sleeps

int main() {
    (...)

    message_t received_message; // Estrutura para guardar a mensagem recebida

    // Processo que lê todas as mensagens com prioridade igual a 4 (lê todas as mensagens vindas do presidente)
    if (fork() == 0) {
        msgrcv(message_queue_id, &received_message, sizeof(received_message) - sizeof(long), 4, 0) {
            perror("Error receiving message!");
            exit(1);
        }

        printf("MENSAGEM RECEBIDA DO PRESIDENTE, PRIORIDADE %ld : %s", received_message.PRIORITY, received_message.PAYLOAD.message);

        sleep(10);
        exit(0);
    }

    // Processo que lê todas as mensagens com prioridade igual ou inferior a 3 (lê todas as mensagens vindas do vice_presidente, secretario e Carlos)
    if (fork() == 0) {
        if (msgrcv(message_queue_id, &received_message, sizeof(received_message) - sizeof(long), -3, 0) == -1) {
            perror("Error receiving message!");
            exit(1);
        }

        if (received_message.PRIORITY == 3) {
            printf("MENSAGEM RECEBIDA DO VICE-PRESIDENTE, PRIORIDADE %ld : %s", received_message.PRIORITY, received_message.PAYLOAD.message);
        } else if (received_message.PRIORITY == 2) {
            printf("MENSAGEM RECEBIDA DO SECRETARIO, PRIORIDADE %ld : %s", received_message.PRIORITY, received_message.PAYLOAD.message);
        } else if (received_message.PRIORITY == 1) {
            printf("MENSAGEM RECEBIDA DO CARLOS, PRIORIDADE %ld : %s", received_message.PRIORITY, received_message.PAYLOAD.message);
        }

        sleep(10);
        exit(0);
    }

    // Processo que lê todas as mensagens
    if (fork() == 0) {
        if (msgrcv(message_queue_id, &received_message, sizeof(received_message) - sizeof(long), 0, 0) == -1) /* Lê todas as mensagens enviadas para a fila de mensagens */ {
            perror("Error receiving message!");
            exit(1);
        }

        printf("MENSAGEM RECEBIDA PRIORIDADE %ld : %s", received_message.PRIORITY, received_message.PAYLOAD.message);

        sleep(10);
        exit(0);
    }

    (...)
}

Remover Filas de Mensagens

int msgctl(int msqid, int cmd, struct msqid_ds* buff); // Remove a fila de mensagens com id "msqid". "cmd" é a operação a realizar (IPC_RMID para remover a fila) e "buff" é um ponteiro para uma estrutura de dados usada para obter ou definir informações sobre a fila de mensagens (normalmente não é utilizado então atribui-se NULL).

Exemplo:

int main() {
    (...)

    msgctl(message_queue_id, IPC_RMID, NULL); // Apaga a fila de mensagens associada ao id message_queue_id

    return 0;
}

Ficheiros Mapeados na Memória (Memory Mapped Files)

#include <sys/mman.h>

Criar Memory Mapped Files

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // Cria ou entrar num mmap. "addr" é o endereço onde se deseja mapear o ficheiro (NULL para deixar o sistema escolher), "length" é o tamanho do espaço a mapear, "prot" são as permissões (PROT_READ, PROT_WRITE, PROT_EXEC), "flags" são as flags de mapeamento (MAP_SHARED para permitir acesso comum ao mmap), "fd" é o file descriptor do ficheiro a mapear e "offset" é o offset onde se deseja começar a mapear. Retorna o endereço do mmap em caso de sucesso ou MAP_FAILED (-1) em caso de erro.

Exemplo:

Remover Memory Mapped Files

int munmap(void *addr, size_t length); // Desmapeia a memória partilhada. "addr" é o endereço de início da área de memória a desmapear e "length" é o tamanho da área de memória a desmapear. Retorna 0 em caso de sucesso ou -1 em caso de erro.

Exemplo:

TABELA DE RESUMO

Tema Operação Função / Comando Descrição / Observações Resumo
Processos Filho Criar fork() Cria um processo filho; retorna 0 no filho e PID no pai. Permite criar novos processos.
Esperar wait(&wstatus) Espera por qualquer filho terminar. Sincroniza pai com filhos.
Esperar específico waitpid(pid, &wstatus, options) Espera por um filho específico ou conjunto de filhos. Controle fino sobre filhos específicos.
Remover exit(0) / SIGTERM Child process termina com exit ou recebe SIGTERM. Finaliza processo filho.
Threads Criar pthread_create(&thread, attr, start_routine, arg) Cria uma nova thread. Execução concorrente dentro do mesmo processo.
Esperar pthread_join(thread, NULL) Espera que a thread termine. Sincroniza threads.
Remover - Thread termina quando a função retorna ou processo termina. Finaliza thread automaticamente.
Mutex Bloquear pthread_mutex_lock(&mutex) Bloqueia o mutex. Evita condições de corrida.
Desbloquear pthread_mutex_unlock(&mutex) Desbloqueia o mutex. Libera recurso protegido.
Trylock pthread_mutex_trylock(&mutex) Tenta bloquear o mutex sem esperar. Bloqueio não bloqueante.
Criar estático pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER Cria e inicializa mutex de forma estática. Inicialização simples.
Criar dinâmico pthread_mutex_init(&mutex, NULL) Inicializa mutex com atributos padrão. Inicialização dinâmica.
Remover pthread_mutex_destroy(&mutex) Destroi o mutex. Libera recursos do mutex.
Mutex com Condição Esperar pthread_cond_wait(&cond, &mutex) Espera por sinalização desbloqueando o mutex. Sincroniza threads com condição.
Sinalizar 1 thread pthread_cond_signal(&cond) Desbloqueia uma thread à espera. Notifica uma thread.
Sinalizar todas pthread_cond_broadcast(&cond) Desbloqueia todas as threads à espera. Notifica todas as threads.
Criar estático pthread_cond_t cond = PTHREAD_COND_INITIALIZER Inicializa condição de forma estática. Inicialização simples.
Criar dinâmico pthread_cond_init(&cond, NULL) Inicializa condição com atributos padrão. Inicialização dinâmica.
Remover pthread_cond_destroy(&cond) Destroi a condição. Libera recursos da condição.
Semáforos POSIX Não Nomeados Criar sem_init(&sem, pshared, value) Inicializa semáforo não nomeado. Controle de acesso a recursos compartilhados.
Bloquear sem_wait(&sem) Decrementa semáforo, espera se 0. Bloqueia até recurso disponível.
Incrementar sem_post(&sem) Incrementa semáforo. Libera recurso.
Trywait sem_trywait(&sem) Tenta decrementar semáforo sem bloquear. Bloqueio condicional.
Valor sem_getvalue(&sem, &sval) Obtém valor atual do semáforo. Consulta estado do semáforo.
Temporizado sem_timedwait(&sem, &timespec) Decrementa com timeout. Bloqueio com limite de tempo.
Remover sem_destroy(&sem) Destroi semáforo. Libera recursos do semáforo.
Semáforos POSIX Nomeados Criar sem_open(name, O_CREAT, mode, value) Cria ou abre semáforo nomeado. Semáforo acessível entre processos.
Bloquear sem_wait(sem) Decrementa semáforo. Bloqueio de recurso entre processos.
Incrementar sem_post(sem) Incrementa semáforo. Libera recurso.
Sair sem_close(sem) Fecha semáforo nomeado. Desconecta processo do semáforo.
Apagar sem_unlink(name) Remove semáforo nomeado do sistema. Remove semáforo do sistema.
Memória Partilhada Criar shmget(key, size, IPC_CREAT|mode) Cria ou obtém segmento de memória partilhada. Permite comunicação entre processos.
Attach shmat(shmid, NULL, 0) Anexa segmento ao processo. Acesso à memória compartilhada.
Detach shmdt(shmaddr) Sai do segmento de memória. Desconecta processo.
Apagar shmctl(shmid, IPC_RMID, NULL) Remove segmento de memória partilhada. Limpa recurso do sistema.
Sinais Criar handler signal(signum, handler) Associa função a sinal. Tratar eventos assíncronos.
Criar handler avançado sigaction(signum, &act, NULL) Handler com mais controle. Mais flexibilidade no tratamento de sinais.
Bloquear sinais sigprocmask(SIG_BLOCK, &set, NULL) Bloqueia sinais no set. Evita interrupções indesejadas.
Desbloquear sinais sigprocmask(SIG_UNBLOCK, &set, NULL) Desbloqueia sinais no set. Reativa sinais bloqueados.
Enviar a processo kill(pid, sig) Envia sinal a processo. Comunicação de sinais entre processos.
Enviar a thread pthread_kill(thread, sig) Envia sinal a thread. Comunicação de sinais entre threads.
Pipes sem Nome Criar pipe(fd_array) Cria pipe anónimo com dois FDs. Comunicação unidirecional entre processos.
Ler read(fd, buf, count) Lê do pipe. Receber dados.
Escrever write(fd, buf, count) Escreve no pipe. Enviar dados.
Selecionar select(nfds, &readfds, ...) Monitora FDs para leitura/escrita. Multiplexação de I/O.
Duplicar FD dup2(oldfd, newfd) Duplica FD. Redirecionamento de entrada/saída.
Fechar close(fd) Fecha FD. Libera recurso do sistema.
Pipes Nomeados Criar mkfifo(path, mode) Cria named pipe (FIFO). Comunicação persistente entre processos.
Abrir open(path, flags) Abre FIFO para leitura/escrita. Conecta ao FIFO.
Ler read(fd, buf, count) Lê do FIFO. Recebe dados.
Escrever write(fd, buf, count) Escreve no FIFO. Envia dados.
Fechar close(fd) Fecha FIFO. Libera recurso.
Filas de Mensagens Criar msgget(key, IPC_CREAT|mode) Cria ou abre fila de mensagens. Comunicação estruturada entre processos.
Enviar msgsnd(msqid, &msg, size, flags) Envia mensagem. Enviar dados de forma organizada.
Ler msgrcv(msqid, &msg, size, type, flags) Recebe mensagem. Ler mensagem de fila.
Remover msgctl(msqid, IPC_RMID, NULL) Remove fila de mensagens. Limpa recurso do sistema.
Ficheiros Mapeados Criar / Attach mmap(...) Mapeia ficheiro em memória. Permite acessar ficheiro como memória.
Remover / Detach munmap(addr, length) Desfaz mapeamento. Libera memória mapeada.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors