SOP29005-2019-2

De MediaWiki do Campus São José
Ir para: navegação, pesquisa

Índice

AULA 1 - Dia 30/07/2019

Objetivos/Conteúdos

  • Apresentação do Plano de Ensino
    • Objetivos e Conteúdo Programático da Disciplina (ver SIGAA)
    • Forma de Avaliação (ver SIGAA)
  • Introdução a Sistemas Operacionais (Cap1. do Silberschatz)
    • O que faz um sistema operacional (1.1)
    • Organização e Arquitetura de um Sistema Computacional (1.2 e 1.3)
      • Importância da Interrupção e Timers (1.2.1)
      • Estrutura de Armazenamento (1.2.2)
      • Estrutura de IO (1.2.3)
    • Estrutura e Operações de um Sistema Operacional (1.4 e 1.5)

Material de Referência

Arliones:

Leitura Recomendada

  • Cap.1 do Silberschatz principalmente:
    • 1.1 a 1.9

Exercícios Práticos de Apoio a Aula

Na sequência. com fins motivacionais, são apresentados alguns exercícios ilustrando conceitos de processos, arquivos e permissionamento.

  1. Comando para ver todos os processos no linux: $ ps aux </syntaxhighlight>
  2. Colocar 2 processos se executando no contexto de um terminal e verificar número dos processos e então "destruí-los": $ yes > /dev/null $ yes > /dev/null $ ps $ kill -9 <pid_yes> $ kill -9 <pid_yes> </syntaxhighlight> Observe que dois processos foram criados a partir do programa "yes". Os processos associados ao terminal são visualizados e então destruídos. Tente destruir também o interpretador de comando (shell) associado ao terminal.
  3. Criar um terminal adicional $ xterm </syntaxhighlight> Ir no terminal criado e listar os processos que se executam associados a este terminal. Verificar qual o dispositivo de entrada e saída associado a ele. Voltar ao terminal original e enviar uma mensagem. Destruir o terminal criado: $ echo Alo Terminal > /dev/pts/2 $ kill -9 <pid_terminal> </syntaxhighlight>
  4. Comunicação entre processos: $ cat /etc/passwd | grep home | wc -l </syntaxhighlight>
  5. Retirando a permissão de leitura de um arquivo em nível de usuário proprietário: $ echo Alo Mundo > alfa.txt $ ls -l alfa.txt $ cat alfa.txt $ chmod u-r alfa.txt $ cat alfa.txt $ chmod u+r alfa.txt $ cat alfa.txt </syntaxhighlight>

AULA 2 - Dia 2/08/2019

Objetivos/Conteúdos

  • Continuação da Aula
  • Estruturas do Sistema Operacional (cap.2)
    • Serviços do Sistema Operacional (2.1)
    • Interfaces com Usuários (2.2)
    • Chamadas do Sistema (2.3)
    • Tipos de Chamadas de Sistema (2.4) ver Fig.2.8
      • Chamadas de Controle de Processos
    • Arquiteturas e Estruturas de Sistemas Operacionais (2.7)
    • Máquinas Virtuais (2.8)

Material de Referência

Leitura Recomendada

  • Cap.2 do Silberschatz principalmente:
    • 2.1 a 2.8

Exercícios

AULA 3 - Dia 7/08/2019

Objetivos

  • Continuação da aula anterior
    • Chamadas do Sistema (2.3)
    • Tipos de Chamadas de Sistema (2.4) ver Fig.2.8
      • Chamadas de Controle de Processos
    • Arquiteturas e Estruturas de Sistemas Operacionais (2.7)
    • Máquinas Virtuais (2.8)

