SOP29005-2020-1

De MediaWiki do Campus São José
Revisão de 07h58min de 18 de setembro de 2020 por Eraldo (discussão | contribs) (→‎Etapa 1)
Ir para navegação Ir para pesquisar

AULA 1 - Dia 11/02/2020

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
    
  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>
    

    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
    

    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>
    
  4. Comunicação entre processos:
    $ cat /etc/passwd | grep home | wc -l
    
  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
    

AULA 2 - Dia 14/02/2020

Objetivos/Conteúdos

  • 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


Material de Referência

Leitura Recomendada

  • Cap.2 do Silberschatz principalmente:
    • 2.1 a 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 e executar o código usando a função (em conformidade com a API POSIX) write() para o hello world:
    #include <unistd.h> 
    
    main()
    {
      write(1,"Alo Mundo\n",10);
    }
    
  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();
    }
    
  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:
    #include <string.h>
    main()
    {
      char *p="Tudo bem com vocês?";
      meu_hello_world(p, strlen(p));
    }
    

    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"
    

    AULA 3 - Dia 18/02/2020

    Objetivos/Conteúdos

      • 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

    AULA 4 - Dia 21/08/2020

    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 28/02/2020

    Objetivos/Conteúdos

    • Gerenciamento de Processos
      • Conceito de Processo (3.1)
        • O processo (3.1.1)
        • Estado do Processo (3.1.2)
        • Bloco de Controle de Processo (3.1.3)
        • Threads (3.1.4)
      • Scheduling (3.2)
        • Filas de Scheduling (3.2.1)
        • Schedulers
        • Mudança de Contexto (3.2.3)
      • Operações Sobre Processos (3.3)

    Slides desta aula

    Laboratório em Sala

    https://wiki.sj.ifsc.edu.br/index.php/SOP29005-2019-1#Processos_no_Linux_-_Modificado_por_Eraldo

    Exercícios de Demonstração

    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>


    AULA 6 - Dia 03/03/2020

    Objetivos/Conteúdos

    • Processos
    • Exercícios Fork/Exec

    AULA 7 - Dia 06/03/2020

    Objetivos/Conteúdos

    • Processos
    • Exercícios Fork/Exec
    • Memória Compartilhada

    Cap.3 - Seção 3.4 e 3.5

    AULA 8 - Dia 10/03/2020

    Objetivos/Conteúdos

    • Memória Compartilhada
    • Troca de Msgs
    • Laboratório de Pipes

    AULA 9 - Dia 13/03/2020

    Objetivos/Conteúdos

    • Questionário de REvisão
    • Conceito de Threads (cap.4)
    • Caracterização do contexto e chaveamento de contexto do thread
    • Laboratório de Thread de Applicação
    • Slides sobre 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?


    Laboratório Threads Aplicação

    Ver https://wiki.sj.ifsc.edu.br/index.php/SOP29005-2019-1#Threads_de_aplica.C3.A7.C3.A3o

    Slides para esta aula

    AULA 10 ?? - REMOTA - Dia 20/03/2020

    Objetivos/Conteúdos

    • Webconf
    • Revisão de Threads
    • Laboratório de Thread de Applicação

    Slides para esta aula

    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/

    AULA 11 ?? - REMOTA - Dia 27/03/2020

    Objetivos/Conteúdos

    • Rever Threads em nível de Aplicação
    • Escalonamento de Threads de Aplicação
    • Exemplo: Escalalonador semi-automático
    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 */
    
    #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 2020-1: 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

    Refinar o handler de chaveamento de threads para que permita escalonar N f_pings, onde N será passado na linha de comando. Sugestão: criar uma fila ou vetor de identificadores de threads.


    AULA 12 ?? - REMOTA - Dia 27/03/2020

    Objetivos/Conteúdos

      • Conceito de Preempção
      • Critérios de Escalonamento
    • Algoritmos de Escalonamento
      • FCFS
      • Menor-Job-Primeiro
      • Prioridades
      • Round Robin

    Slides USados mna Aula

    • Slides do Livro do Professor Maziero e do Prof.Arliones

    Referências

    AULA 13 ?? - REMOTA - Dia 14/04/2020

    Objetivos/Conteúdos

    • Exemplo do Escalonador RR

    Código de Exemplo

    • Criar um relógio global para as demonstraçoes de funcionamento da API que foi criada
    /**
      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 { // bloco de controle do thread
       int id_thread;
       public:
       ucontext_t contexto;
       //metodos que manipulam o thread control block
       thread_cb(threadFn p, int id) //construtor
       {
    	  int stackLen=4*1024;
    	  char *stack=new char[stackLen];
    	  getcontext(&contexto);
    	  contexto.uc_stack.ss_sp=stack;
    	  contexto.uc_stack.ss_size=stackLen;
    	  contexto.uc_stack.ss_flags=0;
    	  makecontext(&contexto,p,0);
    	  id_thread = id;
       };
       ucontext_t *get_context() {
         return &contexto;
       };
    };
     
    std::queue<class thread_cb *> ready_pool; // fila de pronto
     
    int id_thread = 0;
    int id1,id2,id3;
    
    class thread_cb *curr_thread=NULL;
     
    int add_thread(threadFn func) //criar thread e retornar o id do thread criad
    {
      class thread_cb *p = new thread_cb(func, ++id_thread);
      ready_pool.push(p);
      return id_thread;
    }
     
     
    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)
    {
      int x=1;
      for (;;) {
           x++;
           printf("Thread A x=%d\n", x);
       }
    }
     
    void runB(void)
    {
        int x=1;
        for (;;) {
            x++;
            printf("Thread B x=%d\n", x);
            if(x>90000000L)
                yield_thread(); // abandonar a CPU
        }
    }
    
    void runC(void)
    {
        int x=1;
        for (;;) {
            x++;
            printf("Thread B x=%d\n", x);
            if (x>1000000L) //consultar um timer global 
                del_thread(id2);
        }
    }
    
    main()
    {
        id1 = add_thread(runA);
        id2 = add_thread(runB);
        id3 = add_thread(runC);
        preparar_handler();
        for(;;);
    }
    

    AULA 14 ?? - REMOTA - Dia 15/04/2020

    Objetivos/Conteúdos

    • aula somte para dúvidas...

    AULA 15 ?? - REMOTA - Dia 17/04/2020

    Objetivos/Conteúdos

    • Mecanismos de Sncronização

    Material de Referência

    Exemplo de remoção de itens de uma lista do stl

    #include <list>
    #include <iostream>
    using namespace std;
    
    int main()
    {
        struct tcb{
            int id;
        }  *p1,*p2,*p3;
        p1=new struct tcb;
        p1->id=3;
        p2=new struct tcb;
        p2->id=4;
        p3=new struct tcb;
        p3->id=7;
    
        list<struct tcb *> minhalista{ p1,p2,p3 };
        list<struct tcb *>::iterator it;
    
        it= minhalista.begin();
        while(it != minhalista.end()) {
            if ((*it)->id==4) {
                it = minhalista.erase(it);
            } else
                it++;
        }
    
        //imprimindo os ids de quem sobrou
        it= minhalista.begin();
        while(it != minhalista.end()) {
            cout << "ID = " << (*it)->id << "\n";
            it++;
        }
    
        return 0;
    }
    

    AULA 16 ?? - REMOTA - Dia 24/04/2020

    Objetivos/Conteúdos

    • Mecanismos de Sncronização
      • Conceito de Condição de Corrida
      • Conceito de Exclusão Mútua e Seção Crítica


    Material de Referência

    AULA 17 ?? - NÃO SÍNCRONA- Dia 28/04/2020

    Objetivos/Conteúdos

    • Mecanismos de Sincronização
      • Race Condition
      • Exclusão Mútua
      • Soluções Triviais
      • Algoritmo de Peterson
      • Instruções Atômicas

    Material de Referência

    Vídeos elaborados pelo Prof.Eraldo usando os slides/Capítulo 10 do Livro Prof. Carlos Maziero

    REUNIÃO - Dia 19/05/2020

    • ANPs

    AULA - SÍNCRONA- Dia 22/5/2020

    Objetivos

    • Revisão de Sistema Computacional (1.2 e 1.3 do Livro do Silberchatz)
    • Revisão de Serviços do Sistema Operacional
    • Revisão: PARTE 2A: Gerenciamento de Processos (cap.3 Silberchatz ed.8)
      • 3.1.Conceito de Processo (seção do livro)
      • 3.2.Escalonamento de Processos (seção do livro)
      • 3.3.Operações sobre Processos (seção do livro)
      • 4.1.Visão Geral de Threads (seção do livro)

    AULA - NÃO SÍNCRONA - Dia 22/5/2020

    Objetivos

    • Revisão: PARTE 2A: Gerenciamento de Processos (cap.3 Silberchatz ed.8)

    Material Sugerido para Revisão

    AULA SÍNCRONA - Dia 28/5/2020

    Objetivos

    • Uso da biblioteca libpthreads
    • Revisão de conceitos associados a threads

    Laboratório de libpthreads

    Parte 1

    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).


    Parte 2

    Exemplo básico de criação de threads do no POSIX Threads Programming

    1. Implemente o programa em [6]
    2. Compilar com:
      gcc -lpthread hello.c -o hello
    3. Executar:
      ./hello

    Parte 3

    Como o Linux enxerga os pthreads

    1. Faça uma modificação da função associada aos threads colocando um loop infinito após o Hello World
    2. O Linux "enxerga" estes threads? Confira com o comando htop

    Parte 4

    Observe o código abaixo.

    #include <stdio.h>
    
    #define TAM 3
    
    long double  vetor_A[TAM];
    
    
    void calcular_elemento(int i)
    {
      double x;
    
      for (x=0;x<=100;x=x+0.0000001){
         vetor_A[i] = vetor_A[i] + ((i+1)*x + (i+3))*0.0000001;
      }   
    }
     
    int main()
    {
      int i;
      
      /* solução sequencial */
      for (i=0;i<TAM;i++) {
             calcular_elemento(i);
      }   
    }
    


    1. Execute o programa com o auxílio do programa time. Anote o tempo de execução.
      time a.out
    2. Faça uma modificação para que o problema seja decomposto em threads explorando o número de cores físicos da sua máquina.
    3. Execute o programa com o auxílio do programa time. Compare com o tempo de execução sequencial.
      time a.out
    4. Faça uma variação do programa multithread para que o thread associado a função main aguarde os demais threads computando então a soma de todos os elementos do vetor. Veja exemplo em [7]

    Parte 5

    Repita o procedimeto da parte 4 para explorar os cores lógicos. Existe ganho real? Discuta.

    AULA SÍNCRONA - Dia 5/6/2020

    Objetivos/Conteúdos

    • Revisão de Mecanismos de Sincronização (já proposto em aula anterior)
      • Race Condition
      • Exclusão Mútua
      • Soluções Triviais
      • Algoritmo de Peterson
      • Instruções Atômicas
    • Laboratório

    Material de Referência

    Vídeos elaborados pelo Prof.Eraldo usando os slides/Capítulo 10 do Livro Prof. Carlos Maziero

    Slides do Livro do Silberschatz

    Laboratório de Race Condition, Algoritmo de Paterson e Mecanismos de Hardware

    1. Implemente e execute várias o exemplo abaixo de 2 threads acessando uma área comum de dados (conta). Estime o valor final na variável conta. Compare os resultados obtidos.
      #include <pthread.h>
      #include <stdio.h>
      #include <stdlib.h>
      #define NUM_THREADS	2
      
      /* based on https://computing.llnl.gov/tutorials/pthreads/samples/join.c */
      
      double conta=0;
      
      void *Thread1_DepositarConta(void *t)
      {
         int i;
         long tid;
        
         tid = (long)t;
         printf("Thread %ld iniciando...\n",tid);
         for (i=0; i<10000; i++){
      	   conta = conta+1;
         }
         pthread_exit((void*) t);
      }
      
      void *Thread2_DepositarConta(void *t)
      {
         int i;
         long tid;
        
         tid = (long)t;
         printf("Thread %ld iniciando...\n",tid);
         for (i=0; i<10000; i++){
      	   conta = conta+1;
         }
         pthread_exit((void*) t);
      }
      
      int main (int argc, char *argv[])
      {
         pthread_t thread[NUM_THREADS];
         pthread_attr_t attr;
         int rc;
         long t;
         void *status;
      
         /* Initialize and set thread detached attribute */
         pthread_attr_init(&attr);
         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
      
         for(t=0; t<NUM_THREADS; t++) {
            printf("Main: criando threads %ld\n", t);
            if(t==1)
               rc = pthread_create(&thread[t], &attr, Thread1_DepositarConta, (void *)t); 
            else
               rc = pthread_create(&thread[t], &attr, Thread2_DepositarConta, (void *)t);          
            if (rc) {
               printf("ERROR; return code from pthread_create() is %d\n", rc);
               exit(-1);
            }
         }
      
         /* Free attribute and wait for the other threads */
         pthread_attr_destroy(&attr);
         for(t=0; t<NUM_THREADS; t++) {
            rc = pthread_join(thread[t], &status);
            if (rc) {
               printf("ERROR; return code from pthread_join() is %d\n", rc);
               exit(-1);
            }
            printf("Main: esperando thread %ld having a status of %ld\n",t,(long)status);
         }
       
         printf("Main: Programa terminado - conta = %lf\n", conta);
         pthread_exit(NULL);
      }
      
    2. Reimplemente o programa acima com controle da entrada e saída da região crítica usando o algoritmo de Paterson.
    3. Reimplemente o programa acima com controle da entrada e saída da região crítica usando uma instrução simulada de TEST AND SET. Notar que neste caso a atomicidade da instrução não é garantida.

    AULA SÍNCRONA - Dia 19/06/2020

    Objetivos

    • Laboratório: Race Condition e Mutex
      • Explorar o problema de Race Condition
      • Resolver o problema de Race Condition com o Mutex
    • Conceito de Semáforo

    Laboratório Parte 1

    Estude o código abaixo e infira qual o valor da conta ao final da execução do programa.

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #define NUM_THREADS	2
    
    double conta=0;
    
    void *Thread1_DepositarConta(void *t)
    {
       int i;
       long tid;
      
       tid = (long)t;
       printf("Thread %ld iniciando...\n",tid);
       for (i=0; i<10000; i++){
    	   conta = conta+1;
       }
       pthread_exit((void*) t);
    }
    
    void *Thread2_DepositarConta(void *t)
    {
       int i;
       long tid;
      
       tid = (long)t;
       printf("Thread %ld iniciando...\n",tid);
       for (i=0; i<10000; i++){
    	   conta = conta+1;
       }
       pthread_exit((void*) t);
    }
    
    int main (int argc, char *argv[])
    {
       pthread_t thread[NUM_THREADS];
       pthread_attr_t attr;
       int rc;
       long t;
       void *status;
    
       /* Initialize and set thread detached attribute */
       pthread_attr_init(&attr);
       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    
       for(t=0; t<NUM_THREADS; t++) {
          printf("Main: criando threads %ld\n", t);
          if(t==1)
             rc = pthread_create(&thread[t], &attr, Thread1_DepositarConta, (void *)t); 
          else
             rc = pthread_create(&thread[t], &attr, Thread2_DepositarConta, (void *)t);          
          if (rc) {
             printf("ERROR; return code from pthread_create() is %d\n", rc);
             exit(-1);
          }
       }
    
       /* Free attribute and wait for the other threads */
       pthread_attr_destroy(&attr);
       for(t=0; t<NUM_THREADS; t++) {
          rc = pthread_join(thread[t], &status);
          if (rc) {
             printf("ERROR; return code from pthread_join() is %d\n", rc);
             exit(-1);
          }
          printf("Main: esperando thread %ld having a status of %ld\n",t,(long)status);
       }
       printf("Conta = %lf\n", conta);
       return 0;
     }
    

    Laboratório Parte 2

    Proponha uma solução para o problema da Parte 1 usando uma solução trivial com BUSY WAIT. Quais os problemas de uma solução deste tipo? A ideia do busy wait seria algo do tipo:

          while(busy);  // entrada seção crítica
          busy=1;       //
          conta = conta+1;
          busy=0;       // saída seção crítica
    

    Laboratório Parte 3

    Proponha uma solução para o problema da Parte 1 usando uma solução baseada em mutex. Estude o mutex em [9]

    Laboratório Parte 4

    Teste a ideia do mutex servir como um lock para vários threads ou processos. Refaça o exercício da parte 3 para 10 threads =. Use uma única função de base para thread.

    Material de Referência

    AULA SÍNCRONA - Dia 23/06/2020

    Objetivos

    • Conceito de Semáforo e Mutex
    • Uso de Semáforos intra processo

    Laboratório

    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).

    Ver exemplo em [10].

    PARTE 1

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

    PARTE 2

    Refaça o exercício da aula anterior criando dois tipos de tarefas (i) tarefas escritoras (atualizando o saldo) e (ii) tarefas leitoras. Crie 10 tarefas leitoras mas limite o acesso a leitura para no máximo 3 tarefas por vez.

    Material de Referência

    Avaliação 1

    Data: 3/Julho 2020

    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?


    AULA SÍNCRONA - Dia 26/06/2020

    Objetivos

    • Revisão do Conceito de Semáforo e Mutex
    • Uso de Semáforos intra processo
    • Problema Produtor Consumidor

    Material de Referência

    Laboratório

    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).

    Ver exemplo em [11].

    PARTE 1
    Modifique o programa do laboratório anterior para usar um semáforo binário ao invés de um mutex em sua solução.
    PARTE 2
    Analise o código abaixo. Trata-se de uma implementação do problema produtor consumidor sem nenhum mecanismo de sincronização ou coordenação.

    Identifique a seção crítica.

    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <unistd.h>
    
    
    #define BUFFER_SIZE 50
    
    sem_t mutex;
    volatile char fim=1;
    char buffer[BUFFER_SIZE];
    long disponivel=0; /* numero de itens ocupados no no buffer  */
    int in,out=0;
    int itens_prod=1;
    int soma_prod=0, soma_cons=0;
    
    void* produtor(void* arg)
    {
        char  prod=0;
        volatile long aux;
    
        do {
            while(disponivel == BUFFER_SIZE)
               ;
            buffer[in] = prod;
            soma_prod = soma_prod + prod;
            prod = (prod+1) % 5;
            in = (in + 1) % BUFFER_SIZE;
            //sem_wait(&mutex);
            disponivel = disponivel + 1;
            //sem_post(&mutex);
            printf("produzidos = %d\n", itens_prod++);
        } while (itens_prod!=900000);
        sleep(5); //tempo para encerrar
        fim=0;
        printf("TOTAL produzido = %d\n", soma_prod);
        pthread_exit(NULL);
    }
    
    void* consumidor(void* arg)
    {
        char consumo, consumo_ant='a';
        volatile long aux;
    
        do {
    
            while(disponivel == 0 && fim)
                ;
            if (fim==0)
                    break;
            consumo = buffer[out];
            soma_cons = soma_cons + consumo;
            out = (out + 1) % BUFFER_SIZE;
            //sem_wait(&mutex);
            disponivel = disponivel - 1;
            //sem_post(&mutex);
            printf("disponivel = %ld\n", disponivel);
        } while (fim);
        printf("TOTAL consumido = %d\n", soma_cons);
        pthread_exit(NULL);
    }
    
    int main()
    {
        int x;
        //sem_init(&mutex, 0, 1);
        pthread_t t1,t2;
        pthread_create(&t1,NULL,produtor,NULL);
        pthread_create(&t2,NULL,consumidor,NULL);
        printf("Tecle 1 e ENTER para FIM\n");
        scanf("%d", &x);
        fim = 0;
        pthread_join(t1,NULL);
        pthread_join(t2,NULL);
        //sem_destroy(&mutex);
        return 0;
    }
    
    PARTE 3
    Usando semáforos e mutex (ou semáforo binário), faça uma proposta de solução do problema produtor consumidor.
    PARTE 4
    Faça uma proposta para estender o problema para dois produtores e um consumidor.

    AULA SÍNCRONA - Dia 26/06/2020

    Objetivos

    • Conteúdos para avaliação
    • Problema Produtor Consumidor

    Referências

    • Livro Silberchatz: Cap.6 (versão 8)
    • Slides e Cap.11 e 12 do Livro do Prof.Maziero: Parte Semáforo e Mutex

    Conteúdos para Avaliação

    Tópico Referência Comentário
    Introdução e Estrutura do SIstema Operacional 1.1 a 1.8 Silberchatz
    2.1 a 2.7 + Laboratório System Call
    *Papel da Interrupção e Timers para os SOPs;


    • Conceito de multiprogramação, multitarefa e multiprocessamento;
    Processos: Conceitos e IPC 3.1 a 3.5 + Laboratórios de Processos, Pipes e Memória Compartilhada Ver [12]  
    Threads 4.1. a 4.4. + Laboratório de Threads: de aplicação e pthreads  
    Escalonamento do Processos Cap.6 Livro Prof.Maziero (até 6.4.5)[13] *Ver métricas/critérios para escalonamento;


    • ver como calcular os tempos de execução médio e tempo de espera;


    AULA SÍNCRONA - Dia 4/08/2020

    Objetivos

    • Introdução a Gerência de Meméoria
      • Vinculação de endereços lógicos aos físicos (8.1)
      • Swapping (8.2)

    Referência

    Slides Cap.7 Silberschatz

    AULA SÍNCRONA - Dia 7/08/2020

    Objetivos

    • Gerenciamento de Memória Contígua (8.2)
    • Paginação (8.4)
    • Agendar a nova (rec1) avaliação 1 - Sugestão: Disponibilizo na quarta que vem (dia 13/08) - Colocar ANPs complementares
    • Agendar a Avaliação 2 ?

    Referência

    Slides Cap.7 Silberschatz [14]

    AULA SÍNCRONA - Dia 11/08/2020

    Objetivos

    • Gerenciamento de Memória: Contígua, Paginação e Segmentação
    • Conceito de TLB
    • Hierarquia de Paginação

    Referência

    AULA SÍNCRONA - Dia 14/08/2020

    Objetivos

    • Técnicas para estender a memória RAM
    • Algoritmos de escolha de páginas vítima

    Referência


    AULA SÍNCRONA - Dia 18/08/2020

    Objetivos

    • Técnicas para estender a memória RAM (continuação)
    • Algoritmos de escolha de páginas vítima
    • Ver ANP repassada na wiki

    Referência

    REcuperação A1

    • Dia 28/08/2020
    • ver conteúdo tabela acima

    A2

    • Coordenação de Processos
    • Memória Virtual
    • Data 04/Set
    • Sexta: revisamos PRoblemas Classicos

    AULA SÍNCRONA - Dia 21/08/2020

    • Revisão Mecanismos de Coordenação - Preparação para Avaliação 2
    • Problemas Clássicos de Sincronização

    Material de Referência

    AULA - Dia 29/08/2020

    • AVALIAÇÂO 2

    AULA - Dia 1/08/2020

    Objetivos

    • Conceituar Arquivos, DIretórios e Sistemas de Arquivos
    • Atributos de Arquivos
    • Operações sobre arquivos

    ASSISTIR VÍDEO

    Tarefa

    • Ver questionário no SIGAA

    AULA - Dia 4/08/2020

    Objetivos

    • Uso de Arquivos
      • Operações sobre arquivos
      • Formas de Acessos
      • Permissões
      • Travas

    ReferÊncias

    TAREFA ANP

    VER NO SIGAA: TAREFA A3.1 - Permissionamento de Arquivos no Linux

    Adicional

    • Ver Silberschatz:
      • 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)

    AULA - Dia 8/08/2020

    Objetivos

    • Implementação do Sistema de Arquivos

    Material de ReferÊncia

    AULA - Dia 11/08/2020

    Objetivos

    • Implementação do Sistema de Arquivos (Continuação)

    Material de ReferÊncia


    AULA - Dia 15/08/2020

    Objetivos

    • Subsistema de IO: Hardware de IO
    • Avaliação 3 - Dia 25/09/2020 - Prova Objetiva (diferente para cada aluno - 50% Tarefa ANP)
    • Recuperação da Avaliação 1 - Dia 2/10

    Referências

    AULA - Dia 18/08/2020

    Objetivos

    • Projeto Final: Parte 1


    Projeto Final

    Desenvolvimento do um Char Device Driver para Linux baseado em proj1 2020 Universidade do Porto

    Etapa 1

    • Lab 1 - Instalação da VM com o CICA2
    • Lab 2
      • Driver Hello World Personalizado

    Reconfiurar teclado do CICA2

    sudo dpkg-reconfigure console-data

    Escolher o teclado abnt2