Exercícios

  1. Estudar e executar o código em http://cs.lmu.edu/~ray/notes/gasexamples/ Detalhes das chamadas na arquitetura x86_64 ver em https://lwn.net/Articles/604287/
  2. Estudar (usar o man) e executar o código usando a função (em conformidade com a API POSIX) write() para o hello world:
    1. include <unistd.h>
    main() { write(1,"Alo Mundo\n",10); } </syntaxhighlight>
  3. Use o comando strace para verificar todas as chamadas de sistema dos programas acima.
  4. DESAFIO 1: Estude a seção "Mixing C and Assembly Language" da http://cs.lmu.edu/~ray/notes/gasexamples/ e construa uma função meu_hello() usando o código em assembly do exercício inicial. Estude como poderia disponibilizar esta e outras funções de interface (a sua API) em uma biblioteca. Note que esta função deve ser chamada da forma: void meu_hello(); int main() { meu_hello(); return 0; } </syntaxhighlight>
  5. Gere o assembly do código em C e discuta a diferença entre uma chamada de função e uma chamada de sistema.

  6. DESAFIO 2: Estude o link http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ e melhore a função meu_hello_world para suportar uma mensagem adicional da forma:
    1. include <string.h>
    main() { char *p="Tudo bem com vocês?"; meu_hello_world(p, strlen(p)); } </syntaxhighlight> Solução:
    Solução

    modificado de http://cs.lmu.edu/~ray/notes/gasexamples/

           .global meu_hello_world
    
           .text
    

    meu_hello_world:

           push    %rdi                    # salva primeiro parâmetro
           push    %rsi                    # salva segundo parâmetro
           mov     $1, %rax                # system call 1 é write
           mov     $1, %rdi                # file handle 1 é stdout
           mov     $message, %rsi          # endereço da string
           mov     $13, %rdx               # número de bytes da string
           syscall                         # chamada ao sistema
    
           pop     %rdx                    # o que havia sido passado em rsi (número de bytes) é colocado em rdx
           pop     %rsi                    # o que havia sido colocado em rdi (endereço da string) é colocado em rsi
           mov     $1, %rax                # system call 1 is write
           mov     $1, %rdi                # file handle 1 é stdout
           syscall                         # nova chamada ao sistema
    
           ret
    

    message:

           .ascii  "Hello, World\n"
    

    </syntaxhighlight>

    AULA 4 - Dia 9/08/2019

    Objetivos

    • PARTE 2A: Gerenciamento de Processos (cap.3)
    • 3.1.Conceito de Processo
    • 3.2.Escalonamento de Processos
    • 3.3.Operações sobre Processos (Laboratório Operações sobre Processos no Linux: Fork/Exec/Wait)

    Material de Referência

    • Slides do Cap.3 - Silberschatz
    • Laboratório Fork/Exec/Wait

    Exercícios de Demonstração

    1. Executar em um terminal o comando top. Em um outro terminal executar:
      yes > /dev/null &
      yes > /dev/null &
    
    1. Parar um dos processos yes
      kill -STOP <pid_yes>
    
    1. Continuar o processo
      kill -CONT <pid_yes>
    
    1. Destruir todos
      killall yes
    

    AULA 5 - Dia 14/08/2019

    Objetivos

    • Ainda Processos
    • Cap.3.2..Escalonamento de Processos
    • Cap.2.3.Operaçṍes sobre processos
    • Finalização do Laboratório Fork/Exec/Wait

    Adendo

    • Estudar e comentar [4]

    AULA 6 - Dia 16/08/2019

    Objetivos

    • Comunicação Interprocessos (3.4 e 3.5)
    • Laboratório de Memória Compartilhada

    AULA 7 - Dia 21/08/2019

    Objetivos

    • Comunicação Interprocessos: Troca de Mensagens (3.6)
    • Laboratório de Pipes

    AULA 8 - Dia 23/08/2019

    Objetivos

    • Finalização Lab. pipes nomeados.
    • Threads ((cap.4.1, 4.2)
    • Lab.Threads de Aplicação

    AULA 9 - Dia 28/08/2019

    • Laboratório: Ainda Threads - Um escalonador semi-automático


    AULA 10 - Dia 30/08/2019

    Objetivos

    • Repassar um primeiro estudo sobre o RTOS
    • Revisar Processos e Threads

    Questionário - Processos

    1. Conceitue processo. Descreva e explique cada uma das seções de um processo na memória.
    2. Faça um diagrama e explique os possíveis estados de um processo no sistema. O que produz a transição entre estados?
    3. O que é um Bloco de Controle de Processo? Quais informações ficam armazenadas neste bloco?
    4. Faça um diagrama mostrando a alternância entre dois processos na CPU. Indique o procedimento de salvamento e restauração do estado do PCB.
    5. Explique o que é multiprogramação e escalonamento de processos.
    6. O que é a fila de processos prontos?
    7. Diferencie um processo limitado por IO de um processo limitado por CPU.
    8. Diferencie escalonamento de longo prazo de escalonamento de curto prazo.
    9. Discuta o papel da interrupção na troca de contexto entre processos.
    10. Explique como é o processo de criação de novos processos no UNIX/Linux através da chamada fork. O que acontece com a memória do novo processo?
    11. Explique como funciona a chamada exec no UNIX/Linux
    12. Explique como funciona a chamada wait no UNIX/Linux e como é possível retornar valores de um filho para o pai.
    13. Faça um esqueleto de um programa UNIX/Linux que ao se tornar processo cria 3 filhos e espera pela execução dos mesmos.

    Questionário - Comunicação entre Processos

    1. Diferenciar processos independentes de processos cooperantes.
    2. Enumere e explique quatro razões para cooperação entre processos.
    3. A velocidade de processamento é uma das razões da cooperação entre processos. Discuta se um hardware com apenas uma CPU pode usufruir desta característica.
    4. Descreva os dois principais mecanismos de cooperação entre processos. Discuta a situação em que é possível usar a memória compartilhada e/ou a troca de mensagens.
    5. A memória compartilhada requer chamada ao sistema na sua operação? Discuta.
    6. Por que o processo de troca de mensagem deve ser mais lento que a memória compartilhada?
    7. Apresente em pseudocódigo uma proposta de funcionamento do problema do produtor consumidor usando memória compartilhada.
    8. A construção de uma solução para o problema produtor consumidor pode ser facilitado pelo uso da memória compartilhada? Discuta.
    9. Quais as duas operações básicas ma troca de mensagens? O que poderia motivar o uso de mensagens do tamanho fixo? Esta abordagem torna mais complexa a vida dos programadores?
    10. Enumere três métodos de implementação de uma lógica de ligação (link) entre processos.
    11. Explique o que é a comunicação direta em troca de mensagens. Descreva como são as duas primitivas de comunicação direta para esta abordagem e quais as 3 propriedades seguidas por esta comunicação.
    12. Descreva a variante assimétrica da troca direta de mensagens. O que muda em termos de primitiva de comunicação?
    13. Descreva o problema de hard-coding associado a nomeação direta e como pode ser contornado através da nomeação/comunicação indireta?
    14. O que é uma caixa postal? Como o Posix identifica uma caixa postal no caso de fila de mensagens?
    15. Dois processos podem usar mais do que uma caixa postal para se comunicar? Qual a condição para que isto ocorra?
    16. Descreva as duas operações básicas para a troca de mensagens com Caixa Postal.
    17. Descreva na comunicação indireta via Caixa Postal as 3 propriedades básicas.
    18. A questão do compartilhamento de caixas postais por múltiplos processos podem causar um problema na operação da recepção. Descreva quais as 3 possibilidades para contornar este problema.
    19. De que forma a caixa postal estando no espaço de endereçamento de um processo (propriedade) afeta a recepção por mensagens? Descreva.
    20. Quais as operações sobre uma caixa postal criada no espaço do sistema operacional deverão ser disponíveis? Descreva.
    21. A transmissão de mensagem pode ser com e sem bloqueio. Descreva as 4 possibilidades (síncrona/assíncrona).
    22. Discuta as 3 possibilidades de armazenamento de mensagens em buffer. Em que condições um remetente pode ser bloqueado?

    ETAPA 1 - Projeto Final

    • Apresentação (slides) sobre a parte de gerenciamento de tarefas no RTOS;
    • apresentação dia 18/09/2019

    Referências

    AULA 11 - Dia 4/09/2019

    Objetivos

    • Pico de CPU X Pico de IO
    • Preempção X Não Preempção
    • Escalonamento de Processos: Escalonamento FCFS

    AULA 12 - Dia 6/09/2019

    Objetivos

    • Escalonamento de Processos:
      • Shortest Job First (preemptivo e não preemptivo)
      • Prioridade (preemptivo e não preemptivo)
      • Round-Robin: Preemptivo
    • Lab.Round Robin

    Material de Referência

    http://docente.ifsc.edu.br/andre.damato/sop2018/SOP2018-parte2.pdf

    AULA 13 - Dia 11/09/2019

    Objetivos

    Avaliação 1

    AULA 14 - Dia 13/09/2019

    Objetivos

    • Sincronização entre Processos
    • Laboratório do Esquema de Peterson

    Material de Referência

    AULA 15 - Dia 18/09/2019

    Objetivos

    • Sincronização entre Processos
    • Lock de Hardware
    • Laboratório do TSL

    Laboratório Proposto

    • Refazer o problema proposto usando uma instrução simualada de test and set. Prever um liite superior para entrada de um processo na região crítica.

    Material de Referência

    AULA 16 - Dia 20/09/2019

    O ATmega238 usado no Arduino UNO

    Ver [7]

    O Arduino UNO

    Ver [8] Ver [9]

    O FreeRtos para o Arduino

    Ver [10]

    Para instalar no IDE:

    • Baixar o ZIP do link acima
    • No IDE importar a biblioteca:
      • sketch->IncluirBiblioteca->Adicionar do arquivo ZIP


    Código Experimental

    Este código demonstra a ocorrência de "race condition".

    // Code based on Examples of Arduino and examples 
    // from https://github.com/feilipu/Arduino_FreeRTOS_Library
     
    #include <Arduino_FreeRTOS.h>
    #include <FreeRTOSVariant.h>
    #include <task.h>
    #include <semphr.h>
     
    #define TAM_BUFF 10
     
    volatile unsigned long race_flag=0xFFEEDDCC;
    volatile unsigned long int cont;
    int task_cont=0;
     
    void PrintSerial( void *pvParameters ); // Task para imprimir valor de race_flag
    void TaskBlink( void *pvParameters );
    void TaskSetarace_flag(void *pvParameters); //Task que atribui race_flag=0xFFEEDDCC
    void TaskZerarace_flag(void *pvParameters);  //Task que atribui race_flag=0xAA0000AA
     
    // função de setup
     
    void setup() {
     
      Serial.begin(9600);
     
      while (!Serial) {
        ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
      }
     
     
      // criar as 2 tarefas
     
      xTaskCreate(
        TaskBlink
        ,  (const portCHAR *)"Blink"   // A name just for humans
        ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
        ,  NULL
        ,  1  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
        ,  NULL );
     
       xTaskCreate(
        TaskPrintSerial
        ,  (const portCHAR *) "PrintSerial"
        ,  128  // Stack size
        ,  NULL
        ,  1  // Priority
        ,  NULL );
     
        xTaskCreate(
        TaskSetarace_flag
        ,  (const portCHAR *) "Setarace_flag"
        ,  128  // Stack size
        ,  NULL
        ,  1  // Priority
        ,  NULL );
     
           xTaskCreate(
        TaskZerarace_flag
        ,  (const portCHAR *) "Zerarace_flag"
        ,  128  // Stack size
        ,  NULL
        ,  1  // Priority
        ,  NULL );
    
          cont = race_flag;
    
      // escalonador toma conta a partir daqui
     
    }
     
    void loop()
    {
      // nada a fazer aqui
    }
     
    /*--------------------------------------------------*/
    /*---------------------- Tasks ---------------------*/
    /*--------------------------------------------------*/
     
    void TaskBlink(void *pvParameters)  // This is a task.
    {
      (void) pvParameters;
     
    /*
      Blink
      Turns on an LED on for one second, then off for one second, repeatedly.
     
      Most Arduinos have an on-board LED you can control. On the UNO, LEONARDO, MEGA, and ZERO 
      it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN takes care 
      of use the correct LED pin whatever is the board used.
     
      The MICRO does not have a LED_BUILTIN available. For the MICRO board please substitute
      the LED_BUILTIN definition with either LED_BUILTIN_RX or LED_BUILTIN_TX.
      e.g. pinMode(LED_BUILTIN_RX, OUTPUT); etc.
     
      If you want to know what pin the on-board LED is connected to on your Arduino model, check
      the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
     
      This example code is in the public domain.
     
      modified 8 May 2014
      by Scott Fitzgerald
     
      modified 2 Sep 2016
      by Arturo Guadalupi
    */
     
      // initialize digital LED_BUILTIN on pin 13 as an output.
      pinMode(LED_BUILTIN, OUTPUT);
     
      for (;;) // A Task shall never return or exit.
      {
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
        vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
        digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
        vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
      }
    }
     
     
     
     
    void TaskPrintSerial(void *pvParameters)  // Task que imprime na serial - must be improved...
    {
      (void) pvParameters;
    /*
     
    */
     
    
      for (;;)
      {  
        while (cont==0xAA0000AA || cont==0xFFEEDDCC)
              cont = race_flag;
               ;
        
        Serial.println("Problemas: valor race_flag é");
        Serial.println(cont,  HEX);
         
        vTaskDelay(5);  // one tick delay (15ms) in between reads for stability - b
        cont = race_flag;
      }
    }
     
    void TaskSetarace_flag(void *pvParameters)  
    {
      (void) pvParameters;
       const TickType_t xMaxBlockTimeTicks = 0x20;
    /*
     
    */
    
      race_flag=0xAA0000AA;
      for (;;)
      {
        race_flag=0xAA0000AA;
        //Serial.println("Produtor");
        vTaskDelay( ( rand() %xMaxBlockTimeTicks) ); 
      } 
     
    }
     
    void TaskZerarace_flag(void *pvParameters) 
    {
      (void) pvParameters;
         const TickType_t xMaxBlockTimeTicks = 0x20;
    /*
     
    */
      race_flag=0xFFEEDDCC;
    
      for (;;)
      {
        race_flag=0xFFEEDDCC;
        vTaskDelay( ( rand() %xMaxBlockTimeTicks) ); 
        if(task_cont++==100) {
           delay(1);
           Serial.println("Fim TaskZera - valor race_flag ");
           Serial.println(race_flag,  HEX);
           for(;;);
        }   
      }
    }
    

    AULA 17 - Dia 25/09/2019

    Objetivos

    • Preparação de exemplos de RTOS sobre Arduíno para apresentação na sexta;

    Laboratórios Propostos

    Considere o código abaixo. Observe que a região de dados compartilhada shared_data é zerada pelo pai e setada pelo processo filho. O pai detecta inconsistências nos dados que operou. Tente corrigir usando o esquema de Peterson para acesso a região crítica. Crie uma função de entrada e de saída da região crítica.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <string.h>
     
     
    #define BUFFER_SIZE 4096
    char *shared_data;
    char reference_data[BUFFER_SIZE];
     
    main()
    {    
    	key_t key;
    	int shmid;
    	int modo,filho;
     
     
    	shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, S_IRUSR | S_IWUSR);
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
     
            shared_data = shmat(shmid, (void *)0, 0);
     
    	if (shared_data  == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
     
     
    	if (fork() != 0) { /* I am the parent */
             int count=0;	
             memset(reference_data,0,BUFFER_SIZE); 
    		for(;;){	
                         memset(shared_data,0,BUFFER_SIZE);
                         if (memcmp(shared_data, reference_data, BUFFER_SIZE)!=0) {
                             count++;
                             if (count%1000000==0)
    			     printf("Processo pai detectou algo errado nos dados %d\n", count);  
                         }   
    		 }	
     
     
    	}
    	else { /*Child code */
     
    		for(;;){	    		
                       memset(shared_data,1,BUFFER_SIZE);   
    	        }
     
    	}
     
    	if (shmdt(shared_data) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
     
    	exit(0);
    }
    

    Possível solução:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <unistd.h>
     
    #define BUFFER_SIZE 4096
    #define ID_PAI 0
    #define ID_FILHO 1
    #define FALSE 0
    #define TRUE 1
    
    char *shared_data;
    char reference_data[BUFFER_SIZE];
    
    struct tipo_peterson{
      char flag[2];
      char turn;
    } *pet_struct;
    
    void ent_sec_crit(int id)
    {
      char id_outro = (id+1)%2;
    
      pet_struct->flag[id] = TRUE;  // indica que o processo deseja entrar na região crítica
      pet_struct->turn = id_outro;  // o processo sinaliza para o outrao entrar, 
                                    // caso o outro esteja entrando
                                    // este ponto é uma race condition: turn pode ser 0 ou 1 
      while ( (pet_struct->flag[id_outro]==TRUE) && (pet_struct->turn == id_outro) )
         ;
    }
    
    void sai_sec_crit(int id)
    {
      pet_struct->flag[id] = FALSE;
    }
    
    int main()
    {    
    	key_t key;
    	int shmid;
    	int modo,filho;
     
     
    	shmid = shmget(IPC_PRIVATE, BUFFER_SIZE+sizeof(struct tipo_peterson), S_IRUSR | S_IWUSR);
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
     
            shared_data = shmat(shmid, (void *)0, 0);
            pet_struct = (struct tipo_peterson *) (shared_data + BUFFER_SIZE); 
     
    	if (shared_data  == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
     
     
    	if (fork() != 0) { /* I am the parent */
                    int count=0;	
                    memset(reference_data,0,BUFFER_SIZE); 
    		for(;;){	
                         ent_sec_crit(ID_PAI);
                         memset(shared_data,0,BUFFER_SIZE);
                         if (memcmp(shared_data, reference_data, BUFFER_SIZE)!=0) {
                             count++;
                             if (count%1000000==0)
    			     printf("Processo pai detectou algo errado nos dados %d\n", count);  
                         }   
                         sai_sec_crit(ID_PAI);
    		 }	
     
     
    	}
    	else { /*Child code */
     
    		for(;;){
                       ent_sec_crit(ID_FILHO);	    		
                       memset(shared_data,1,BUFFER_SIZE);
                       sai_sec_crit(ID_FILHO);   
    	        }
     
    	}
     
    	if (shmdt(shared_data) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
     
    	exit(0);
    }
    

    AULA 18 - Dia 27/09/2019

    Objetivos

    • Apresentação dos Trabalhos
    • Definição da próxima etapa

    AULA 19 - Dia 02/10/2019

    Objetivos

    • Semáforos, Deadlock e o Problema da Inversão de Prioridade
    • Laboratório de Pthreads e Mutex;

    Laboratório Proposto

    • Lab. Programação concorrente - Parte 1

    Materia de Referência

    Desafio

    Usar

     ps -T aux | grep ex1vA
    

    Para ver os threads dormindo...

    Otimizar o programa abaixo

    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <time.h>
     
    #define NUM_THREADS 5
     
    pthread_mutex_t mut;
     
    int saldo = 1000;
     
    int AtualizaSaldo(int n)
    {
    
    
            long long i;
            pthread_mutex_lock(&mut);
    	int meu_saldo = saldo;
    	int novo_saldo = meu_saldo + n*100;
            usleep(5000000);
    	printf("Novo saldo = %d\n",novo_saldo);
    	saldo = novo_saldo;
            pthread_mutex_unlock(&mut);
    }
     
    int main()
    {
    	pthread_t threads[NUM_THREADS];
            int i,ret;
    
            pthread_mutex_init(&mut, NULL);
    
     
    	// Cria cinco threads que executarão a mesma função
    	for(i=0; i<5; ++i){
    		ret = pthread_create(&threads[i], NULL, (void*(*)(void*))AtualizaSaldo,(void*)((long)i+1));
    		if(ret != 0){
    			fprintf(stderr, "Erro thread %d\n", (i+1));
    			exit(-1);
    		}
    	}
    	// Aguarda o fim das threads
    	for(i=0; i<5; ++i)
    		pthread_join(threads[i], NULL);
     
    	printf("Saldo final é %d\n", saldo);
    }
    

    AULA 20 - Dia 04/10/2019

    Objetivos

    • Ainda Semáforos, Deadlock e o Problema da Inversão de Prioridade
    • Laboratório de Pthreads com semáforo binário;
    • Laboratório de Pthreads com semáforos contador como solução do problema do produtor e consumidor.


    Material de Referência

    • ver laboratório de Programação concorrente

    Laboratório Proposto

    AULA 21 - Dia 9/10/2019

    Objetivos

    • Problemas clássicos de sincronização (seção 6.6 do Silberschatz));

    Material de Referência

    https://www.os-book.com/OSE1/slide-dir/PDF-dir/ch6.pdf

    https://randu.org/tutorials/threads/

    AULA 22 - Dia 11/10/2019

    Objetivos

    Problemas clássicos de sincronização (seção 6.6 do Silberschatz));

    • revisão
    • tarefa em sala de aula

    Tarefa

    A tarefa abaixo valerá 1 ponto na segunda prova. O

    #include <stdio.h> 
    #include <pthread.h> 
    #include <semaphore.h> 
    #include <unistd.h> 
    
    #define NUM_LEITORES 5
    #define NUM_GRAVADORES 1
      
    sem_t wr; 
    pthread_mutex_t mut;
    int dado=0xFFFFFFFF;
    int erros;
    int contador_wr[NUM_GRAVADORES];
    int contador_rd[NUM_LEITORES];
    
    void* gravador(void* arg) 
    { 
        printf("gravador: valor de arg %ld\n", (long)arg);
        for(;;) {
          //seção critica 
          dado=((dado==0xFFFFFFFF)?0x0:0xFFFFFFFF);     
          contador_wr[(long)arg]++;
        } 
    } 
    
    void* leitor(void* arg) 
    { 
        int x;
    
        printf("leitor: valor de arg %ld\n", (long)arg);
        for(;;) {
          //sem_wait(&wr);
          pthread_mutex_lock(&mut); 
    
          //secao critica
          if(!(dado==0xFFFFFFFF || dado==0x0) )
               erros++;
    
    
          contador_rd[(long)arg]++;      
          pthread_mutex_unlock(&mut);
          //sem_post(&wr);
        } 
    }   
      
    int main() 
    { 
        long int i;
    
        sem_init(&wr, 0, 1); 
        pthread_mutex_init(&mut, NULL);
    
        pthread_t t_leitor[NUM_LEITORES],t_gravador[NUM_GRAVADORES];
       
        for(i=0;i<NUM_LEITORES;i++)
           pthread_create(&t_leitor[i],NULL,leitor,(void *)i);  
        for(i=0;i<NUM_GRAVADORES;i++)
           pthread_create(&t_gravador[i],NULL,gravador,(void *)i); 
        printf("aguardando\n");
        sleep (5);
        printf("voltando\n");
        for(i=0;i<NUM_LEITORES;i++)
            pthread_cancel(t_leitor[i]);
        for(i=0;i<NUM_GRAVADORES;i++)
            pthread_cancel(t_gravador[i]);
        printf("cancelando\n");
        //pthread_join(t_leitor,NULL); 
        //pthread_join(t_gravador,NULL);
        for(i=0;i<NUM_LEITORES;i++)
            printf("contador do leitor %d\n", contador_rd[i]);
        for(i=0;i<NUM_GRAVADORES;i++)
            printf("contador do gravador %d\n", contador_wr[i]); 
        printf("erros %d\n", erros);
        sem_destroy(&wr); 
        return 0; 
    }
    

    AULA 23 - Dia 16/10/2019

    Objetivos

    • Gerenciamento da Memória Principal (Cap.8 - Seção 8.1 e 8.2)
    • Vinculação de Endereços Lógicos a Endereços Físicos.
    • Permuta de Processos (8.2)

    AULA 24 - Dia 18/10/2019

    Objetivos

    • Revisão da Aula Anterior
    • Alocação de Memória Contígua (8.3)
    • Paginação: Método Básico (8.4.1)

    AULA 25 - Dia 23/10/2019

    Objetivos

    • Paginação:
      • revisão aula passada (8.4.1)
      • suporte de hardware (8.4.2) - conceito de PTBR, TLB, ASID
      • proteção com paginação (8.4.3) - bits para proteção de leitura/gravação - bit válido-inválido (conceito de referência inválida de página)
      • páginas compartilhadas (8.4.4) - exemplo do editor de texto
      • introdução a paginação hierárquica (8.5.1) - somente entender o problema e Fig.14 e 8.15
    • Segmentação

    AULA 26 - Dia 25/10/2019

    Objetivos

    • revisão aula passada;
    • Segmentação (cap.8.6)
    • memória virtal - anecedentes (9.1)
    • paginação por demanda (cap.9.2)
    • cópia após gravação

    paginação por demanda

    • Discutir 8.17 e 8.18

    AULA 27 - Dia 29/10/2019

    • desenvolvimento do projeto

    AULA 28 - Dia 30/10/2019

    Objetivos

    • exercícios de preparação para avaliação

    AULA 29 - Dia 1/11/2019

    Objetivos

    • Desenvolvimento do Projeto

    Pontos a serem abordados na parte 2 do projeto

    • Slides: Cada exemplo deve se precedido por um conjunto de slides explicativos;
    • Exemplos: pelo menos 3 exemplos conforme descrito a seguir.

    Queue Management

    • Exemplo simples de uso de uma fila demoonstrando bloquei;
    • Exemplo de uso de um conjunto de filas;
    • Exemplo demonstrando a questão da prioridade de tarefas no contexto de filas.

    Timer Management

    • Exemplo sobre OneShot e AutoReload Timer;
    • COmo modificar o tempo associado ao timer;
    • Reset a Timer: mostrar o exemplo da luz de fundo do celular;

    Interrupt Management

    • Preparar um exemplo ṕara receber interrupção a partir da entrada INT0 e acordar uma tarefa usando um semáforo binário.
    • Estudar a possibilidade de usar um semáforo contador. (seção 6.5)

    Questionário - Sincronização entre Processos

    1. Discuta o problema da inversão de prioridades no contexto de sincronização de processos. Apresente uma possível solução.
    2. Proponha um exemplo usando 3 processos que entram em estado de deadlock.
    3. Crie um exemplo de controle de acesso a uma região crítica em que o método de Peterson é utilizado. Explique o funcionamento do mecanismo.
    4. Discuta como funciona a sincronização entre processos baseada em hardware. Explique a semântica da instrução TestAndSet e da instrução Swap.
    5. Explique a semântica das primitivas signal e wait de um semáforo contador.

    Pontos e Questões da Aval.2 sobre Memória

    • Estudar os exemplos em assembly que discuti na sala discutindo vinculações de endereços lógicos e físicos e a alocação em tempo de compilação, carga e execução.
    • Estudar as seções estudas e indicadas na wiki.
    • Em particular saber explicar claramente o processo das figuras 9.6 e a computação do desempenho em 9.2.2
    • Saber aplicar o algoritmo básico de substituição de página FIFO;
    • Estudar exercícios 8.4, 8.5, 8.6, 8.9 a 8.18. Exercício 8.19a. Exercício 8.20. Exercício 8.24.
    • Exercícios 9.14, 9.18, 9.19, 9.21, 9.23

    AULA 30 - Dia 6/11/2019

    • Exercícios para avaliação

    AULA 31 - Dia 8/11/2019

    • Avaliação 2

    AULA 32 - Dia 13/11/2019

    Objetivos

    • Interface de Sistema de Arquivos (cap.10)
      • Conceito de Arquivo (10.1):
        • Atributo de Arquivos (10.1.1),
        • Operações sobre Arquivos (10.1.2),
        • Tipos de Arquivos (10.1.3) e
        • Estrutura de Arquivos (10.1.4)
    • Métodos de Acesso (10.2)
      • Acesso Sequencial (10.2.1)
      • Acesso Direto (10.2.2)
      • Outros métodos de acesso (10.2.3)

    Material de Referência

    • Livro do Silberschatz
    • Slides Sliberschatz

    Laboratório de Apoio

    Escrevendo na saída padrão e de erro usando write()

    Ver:

    #include <unistd.h>
    #include <stdlib.h>
    int main()
    {
      write(1, "Escrevendo dados na saída padrão\n", 35);
      write(2, "Agora escrevendo na saída de erros\n",37);
      exit(0);
    }
    

    Agora execute testando as redireções:

    ./esc
    
    ./esc 1> lixo.dat
    
    ./esc 1> lixo.dat 2> erro.dat
    

    Vamos fazer um exemplo de cópia de arquivos passados como parâmetro:

    #include  <unistd.h>
    #include  <sys/stat.h>
    #include  <fcntl.h>
    #include  <stdlib.h>
    int main()
    {
        char c;
        int in, out;
        in = open("file.in", O_RDONLY);
        out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
        while(read(in,&c,1) == 1)
             write(out,&c,1);
        close(in);
        close(out);
        exit(0);
    }
    

    Estrutura do Arquivo Executável ELF no Linux

    1. Compilar um programa simples em C;
    2. Averiguar a estrutura do elf: readelf a.out</syntaxhighlight>
    3. Averiguar código desassemblado: objdump -d a.out</syntaxhighlight>


    Referências

    AULA 33 - Dia 20/11/2019

    Objetivos

    • Interface Sistema de Arquivos
      • Estrutura de Diretórios (10.3)
      • Montagem de Sistema de Arquivos (10.4)
      • Compartilhamento de Arquivos (10.5)

    AULA 34 - Dia 22/11/2019

    • Apresentação Etapa 2 Projeto
    • Definição da Etapa 3

    AULA 35 - Dia 27/11/2019

    Objetivos

    • Finalização Sistema de Arquivos
    • Exercícios no Linux

    Referencia

    Exercícios no Linux

    1. Verificando o espaço livre em cada sistema de arquivos montado: df -Th</syntaxhighlight>
    2. Verificando o inode de um arquivo (metadados) stat /etc/passwd</syntaxhighlight>
    3. Vendo dados do superbloco de um sistema de arquivos (ver o dispositivo com df) sudo dumpe2fs -h /dev/sda3</syntaxhighlight>
    4. Vendo backups do superblock dumpe2fs /dev/sda3 | grep -i superblock</syntaxhighlight>
    5. Fazendo dump de arquivo usando os blocos (adaptar para o device) sudo hdparm --fibmap /etc/passwd /etc/passwd: filesystem blocksize 4096, begins at LBA 648380416; assuming 512 byte sectors. byte_offset begin_LBA end_LBA sectors 0 899444976 899444983 8 sudo hd /dev/sda -n 512 -v -s 899444976b </syntaxhighlight>
    6. mount -t tmpfs -o size=1m temporario /tmp

    AULA 36 - Dia 29/11/2019

    Objetivos

    • Subsistema IO

    AULA 37 - Dia 4/12/2019

    Objetivos

    • Subsistema IO

    Material de Referência

    AULA 38 - Dia 6/12/2019

    • Avaliação 3

    AULA 39 - Dia 11/12/2019

    Objetivos

    • Desenvolvimento/Apresentação do Projeto


    ProjetoArduinoRtos.png


    AULA 40 - Dia 13/12/2019

    Objetivos

    • Recuperação Projeto
    Programação concorrente

    Programação concorrente

    PARTE 1 - Mutex e Biblioteca libpthreads

    POSIX Threads

    A API POSIX disponibiliza uma biblioteca de threads chamada pthread. As threads são implementadas pela estrutura pthread_t, e manipuladas pelas funções (acesse as man-pages das chamadas para maiores detalhes):

    • pthread_create: cria uma thread;
    • pthread_kill: força a terminação de uma thread;
    • pthread_join: sincroniza o final de uma thread (qual a diferença/semelhança com o wait que usamos para processos?);
    • pthread_exit: finaliza uma thread.

    Para utilizar estas funções é necessário linkar o programa à libpthread (-lpthread).


    POSIX pthread mutex

    A biblioteca pthread implementa um tipo pthread_mutex_t, que garante a exclusão mútua entre threads. Estes mutex são manipulados através das funções (acesse as man-pages das chamadas para maiores detalhes):

    • pthread_mutex_lock: acessa um mutex.
    • pthread_mutex_trylock: tenta acessar um mutex (retorna valor indicando sucesso ou falha no lock).
    • pthread_mutex_unlock: libera um mutex.


    Exercício 1

    O programa abaixo cria 5 threads, e cada uma destas threads atualiza uma variável global (memória compartilhada).

    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    #define NUM_THREADS 5
     
    pthread_mutex_t mut;
     
    int saldo = 1000;
     
    int AtualizaSaldo(int n)
    {
            long long i;
            pthread_mutex_lock(&mut);
    	int meu_saldo = saldo;
    	int novo_saldo = meu_saldo + n*100;
            sleep(1);
    	printf("Novo saldo = %d\n",novo_saldo);
    	saldo = novo_saldo;
            pthread_mutex_unlock(&mut);
    }
     
    int main()
    {
    	pthread_t threads[NUM_THREADS];
            int i,ret;
    
            pthread_mutex_init(&mut, NULL);
    
     
    	// Cria cinco threads que executarão a mesma função
    	for(i=0; i<5; ++i){
    		ret = pthread_create(&threads[i], NULL, (void*(*)(void*))AtualizaSaldo,(void*)((long)i+1));
    		if(ret != 0){
    			fprintf(stderr, "Erro thread %d\n", (i+1));
    			exit(-1);
    		}
    	}
    	// Aguarda o fim das threads
    	for(i=0; i<5; ++i)
    		pthread_join(threads[i], NULL);
     
    	printf("Saldo final é %d\n", saldo);
    }
    
    1. Compile este programa.
    2. Execute este programa várias vezes. Ele funciona? Será que ele gera as saídas esperadas?
    3. Identifique as seções críticas do programa.
    4. Corrija o programa utilizando mutex.
    5. Analise a função AtualizaSaldo() com a sua solução. Lembre-se que o uso do mutex implica em apenas uma thread acessar a seção crítica por vez, enquanto outras threads ficam bloqueadas, esperando. Disso vem que, quanto menor o trecho de código entre um lock e um unlock, menos tempo uma thread necessita ficar esperando.


    PARTE 2 - POSIX Semaphores Binários

    Nos sistemas POSIX, semáforos são implementados pelo tipo sem_t e manipulado através das funções (acesse as man-pages das chamadas para maiores detalhes):

    • sem_init: inicializa um semáforo;
    • sem_destroy: destroy um semáforo;
    • sem_wait: implementa a operação p;
    • sem_post: implementa a operação v.

    Para utilizar estas funções é necessário linkar o programa à librt ou à libpthread (-lrt ou -lpthread).

    Exercício 1

    Modifique o programa para usar um semáforo binário ao invés de um mutex em sua solução.

    Exercício 2 - Fazer em casa

    Refaça o exercício 3 usando processos criados com fork e exec. O semáforo deve ser criado em uma região de memória compartilhada.

    PARTE 3 - POSIX Semaphores

    Exercício 3

    Implemente com pthreads e semáforos/mutex a solução do produtor/comsumidor conforme sugerido no livro do Silberchatz. USe dois semáforos: (i) um para controlar o numero de itens vazios no buffer (semáforo empty - quando zero o thread produtor deve parar de produzir). (ii) um semáforo para controlar o numero de itens ocupados no buffer (semáforo full - quando zero o consumidor deve parar de consumir).


    PARTE 4 - POSIX Semaphores

    Exercício 4

    O programa abaixo manipula uma matriz de tamanho MxN (veja os defines para o tamanho da matriz). A função SumValues soma todos os valores em uma linha da matriz. A linha a ser somada é identificada pela variável i. Modifique o programa principal (main) nos locais indicados para:

    1. Criar N threads, uma para somar os valores de cada linha.
    2. Receber o resultado do somatório de cada linha e gerar o somatório total da matriz.
    3. Analise o programa: há problemas de sincronização que precisam ser resolvidos? Se sim, resolva-os.
    #include <iostream>
    #include "thread.h"
    
    /* number of matrix columns and rows */
    #define M 5
    #define N 10
    
    using namespace std;
    
    int matrix[N][M];
    Thread *threads[N];
    
    
    /* thread function; it sums the values of the matrix in the row */
    int SumValues(int i)
    {
    	int n = i; /* number of row */
    	int total = 0; /* the total of the values in the row */
    	int j;
    	for (j = 0; j < M; j++) /* sum values in the "n" row */
    		total += matrix[n][j];
    	cout << "The total in row" << n << " is " << total << "." << endl;
    	/* terminate a thread and return a total in the row */
    	exit(total);
    }
    
    int main(int argc, char *argv[])
    {
    	int i, j;
    	int total = 0; /* the total of the values in the matrix */
    
    	 /* initialize the matrix */
    	for (i = 0; i < N; i++)
    		for (j = 0; j < M; j++)
    			matrix[i][j] = i * M + j;
    
    	/* create threads */
    	/* COLOQUE SEU CÓDIGO PARA CRIAR AS THREADS AQUI! */
    
    	/* wait for terminate a threads */
    	/* COLOQUE SEU CÓDIGO PARA PEGAR O SOMATÓRIO DE LINHAS E TOTALIZAR A SOMA DA MATRIZ AQUI! */
    
    	cout << "The total values in the matrix is " << total << endl;
    
    	return 0;
    }
    



    Ainda Threads - Escalonamento Round Robin e FCFS para Threads"

    Escalonamento Round-Robin

    OBS: ver https://www.quora.com/What-exactly-does-typedef-do-in-C

    O Exemplo abaixo mostra a implementação de um esquema de escalonamento Round Robin do exercício anterior.

    /**
      User-level threads example.
      
      Orion Sky Lawlor, olawlor@acm.org, 2005/2/18 (Public Domain)
      Modificado por Eraldo 
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h> /* for makecontext/swapcontext routines */
    #include <queue> /* C++ STL queue structure */
    #include <vector>
    
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
    
    #define TIME_SLICE 5
    
    typedef void (*threadFn)(void);
    
    class thread_cb {
       int id_thread;
       public:
       ucontext_t contexto;
       thread_cb(threadFn p, int id)
       {
    	  getcontext(&contexto);
    	  int stackLen=32*1024;
    	  char *stack=new char[stackLen];
    	  contexto.uc_stack.ss_sp=stack;
    	  contexto.uc_stack.ss_size=stackLen;
    	  contexto.uc_stack.ss_flags=0;      
         id_thread = id;
         makecontext(&contexto,p,0);
       };
       ucontext_t *get_context() {
         return &contexto;
       };
    };
    
    std::queue<class thread_cb *> ready_pool;
    
    int id_thread = 0;
    
    
    class thread_cb *curr_thread=NULL;
    
    void add_thread(threadFn func)
    {
      class thread_cb *p = new thread_cb(func, ++id_thread);
      ready_pool.push(p);
    }
    
    
    void dispatcher(ucontext_t *old_task, ucontext_t *new_task)
    {
      if (old_task!=NULL)
          swapcontext(old_task, new_task);
      else
          setcontext(new_task);
    }
    
    void scheduler_rr()
    {
        class thread_cb *next,*last;
        
       
       if(curr_thread!=NULL) {
          printf("Aqui\n");
          ready_pool.push(curr_thread);
          last=curr_thread;
    	   next=ready_pool.front();
    	   ready_pool.pop();
          curr_thread=next;    
          dispatcher(last->get_context(), curr_thread->get_context()); 
       } else {
    	   next=ready_pool.front();
       	ready_pool.pop();
          curr_thread = next;
          dispatcher(NULL, next->get_context());
       }
    }
    
    void sig_handler(int signo)
    {
    
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
      
    	if (ready_pool.empty()) {
    		printf("Nothing more to run!\n");
    		exit(0);
    	}
       scheduler_rr();
    }
    
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
    
    void runA(void) {
    	for (;;) {
          printf("running A\n");
          sleep(1);
       }
    }
    
    void runB(void) {
    	for (;;) {
          printf("running B\n");
          sleep(1);
       }
    }
    
    main()
    {
      add_thread(runA);
      add_thread(runB);
      preparar_handler();
      for(;;);
    }
    
    /**
      User-level threads example.
     
      Orion Sky Lawlor, olawlor@acm.org, 2005/2/18 (Public Domain)
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h> /* for makecontext/swapcontext routines */
    #include <queue> /* C++ STL queue structure */
    #include <vector>
     
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
     
    #define TIME_SLICE 1
     
    typedef void (*threadFn)(void);
     
    class thread_cb {
       int id_thread;
       public:
       ucontext_t contexto;
       thread_cb(threadFn p, int id)
       {
    	  getcontext(&contexto);
    	  int stackLen=32*1024;
    	  char *stack=new char[stackLen];
    	  contexto.uc_stack.ss_sp=stack;
    	  contexto.uc_stack.ss_size=stackLen;
    	  contexto.uc_stack.ss_flags=0;      
         id_thread = id;
         makecontext(&contexto,p,0);
       };
       ucontext_t *get_context() {
         return &contexto;
       };
    };
     
    std::queue<class thread_cb *> ready_pool;
     
    int id_thread = 0;
     
    class thread_cb *curr_thread=NULL;
     
    
    void scheduler_rr();
    
    void add_thread(threadFn func)
    {
      class thread_cb *p = new thread_cb(func, ++id_thread);
      ready_pool.push(p);
    }
     
     
    void yield_thread()
    {
      scheduler_rr();
    }
    
    void delete_thread()
    {
      delete curr_thread;
      curr_thread=NULL;
      scheduler_rr();
    }
    
    void dispatcher(ucontext_t *old_task, ucontext_t *new_task)
    {
      if (old_task!=NULL)
          swapcontext(old_task, new_task);
      else
          setcontext(new_task);
    }
     
    void scheduler_rr()
    {
        class thread_cb *next,*last;
     
     
       if(curr_thread!=NULL) {
          printf("Aqui\n");
          ready_pool.push(curr_thread);
          last=curr_thread;
    	   next=ready_pool.front();
    	   ready_pool.pop();
          curr_thread=next;    
          dispatcher(last->get_context(), curr_thread->get_context()); 
       } else {
    	   next=ready_pool.front();
       	ready_pool.pop();
          curr_thread = next;
          dispatcher(NULL, next->get_context());
       }
    }
     
    void sig_handler(int signo)
    {
     
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
     
    	if (ready_pool.empty()) {
    		printf("Nothing more to run!\n");
    		exit(0);
    	}
       scheduler_rr();
    }
     
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
     
    struct delta{
      long alfa;
      char epson[1000];
      long beta;
    } shar;
    
    int turn;
    int flag[2];
    
    #define TRUE 1
    #define FALSE 0
    
    void ent_rc(int p, int vt)
    {
      flag[p]=TRUE;
      turn = vt;
      if(p) p=0; else p=1;
      while (flag[p] && turn == vt);
         //printf("Thread %d: esperando para acessar a região crítica\n", p);
    }
    
    void sai_rc(int p)
    {
      flag[p]=FALSE;
    }
    
    void runA(void) {
      struct delta x = {0, 100};
     
      for (;;) {
          x.alfa=0;x.beta=0;
          ent_rc(0,1);
          shar=x;  // regiao crítica
          sai_rc(0);
          x.alfa=100;x.beta=100;
          ent_rc(0,1);
          shar=x;  // regiao crítica
          sai_rc(0);              
       }
    }
     
    void runB(void) {
     
       for (;;) {
          ent_rc(1,0);
          printf("shar alfa = %ld shar beta = %ld \n",shar.alfa, shar.beta);  // regiao crítica
          sai_rc(1);
          sleep(1);
       }
    }
    
    void runC(void) {
       int i;
       for (i=0;i<10;i++) {
          printf("Thread C - parte 1\n");
          yield_thread();
          printf("Thread C - parte 2\n");
          yield_thread();
       }
       delete_thread();
    }
    
     
    main()
    {
      add_thread(runA);
      add_thread(runB);
      add_thread(runC);
      preparar_handler();
      for(;;);
    }
    


    Ainda Threads - Um escalonador semi-automático"

    Ainda Threads - Um escalonador semi-automático usando sinais

    Neste laboratório serão aprimorados os threads em nível de aplicação vistos na aula anterior. Usaremos sinais do Unix/Linux para escalonar os threads via handlers.

    Exemplo 1:

    No Linux/Unix o kernel ou um processo podem enviar sinais para outros processos. Trata-se de uma notificação de que um evento ocorreu. O processo pode ter tratadores (handlers) para estes sinais de forma a tratá-los de forma assíncrona. Pode-se fazer uma analogia com a interrupção por hardware de um programa em execução. Um tratador da interrupção trata a causa da mesma (evento) e no final do tratador (handler) é restaurado o contexto do programa interrompido. Por exemplo, quando um filho se encerra, o pai recebe do kernel o sinal SIGCHLD.

    No exemplo abaixo utilizamos o sinail USR1 de uso geral (disponível para o usuário). Associamos um handler a este sinal (sig_handler). O programa sendo executado, fica em loop no main(). Usando um outro terminal podemos enviar um sinal e verificar o efeito do mesmo.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    
    void sig_handler(int signo)
    {
      int x;
      printf("Turma de SOP: recebido SIGUSR1\n");
    }
    
    int main(void)
    {
      int x;
    
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      // Loop  eterno dormindo 1s
      while(1) {
        sleep(1);
      }
      return 0;
    }
    

    Fazer no terminal:

     ps aux
    

    anotar o pid do processo e enviar o sinal:

     kill -USR1 pid
    

    Exemplo 2:

    O programa anterior é aprimorado para começar a executar um thread Ping similar a aula anterior. Ainda não existe escalonamento dos threads. Somente o thread ping é executado. O sinal USR1 pode ser enviado para o processo interrompendo a execução do thread.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
     
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
     
    #define PING_ID 1
     
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
     
    int curr_thread;
     
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("Turma de SOP: recebido SIGUSR1\n");   
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
     
    }
     
    void f_pong(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
    }
     
    void preparar_contexto_ping()
    {
       char *stack;
     
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    }
     
    void preparar_contexto_pong()
    {
       char *stack;
     
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
     
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    }
     
    int main(void)
    {
      int x;
     
      printf ("Main INICIO\n");
      preparar_contexto_ping();
      preparar_contexto_pong();
     
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
     
      curr_thread=PING_ID;
      swapcontext(&cMain, &cPing);
      // loop eterno que nunca é executado
      while(1) {
        sleep(1);
      }
      return 0;
    }
    

    Enviar o sinal USR1 para ver o efeito.

    Exemplo 3:

    Neste exemplo preparamos o handler para chavear o contexto de ping para pong e vice-versa. A cada USR1 recebido será realizada a troca de contexto.
    
    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
    
    #define PING_ID 1
    #define PONG_ID 2
    
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    
    int curr_thread;
    
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("Turma de SOP: recebido SIGUSR1\n");   
      if (curr_thread==PING_ID) {
         curr_thread=PONG_ID;
         swapcontext(&cPing, &cPong);    
      } else {
         curr_thread=PING_ID;
         swapcontext(&cPong, &cPing);
      }
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
       
    }
     
    void f_pong(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
    }
    
    void prepara_contexto_ping()
    {
       char *stack;
     
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    }
    
    void prepara_contexto_pong()
    {
       char *stack;
    
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
     
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    }
    
    int main(void)
    {
      int x;
    
      printf ("Main INICIO\n");
      prepara_contexto_ping();
      prepara_contexto_pong();
      
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
    
      curr_thread=PING_ID;
      swapcontext(&cMain, &cPing);
      // A long long wait so that we can easily issue a signal to this process
      while(1) {
        sleep(1);
      }
      return 0;
    }
    

    Exercício: Use o comando htop para verificar como estes threads são vistos pelo SOP.

    Exercício: Tente analisar as pilhas dos threads e verificar sob qual pilha o handler se executa.


    Exercício 4:

    Neste ponto associamos um timer ao handler com o apoio do sinal ALRM. Agora os threads são escalonados automaticamente.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
    
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
    
    // number of seconds for setting the interval used by the timer
    #define QUANTUM_SEC 0
    // number of microseconds for setting the interval used by the timer (0 - 999999)
    #define QUANTUM_MICRO_SEC 100000
    
    #define PING_ID 1
    #define PONG_ID 2
    #define TIME_SLICE 5
    
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    int curr_thread;
    
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
      if (curr_thread==PING_ID) {
         curr_thread=PONG_ID;
         swapcontext(&cPing, &cPong);    
      } else {
         curr_thread=PING_ID;
         swapcontext(&cPong, &cPing);
      }
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
       
    }
     
    void f_pong(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
    }
    
    void preparar_contexto_ping()
    {
       char *stack;
     
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    }
    
    void preparar_contexto_pong()
    {
       char *stack;
    
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
     
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    }
    
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
    
    int main(void)
    {
      int x;
    
      printf ("Main INICIO\n");
      preparar_contexto_ping();
      preparar_contexto_pong();
      preparar_handler();
      curr_thread=PING_ID; //ajusta primeiro thread
      swapcontext(&cMain, &cPing); //nunca mais volta...
      return 0;
    }
    

    Exercício Proposto 5

    Colocar 3 threads rodando.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
     
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
     
    // number of seconds for setting the interval used by the timer
    #define QUANTUM_SEC 0
    // number of microseconds for setting the interval used by the timer (0 - 999999)
    #define QUANTUM_MICRO_SEC 100000
     
    #define PING_ID 1
    #define PONG_ID 2
    #define TIME_SLICE 5
     
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    int curr_thread;
     
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
      if (curr_thread==PING_ID) {
         curr_thread=PONG_ID;
         swapcontext(&cPing, &cPong);    
      } else {
         curr_thread=PING_ID;
         swapcontext(&cPong, &cPing);
      }
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
     
    }
     
    void preparar_contexto_ping(ucontext_t *pContext, char *p)
    {
       char *stack;
     
       getcontext(pContext);
       stack = malloc(STACKSIZE);
       if(stack) {
          (*pContext).uc_stack.ss_sp = stack ;
          (*pContext).uc_stack.ss_size = STACKSIZE;
          (*pContext).uc_stack.ss_flags = 0;
          (*pContext).uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(pContext, (void*)(*f_ping), 1, p);
    }
     
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
     
    int main(void)
    {
      int x;
     
      printf ("Main INICIO\n");
      preparar_contexto_ping(&cPing, "ping");
      preparar_contexto_ping(&cPong, "pong");
      preparar_handler();
      curr_thread=PING_ID; //ajusta primeiro thread
      swapcontext(&cMain, &cPing); //nunca mais volta...
      return 0;
    }
    


    Threads de aplicação

    Threads de aplicação

    O Linux, através da API POSIX, oferece um conjunto de funções que permite às aplicações manipular contextos, facilitando a vida do programador que quer implementar tarefas "simultâneas" dentro de um único processo, ou seja, threads. As seguintes funções e tipos estão disponíveis:

    • getcontext(&a): salva o contexto na variável a;
    • setcontext(&a): restaura um contexto salvo anteriormente na variável a;
    • swapcontext(&a,&b): salva o contexto atual na variável a e restaura o contexto salvo anteriormente na variável b;
    • makecontext(&a, ...): ajusta parâmetros internos do contexto salvo em a;
    • ucontext_t: as variáveis a e b são do tipo ucontext_t. Este tipo armazena um contexto.

    Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext).

    Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.

    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h>
    
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
    
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i;
    
       printf("%s iniciada\n", (char *) arg);
    
       for (i=0; i<4; i++) {
          printf("%s %d\n", (char *) arg, i);
          swapcontext(&cPing, &cPong);
       }
       printf("%s FIM\n", (char *) arg);
    
       swapcontext(&cPing, &cMain);
    }
    
    void f_pong(void * arg) {
       int i;
    
       printf("%s iniciada\n", (char *) arg);
    
       for (i=0; i<4; i++) {
          printf("%s %d\n", (char *) arg, i);
          swapcontext(&cPong, &cPing);
       }
       printf("%s FIM\n", (char *) arg);
    
       swapcontext(&cPong, &cMain);
    }
    
    /* MAIN */
    int main(int argc, char *argv[]) {
       char *stack;
    
       printf ("Main INICIO\n");
    
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
    
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
    
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    
       swapcontext(&cMain, &cPing);
       swapcontext(&cMain, &cPong);
    
       printf("Main FIM\n");
    
       exit(0);
    }
    
    • Exercício 1: Primeiramente compile e execute o código. Agora estude o código e entenda completamente o seu funcionamento. Explique em DETALHES o código, comentando todas as linhas. Na seção de diagrama do seu relatório, desenhe um diagrama de funcionamento do código para mostrar exatamente como acontece a troca de contexto entre as threads.
    • Exercício 2: Acrescente um procedimento f_new que receba 4 strings como parâmetros e imprima todas na tela. Antes do final da execução do main faça uma mudança de contexto para chamar o procedimento criado.

    Exercício 3: O que acontece se um dos threads é colocado para dormir? Todos os demais threads param a sua execução.

    Exercício 4: Note que um thread somente deixa a execução para outro thread de forma explícita. Será que é possível realizar um escalonamento de threads de forma similar ao que o kernel faz com os processos? Ver http://www.cplusplus.com/forum/unices/6452/

    Processos no Linux

    Processos no Linux - Modificado por Eraldo

    Processos no Linux

    • Exercícios propostos pelo Prof.Arliones e Modificados por Eraldo


    DesenhoForkExec.png


    Syscall FORK
    • Em um terminal, execute "man fork"
      • A função da API POSIX fork() aciona uma chamada de sistema (system call - syscall) que cria um novo processo duplicando o processo que realiza a chamada. O novo processo, chamado de filho, é uma cópia exata do processo criador, chamado de pai, exceto por alguns detalhes listados na manpage. O principal destes detalhes para nós agora é o fato de terem PIDs diferentes.
      • O código dos dois processos (pai e filho) são idênticos;
      • Os dados dos dois processos (pai e filho) são idênticos NO MOMENTO DA CRIAÇÃO;
      • Execução do processo filho inicia na próxima instrução do programa (no retorno da chamada FORK);
      • Não é possível saber qual dos processos (pai ou filho) retormará a execução primeiro - isto fica a cargo do excalonador do SO;
      • Valores de retorno da chamada FORK:
        • (-1): erro na criação do processo (ex.: memória insuficiente);
        • (0): em caso de sucesso, este é o valor de retorno recebido pelo processo filho;
        • (>0): em caso de sucesso, este é o valor de retorno recebido pelo processo pai;
    Syscall JOIN
    • A syscall JOIN é implementada no POSIX pela função wait(). Execute "man wait".
      • Além da função wait(), há também waitpid() e waitid();
      • Todas estas syscalls são utilizadas para aguardar por mudanças no estado de um processo filho e obter informações sobre o processo filho cujo estado tenha mudado. São consideradas mudanças de estado: o filho terminou; o filho foi finalizado por um sinal (ex.: kill); o filho foi retomado por um sinal (ex.: alarme);
      • A chamada wait também libera os recursos do processo filho que termina;
      • wait(): esta função suspende a execução do processo chamador até que UM DOS SEUS FILHOS finalize;
      • waitpid(): suspende a execução do processo chamador até que UM FILHO ESPECÍFICO finalize;
    Syscall EXEC
    • A syscall EXEC é implementada no POSIX pela família de funções exec(). Execute "man exec".
      • As principais funções da família são execl(), execlp() e execvp();
      • Todas estas funções são, na realidade, front-ends (abstrações) para a syscall execve. Esta syscall substitui a imagem do processo corrente (aquele que chama a syscall) pela a imagem de um novo processo;
      • Os parâmetros passados a estas funções são, basicamente, o nome de um arquivo com a imagem do programa a ser executado (um binário de um programa), e uma lista de parâmetros a serem passados a este novo programa;


    Exemplos POSIX utilizando fork/wait/exec
    • Exemplo 1: fork/wait básico
    // ex1: fork/wait básico
    #include <sys/types.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main()
    {
        int pid, status;
        pid = fork();
    
        if(pid == -1) // fork falhou
        {
            perror("fork falhou!");
            exit(-1);
        }
        else if(pid == 0) // Este é o processo filho
        {
            printf("processo filho\t pid: %d\t pid pai: %d\n", getpid(), getppid());
            exit(0);
        }
        else // Este é o processo pai
        {
            wait(&status);
            printf("processo pai\t pid: %d\t pid pai: %d\n", getpid(), getppid());
            exit(0);
        }
    }
    
    arliones@socrates:~/tmp$ gcc ex1.c -o ex1 
    arliones@socrates:~/tmp$ ./ex1 
    processo filho	 pid: 27858	 pid pai: 27857
    processo pai	 pid: 27857	 pid pai: 5337
    arliones@socrates:~/tmp$
    
    • Exemplo 2: processos pai e filho compartilham código, mas não dados.
    // ex2: fork/wait "compartilhando" dados
    // ex2: fork/wait "compartilhando" dados
    #include <sys/types.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main()
    {
        int pid, status, k=0;
        printf("processo %d\t antes do fork\n", getpid());
        pid = fork();
        printf("processo %d\t depois do fork\n", getpid());
     
        if(pid == -1) // fork falhou
        {
            perror("fork falhou!");
            exit(-1);
        }
        else if(pid == 0) // Este é o processo filho
        {
            k += 1000;
            printf("processo filho\t pid: %d\t K: %d \t endereço K: %p\n", getpid(), k, &k);
            exit(0);
        }
        else // Este é o processo pai
        {
            wait(&status);  // espera o filho terminar
            k += 10;
            printf("processo pai\t pid: %d\t K: %d  \t endereço K:  %p\n", getpid(), k,  &k);
            exit(0);
        }
        k += 1000;
        printf("FIM processo %d\t K: %d\n", getpid(), k); 
        exit(0);
    }
    
    processo 17056   antes do fork
    processo 17056   depois do fork
    processo 17057   depois do fork
    processo filho   pid: 17057      K: 1000         endereço K: 0x7ffd8923e318
    processo pai     pid: 17056      K: 10           endereço K:  0x7ffd8923e318
    
    • Modificação no código: comentar linhas 23 e 30
    • Analise os resultados e busque entender a diferença.
    • Perguntas e desafios.:
    1. Analisando os valores impressos de k pode-se dizer que os dados são compartilhados entre os dois processos?
    2. O endereço de k impresso é o mesmo nos dois processos. ISto não contradiz a afirmação anterior?
    3. Substitua o k por uma área criada dinâmicamente com o malloc (área de HEAP). Verifique se esta área é ou não compartilhada pelos processos.


    Exercício fork/wait

    Excrever um programa C que cria uma árvore de 3 processos, onde o processo A faz um fork() criando um processo B, o processo B, por sua vez, faz um fork() criando um processo C. Cada processo deve exibir uma mensagem "Eu sou o processo XXX, filho de YYY", onde XXX e YYY são PIDs de processos. Utilizar wait() para garantir que o processo C imprima sua resposta antes do B, e que o processo B imprima sua resposta antes do A. Utilizar sleep() (man 3 sleep) para haver um intervalo de 1 segundo entre cada mensagem impressa.

    • Use o comando pstree para verificar a árvore de processos criada.
    Solução

    /* ex3: Excrever um programa C que cria uma arvore de 3 processos, onde o processo A faz um fork() criando um processo B, o processo B, por sua vez, faz um fork() criando um processo C. Cada processo deve exibir uma mensagem "Eu sou o processo XXX, filho de YYY", onde XXX e YYY sao PIDs de processos. Utilizar wait() para garantir que o processo C imprima sua resposta antes do B, e que o processo B imprima sua resposta antes do A. Utilizar sleep() (man 3 sleep) para haver um intervalo de 1 segundo entre cada mensagem impressa.

    • /
    1. include <sys/types.h>
    2. include <stdlib.h>
    3. include <stdio.h>
    4. include <unistd.h>
    5. include <sys/wait.h>

    int main() {

       int pid, status;
       pid = fork();
    
       if(pid == -1) // fork falhou
       {
           perror("fork falhou!");
           exit(-1);
       }
       else if(pid == 0) // Este é o processo filho
       {
           pid = fork();
           if(pid == -1)
           {
               perror("fork falhou!");
               exit(-1);
           }
           else if(pid == 0) // Este é o filho do filho
           {
               sleep(1);
               printf("Eu sou o processo C (PID %d), filho de %d\n", getpid(), getppid());
               exit (0);
           }
           else
           {
               wait(&status);
               sleep(1);
               printf("Eu sou o processo B (PID %d), filho de %d\n", getpid(), getppid());
               exit(0);
           }
       }
       else // Este é o processo pai
       {
           wait(&status);
           sleep(1);
           printf("Eu sou o processo A (PID %d), filho de %d\n", getpid(), getppid());
           exit(0);
       }
    

    }

    </syntaxhighlight>


    DESAFIO
    fork/wait

    Reimplementar o exercício anterior de criação de uma árvore de 3 processos, generalizando a criação de N processos onde N é repassado na linha de comando do programa. SUGESTÃO: usar um comando for, mas lembrar que se existe um fork dentro do for, então cada filho gerado dará continuidade a execução do for. É necessário que o processo faça um exit ou retorne neste momento.

    DESAFIO
    Exercício status/wait

    O status passado como parâmetro à função wait(&status) é, na verdade, o mecanismo de retorno de resultado do wait/waitpid. Ao retornar, esta variável contém informações sobre o resultado da execução do processo filho. Por exemplo, se um processo terminou normalmente (i.e., chamou exit), o comando WIFEXITED(status) retorna true. Este comando retorna false se o processo foi abortado (e.g., segmentation fault) ou morto (e.g., kill). Investigue no manual do wait no Linux (man wait) o funcionamento do comando WEXITSTATUS(status). Imagine um problema de busca de dados armazenados na forma de uma matriz de inteiros 4x30. Você está interessado em saber quantas ocorrências de um determinado número existe em cada linha da matriz. Note que que são tarefas que podem ser paralelizadas e usufruir de um sistema capaz de executá-las em paralelo. Faça uma implementação paralelizando 4 processos filhos a partir de um pai, onde cada processo é responsável por uma busca. A quantidade de ocorrências do número buscado é retornada e capturada através de WEXITSTATUS.

    Syscall EXEC
    • Exemplo exec()
    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      execl("/bin/ls","ls","-l", NULL);
      return 0;
    }
    
    • Exercício 1: Modificar o código para mostrar que o exec() não retorna (colocar um printf após o exec).
    • Exercício 2: Criar um exemplo (dois programas ) para demonstrar que o exec não cria novo processo.
      • Crie um primeiro programa (prog1) que imprime o seu pid e depois faz um exec do segundo programa.
      • Crie o segundo programa que simplesmente imprime o pid.


    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      printf("EU ANTES DO EXEC: Meu pid é %d\n", getpid());
      execl("./prog2","prog2", NULL);
      return 0;
    }
    
    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      printf("EU DEPOIS DO EXEC:Meu pid é %d\n", getpid());
      return 0;
    }
    
    • Exercício 3: Criar um exemplo usando fork/exec mostrando que um processo pai cria um filho e espera por sua execução. O filho executa o comando "ps aux". Ambos devem mostrar seus pids.


    Laboratório Memória Compartilhada entre Pai e Filho - Produtor Consumidor

    Baseado no Lab. do Prof.Arliones

    Objetivo

    • Familiarizar o aluno com o conceito de Memória Compartilhada entre Processos Pai e Filho
    • Familizarizar o aluno com o conceito de Produtor Consumidor

    Referencial Teórico-Prático

    • Silberchatz - 3.4 e 3.5
    • man nos comandos shmat e shmget

    Exemplo de Funcionamento

    No exemplo abaixo um processo pai compartilha uma área de memória com o processo filho. O processo filho escreve uma mensagem para o pai na área de memória compartilhada e termina. O processo pai é desbloqueado no wait e imprime a mensagem escrita.

    ##include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <wait.h>
    
    #define SHM_SIZE 1024 
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	char *segmento;
    	int modo,filho;
    
    	
    	shmid = shmget(IPC_PRIVATE, SHM_SIZE, S_IRUSR | S_IWUSR);
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
    
    	
    	segmento = shmat(shmid, (void *)0, 0);
    	if (segmento == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
         
    	if((filho = fork()) == -1)
    	{
    		perror("fork");
    		exit(1);
    	}
    
    	if(filho == 0)
    	{
    		char *ptr_msg = "alo pai, tudo bem?";   
    		printf("Filho escrevendo no segmento compartilhado\n\n");
    		strcpy(segmento, ptr_msg);       //aqui deveria testar a cpacidade da área...
    		exit(0);
    	}
    	else
    	{
    		wait(NULL);	            
    		printf("Mensagem para o pai: %s\n", segmento);
    	       
    	}
            
    	
    	if (shmdt(segmento) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
    
    	return 0;
    }
    

    Desafio a ser Implementado em Sala

    Construir um programa em que um processo pai atua como produtor e o processo filho atua como consumidor. O buffer circular compartilhado é um buffer de inteiro de tamanho 5. O pai alimentará o buffer com 50 itens gerados aleatóriamente entre 1 e 100. A cada escrita/leitura no buffer o produtor/consumidor deve esperar alguns milisegundos de forma a acontecer situações de buffer cheio e vazio.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <unistd.h>
    #include <time.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    
     
    #define SHM_SIZE 1024 
    
    #define BUFF_COMP_SIZE 5
    
    struct buff_comp {
       int buffer[BUFF_COMP_SIZE];
       int in,out;
    };
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	char *segmento;
    	int modo,filho;
            struct buff_comp *p; 
             
            /* initialize random seed: */
            srand ( time(NULL) );
    
    	shmid = shmget(IPC_PRIVATE, SHM_SIZE, S_IRUSR | S_IWUSR);
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
     
     
    	p = (struct buff_comp *) shmat(shmid, (void *)0, 0);
    	if (segmento == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
              
            p->out = p->in = 0;
    
    	if((filho = fork()) == -1)
    	{
            
    		perror("fork");
    		exit(1);
    	}
     
    	if(filho == 0)
    	{
                int i;
                for(i=0;i<50;i++) {
                   while  (p->in==p->out) {
                      printf("FILHO: BUFFER VAZIO !!!!!!!\n");
                      usleep(rand() % 10000 + 1);
                   }
                   printf("FILHO: item lido = %d\n", p->buffer[p->out]);
                   p->out = (p->out+1)%BUFF_COMP_SIZE;
                }
     
    	    exit(0);
    	}
    	else
    	{
               int i; 
    
               for(i=0;i<50;i++) {
                   while  (((p->in+1)%BUFF_COMP_SIZE) == p->out) {
                      printf("PAI: BUFFER CHEIO !!!!!!!\n");
                      usleep(rand() % 10000 + 1);
                   }
                   p->buffer[p->in] = rand() % 100 + 1;
                   printf("PAI: item produzido = %d\n", p->buffer[p->in]);
                   p->in = (p->in+1)%BUFF_COMP_SIZE;
                   
                }
    	   wait(&filho);	            
     
    	}
     
     
    	if (shmdt(p) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
     
       return 0;
    }
    
    Trocas de mensagens com pipes

    Trocas de mensagens com pipes

    Troca de mensagens

    Um mecanismo disponibilizado por sistemas UNIX para troca de mensagens entre processos é o PIPE. Pipes são mecanismos de comunicação indireta onde mensagens são trocadas através de mailboxes. Cada mailbox possui um identificador único, permitindo que processos identifiquem o canal de comunicação entre eles. O fluxo de mensagens em um Pipe é:

    • unidirecional: sobre um mesmo pipe, apenas um processo envia mensagens e um processo recebe mensagens;
    • FIFO: as mensagens são entregues na ordem de envio;
    • não-estruturado: não há estrutura pré-definida para o formato da mensagem.

    No UNIX, pipes são inicializados através da SystemCall pipe, que possui a seguinte sintaxe:

    • int pipe(int pipefd[2]): pipe inicializa um novo pipe no sistema e retorna, no array pipefd, os descritores identificando cada uma das pontas do pipe. A primeira posição do array, i.e. pipefd[0], recebe o descritor que pode ser aberto apenas para leitura, enquanto a segunda posição do array, i.e. pipefd[1], recebe o descritor que pode ser aberto apenas para escrita. A função retorna zero no caso de sucesso, ou -1 se ocorrer erro.

    As primitivas send/receive para uso de um pipe no UNIX são implementadas por SystemCalls read/write, conforme segue:

    • ssize_t read(int fd, void *buf, size_t count): “puxa” dados do pipe identificado pelo descritor fd. Os dados recebidos são os apontados pelo ponteiro buf, sendo count a quantidade máxima de bytes a serem recebidos. A função retorna o número de bytes recebidos.
    • ssize_t write(int fd, const void *buf, size_t count): “empurra” dados no pipe identificado pelo descritor fd. Os dados transmitidos são os apontados pelo ponteiro buf, sendo count a quantidade de bytes a serem transmitidos. A função retorna o número de bytes transmitidos.
    • Exemplo 1: Transmitindo e recebendo pelo próprio processo
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    
    #define SHM_SIZE 1024 
    
    int main()
    {
       int fd[2];
       char *ptr = "Alo eu mesmo";
       char *ptr_alvo;
       int tamanho_dados, ret;
    
       tamanho_dados = strlen(ptr)+1;
    
       if (pipe(fd)==-1){
          printf ("erro criação pipe\n");
          exit(-1);
       }
       printf("Transmitido %d bytes\n", tamanho_dados);
       write (fd[1], ptr, tamanho_dados);
    
       ptr_alvo = malloc(tamanho_dados);
    
       ret=read(fd[0],ptr_alvo,tamanho_dados);
      
       printf("ret = %d dados => %s\n", ret, ptr_alvo);  
       return 0;
    }
    


    • Exemplo 2: Abaixo há um exemplo de programa criando um pipe e compartilhando os descritores entre dois processos (criados via fork()).
    #include <unistd.h>   
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    
    char *message = "This is a message!!!" ;
    
    main()
    {
        char buf[1024] ;
        int fd[2];
        pipe(fd);    /*create pipe*/
        if (fork() != 0) { /* I am the parent */
            write(fd[1], message, strlen (message) + 1) ;
        }
        else { /*Child code */
            read(fd[0], buf, 1024) ;
            printf("Got this from MaMa!!: %s\n", buf) ;
        }
    }
    
    • Exercício 1: construa um “pipeline”. Crie um programa que conecta 4 processos através de 3 pipes. Utilize fork() para criar vários processos.
    //Solução possível
    
    #include <unistd.h>   
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h> 
    
    char *message = "This is a message to send!!!" ;
    
    main()
    {
    
        int size = strlen(message)+1;
        char buf[size];
        char buf1[size];
        char buf2[size];
    
        int status;
        int fd[2];  
        pipe(fd);
    
        if (fork() != 0) { /* I am the parent */
    
    	printf("Processo A PID: %d\n", getpid());
    	write(fd[1], message, size);	
            wait(&status);
           
        }
        else { /*Child code */
            	
              int status1;
              int fd1[2];
    	  pipe(fd1);
    	  
    	  if (fork() != 0) { /* I am the parent */
    
    		  printf("Processo B PID: %d\n", getpid());
    		  read(fd[0], buf, size); 	
                      write(fd1[1], buf, size);
    		  wait(&status1);
           
    	  }else { /*Child code */
    		  
                      int status2;
             	  int fd2[2];
    	          pipe(fd2);
              
    		  
    		  if (fork() != 0) { /* I am the parent */
    			printf("Processo C PID: %d\n", getpid());
    
    			 read(fd1[0], buf1, size); 	
                             write(fd2[1], buf1, size);
    		         wait(&status2);
    				
    	       
    		  }else { /*Child code */
    			
    			printf("Processo D PID: %d\n", getpid());
    			read(fd2[0], buf2, size);
    			printf("\n Mensagem -> %s <- \n ", buf2);
    	 			
    		  }	 		
    
    	  }	
    
        }	
        exit(0);	 			
    }
    
    • Exercício 2: Modifique o exercício anterior para que o processo D, através de um novo pipe, mande uma mensagem diretamente para o pai de todos.


    • Exercício 3: Consultor de Login de Acesso:. Estude o link https://www.geeksforgeeks.org/named-pipe-fifo-example-c-program/ e projete um programa cliente servidor da seguinte forma: (i) O servidor possui uma tabela de usuários (userid). O user_id é de tamanho fixo de 7 caracteres. O servidor espera por consultas para verificar se um usuário está na tabela. Se estiver responde com o caracter 'S' senão com 'N'. (ii) O cliente espera por user_id no teclado e consulta o servidor sobre sua existência na tabela. O cliente imprime na tabela a existência ou não do usuário.
    • Exercício 4: cópia de arquivo. Projete um programa de cópia de arquivos chamado FileCopy usando pipes comuns. Esse programa receberá dois parâmetros: o primeiro é o nome do arquivo a ser copiado e o segundo é o nome do arquivo copiado. Em seguida, o programa criará um pipe comum e gravará nele o conteúdo do arquivo a ser copiado. O processo filho lerá esse arquivo do pipe e o gravará no arquivo de destino. Por exemplo, se chamarmos o programa como descrito a seguir:
    $ FileCopy entrada.txt copia.txt
    
    o arquivo entrada.txt será gravado no pipe. O processo filho lerá o conteúdo desse arquivo e o gravará no arquivo de destino copia.txt. Escreva o programa usando os pipes da API POSIX no Linux.


    Laboratórios Disponíveis

    Ainda Threads - Escalonamento Round Robin e FCFS para Threads"


    Escalonamento Round-Robin preemptivo

    OBS: ver https://www.quora.com/What-exactly-does-typedef-do-in-C

    A fazer:

    • primitiva de término de processo (destrutor do objeto);
    • primitiva de yield
    • isolar o escalonador em uma função;
    • isolar o dispatcher em uma função;
    /**
      User-level threads example.
      
      Orion Sky Lawlor, olawlor@acm.org, 2005/2/18 (Public Domain)
      Modificado por Eraldo 
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h> /* for makecontext/swapcontext routines */
    #include <queue> /* C++ STL queue structure */
    #include <vector>
    
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
    
    #define TIME_SLICE 5
    
    typedef void (*threadFn)(void);
    
    class thread_cb {
       int id_thread;
       public:
       ucontext_t contexto;
       thread_cb(threadFn p, int id)
       {
    	  getcontext(&contexto);
    	  int stackLen=32*1024;
    	  char *stack=new char[stackLen];
    	  contexto.uc_stack.ss_sp=stack;
    	  contexto.uc_stack.ss_size=stackLen;
    	  contexto.uc_stack.ss_flags=0;      
         id_thread = id;
         makecontext(&contexto,p,0);
       };
       ucontext_t *get_context() {
         return &contexto;
       };
    };
    
    std::queue<class thread_cb *> ready_pool;
    
    int id_thread = 0;
    
    
    class thread_cb *curr_thread=NULL;
    
    void add_thread(threadFn func)
    {
      class thread_cb *p = new thread_cb(func, ++id_thread);
      ready_pool.push(p);
    }
    
    
    void dispatcher(ucontext_t *old_task, ucontext_t *new_task)
    {
      if (old_task!=NULL)
          swapcontext(old_task, new_task);
      else
          setcontext(new_task);
    }
    
    void scheduler_rr()
    {
        class thread_cb *next,*last;
        
       
       if(curr_thread!=NULL) {
          printf("Aqui\n");
          ready_pool.push(curr_thread);
          last=curr_thread;
    	   next=ready_pool.front();
    	   ready_pool.pop();
          curr_thread=next;    
          dispatcher(last->get_context(), curr_thread->get_context()); 
       } else {
    	   next=ready_pool.front();
       	ready_pool.pop();
          curr_thread = next;
          dispatcher(NULL, next->get_context());
       }
    }
    
    void sig_handler(int signo)
    {
    
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
      
    	if (ready_pool.empty()) {
    		printf("Nothing more to run!\n");
    		exit(0);
    	}
       scheduler_rr();
    }
    
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
    
    void runA(void) {
    	for (;;) {
          printf("running A\n");
          sleep(1);
       }
    }
    
    void runB(void) {
    	for (;;) {
          printf("running B\n");
          sleep(1);
       }
    }
    
    main()
    {
      add_thread(runA);
      add_thread(runB);
      preparar_handler();
      for(;;);
    }
    
    /**
      User-level threads example.
     
      Orion Sky Lawlor, olawlor@acm.org, 2005/2/18 (Public Domain)
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h> /* for makecontext/swapcontext routines */
    #include <queue> /* C++ STL queue structure */
    #include <vector>
     
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
     
    #define TIME_SLICE 1
     
    typedef void (*threadFn)(void);
     
    class thread_cb {
       int id_thread;
       public:
       ucontext_t contexto;
       thread_cb(threadFn p, int id)
       {
    	  getcontext(&contexto);
    	  int stackLen=32*1024;
    	  char *stack=new char[stackLen];
    	  contexto.uc_stack.ss_sp=stack;
    	  contexto.uc_stack.ss_size=stackLen;
    	  contexto.uc_stack.ss_flags=0;      
         id_thread = id;
         makecontext(&contexto,p,0);
       };
       ucontext_t *get_context() {
         return &contexto;
       };
    };
     
    std::queue<class thread_cb *> ready_pool;
     
    int id_thread = 0;
     
    class thread_cb *curr_thread=NULL;
     
    
    void scheduler_rr();
    
    void add_thread(threadFn func)
    {
      class thread_cb *p = new thread_cb(func, ++id_thread);
      ready_pool.push(p);
    }
     
     
    void yield_thread()
    {
      scheduler_rr();
    }
    
    void delete_thread()
    {
      delete curr_thread;
      curr_thread=NULL;
      scheduler_rr();
    }
    
    void dispatcher(ucontext_t *old_task, ucontext_t *new_task)
    {
      if (old_task!=NULL)
          swapcontext(old_task, new_task);
      else
          setcontext(new_task);
    }
     
    void scheduler_rr()
    {
        class thread_cb *next,*last;
     
     
       if(curr_thread!=NULL) {
          printf("Aqui\n");
          ready_pool.push(curr_thread);
          last=curr_thread;
    	   next=ready_pool.front();
    	   ready_pool.pop();
          curr_thread=next;    
          dispatcher(last->get_context(), curr_thread->get_context()); 
       } else {
    	   next=ready_pool.front();
       	ready_pool.pop();
          curr_thread = next;
          dispatcher(NULL, next->get_context());
       }
    }
     
    void sig_handler(int signo)
    {
     
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
     
    	if (ready_pool.empty()) {
    		printf("Nothing more to run!\n");
    		exit(0);
    	}
       scheduler_rr();
    }
     
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
     
    struct delta{
      long alfa;
      char epson[1000];
      long beta;
    } shar;
    
    int turn;
    int flag[2];
    
    #define TRUE 1
    #define FALSE 0
    
    void ent_rc(int p, int vt)
    {
      flag[p]=TRUE;
      turn = vt;
      if(p) p=0; else p=1;
      while (flag[p] && turn == vt);
         //printf("Thread %d: esperando para acessar a região crítica\n", p);
    }
    
    void sai_rc(int p)
    {
      flag[p]=FALSE;
    }
    
    void runA(void) {
      struct delta x = {0, 100};
     
      for (;;) {
          x.alfa=0;x.beta=0;
          ent_rc(0,1);
          shar=x;  // regiao crítica
          sai_rc(0);
          x.alfa=100;x.beta=100;
          ent_rc(0,1);
          shar=x;  // regiao crítica
          sai_rc(0);              
       }
    }
     
    void runB(void) {
     
       for (;;) {
          ent_rc(1,0);
          printf("shar alfa = %ld shar beta = %ld \n",shar.alfa, shar.beta);  // regiao crítica
          sai_rc(1);
          sleep(1);
       }
    }
    
    void runC(void) {
       int i;
       for (i=0;i<10;i++) {
          printf("Thread C - parte 1\n");
          yield_thread();
          printf("Thread C - parte 2\n");
          yield_thread();
       }
       delete_thread();
    }
    
     
    main()
    {
      add_thread(runA);
      add_thread(runB);
      add_thread(runC);
      preparar_handler();
      for(;;);
    }
    
    Ainda Threads - Um escalonador semi-automático"

    Ainda Threads - Um escalonador semi-automático usando sinais

    Neste laboratório serão aprimorados os threads em nível de aplicação vistos na aula anterior. Usaremos sinais do Unix/Linux para escalonar os threads via handlers.

    Exemplo 1:

    No Linux/Unix o kernel ou um processo podem enviar sinais para outros processos. Trata-se de uma notificação de que um evento ocorreu. O processo pode ter tratadores (handlers) para estes sinais de forma a tratá-los de forma assíncrona. Pode-se fazer uma analogia com a interrupção por hardware de um programa em execução. Um tratador da interrupção trata a causa da mesma (evento) e no final do tratador (handler) é restaurado o contexto do programa interrompido. Por exemplo, quando um filho se encerra, o pai recebe do kernel o sinal SIGCHLD.

    No exemplo abaixo utilizamos o sinail USR1 de uso geral (disponível para o usuário). Associamos um handler a este sinal (sig_handler). O programa sendo executado, fica em loop no main(). Usando um outro terminal podemos enviar um sinal e verificar o efeito do mesmo.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    
    void sig_handler(int signo)
    {
      int x;
      printf("Turma de SOP: recebido SIGUSR1\n");
    }
    
    int main(void)
    {
      int x;
    
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      // Loop  eterno dormindo 1s
      while(1) {
        sleep(1);
      }
      return 0;
    }
    

    Fazer no terminal:

     ps aux
    

    anotar o pid do processo e enviar o sinal:

     kill -USR1 pid
    

    Exemplo 2:

    O programa anterior é aprimorado para começar a executar um thread Ping similar a aula anterior. Ainda não existe escalonamento dos threads. Somente o thread ping é executado. O sinal USR1 pode ser enviado para o processo interrompendo a execução do thread.

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
     
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
     
    #define PING_ID 1
     
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
     
    int curr_thread;
     
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("Turma de SOP: recebido SIGUSR1\n");   
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
     
    }
     
    void f_pong(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
    }
     
    void preparar_contexto_ping()
    {
       char *stack;
     
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    }
     
    void preparar_contexto_pong()
    {
       char *stack;
     
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
     
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    }
     
    int main(void)
    {
      int x;
     
      printf ("Main INICIO\n");
      preparar_contexto_ping();
      preparar_contexto_pong();
     
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
     
      curr_thread=PING_ID;
      swapcontext(&cMain, &cPing);
      // loop eterno que nunca é executado
      while(1) {
        sleep(1);
      }
      return 0;
    }
    

    Enviar o sinal USR1 para ver o efeito.

    Exemplo 3:

    Neste exemplo preparamos o handler para chavear o contexto de ping para pong e vice-versa. A cada USR1 recebido será realizada a troca de contexto.
    
    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
    
    #define PING_ID 1
    #define PONG_ID 2
    
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    
    int curr_thread;
    
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("Turma de SOP: recebido SIGUSR1\n");   
      if (curr_thread==PING_ID) {
         curr_thread=PONG_ID;
         swapcontext(&cPing, &cPong);    
      } else {
         curr_thread=PING_ID;
         swapcontext(&cPong, &cPing);
      }
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
       
    }
     
    void f_pong(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
    }
    
    void prepara_contexto_ping()
    {
       char *stack;
     
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    }
    
    void prepara_contexto_pong()
    {
       char *stack;
    
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
     
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    }
    
    int main(void)
    {
      int x;
    
      printf ("Main INICIO\n");
      prepara_contexto_ping();
      prepara_contexto_pong();
      
      if (signal(SIGUSR1, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
    
      curr_thread=PING_ID;
      swapcontext(&cMain, &cPing);
      // A long long wait so that we can easily issue a signal to this process
      while(1) {
        sleep(1);
      }
      return 0;
    }
    


    Exercício: Tente analisar as pilhas dos threads e verificar sob qual pilha o handler se executa.

    Exercício 4:

    Neste ponto associamos um timer ao handler com o apoio do sinal ALRM. Agora os threads são escalonados automaticamente.

    1. include<stdio.h>
    2. include <stdlib.h>
    3. include<signal.h>
    4. include<unistd.h>
    5. include <ucontext.h>
    6. include <sys/time.h>
    1. define STACKSIZE 32768 /* tamanho de pilha das threads */

    // number of seconds for setting the interval used by the timer

    1. define QUANTUM_SEC 0

    // number of microseconds for setting the interval used by the timer (0 - 999999)

    1. define QUANTUM_MICRO_SEC 100000
    1. define PING_ID 1
    2. define PONG_ID 2
    3. define TIME_SLICE 5

    /* VARIÁVEIS GLOBAIS */ ucontext_t cPing, cPong, cMain; int curr_thread;

    /* Handler para tratar o sinal */ void sig_handler(int signo) {

     printf("SOP da Turma 2019-2: recebido SIGALRM\n");
     alarm(TIME_SLICE);   
     if (curr_thread==PING_ID) {
        curr_thread=PONG_ID;
        swapcontext(&cPing, &cPong);    
     } else {
        curr_thread=PING_ID;
        swapcontext(&cPong, &cPing);
     }
    

    }

    /* Funções-comportamento das Tarefas */ void f_ping(void * arg) {

      int i=0;
    
      printf("%s iniciada\n", (char *) arg);
    
      for (;;) {
         printf("%s %d\n", (char *) arg, i++);
         sleep(1);
      }
      printf("%s FIM\n", (char *) arg);
    
      
    

    }

    void f_pong(void * arg) {

      int i=0;
    
      printf("%s iniciada\n", (char *) arg);
    
      for (;;) {
         printf("%s %d\n", (char *) arg, i++);
         sleep(1);
      }
      printf("%s FIM\n", (char *) arg);
    

    }

    void preparar_contexto_ping() {

      char *stack;
    
      getcontext(&cPing);
      stack = malloc(STACKSIZE);
      if(stack) {
         cPing.uc_stack.ss_sp = stack ;
         cPing.uc_stack.ss_size = STACKSIZE;
         cPing.uc_stack.ss_flags = 0;
         cPing.uc_link = 0;
      }
      else {
         perror("Erro na criação da pilha: ");
         exit(1);
      }
      makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    

    }

    void preparar_contexto_pong() {

      char *stack;
    
      getcontext(&cPong);
      stack = malloc(STACKSIZE);
      if(stack) {
         cPong.uc_stack.ss_sp = stack ;
         cPong.uc_stack.ss_size = STACKSIZE;
         cPong.uc_stack.ss_flags = 0;
         cPong.uc_link = 0;
      }
      else {
         perror("Erro na criação da pilha: ");
         exit(1);
      }
    
      makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    

    }

    void preparar_handler() {

     if (signal(SIGALRM, sig_handler) == SIG_ERR) {
         printf("\nProblemas com SIGUSR1\n");
         exit(-1);
     }
     alarm(TIME_SLICE);
    

    }

    int main(void) {

     int x;
    
     printf ("Main INICIO\n");
     preparar_contexto_ping();
     preparar_contexto_pong();
     preparar_handler();
     curr_thread=PING_ID; //ajusta primeiro thread
     swapcontext(&cMain, &cPing); //nunca mais volta...
     return 0;
    

    }

    </syntaxhighlight>

    #include<stdio.h>
    #include <stdlib.h>
    #include<signal.h>
    #include<unistd.h>
    #include <ucontext.h>
    #include <sys/time.h>
     
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
     
    // number of seconds for setting the interval used by the timer
    #define QUANTUM_SEC 0
    // number of microseconds for setting the interval used by the timer (0 - 999999)
    #define QUANTUM_MICRO_SEC 100000
     
    #define PING_ID 1
    #define PONG_ID 2
    #define TIME_SLICE 5
     
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    int curr_thread;
     
    /* Handler para tratar o sinal */
    void sig_handler(int signo)
    {
      printf("SOP da Turma 2019-2: recebido SIGALRM\n");
      alarm(TIME_SLICE);   
      if (curr_thread==PING_ID) {
         curr_thread=PONG_ID;
         swapcontext(&cPing, &cPong);    
      } else {
         curr_thread=PING_ID;
         swapcontext(&cPong, &cPing);
      }
    }
     
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i=0;
     
       printf("%s iniciada\n", (char *) arg);
     
       for (;;) {
          printf("%s %d\n", (char *) arg, i++);
          sleep(1);
       }
       printf("%s FIM\n", (char *) arg);
     
     
    }
     
    void preparar_contexto_ping(ucontext_t *pContext, char *p)
    {
       char *stack;
     
       getcontext(pContext);
       stack = malloc(STACKSIZE);
       if(stack) {
          (*pContext).uc_stack.ss_sp = stack ;
          (*pContext).uc_stack.ss_size = STACKSIZE;
          (*pContext).uc_stack.ss_flags = 0;
          (*pContext).uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
       makecontext(pContext, (void*)(*f_ping), 1, p);
    }
     
    void preparar_handler()
    {
      if (signal(SIGALRM, sig_handler) == SIG_ERR) {
          printf("\nProblemas com SIGUSR1\n");
          exit(-1);
      }
      alarm(TIME_SLICE);
    }
     
    int main(void)
    {
      int x;
     
      printf ("Main INICIO\n");
      preparar_contexto_ping(&cPing, "ping");
      preparar_contexto_ping(&cPong, "pong");
      preparar_handler();
      curr_thread=PING_ID; //ajusta primeiro thread
      swapcontext(&cMain, &cPing); //nunca mais volta...
      return 0;
    }
    


    Um Exemplo de Uso "API Padrão POSIX"

    Um Exemplo de Uso "API Padrão POSIX"

    Referências


    Crie uma função soma que receba 2 ponteiros referenciando posições na memória, criadas utilizando nmap(), de maneira que estas posições armazenem números inteiros. A função soma deverá retornar a soma dos números apontados em regiões da memória sem a utilização de nenhuma rotina da biblioteca do C, que não sejam definidas por APIs posix, para criação destas regiões na memória (malloc, alloc, calloc). Após retornar o resultado da soma os devidos ponteiros deverão ser extintos da memória.


    • Experimento 1: Aumente o tamanho da memória alocada até quando for possível.

    Qual o tamanho limite da memória que você conseguiu alocar?

    • Experimento 2: Mude o escopo para PROT_NONE, após executar e depurar o código explique o que aconteceu.

    Em sua opinião NMAP trata-se de uma syscall ou de uma API? Afinal API e syscall são a mesma coisa? Explique.

    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *addr, size_t length);
    
    addr = Valor do início do mapeamento.
    length = valor do tamanho da região a ser alocada.
    prot = especificações de proteção da região alocada (consultar http://man7.org/linux/man-pages/man2/mmap.2.html).
    flags = especificação do escopo e do tipo da região criada (exemplo publica ou privada, se é anônima ou não).
    


    void* meu_malloc(size_t tamanho) {
      void* addr = mmap(0,                      // addr
                        tamanho,   // len
                        PROT_READ | PROT_WRITE,  // prot
                        MAP_ANON | MAP_PRIVATE, // flags
                        -1,                     // filedes
                        0);                     // off
      *(size_t*)addr = tamanho;
      return addr;
    }
    
    int meu_free(void* addr) {
      return munmap(addr - sizeof(size_t), (size_t) addr);
    }
    
    
    int soma(int *N1, int *N2){
    
    return (*N1+*N2);
    
    }
    
    
    int main(int argc, char* argv[]) {
      
      int* numero1 = meu_malloc(sizeof(int));
      int* numero2 = meu_malloc(sizeof(int)); 
      
    
      *numero1 = 10;
      *numero2 = 20;	
    
      int resultado = soma(numero1, numero2);
    
      printf("\n\n O resultado da soma é %d \n\n",resultado);	  	
      
      meu_free(numero1);
      meu_free(numero2);
    
      return 0;
    }
    


    Processos no Linux

    Processos no Linux - Modificado por Eraldo

    Processos no Linux

    • Exercícios propostos pelo Prof.Arliones e Modificados por Eraldo


    DesenhoForkExec.png


    Syscall FORK
    • Em um terminal, execute "man fork"
      • A função da API POSIX fork() aciona uma chamada de sistema (system call - syscall) que cria um novo processo duplicando o processo que realiza a chamada. O novo processo, chamado de filho, é uma cópia exata do processo criador, chamado de pai, exceto por alguns detalhes listados na manpage. O principal destes detalhes para nós agora é o fato de terem PIDs diferentes.
      • O código dos dois processos (pai e filho) são idênticos;
      • Os dados dos dois processos (pai e filho) são idênticos NO MOMENTO DA CRIAÇÃO;
      • Execução do processo filho inicia na próxima instrução do programa (no retorno da chamada FORK);
      • Não é possível saber qual dos processos (pai ou filho) retormará a execução primeiro - isto fica a cargo do excalonador do SO;
      • Valores de retorno da chamada FORK:
        • (-1): erro na criação do processo (ex.: memória insuficiente);
        • (0): em caso de sucesso, este é o valor de retorno recebido pelo processo filho;
        • (>0): em caso de sucesso, este é o valor de retorno recebido pelo processo pai;
    Syscall JOIN
    • A syscall JOIN é implementada no POSIX pela função wait(). Execute "man wait".
      • Além da função wait(), há também waitpid() e waitid();
      • Todas estas syscalls são utilizadas para aguardar por mudanças no estado de um processo filho e obter informações sobre o processo filho cujo estado tenha mudado. São consideradas mudanças de estado: o filho terminou; o filho foi finalizado por um sinal (ex.: kill); o filho foi retomado por um sinal (ex.: alarme);
      • A chamada wait também libera os recursos do processo filho que termina;
      • wait(): esta função suspende a execução do processo chamador até que UM DOS SEUS FILHOS finalize;
      • waitpid(): suspende a execução do processo chamador até que UM FILHO ESPECÍFICO finalize;
    Syscall EXEC
    • A syscall EXEC é implementada no POSIX pela família de funções exec(). Execute "man exec".
      • As principais funções da família são execl(), execlp() e execvp();
      • Todas estas funções são, na realidade, front-ends (abstrações) para a syscall execve. Esta syscall substitui a imagem do processo corrente (aquele que chama a syscall) pela a imagem de um novo processo;
      • Os parâmetros passados a estas funções são, basicamente, o nome de um arquivo com a imagem do programa a ser executado (um binário de um programa), e uma lista de parâmetros a serem passados a este novo programa;


    Exemplos POSIX utilizando fork/wait/exec
    • Exemplo 1: fork/wait básico
    // ex1: fork/wait básico
    #include <sys/types.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main()
    {
        int pid, status;
        pid = fork();
    
        if(pid == -1) // fork falhou
        {
            perror("fork falhou!");
            exit(-1);
        }
        else if(pid == 0) // Este é o processo filho
        {
            printf("processo filho\t pid: %d\t pid pai: %d\n", getpid(), getppid());
            exit(0);
        }
        else // Este é o processo pai
        {
            wait(&status);
            printf("processo pai\t pid: %d\t pid pai: %d\n", getpid(), getppid());
            exit(0);
        }
    }
    
    arliones@socrates:~/tmp$ gcc ex1.c -o ex1 
    arliones@socrates:~/tmp$ ./ex1 
    processo filho	 pid: 27858	 pid pai: 27857
    processo pai	 pid: 27857	 pid pai: 5337
    arliones@socrates:~/tmp$
    
    • Exemplo 2: processos pai e filho compartilham código, mas não dados.
    // ex2: fork/wait "compartilhando" dados
    // ex2: fork/wait "compartilhando" dados
    #include <sys/types.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main()
    {
        int pid, status, k=0;
        printf("processo %d\t antes do fork\n", getpid());
        pid = fork();
        printf("processo %d\t depois do fork\n", getpid());
     
        if(pid == -1) // fork falhou
        {
            perror("fork falhou!");
            exit(-1);
        }
        else if(pid == 0) // Este é o processo filho
        {
            k += 1000;
            printf("processo filho\t pid: %d\t K: %d \t endereço K: %p\n", getpid(), k, &k);
            exit(0);
        }
        else // Este é o processo pai
        {
            wait(&status);  // espera o filho terminar
            k += 10;
            printf("processo pai\t pid: %d\t K: %d  \t endereço K:  %p\n", getpid(), k,  &k);
            exit(0);
        }
        k += 1000;
        printf("FIM processo %d\t K: %d\n", getpid(), k); 
        exit(0);
    }
    
    processo 17056   antes do fork
    processo 17056   depois do fork
    processo 17057   depois do fork
    processo filho   pid: 17057      K: 1000         endereço K: 0x7ffd8923e318
    processo pai     pid: 17056      K: 10           endereço K:  0x7ffd8923e318
    
    • Modificação no código: comentar linhas 23 e 30
    • Analise os resultados e busque entender a diferença.
    • Perguntas e desafios.:
    1. Analisando os valores impressos de k pode-se dizer que os dados são compartilhados entre os dois processos?
    2. O endereço de k impresso é o mesmo nos dois processos. ISto não contradiz a afirmação anterior?
    3. Substitua o k por uma área criada dinâmicamente com o malloc (área de HEAP). Verifique se esta área é ou não compartilhada pelos processos.


    Exercício fork/wait

    Excrever um programa C que cria uma árvore de 3 processos, onde o processo A faz um fork() criando um processo B, o processo B, por sua vez, faz um fork() criando um processo C. Cada processo deve exibir uma mensagem "Eu sou o processo XXX, filho de YYY", onde XXX e YYY são PIDs de processos. Utilizar wait() para garantir que o processo C imprima sua resposta antes do B, e que o processo B imprima sua resposta antes do A. Utilizar sleep() (man 3 sleep) para haver um intervalo de 1 segundo entre cada mensagem impressa.

    • Use o comando pstree para verificar a árvore de processos criada.
    Solução

    /* ex3: Excrever um programa C que cria uma arvore de 3 processos, onde o processo A faz um fork() criando um processo B, o processo B, por sua vez, faz um fork() criando um processo C. Cada processo deve exibir uma mensagem "Eu sou o processo XXX, filho de YYY", onde XXX e YYY sao PIDs de processos. Utilizar wait() para garantir que o processo C imprima sua resposta antes do B, e que o processo B imprima sua resposta antes do A. Utilizar sleep() (man 3 sleep) para haver um intervalo de 1 segundo entre cada mensagem impressa.

    • /
    1. include <sys/types.h>
    2. include <stdlib.h>
    3. include <stdio.h>
    4. include <unistd.h>
    5. include <sys/wait.h>

    int main() {

       int pid, status;
       pid = fork();
    
       if(pid == -1) // fork falhou
       {
           perror("fork falhou!");
           exit(-1);
       }
       else if(pid == 0) // Este é o processo filho
       {
           pid = fork();
           if(pid == -1)
           {
               perror("fork falhou!");
               exit(-1);
           }
           else if(pid == 0) // Este é o filho do filho
           {
               sleep(1);
               printf("Eu sou o processo C (PID %d), filho de %d\n", getpid(), getppid());
               exit (0);
           }
           else
           {
               wait(&status);
               sleep(1);
               printf("Eu sou o processo B (PID %d), filho de %d\n", getpid(), getppid());
               exit(0);
           }
       }
       else // Este é o processo pai
       {
           wait(&status);
           sleep(1);
           printf("Eu sou o processo A (PID %d), filho de %d\n", getpid(), getppid());
           exit(0);
       }
    

    }

    </syntaxhighlight>


    DESAFIO
    fork/wait

    Reimplementar o exercício anterior de criação de uma árvore de 3 processos, generalizando a criação de N processos onde N é repassado na linha de comando do programa. SUGESTÃO: usar um comando for, mas lembrar que se existe um fork dentro do for, então cada filho gerado dará continuidade a execução do for. É necessário que o processo faça um exit ou retorne neste momento.

    DESAFIO
    Exercício status/wait

    O status passado como parâmetro à função wait(&status) é, na verdade, o mecanismo de retorno de resultado do wait/waitpid. Ao retornar, esta variável contém informações sobre o resultado da execução do processo filho. Por exemplo, se um processo terminou normalmente (i.e., chamou exit), o comando WIFEXITED(status) retorna true. Este comando retorna false se o processo foi abortado (e.g., segmentation fault) ou morto (e.g., kill). Investigue no manual do wait no Linux (man wait) o funcionamento do comando WEXITSTATUS(status). Imagine um problema de busca de dados armazenados na forma de uma matriz de inteiros 4x30. Você está interessado em saber quantas ocorrências de um determinado número existe em cada linha da matriz. Note que que são tarefas que podem ser paralelizadas e usufruir de um sistema capaz de executá-las em paralelo. Faça uma implementação paralelizando 4 processos filhos a partir de um pai, onde cada processo é responsável por uma busca. A quantidade de ocorrências do número buscado é retornada e capturada através de WEXITSTATUS.

    Syscall EXEC
    • Exemplo exec()
    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      execl("/bin/ls","ls","-l", NULL);
      return 0;
    }
    
    • Exercício 1: Modificar o código para mostrar que o exec() não retorna (colocar um printf após o exec).
    • Exercício 2: Criar um exemplo (dois programas ) para demonstrar que o exec não cria novo processo.
      • Crie um primeiro programa (prog1) que imprime o seu pid e depois faz um exec do segundo programa.
      • Crie o segundo programa que simplesmente imprime o pid.


    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      printf("EU ANTES DO EXEC: Meu pid é %d\n", getpid());
      execl("./prog2","prog2", NULL);
      return 0;
    }
    
    #include <sys/types.h>
    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
      printf("EU DEPOIS DO EXEC:Meu pid é %d\n", getpid());
      return 0;
    }
    
    • Exercício 3: Criar um exemplo usando fork/exec mostrando que um processo pai cria um filho e espera por sua execução. O filho executa o comando "ps aux". Ambos devem mostrar seus pids.
    Threads de aplicação

    Threads de aplicação

    O Linux, através da API POSIX, oferece um conjunto de funções que permite às aplicações manipular contextos, facilitando a vida do programador que quer implementar tarefas "simultâneas" dentro de um único processo, ou seja, threads. As seguintes funções e tipos estão disponíveis:

    • getcontext(&a): salva o contexto na variável a;
    • setcontext(&a): restaura um contexto salvo anteriormente na variável a;
    • swapcontext(&a,&b): salva o contexto atual na variável a e restaura o contexto salvo anteriormente na variável b;
    • makecontext(&a, ...): ajusta parâmetros internos do contexto salvo em a;
    • ucontext_t: as variáveis a e b são do tipo ucontext_t. Este tipo armazena um contexto.

    Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext).

    Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.

    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h>
    
    #define STACKSIZE 32768		/* tamanho de pilha das threads */
    
    /* VARIÁVEIS GLOBAIS */
    ucontext_t cPing, cPong, cMain;
    
    /* Funções-comportamento das Tarefas */
    void f_ping(void * arg) {
       int i;
    
       printf("%s iniciada\n", (char *) arg);
    
       for (i=0; i<4; i++) {
          printf("%s %d\n", (char *) arg, i);
          swapcontext(&cPing, &cPong);
       }
       printf("%s FIM\n", (char *) arg);
    
       swapcontext(&cPing, &cMain);
    }
    
    void f_pong(void * arg) {
       int i;
    
       printf("%s iniciada\n", (char *) arg);
    
       for (i=0; i<4; i++) {
          printf("%s %d\n", (char *) arg, i);
          swapcontext(&cPong, &cPing);
       }
       printf("%s FIM\n", (char *) arg);
    
       swapcontext(&cPong, &cMain);
    }
    
    /* MAIN */
    int main(int argc, char *argv[]) {
       char *stack;
    
       printf ("Main INICIO\n");
    
       getcontext(&cPing);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPing.uc_stack.ss_sp = stack ;
          cPing.uc_stack.ss_size = STACKSIZE;
          cPing.uc_stack.ss_flags = 0;
          cPing.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
    
       makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
    
       getcontext(&cPong);
       stack = malloc(STACKSIZE);
       if(stack) {
          cPong.uc_stack.ss_sp = stack ;
          cPong.uc_stack.ss_size = STACKSIZE;
          cPong.uc_stack.ss_flags = 0;
          cPong.uc_link = 0;
       }
       else {
          perror("Erro na criação da pilha: ");
          exit(1);
       }
    
       makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
    
       swapcontext(&cMain, &cPing);
       swapcontext(&cMain, &cPong);
    
       printf("Main FIM\n");
    
       exit(0);
    }
    
    • Exercício 1: Primeiramente compile e execute o código. Agora estude o código e entenda completamente o seu funcionamento. Explique em DETALHES o código, comentando todas as linhas. Na seção de diagrama do seu relatório, desenhe um diagrama de funcionamento do código para mostrar exatamente como acontece a troca de contexto entre as threads.
    • Exercício 2: Acrescente um procedimento f_new que receba 4 strings como parâmetros e imprima todas na tela. Antes do final da execução do main faça uma mudança de contexto para chamar o procedimento criado.

    Exercício 3: O que acontece se um dos threads é colocado para dormir? Todos os demais threads param a sua execução.

    Exercício 4: Note que um thread somente deixa a execução para outro thread de forma explícita. Será que é possível realizar um escalonamento de threads de forma similar ao que o kernel faz com os processos? Ver http://www.cplusplus.com/forum/unices/6452/

    Trocas de mensagens com pipes

    Trocas de mensagens com pipes

    Troca de mensagens

    Um mecanismo disponibilizado por sistemas UNIX para troca de mensagens entre processos é o PIPE. Pipes são mecanismos de comunicação indireta onde mensagens são trocadas através de mailboxes. Cada mailbox possui um identificador único, permitindo que processos identifiquem o canal de comunicação entre eles. O fluxo de mensagens em um Pipe é:

    • unidirecional: sobre um mesmo pipe, apenas um processo envia mensagens e um processo recebe mensagens;
    • FIFO: as mensagens são entregues na ordem de envio;
    • não-estruturado: não há estrutura pré-definida para o formato da mensagem.

    No UNIX, pipes são inicializados através da SystemCall pipe, que possui a seguinte sintaxe:

    • int pipe(int pipefd[2]): pipe inicializa um novo pipe no sistema e retorna, no array pipefd, os descritores identificando cada uma das pontas do pipe. A primeira posição do array, i.e. pipefd[0], recebe o descritor que pode ser aberto apenas para leitura, enquanto a segunda posição do array, i.e. pipefd[1], recebe o descritor que pode ser aberto apenas para escrita. A função retorna zero no caso de sucesso, ou -1 se ocorrer erro.

    As primitivas send/receive para uso de um pipe no UNIX são implementadas por SystemCalls read/write, conforme segue:

    • ssize_t read(int fd, void *buf, size_t count): “puxa” dados do pipe identificado pelo descritor fd. Os dados recebidos são os apontados pelo ponteiro buf, sendo count a quantidade máxima de bytes a serem recebidos. A função retorna o número de bytes recebidos.
    • ssize_t write(int fd, const void *buf, size_t count): “empurra” dados no pipe identificado pelo descritor fd. Os dados transmitidos são os apontados pelo ponteiro buf, sendo count a quantidade de bytes a serem transmitidos. A função retorna o número de bytes transmitidos.
    • Exemplo 1: Transmitindo e recebendo pelo próprio processo
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    
    #define SHM_SIZE 1024 
    
    int main()
    {
       int fd[2];
       char *ptr = "Alo eu mesmo";
       char *ptr_alvo;
       int tamanho_dados, ret;
    
       tamanho_dados = strlen(ptr)+1;
    
       if (pipe(fd)==-1){
          printf ("erro criação pipe\n");
          exit(-1);
       }
       printf("Transmitido %d bytes\n", tamanho_dados);
       write (fd[1], ptr, tamanho_dados);
    
       ptr_alvo = malloc(tamanho_dados);
    
       ret=read(fd[0],ptr_alvo,tamanho_dados);
      
       printf("ret = %d dados => %s\n", ret, ptr_alvo);  
       return 0;
    }
    


    • Exemplo 2: Abaixo há um exemplo de programa criando um pipe e compartilhando os descritores entre dois processos (criados via fork()).
    #include <unistd.h>   
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    
    char *message = "This is a message!!!" ;
    
    main()
    {
        char buf[1024] ;
        int fd[2];
        pipe(fd);    /*create pipe*/
        if (fork() != 0) { /* I am the parent */
            write(fd[1], message, strlen (message) + 1) ;
        }
        else { /*Child code */
            read(fd[0], buf, 1024) ;
            printf("Got this from MaMa!!: %s\n", buf) ;
        }
    }
    
    • Exercício 1: construa um “pipeline”. Crie um programa que conecta 4 processos através de 3 pipes. Utilize fork() para criar vários processos.
    //Solução possível
    
    #include <unistd.h>   
    #include <fcntl.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h> 
    
    char *message = "This is a message to send!!!" ;
    
    main()
    {
    
        int size = strlen(message)+1;
        char buf[size];
        char buf1[size];
        char buf2[size];
    
        int status;
        int fd[2];  
        pipe(fd);
    
        if (fork() != 0) { /* I am the parent */
    
    	printf("Processo A PID: %d\n", getpid());
    	write(fd[1], message, size);	
            wait(&status);
           
        }
        else { /*Child code */
            	
              int status1;
              int fd1[2];
    	  pipe(fd1);
    	  
    	  if (fork() != 0) { /* I am the parent */
    
    		  printf("Processo B PID: %d\n", getpid());
    		  read(fd[0], buf, size); 	
                      write(fd1[1], buf, size);
    		  wait(&status1);
           
    	  }else { /*Child code */
    		  
                      int status2;
             	  int fd2[2];
    	          pipe(fd2);
              
    		  
    		  if (fork() != 0) { /* I am the parent */
    			printf("Processo C PID: %d\n", getpid());
    
    			 read(fd1[0], buf1, size); 	
                             write(fd2[1], buf1, size);
    		         wait(&status2);
    				
    	       
    		  }else { /*Child code */
    			
    			printf("Processo D PID: %d\n", getpid());
    			read(fd2[0], buf2, size);
    			printf("\n Mensagem -> %s <- \n ", buf2);
    	 			
    		  }	 		
    
    	  }	
    
        }	
        exit(0);	 			
    }
    
    • Exercício 3: Modifique o exercício anterior para que o processo D, através de um novo pipe, mande uma mensagem diretamente para o pai de todos.


    • Exercício 3: Consultor de Login de Acesso:. Estude o link https://www.geeksforgeeks.org/named-pipe-fifo-example-c-program/ e projete um programa cliente servidor da seguinte forma: (i) O servidor possui uma tabela de usuários (userid). O user_id é de tamanho fixo de 7 caracteres. O servidor espera por consultas para verificar se um usuário está na tabela. Se estiver responde com o caracter 'S' senão com 'N'. (ii) O cliente espera por user_id no teclado e consulta o servidor sobre sua existência na tabela. O cliente imprime na tabela a existência ou não do usuário.
    • Exercício 4: cópia de arquivo. Projete um programa de cópia de arquivos chamado FileCopy usando pipes comuns. Esse programa receberá dois parâmetros: o primeiro é o nome do arquivo a ser copiado e o segundo é o nome do arquivo copiado. Em seguida, o programa criará um pipe comum e gravará nele o conteúdo do arquivo a ser copiado. O processo filho lerá esse arquivo do pipe e o gravará no arquivo de destino. Por exemplo, se chamarmos o programa como descrito a seguir:
    $ FileCopy entrada.txt copia.txt
    
    o arquivo entrada.txt será gravado no pipe. O processo filho lerá o conteúdo desse arquivo e o gravará no arquivo de destino copia.txt. Escreva o programa usando os pipes da API POSIX no Linux.



    Exercícios sobre Memória Compartilhada

    SHARED MEMORY: Produtor (pai) - Consumidor (filho) -

    Memória Compartilhada
    • Experimento Shared Memory: Complete o código a seguir para que os processos pai e filho possam compartilhar um segmento de memória. O filho escreve no segmento e o pai imprime na tela o conteúdo da mensagem.


    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    
    #define SHM_SIZE 1024 
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	char *segmento;
    	int modo,filho;
    
    	
    	shmid = shmget(IPC_PRIVATE, SHM_SIZE, S_IRUSR | S_IWUSR);
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
    
    	
    	segmento = shmat(shmid, (void *)0, 0);
    	if (segmento == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
         
    	if((filho = fork()) == -1)
    	{
    		perror("fork");
    		exit(1);
    	}
    
    	if(filho == 0)
    	{
    	        char *ptr_msg = "alo pai, tudo bem?";   
    		printf("Filho escrevendo no segmento compartilhado\n\n");
    		//completar aqui strcpy(segmento, ptr_msg);       //aqui deveria testar a cpacidade da área...
    
    		exit(0);
    	}
    	else
    	{
    	   wait(filho);	            
    	   printf("Mensagem para o pai: %s\n", segmento);
    	       
    	}
            
    	
    	if (shmdt(segmento) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
    
       return 0;
    }
    
    • Exemplo com ftok:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    #define SHM_SIZE 1024 
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	char *segmento;
    	int modo,filho;
    
    
    	key = ftok("./teste.txt", 'A'); /*O arquivo deve existir de verdade*/ 	
    	if (key == -1) 
    	{
    		perror("ftok");
    		exit(1);
    	}
    
    	
    	shmid = shmget(key, SHM_SIZE, (0644 | IPC_CREAT));
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
    
    	
    	segmento = shmat(shmid, (void *)0, 0);
    	if (segmento == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
         
    	if((filho = fork()) == -1)
    	{
    		perror("fork");
    		exit(1);
    	}
    
    	if(filho == 0)
    	{
    	      
    		printf("Filho escrevendo no segmento compartilhado\n\n");
    		strncpy(segmento, "mensagem compartilhada", SHM_SIZE);       
    
     		exit(0);
    	}
    	else
    	{
    	       wait(filho);	            
    	       printf("Mensagem para o pai: %s\n", segmento);
    	       
    	}
            
    	
    	if (shmdt(segmento) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
    
        return 0;
    }
    
    • Exemplo com ftok entre processos sem parentesco:

    Criar um arquivo teste.txt e em um terminal executar:

    #define SHM_SIZE 1024 
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	int modo,filho;
       
            struct minha_struct {
               char flag;
               int  numero;
            } *ptr;
       
    
    	key = ftok("./teste.txt", 'A'); /*O arquivo deve existir de verdade*/ 	
    	if (key == -1) 
    	{
    		perror("ftok");
    		exit(1);
    	}
    
    	
    	shmid = shmget(key, SHM_SIZE, (0644 | IPC_CREAT));
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
    
    	
    	ptr = (struct minha_struct *) shmat(shmid, (void *)0, 0);
    	if ((char *)ptr == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
       
            ptr->flag =0;
            ptr->numero = 0;
            while (ptr->flag==0) {  
               printf("%d\n", ptr->numero);
               sleep(1);
            }    
    	
    	if (shmdt(ptr) == -1) {
    		perror("shmdt");
    		exit(1);
    	}
    
            return 0;
    }
    

    Em outro terminal executar:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    #define SHM_SIZE 1024 
    
    int main(int argc, char *argv[])
    {
    	key_t key;
    	int shmid;
    	char *segmento;
    	int modo,filho;
       
            struct minha_struct {
               char flag;
               int  numero;
            } *ptr;
       
    
    	key = ftok("./teste.txt", 'A'); /*O arquivo deve existir de verdade*/ 	
    	if (key == -1) 
    	{
    		perror("ftok");
    		exit(1);
    	}
    
    	
    	shmid = shmget(key, SHM_SIZE, (0644));
    	if (shmid == -1) {
    		perror("shmget");
    		exit(1);
    	}
    
    	
    	ptr = (struct minha_struct *) shmat(shmid, (void *)0, 0);
    	if ((char *)ptr == (char *)(-1)) {
    		perror("shmat");
    		exit(1);
    	}  
       
    
    
       while (ptr->numero++<10)
         sleep(1);
    
       ptr->flag = 1;
    
       return 0;
    }
    

    Exercício Desafio: Implementar o problema do buffer circular usando memória compartilhada.

    Exercício (Algoritmo de Peterson)

    Exercício (Algoritmo de Peterson)

    Exercício 1: Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize o algoritmo de Peterson visto em aula. Utilize memória compartilhada para comunicação entre os processos.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
      
    main()
    {    
    	     	
    
    	if (fork() != 0) { /* I am the parent */
    		int i;	
    			
    		for(i = 0;i < 10;i=i+2){	
    			printf("Processo pai %d  \n", i);      
    	   			
    		}	
    		
    
    	}
    
    	else { /*Child code */
    	        int i;                
    		for(i = 1;i < 10;i=i+2){		    		
    			printf("Processo filho %d  \n", i);    
    		
    	        }
    		
    			         	
    	}
    	
    	exit(0);
    
    }
    


    Exercício 2: Considerando o exercício anterior faça a mesma sincronização, no entanto desta vez utilize a modelagem em software do TSL.

    • Em sua experiência, depois de testar diversas vezes as execuções de suas soluções baseadas no algoritmo de Peterson e Tsl, qual sua opinião sobre as abordagens?

    Explique seu raciocínio.

    Exercício (Semáforos)

    Exercício (Semáforos)

    Exercício 1: Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize Semáforos de acordo com a implementação em semaforo.h. Utilize memória compartilhada para comunicação entre os processos.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
      
    main()
    {    
    	     	
    
    	if (fork() != 0) { /* I am the parent */
    		int i;	
    			
    		for(i = 0;i < 10;i=i+2){	
    			printf("Processo pai %d  \n", i);  
                            sleep(1);
    	   			
    		}	
    		
    
    	}
    
    	else { /*Child code */
    	        int i;                
    		for(i = 1;i < 10;i=i+2){		    		
    			printf("Processo filho %d  \n", i);    
    		
    	        }
    		
    			         	
    	}
    	
    	exit(0);
    
    }
    


    SEMAFORO.H


    #include <sys/sem.h>
    
    
    int criar_semaforo(int val, int chave)  
    {
         int semid ;
    	
         union semun {
              int val;
              struct semid_ds *buf ;
              ushort array[1];
         } arg_ctl ;
    	
         key_t ft = ftok("/tmp", chave);
     	
         semid = semget(ft,1,IPC_CREAT|IPC_EXCL|0666);
         if (semid == -1) {
    	  semid = semget(ft,1,0666); 
              if (semid == -1) {
                   perror("Erro semget()");
                   exit(1) ;
              }
         }
         
         arg_ctl.val = val; //valor de início
         if (semctl(semid,0,SETVAL,arg_ctl) == -1) {
              perror("Erro inicializacao semaforo");
              exit(1);
         }
         return(semid) ;
    }
    
    void P(int semid){
    
         struct sembuf *sops = malloc(10*sizeof(int));
         sops->sem_num = 0;
         sops->sem_op = -1;
         sops->sem_flg = 0;
         semop(semid, sops, 1);  
         free(sops);
    
    }
    
    
    void V(int semid){
    
         struct sembuf *sops = malloc(10*sizeof(int));
         sops->sem_num = 0;
         sops->sem_op = 1;
         sops->sem_flg = 0; 
         semop(semid, sops, 1);  
         free(sops);
    
    } 
    
    
    
    void sem_delete(int semid) 
    {
         	
         if (semctl(semid,0,IPC_RMID,0) == -1)
           perror("Erro na destruicao do semaforo");
    }
    
    Programação concorrente

    Programação concorrente

    POSIX Threads

    A API POSIX disponibiliza uma biblioteca de threads chamada pthread. As threads são implementadas pela estrutura pthread_t, e manipuladas pelas funções (acesse as man-pages das chamadas para maiores detalhes):

    • pthread_create: cria uma thread;
    • pthread_kill: força a terminação de uma thread;
    • pthread_join: sincroniza o final de uma thread (qual a diferença/semelhança com o wait que usamos para processos?);
    • pthread_exit: finaliza uma thread.

    Para utilizar estas funções é necessário linkar o programa à libpthread (-lpthread).


    POSIX pthread mutex

    A biblioteca pthread implementa um tipo pthread_mutex_t, que garante a exclusão mútua entre threads. Estes mutex são manipulados através das funções (acesse as man-pages das chamadas para maiores detalhes):

    • pthread_mutex_lock: acessa um mutex.
    • pthread_mutex_trylock: tenta acessar um mutex (retorna valor indicando sucesso ou falha no lock).
    • pthread_mutex_unlock: libera um mutex.



    POSIX Semaphores

    Nos sistemas POSIX, semáforos são implementados pelo tipo sem_t e manipulado através das funções (acesse as man-pages das chamadas para maiores detalhes):

    • sem_init: inicializa um semáforo;
    • sem_destroy: destroy um semáforo;
    • sem_wait: implementa a operação p;
    • sem_post: implementa a operação v.

    Para utilizar estas funções é necessário linkar o programa à librt ou à libpthread (-lrt ou -lpthread).

    Exercício 1

    O programa abaixo cria 5 threads, e cada uma destas threads atualiza uma variável global (memória compartilhada).

    #include <iostream>
    #include <pthread.h>
    #include <signal.h>
     
    #define NUM_THREADS 5
     
    using namespace std;
    
    pthread_mutex_t mut;
    
    int saldo = 1000;
     
    int AtualizaSaldo(int n)
    {
    	int meu_saldo = saldo;
    	int novo_saldo = meu_saldo + n*100;
    	cout << "Novo saldo = " << novo_saldo << endl;
    	saldo = novo_saldo;
    }
     
    int main()
    {
    	pthread_t threads[NUM_THREADS];
            int i,ret;
    
    	// Cria cinco threads que executarão a mesma função
    	for(i=0; i<5; ++i){
    		ret = pthread_create(&threads[i], NULL, (void*(*)(void*))AtualizaSaldo,(void*)((long)i+1));
    		if(ret != 0){
    			fprintf(stderr, "Erro thread %d. Código %d: %s\n", (i+1), ret, strerror(ret));
    			exit(EXIT_FAILURE);
    		}
    	}
    	// Aguarda o fim das threads
    	for(i=0; i<5; ++i)
    		pthread_join(threads[i], NULL);
      
    	cout << "Saldo final é " << saldo << "." << endl;
    }
    
    1. Compile este programa.
    2. Execute este programa várias vezes. Ele funciona? Será que ele gera as saídas esperadas?
    3. Identifique as seções críticas do programa.
    4. Corrija o programa utilizando mutex.
    5. Analise a função AtualizaSaldo() com a sua solução. Lembre-se que o uso do mutex implica em apenas uma thread acessar a seção crítica por vez, enquanto outras threads ficam bloqueadas, esperando. Disso vem que, quanto menor o trecho de código entre um lock e um unlock, menos tempo uma thread necessita ficar esperando.
    6. Modifique o programa para usar um semáforo binário ao invés de um mutex em sua solução.


    Exercício 2

    Refaça o exercício 3 usando processos criados com fork e exec. O semáforo deve ser criado em uma região de memória compartilhada.

    Exercício 3

    Implemente com pthreads e semáforos/mutex a solução do produtor/comsumidor.

    Exercício 4

    O programa abaixo manipula uma matriz de tamanho MxN (veja os defines para o tamanho da matriz). A função SumValues soma todos os valores em uma linha da matriz. A linha a ser somada é identificada pela variável i. Modifique o programa principal (main) nos locais indicados para:

    1. Criar N threads, uma para somar os valores de cada linha.
    2. Receber o resultado do somatório de cada linha e gerar o somatório total da matriz.
    3. Analise o programa: há problemas de sincronização que precisam ser resolvidos? Se sim, resolva-os.
    #include <iostream>
    #include "thread.h"
    
    /* number of matrix columns and rows */
    #define M 5
    #define N 10
    
    using namespace std;
    
    int matrix[N][M];
    Thread *threads[N];
    
    
    /* thread function; it sums the values of the matrix in the row */
    int SumValues(int i)
    {
    	int n = i; /* number of row */
    	int total = 0; /* the total of the values in the row */
    	int j;
    	for (j = 0; j < M; j++) /* sum values in the "n" row */
    		total += matrix[n][j];
    	cout << "The total in row" << n << " is " << total << "." << endl;
    	/* terminate a thread and return a total in the row */
    	exit(total);
    }
    
    int main(int argc, char *argv[])
    {
    	int i, j;
    	int total = 0; /* the total of the values in the matrix */
    
    	 /* initialize the matrix */
    	for (i = 0; i < N; i++)
    		for (j = 0; j < M; j++)
    			matrix[i][j] = i * M + j;
    
    	/* create threads */
    	/* COLOQUE SEU CÓDIGO PARA CRIAR AS THREADS AQUI! */
    
    	/* wait for terminate a threads */
    	/* COLOQUE SEU CÓDIGO PARA PEGAR O SOMATÓRIO DE LINHAS E TOTALIZAR A SOMA DA MATRIZ AQUI! */
    
    	cout << "The total values in the matrix is " << total << endl;
    
    	return 0;
    }
    


    Lista de exercícios "Revisão para Prova"

    Lista de exercícios/Revisão para Prova

    A lista de exercícios referente a primeira prova (Parte introdutória + Processos) segue neste LINK | Lista de exercícios.

    Primeiro trabalho

    JOGO DA VIDA DE CONWAY e JANTAR DOS FILÓSOFOS

    Referências



    A partir da implementação sequencial do Game of Life apresentada a seguir, estude o código e transforme esta implementação em uma implementação paralela utilizando o protocolo de passagem de mensagens MPI. Perceba que a impĺementação mencionada utiliza como parâmetros de entrada um aquivo contendo o estado inicial do jogo, e o número de gerações que o usuário deseja rodar. A evolução do estado inicial é impressa na tela passo a passo. Sendo assim, a partir disso:

    • 1: Elabore uma implementação com 2 processos para o jogo da vida utilizando o protocolo MPI, de maneira que um processo execute uma geração i qualquer e o outro execute a geração seguinte i+1
    • 2: (0,7) Os processos deverão se comunicar utilizando as funções do protocolo MPI.
    • 3: (0,3) Relatório simplificado explicando a sua solução. Utilize diagramas para representar a comunicação entre os processos, explicando como a matriz do jogo é transferida entre os processos.
    • 4: Entregue o Relatório e o código fonte do trabalho em um pacote compactado via e-mail (PRAZO 19/10).


    • Compilar o código com MPI
           
    

    mpicc -o nomeApp arquivo.c

           </syntaxhighlight>
    
    • Rodar rodar programa utilizando MPI
           
           mpirun -np numeroDeProcessos ./nomeApp entrada.txt gerações
           </syntaxhighlight>
    

    * (int) -np: Quantidade de processos que você deseja executar

    * entrada.txt: Arquivo de entrada com estado inicial do jogo

    * (int) gerações: número de gerações que você deseja rodar

           * Arquivo de entrada para Testes
    


    /*
    
    	Conway's Game of Life
    
    Compilar código com MPI
    	mpicc -o nomeApp arquivo.c
    
    
    Rodar rodar programa utilizando MPI
    
            mpirun -np numeroDeProcessos ./nomeApp entrada.txt gerações
    
    	(int) -np: Quantidade de processos que você deseja executar 
    	entrada.txt: Arquivo de entrada com estado inicial do jogo 
    	(int) gerações: número de gerações que você deseja rodar
    
    
    */
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include "mpi.h"
    
    
    //Numero de linhas e colunas
    #define nLinhas 32
    #define nColunas 82  
    
    char  *matriz[nLinhas];
    char  *entrada[nLinhas];
    
    
    void iniciar() {
    
      int i;
    
      //Alocar espaço em memória para as matrizes
      for (i = 0; i < nLinhas; i++ )  
        matriz[i] = (char *) malloc((nColunas) * sizeof( char ));   
       
    
    }
    
    int adjacente(char **matriz, int i, int j){
          
            int x,y, initX, initY, limitX, limitY;
    	int vizinhos = 0;
            
    	if(i == 0)
    		initX = 0;
    	else 
    		initX = -1;
    
            if(i == (nLinhas-1))	
    		limitX = 1;
    	else
    		limitX = 2;
    	
    
    	if(y == 0)
    		initY = 0;
    	else 
    		initY = -1;
    
            if(y == (nColunas-3))	
    		limitY = 1;
    	else
    		limitY = 2;
    	
    	for(x = initX ; x < limitX; x++){
    		for(y = initY; y < limitY; y++){
    			if(matriz[i+x][j+y] == '#')	
    				vizinhos++;		
    		}			
    	}
    
    
    	if(matriz[i][j] == '#') 
    		return (vizinhos-1);
    	else 
    		return vizinhos;
    
    }
    
    
    void calculoGeracao(char **matriz, int ger) {
    
    	int i, j, a;
    	char novaGeracao[nLinhas][nColunas];
    
    	/* Aplicando as regras do jogo da vida */
    	for (i=0; i < nLinhas; i++){ 
    		for (j=0; j < nColunas; j++) {
    
    			a = adjacente(matriz, i, j);
    				
    			if (a == 2) novaGeracao[i][j] = matriz[i][j];
    			if (a == 3) novaGeracao[i][j] = '#';
    			if (a < 2)  novaGeracao[i][j] = ' ';
    			if (a > 3)  novaGeracao[i][j] = ' ';
    
    			if (j == 0)
    				novaGeracao[i][j] = '"';
                    }	
    		
    		novaGeracao[i][79] = '"';
    		novaGeracao[i][80] = '\n';
    								
    
    	}
    
    	/* Passando o resultado da nova geração para a matriz de entrada */
    	for (i=0; i < nLinhas; i++){ 
    		for (j=0; j < nColunas; j++) 
    			matriz[i][j] = novaGeracao[i][j];
    	}
    }
    
    
    
    
    
    
    
    main(int argc, char *argv[2]){
    
    	/* Para uso com MPI
    	
    	
    	//Variáveis para uso com MPI
    	int numeroDeProcessos = 0;
    	int rank = 0;
    	MPI_Status status;    
    
    	//Iniciar MPI---
    	MPI_Init( &argc, &argv );
    
    	//Atribui a variável numeroDeProcessos o número de processos passado como parâmetro em -np 
    	MPI_Comm_size( MPI_COMM_WORLD, &numeroDeProcessos );
    	
    	//Pega o valor do rank (processo)
    	MPI_Comm_rank( MPI_COMM_WORLD, &rank );
    	*/
    
    
    	FILE *matrizEntrada;
    	matrizEntrada = fopen(argv[1], "r");
            iniciar();
            
    
    	if (matrizEntrada == NULL)
    		printf ("Não posso abrir o arquivo \"%s\"\n", argv[1]);
    
    
            char str[nColunas];
    	int linha = 0;
    	
    	
    	
    	//Lendo o estado inicial do jogo a partir do arquivo
    	while((fgets(str, nColunas, matrizEntrada) != NULL)&&(linha < nLinhas)){
    		strcat(matriz[linha], str);
    		linha++;
            }
    	
            int i,gens; 
    		
    
    	for(gens = 0; gens < atoi(argv[2]); gens++){ 	//Gens é o número de gerações especificado no segundo parâmetro 
    		
    		calculoGeracao(matriz, gens);
    		printf("%c[2J",27);  // Esta linha serve para limpar a tela antes de imprimir o resultado de uma geração 	
    		
    		//Lendo o estado do jogo e imprime na tela 
    		for(i = 0; i < nLinhas; i++)
    			printf("%s", matriz[i]);			
    
    		sleep(1);
    		
    	}
    
    
    	for(i = 0; i < nLinhas; i++)		
    		free(matriz[i]);	
    	  	
    	
    
    	/* Finaliza o MPI */	
    	//MPI_Finalize();
    
    	exit(0);
    }
    


    Jantar dos Filósofos

    O problema clássico Jantar dos Filósofos consiste em que n fluxos (n filósofos) disputam n recursos (n talheres). No problema, para conseguir "jantar" (ou executar), cada filósofo precisa pegar dois talheres adjascentes a ele. Cada recurso é compartilhado por dois filósofos.


    • (0,7) programa abaixo implementa um Jantar dos Filósofos utilizando semáforos para sincronização. Contudo, as chamadas para as operações v e p foram removidas, conforme comentários no código. Re-insira as operações no código e analise a solução. Esta modificação é suficiente para garantir que não haverá deadlock? Se sim, mostre o porque. Se não, proponha uma solução completa.


    • (0,3) Relatório simplificado explicando a sua solução.
    • Entregue o Relatório e o código fonte do trabalho em um pacote compactado via e-mail (PRAZO 19/10).



    #include <iostream>
    #include "thread.h"
    #include "semaphore.h"
    
    using namespace std;
    
    const int DELAY = 10000000;
    const int ITERATIONS = 5;
    
    Semaphore chopstick[5];
    
    int philosopher(int n)
    {
        cout << "Philosopher " << n << " was born!\n";
    
        int first = (n < 4)? n : 0; // left for phil 0 .. 3, right for phil 4
        int second = (n < 4)? n + 1 : 4; // right for phil 0 .. 3, left for phil 4
    
        // Foram removidos do laço abaixo:
        //  - uma chamada para chopstick[first].p()
        //  - uma chamada para chopstick[second].p()
        //  - uma chamada para chopstick[first].v()
        //  - uma chamada para chopstick[second].v()
        for(int i = 0; i < ITERATIONS; i++) {
    	cout << "Philosopher " << n << " thinking ...\n";
    	for(int i = 0; i < DELAY * 10; i++);
    
    	cout << "Philosopher " << n << " eating ...\n";
    	for(int i = 0; i < DELAY; i++);
        }
    
        return n;
    }
    
    int main()
    {
        cout << "The Dining-Philosophers Problem\n";
    
        Thread * phil[5];
        for(int i = 0; i < 5; i++)
    	phil[i] = new Thread(&philosopher, i);
    
        int status;
        for(int i = 0; i < 5; i++) {
    	phil[i]->join(&status);
    	if(status == i)
    	    cout << "Philosopher " << i << " went to heaven!\n";
    	else
    	    cout << "Philosopher " << i << " went to hell!\n";
        }
    
        return 0;
    }
    


    Softwares básicos, caso Hello Word! (Trabalho 2) Entrega dia 09/11

    Softwares básicos, caso Hello Word!

    O objetivo do experimento de hoje é pesquisar e entender os processos de atribuição de endereços de programas realizados em tempo de compilação pelos softwares básicos como: compilador, linker, e assembler. Sendo assim, neste experimento vamos utilizar os seguintes softwares para criação e análise de código:

    • GCC: compilador para gerar código objeto a partir de um código de programa escrito na linguagem c;
    • GNU Linker (LD): Para vincular os códigos (módulos) objetos do programa;
    • Linker
    • GNU Assembler: Para gerar o código executável a partir do código objeto;
    • | assembler
    • OBJDUMP: Para mostrar informações do código;
    • | Objdump


    A seguir segue descrito o programa a ser utilizado no exercício 1:

    #include <stdio.h>
    int main()
    {
       printf("Hello, World!");
       return 0;
    }
    

    Trata-se do programa hello word, este programa apenas exibe uma mensagem na tela. No entanto, vamos analisar como são as etapas confecção do executável a partir deste código simples.

    Exercício 1:

    • Compile o programa hello word, e o transforme em código objeto utilizando o programa GCC. Para esta tarefa execute o seguinte comando:

    gcc -o hello hello.c </syntaxhighlight>

    • Agora abra o código objeto utilizando o programa OBJDUMP.

    objdump -D hello </syntaxhighlight>

    • Identifique quais são as seções de código obtidas a partir do hello.c.
    • Pesquise e entenda o significado das seções de código: .bss, .txt, .data, .init.
    • Faça uma análise e identifique o endereço de memória que o programa hello world vai ser carregado.
    • Este código objeto é relocável? Justifique sua resposta.
    • Agora gere o código assembly do hello world.

    gcc -S hello.c </syntaxhighlight>

    • Agora gere o código executável utilizando o programa AS e o programa LD.

    as -o hello.o hello.s </syntaxhighlight>

    • Como a etapa de linkagem para a construção do código executável está sendo executada sem o auxílio do GCC, necessitamos vincular manualmente as bibliotecas necessárias. Para criar apropriadamente o executável vamos precisar das bibliotecas ld-linux-x86-64.so.2, crt1.o, crti.o, crtn.o.
    • Para descobrir suas respectivas localizações use o comando LOCATE. Por exemplo:

    locate crti.o

    </syntaxhighlight>

    ou

    find /usr/ -name crti*

    </syntaxhighlight>

    • Agora que já sabemos a localização das bibliotecas necessárias vamos vincular essas a nossa aplicação.

    ld --dynamic-linker /caminho/ld-linux-x86-64.so.2 /caminho/crt1.o /caminho/crti.o /caminho/crtn.o hello.o -lc -o hello.exe </syntaxhighlight>

    • Agora abra o código objeto utilizando o programa OBJDUMP.
    • Faça uma análise e identifique o endereço de memória em que o programa hello world inicializa suas estruturas na memória.
    • Dica!

    objdump -D -s -j .init hello.exe </syntaxhighlight>

    • Houve diferença entre os endereços do programa executável em relação ao código objeto? Explique.
    • Explique as principais diferenças entre o arquivo objeto .o e o executável final. (dica utilize o objdump para fazer essa análise)
    1. Estudar e executar o código em http://cs.lmu.edu/~ray/notes/gasexamples/ Detalhes das chamadas na arquitetura x86_64 ver em https://lwn.net/Articles/604287/
    2. Estudar e executar o código usando a função (em conformidade com a API POSIX) write() para o hello world:
      1. include <unistd.h>
      main() { write(1,"Alo Mundo\n",10); } </syntaxhighlight>
    3. Use o comando strace para verificar todas as chamadas de sistema dos programas acima.
    4. DESAFIO 1: Estude a seção "Mixing C and Assembly Language" da http://cs.lmu.edu/~ray/notes/gasexamples/ e construa uma função meu_hello_world() usando o código em assembly do exercício inicial. Estude como poderia disponibilizar esta e outras funções de interface (a sua API) em uma biblioteca. Note que esta função deve ser chamada da forma: main() { meu_hello_world(); } </syntaxhighlight>
    5. Gere o assembly do código em C e discuta a diferença entre uma chamada de função e uma chamada de sistema.

    6. DESAFIO 2: Estude o link http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ e melhore a função meu_hello_world para suportar uma mensagem adicional da forma:
      1. include <string.h>
      main() { char *p="Tudo bem com vocês?"; meu_hello_world(p, strlen(p)); } </syntaxhighlight> Solução:
      Solução

      modificado de http://cs.lmu.edu/~ray/notes/gasexamples/

             .global meu_hello_world
      
             .text
      

      meu_hello_world:

             push    %rdi                    # salva primeiro parâmetro
             push    %rsi                    # salva segundo parâmetro
             mov     $1, %rax                # system call 1 é write
             mov     $1, %rdi                # file handle 1 é stdout
             mov     $message, %rsi          # endereço da string
             mov     $13, %rdx               # número de bytes da string
             syscall                         # chamada ao sistema
      
             pop     %rdx                    # o que havia sido passado em rsi (número de bytes) é colocado em rdx
             pop     %rsi                    # o que havia sido colocado em rdi (endereço da string) é colocado em rsi
             mov     $1, %rax                # system call 1 is write
             mov     $1, %rdi                # file handle 1 é stdout
             syscall                         # nova chamada ao sistema
      
             ret
      

      message:

             .ascii  "Hello, World\n"
      

      </syntaxhighlight>