Mudanças entre as edições de "SOP29005-2014-2"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
 
(31 revisões intermediárias por 3 usuários não estão sendo mostradas)
Linha 34: Linha 34:
 
{{Cl|12 |10/9 | 2 | P0 (introdução e gerência de tarefas) | Lab. Informática}}
 
{{Cl|12 |10/9 | 2 | P0 (introdução e gerência de tarefas) | Lab. Informática}}
 
{{Cl|13 |11/9 | 2 | Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades | Lab. Informática}}
 
{{Cl|13 |11/9 | 2 | Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades | Lab. Informática}}
{{Cl|14 |17/9 | 2 | Comunicação entre processos: troca de mensagens e memória compartilhada | Lab. Informática}}
+
{{Cl|14 |17/9 | 2 | Comunicação entre processos: Troca de mensagens e Memória Compartilhada | Lab. Informática}}
 
{{Cl|15 |18/9 | 2 | Atividade em laboratório: programação com pipes e API shm – t3: Preempção e compartilhamento de tempo | Lab. Informática}}
 
{{Cl|15 |18/9 | 2 | Atividade em laboratório: programação com pipes e API shm – t3: Preempção e compartilhamento de tempo | Lab. Informática}}
 
{{Cl|16 |24/9 | 2 | Coordenação entre processos | Lab. Informática}}
 
{{Cl|16 |24/9 | 2 | Coordenação entre processos | Lab. Informática}}
{{Cl|17 |25/9 | 2 | Atividade em laboratório: pthread_mutex e POSIX sem_t – t4: contabilização de tarefas | Lab. Informática}}
+
{{Cl|17 |25/9 | 2 | Laboratório: Problemas clássicos de coordenação; impasses | Lab. Informática}}
{{Cl|18 |1/10 | 2 | Coordenação entre processos | Lab. Informática}}
+
{{Cl|18 |1/10 | 2 | Laboratório: Problemas clássicos de coordenação; impasses | Lab. Informática}}
{{Cl|19 |2/10 | 2 | Atividade em laboratório: produtor/consumidor e jantar dos filósofos – t5: join e sleep | Lab. Informática}}
+
{{Cl|19 |2/10 | 2 | Laboratório: Problemas clássicos de coordenação; impasses | Lab. Informática}}
{{Cl|20 |8/10 | 2 | Problemas clássicos de coordenação; impasses | Lab. Informática}}
+
{{Cl|20 |8/10 | 2 | Laboratório: Problemas clássicos de coordenação; impasses | Lab. Informática}}
{{Cl|21 |9/10 | 2 | Atividade em laboratório: acompanhamento de projetos – t6: semáforo, produtor/consumidor e fila de mensagens | Lab. Informática}}
+
{{Cl|21 |9/10 | 2 | Laboratório: Problemas clássicos de coordenação; impasses | Lab. Informática}}
{{Cl|22 |15/10 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
+
{{Cl|22 |15/10 | 2 | Encaminhamento para palestra MCC | Lab. Informática}}
{{Cl|23 |16/10 | 2 | P1 (comunicação e coordenação de tarefas) | Lab. Informática}}
+
{{Cl|23 |16/10 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
{{Cl|24 |22/10 | 2 | Gerenciamento de memória: Introdução | Lab. Informática}}
+
{{Cl|24 |22/10 | 2 | P1 (comunicação e coordenação de tarefas) | Lab. Informática}}
{{Cl|25 |23/10 | 2 | Atividade em laboratório: alocação de memória – t7: gerência de memória (lista de blocos livres e first-fit) | Lab. Informática}}
+
{{Cl|25 |23/10 | 2 | Gerenciamento de memória: Introdução | Lab. Informática}}
{{Cl|26 |29/10 | 2 | Gerenciamento de memória: paginação e segmentação | Lab. Informática}}
+
{{Cl|26 |29/10 | 4 | Gerenciamento de memória: paginação e segmentação. | Lab. Informática}}
{{Cl|27 |30/10 | 2 | Atividade em laboratório: mmap – t8: gerência de memória (best-fit, worst-fit e cálculo de fragmentação) | Lab. Informática}}
+
{{Cl|27 |30/10 | 2 | Atividade em laboratório: alocação de memória. | Lab. Informática}}
{{Cl|28 |5/11 | 2 | Gerenciamento de memória: memória virtual | Lab. Informática}}
+
{{Cl|28 |5/11 | 2 | Aula adiantada em 29/out | Lab. Informática}}
{{Cl|29 |6/11 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
+
{{Cl|29 |6/11 | 0 | Aula adiada: viagem do professor | Lab. Informática}}
{{Cl|30 |12/11 | 2 | P2 (gerenciamento de memória) | Lab. Informática}}
+
{{Cl|30 |12/11 | 2 | Gerenciamento de memória: memória virtual | Lab. Informática}}
{{Cl|31 |13/11 | 2 | Sistemas de arquivos: introdução e controle de acesso | Lab. Informática}}
+
{{Cl|31 |13/11 | 2 | Gerenciamento de memória: memória virtual | Lab. Informática}}
{{Cl|32 |19/11 | 2 | Atividade em laboratório: particionamento, criação de sistema de arquivos e controle de acesso no Linux | Lab. Informática}}
+
{{Cl|32 |19/11 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
{{Cl|33 |20/11 | 2 | Sistemas de arquivos: estudos de caso e gerenciamento de memória secundária | Lab. Informática}}
+
{{Cl|33 |20/11 | 2 | P2 (gerenciamento de memória) | Lab. Informática}}
{{Cl|34 |26/11 | 2 | Gerenciamento de entrada e saída: introdução | Lab. Informática}}
+
{{Cl|34 |26/11 | 2 | Sistemas de arquivos: introdução e controle de acesso | Lab. Informática}}
{{Cl|35 |27/11 | 2 | Atividade em laboratório: construção de módulo para Linux | Lab. Informática}}
+
{{Cl|35 |27/11 | 2 | Sistemas de arquivos: estudos de caso e gerenciamento de memória secundária | Lab. Informática}}
{{Cl|36 |3/12 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
+
{{Cl|36 |3/12 | 2 | Gerenciamento de entrada e saída: introdução / Atividade em laboratório: construção de módulo para Linux | Lab. Informática}}
{{Cl|37 |4/12 | 2 | P3 (sistemas de arquivo e gerenciamento de entrada e saída) | Lab. Informática}}
+
{{Cl|37 |4/12 | 2 | Revisão e correção de listas de exercícios | Lab. Informática}}
{{Cl|38 |10/12 | 2 | REC (todo conteúdo da disciplina) | Lab. Informática}}
+
{{Cl|38 |10/12 | 2 | P3 (sistemas de arquivo e gerenciamento de entrada e saída) | Lab. Informática}}
 
{{cronograma-botton |76}}
 
{{cronograma-botton |76}}
  
Linha 74: Linha 74:
 
! Final
 
! Final
 
|-
 
|-
| 122001838-4 || A ||     ||     ||     ||   ||   ||  
+
| 122001838-4 || A || A || B || B || A || B || A
 
|-
 
|-
| 122005023-7 ||     ||     ||     ||     ||   ||   ||  
+
| 122001432-0 || A || B || B || A || A || B || A
 
|-
 
|-
| 122001432-0 || A ||     ||     ||     ||   ||   ||  
+
| 122005026-1 || A || B || A || B || A || B || A
 
|-
 
|-
| 122005026-1 || A ||     ||     ||     ||   ||   ||  
+
| 122006899-3 || B || A || C || A || A || C || B
 
|-
 
|-
| 122006899-3 || B ||    ||    ||    ||    ||    ||   
+
| 122001913-5 || B || A || A || B || A || C || B
|-
 
| 122001913-5 || B ||     ||     ||     ||   ||   ||  
 
|-
 
| 121001065-8 ||    ||    ||    ||    ||    ||    ||   
 
 
|}
 
|}
  
Linha 97: Linha 93:
 
* [[Arquivo:SOP29005-parte3.pdf]]
 
* [[Arquivo:SOP29005-parte3.pdf]]
 
* [[Arquivo:SOP29005-parte4.pdf]]
 
* [[Arquivo:SOP29005-parte4.pdf]]
 +
* [[Arquivo:SOP29005-parte5.pdf]]
 +
* [[Arquivo:SOP29005-parte6.pdf]]
 +
* [[Arquivo:SOP29005-parte7.pdf]]
  
 
== Listas de exercícios ==
 
== Listas de exercícios ==
Linha 115: Linha 114:
 
*Parte 3
 
*Parte 3
 
**Capítulo 6: 1, 2 (utilizar semáforos POSIX), 6, 8, 11-15, 18, 20, 21, 25, 29, 39.
 
**Capítulo 6: 1, 2 (utilizar semáforos POSIX), 6, 8, 11-15, 18, 20, 21, 25, 29, 39.
 +
*Parte 4
 +
**Capítulo 8: 1-6, 9-21, 23.
 +
**Capítulo 9: 1-8, 14-16, 19-23, 28.
 +
*Parte 5
 +
**Capítulo 10: 1-20
 +
**Capítulo 11: 1-7
 +
**Capítulo 12: 1-7, 13-14 (desafio).
 +
 +
== Projeto ==
 +
 +
Esta disciplina utiliza um projeto contínuo, no qual os alunos desenvolvem um aplicativo que emula um sistema operacional no espaço de usuário no Linux. Este projeto é chamado de [[BOOOS - Basic Object Oriented Operating System]].
  
 
=Diário de Aulas=
 
=Diário de Aulas=
Linha 456: Linha 466:
  
  
=== t0: Biblioteca de Filas ===
+
== 20/08: Escalonamento de tarefas ==
  
Uma maneira de descrever um Sistema Operacional, sob o ponto de vista da programação, é definir-lo como um grande '''gerenciador de filas'''. São exemplos de filas importantes de um SO as filas de processos prontos a serem executados, processos suspensos, processos dormindo e processos bloqueados em semáforos. Para o trabalho da disciplina implementaremos nossas filas através de uma ''lista circular duplamente encadeada'', cuja estrutura pode ser vista na figura abaixo.
+
* Notas de aula ([[Arquivo:SOP29005-parte2.pdf]])
 +
* [http://courses.cs.vt.edu/csonline/OS/Lessons/Processes/index.html Animação de escalonamento de processos - Virginia Tech]
 +
* Capítulo 5 do livro do Silberschatz.
 +
 
 +
== 21/08: Atividades em laboratório: Estrutura de processos ==
  
[[Arquivo:DoublyCircularlyLinkedList.png]]
+
* [http://www.lisha.ufsc.br/teaching/os/exercise/hello.html A verdadeira hitória do "Hello World!"]
  
Para recapitular a execução de operações sobre uma lista encadeada, consulte o material de [http://wiki.sj.ifsc.edu.br/index.php/Professores:PRG2-2013-2#Detalhamento_das_opera.C3.A7.C3.B5es_da_lista Programação II]. Lembre-se, nossa estrutura de dados é uma Fila, ou seja, elementos são inseridos no final e removidos do início.
 
  
Neste projeto você deve construir uma pequena biblioteca que ofereça uma classe ''Queue'' com métodos de inserção e remoção de elementos genéricos. O código-base da classe, que está nos arquivos [https://www.dropbox.com/s/sx0z1q2tufyp46f/Queue.h] e [https://www.dropbox.com/s/94h3mxgs82hy46m/Queue.cc]. A declaração da classe é apresentada abaixo:
+
=== Troca de contexto em nível de usuário ===
  
<syntaxhighlight lang=cpp>
+
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''';
* BOOOS.h
+
*'''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''';
*  Created on: Aug 14, 2014
+
*'''makecontext(&a, ...)''': ajusta parâmetros internos do contexto salvo em '''a''';
*      Author: arliones
+
*'''ucontext_t''': as variáveis '''a''' e '''b''' são do tipo '''ucontext_t'''. Este tipo armazena um contexto.
*/
 
 
#ifndef QUEUE_H_
 
#define QUEUE_H_
 
 
namespace BOOOS {
 
 
        class Queue {
 
        public:
 
                Queue();
 
                virtual ~Queue();
 
 
                class Element {
 
                public:
 
                        Element() { _prev = 0; _next = 0; _rank = 0; }
 
                        virtual ~Element() {}
 
 
                        Element * prev() { return _prev; }
 
                        Element * next() { return _next; }
 
                        int rank() { return _rank; }
 
                        void prev(Element * p) { _prev = p; }
 
                        void next(Element * p) { _next = p; }
 
                        void rank(int r) { _rank = r; }
 
 
                private:
 
                        Element * _prev;
 
                        Element * _next;
 
                        int _rank;
 
                };
 
 
                Element * head() { return &_head; }
 
 
                int length() { return _length; }
 
 
                void insert(Element * elem);
 
                void insert_ordered(Element * elem);
 
   
 
                Element * remove();
 
                void remove(Element * e);
 
 
        private:
 
                Element * search(Element * elem);
 
 
                Element _head; // _head.next will point to head, _head.prev will point to tail
 
                int _length;
 
        };
 
 
}
 
 
#endif /* QUEUE_H_ */
 
</syntaxhighlight>
 
  
Esta fila organiza objetos do tipo ''Element*''. A classe ''Element'' deve ser estendida para implementar os detalhes da aplicação, como no exemplo do arquivo [https://www.dropbox.com/s/h6a1e238r0n11c2/Queue_Test.cc Queue_Test.cc].
+
Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext).
  
É responsabilidade do aluno implementar mais testes além dos que estão no exemplo para garantir o funcionamento da fila. Apenas o arquivo Queue.cc deve ser entregue ao professor, devidamente preenchido (os métodos estão vazios no original).
+
Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.
 +
<syntaxhighlight lang=c>
 +
#include <stdio.h>
 +
#include <stdlib.h>
 +
#include <ucontext.h>
  
Um projeto pré-configurado para o Elicpse Luna também foi disponibilizado [https://www.dropbox.com/s/enxmc3vvzpl8g81/booos-t0.tar.gz aqui]. Para utilizar este projeto:
+
#define STACKSIZE 32768 /* tamanho de pilha das threads */
# Baixe o [https://eclipse.org/downloads/packages/eclipse-ide-cc-developers/lunar Eclipse Luna para C/C++];
 
# O eclipse não precisa ser instalado, basta descompactar o arquivo baixado;
 
# Execute o eclipse através do executável "eclipse" na basta descompactada;
 
# Baixe o projeto disponibilizado pelo professor;
 
# No eclipse, acesse "Arquivo->Importar..." ou "File->Import...";
 
# Selecione a opção "Geral->Projetos Existentes para a Área de Trabalho" ou "General->Existing Projects into Workspace" e clique "Next" ou "Próximo";
 
# Selecione a opção "Selecionar arquivo compactado" ou "Select archive file", clique em "Buscar..." ou "Browse..." e selecione o arquivo compactado do projeto;
 
# Verifique se o projeto chamado "booos-t0" está selecionado na lista de "Projetos" ou "Projects";
 
# Clique em "Encerrar" ou "Finish", e o projeto deve aparecer no espaço de trabalho.
 
  
Observe que a pasta do projeto possui Makefiles. Se preferir, não é necessário utilizar o Eclipse para compilar e testar o projeto, basta usar os seguintes comandos, a partir do diretório do projeto (booos-t0):
+
/* VARIÁVEIS GLOBAIS */
<syntaxhighlight lang=bash>
+
ucontext_t cPing, cPong, cMain;
$ make all    #Compila sistema e gera aplicação padrão
 
$ ./booos      #Executa aplicação padrão
 
$ make TEST=Queue_Test test    #Compila teste implementado em "Queue_Teste.cc"
 
$ ./test/Queue_Test    #Executa aplicação teste
 
</syntaxhighlight>
 
  
<!--
+
/* Funções-comportamento das Tarefas */
* Se você está utilizando o Ubuntu 13.10 e encontrou problemas com os menus, crie um script chamado "run_eclipse.sh" na mesma pasta do executável do eclipse com o conteúdo abaixo e execute este script ao invés do executável. Você pode precisar ajustar as permissões do arquivo executando "chmod +x run_eclipse.sh".
+
void f_ping(void * arg) {
<syntaxhighlight lang=bash>
+
  int i;
#!/bin/bash
 
UBUNTU_MENUPROXY= ./eclipse &
 
</syntaxhighlight>
 
-->
 
  
 +
  printf("%s iniciada\n", (char *) arg);
  
==== Resumo do trabalho ====
+
  for (i=0; i<4; i++) {
*'''O que é?''' Classe implementando uma fila usando uma lista circular duplamente encadeada.
+
      printf("%s %d\n", (char *) arg, i);
*'''Duração estimada:''' 6 horas.
+
      swapcontext(&cPing, &cPong);
*'''Dependências:''' Conhecimento básico de C/C++ e de estruturas de dados.
+
  }
*'''Entrega:''' Até 04/09, por email, apenas o arquivo Queue.cc (não modifiquem o Queue.h!).
+
  printf("%s FIM\n", (char *) arg);
*'''Observação:''' Este trabalho será base para os demais trabalhos realizados no curso, logo, dediquem-se a esta implementação para evitar problemas futuros.
 
  
== 20/08: Escalonamento de tarefas ==
+
  swapcontext(&cPing, &cMain);
 +
}
  
* Notas de aula ([[Arquivo:SOP29005-parte2.pdf]])
+
void f_pong(void * arg) {
* [http://courses.cs.vt.edu/csonline/OS/Lessons/Processes/index.html Animação de escalonamento de processos - Virginia Tech]
+
  int i;
* Capítulo 5 do livro do Silberschatz.
 
  
== 21/08: Atividades em laboratório: Estrutura de processos ==
+
  printf("%s iniciada\n", (char *) arg);
 
 
* [http://www.lisha.ufsc.br/teaching/os/exercise/hello.html A verdadeira hitória do "Hello World!"]
 
  
 +
  for (i=0; i<4; i++) {
 +
      printf("%s %d\n", (char *) arg, i);
 +
      swapcontext(&cPong, &cPing);
 +
  }
 +
  printf("%s FIM\n", (char *) arg);
  
=== Troca de contexto em nível de usuário ===
+
  swapcontext(&cPong, &cMain);
 +
}
  
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:
+
/* MAIN */
*'''getcontext(&a)''': salva o contexto na variável '''a''';
+
int main(int argc, char *argv[]) {
*'''setcontext(&a)''': restaura um contexto salvo anteriormente na variável '''a''';
+
  char *stack;
*'''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).
+
  printf ("Main INICIO\n");
  
Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.
+
  getcontext(&cPing);
<syntaxhighlight lang=c>
+
  stack = malloc(STACKSIZE);
#include <stdio.h>
+
  if(stack) {
#include <stdlib.h>
+
      cPing.uc_stack.ss_sp = stack ;
#include <ucontext.h>
+
      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);
 +
  }
  
#define STACKSIZE 32768 /* tamanho de pilha das threads */
+
  makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
  
/* VARIÁVEIS GLOBAIS */
+
  getcontext(&cPong);
ucontext_t cPing, cPong, cMain;
+
  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);
 +
  }
  
/* Funções-comportamento das Tarefas */
+
  makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
void f_ping(void * arg) {
 
  int i;
 
  
   printf("%s iniciada\n", (char *) arg);
+
   swapcontext(&cMain, &cPing);
 +
  swapcontext(&cMain, &cPong);
  
  for (i=0; i<4; i++) {
+
   printf("Main FIM\n");
      printf("%s %d\n", (char *) arg, i);
 
      swapcontext(&cPing, &cPong);
 
  }
 
   printf("%s FIM\n", (char *) arg);
 
  
   swapcontext(&cPing, &cMain);
+
   exit(0);
 
}
 
}
 +
</syntaxhighlight>
  
void f_pong(void * arg) {
+
== 27/08: Atividades em laboratório: acompanhamento de projetos ==
  int i;
 
  
  printf("%s iniciada\n", (char *) arg);
+
== 28/08: Algoritmos de escalonamento ==
  
  for (i=0; i<4; i++) {
+
* Notas de aula ([[Arquivo:SOP29005-parte2.pdf]])
      printf("%s %d\n", (char *) arg, i);
+
* Capítulo 5 do livro do Silberschatz.
      swapcontext(&cPong, &cPing);
 
  }
 
  printf("%s FIM\n", (char *) arg);
 
  
  swapcontext(&cPong, &cMain);
 
}
 
  
/* MAIN */
+
== 03/09: Algoritmos de escalonamento / Revisão para prova ==
int main(int argc, char *argv[]) {
+
 
  char *stack;
+
== 04/09: Listas de exercícios / Revisão para prova ==
  
  printf ("Main INICIO\n");
+
== 10/09: Prova 0 - Processos ==
  
  getcontext(&cPing);
+
== 11/09: Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades ==
  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");
+
* Conclusão do conteúdo da aula anterior
  
  getcontext(&cPong);
+
=== Introdução a POSIX Threads ===
  stack = malloc(STACKSIZE);
+
 
  if(stack) {
+
A biblioteca POSIX Threads, ou simplesmente ''pthreads'', é parte do padrão POSIX para programar utilizando threads. O padrão POSIX.1c define a API para criação e manipulação de threads. Esta API é encontrada na maioria dos sistemas baseados no Unix, como Linux, Mac OS X, Solaris, entre outros. Também existem alternativas adaptando a API para sistemas Windows, como a pthreads-w32.
      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");
+
A API da pthreads inclui métodos para criar, manipular e destruir threads, além de outras estruturas de dados para sincronizar as threads, incluindo implementações de mutexes e variáveis de condição.
  
  swapcontext(&cMain, &cPing);
+
A API POSIX ''semaphore'', utilizada para sincronização de processos ou threads, também funciona em conjunto com a ''pthreads'', embora sua implementação esteja definida em outro padrão, o POSIX.1b.
  swapcontext(&cMain, &cPong);
 
  
  printf("Main FIM\n");
+
Programas em C/C++ que utilizarão a ''pthreads'' devem incluir o cabeçalho ''pthread.h'':
  
  exit(0);
+
<syntaxhighlight lang=c>
}
+
#include <pthread.h>
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== t1: troca de contexto e tarefas cooperativas ===
 
  
Neste trabalho deve-se estender o projeto sendo desenvolvido no curso com a construção de uma classe para abstrair processos em nível de usuários - na prática, threads. A classe implementada será chamada de '''Task''' (tarefa). Lembre-se que uma classe é uma estrutura de dados, logo, nossa classe '''Task''' será o PCB ('''Proccess Control Block''') do sistema. A partir deste trabalho, será disponibilizado um gabarito em C++ como no código abaixo, geralmente incompleto, e um diagrama UML de uma versão completa da solução implementada pelo professor, como na imagem abaixo.
+
==== Principais elementos da API ====
  
[[Arquivo:Booos.png|600px]]
+
* pthread_t (struct)
 +
:Estrutura que armazena dados/atributos de uma pthread.
 +
 
 +
* pthread_create
 +
: Função que cria uma thread, incializando-a e deixando-a pronta para executar. O código abaixo apresenta um exemplo simples de um programa utilizando pthreads.
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
/*
 
* Task.h
 
*
 
*  Created on: Aug 15, 2014
 
*      Author: arliones
 
*/
 
  
#ifndef TASK_H_
+
#include <pthread.h>
#define TASK_H_
 
  
#include <Queue.h>
+
pthread_t threads[2];
#include <ucontext.h>
 
  
namespace BOOOS {
+
void *thread_func(void *arg) {
 +
    // ...
 +
}
  
class Task : public Queue::Element {
+
int main(int argc, char **argv) {
public:
+
    int i;
enum State {
+
    for(i = 0; i < 2; i++) {
READY,
+
        pthread_create(&(threads[i]), NULL, thread_func,NULL);
WAITING,
+
    }
RUNNING,
+
    for(i = 0; i < 2; i++) {
FINISHING
+
        pthread_join(threads[i], NULL);
};
+
    }
 +
}
  
Task(void (*entry_point)(void *), int nargs, void * arg);
+
</syntaxhighlight>
virtual ~Task();
 
  
int tid() { return _tid; }
 
State state() { return _state; }
 
  
void pass_to(Task * t, State s = READY);
+
* pthread_join
 +
: Bloqueia execução de uma thread até que outra thread termine. Similar à chamada de sistema ''wait'' usada em processos Unix.
  
void exit(int code);
+
* pthread_exit
 +
: Encerra a execução de uma thread. A chamada a esta função por uma thread gera a liberação de outras threads que estejam, eventualmente, bloqueadas nela por uma chamada ''pthread_join''.
  
static Task * self() { return (Task*)__running; }
+
O código abaixo apresenta um programa completo utilizando pthreads, onde threads recebem argumentos.
static void init();
 
  
private:
+
<syntaxhighlight lang=c>
static volatile Task * __running;
 
  
State _state;
+
#include <stdlib.h>
int _tid; // task ID
+
#include <stdio.h>
// ...
+
#include <pthread.h>
};
 
  
} /* namespace BOOOS */
+
typedef struct {
 +
    int idx, length;
 +
} thread_arg, *ptr_thread_arg;
  
#endif /* TASK_H_ */
+
pthread_t threads[2];
</syntaxhighlight>
 
  
Os métodos de interface (i.e., os ''públicos'') que precisam ser implementados na classe estão na declaração acima. Não altere a assinatura destes métodos! Observe que você certamente precisará de novos atributos para o correto funcionamento da classe, e seria bom utilizar alguns métodos privados para auxiliar na implementação. Você pode criar métodos e atributos ''privados'' à vontade. ''Onde será que será declarado o ucontext_t de cada Task?''
+
void *thread_func(void *arg) {
 +
    ptr_thread_arg targ = (ptr_thread_arg) arg;
 +
    int i;
  
O teste no arquivo test/Task_Test.cc implementa uma aplicação para testar sua classe Task. Use este exemplo como teste inicial. Abaixo há uma descrição detalhada dos métodos de interface da classe.
+
    for(i = targ->idx; i < (targ->idx + targ->length); i++)
 +
        printf(“Thread %d – value %d\n”, pthread_self(), i);
 +
}
  
:'''Task(void (*entry_point)(void *), int nargs, void * arg)''': construtor - deve inicializar todos os atributos dos '''objetos'''.
+
int main(int argc, char **argv) {
 
+
    thread_arg arguments[2];
:'''virtual ~Task()''': destrutor - deve liberar recursos alocados pelo construtor (new/malloc).
+
    int i;
 
+
    for(i = 0; i < 2; i++) {
:'''int tid()''': ''getter'' do Task ID (_tid).
+
        arguments[i].idx = i * 10;
 +
        arguments[i].length = 10;
 +
        pthread_create(&(threads[i]), NULL, thread_func, &(arguments[i]));
 +
    }
 +
    for(i = 0; i < 2; i++) {
 +
        pthread_join(threads[i], NULL);
 +
    }
 +
}
  
:'''State state()''': ''getter'' do estado do processo (_state).
+
</syntaxhighlight>
  
:'''void pass_to(Task * t, State s = READY)''': este método salva o contexto do objeto (this) e carrega o contexto da Task recebida por parâmetro. O estado passado em ''s'' é o novo estado do objeto que está deixando a CPU. ''Há algum atributo de classe (static) que precisa ser atualizado aqui?''
+
Ao compilar um programa com pthreads é necessário "linkar" com a biblioteca. Para isso, deve ser usando a opção -lpthread com o gcc.
  
:'''void exit(int code)''': finaliza a Task (this) e configura o valor de resultado da task com o valor de ''code''. Por enquanto, se preocupem em fazer a Task retornar a execução para a main. Ignorem o parâmetro ''code'' agora - utilizaremos ele mais adiante.
+
<syntaxhighlight lang=bash>
 +
gcc ... -lpthread
 +
</syntaxhighlight>
  
:'''static Task * self()''': método de classe (static) que retorna a Task executando no momento.
+
==== Exercícios ====
  
:'''static void init()''': método de classe que precisa ser chamando na inicialização do sistema e deve inicializar os atributos de classe (static).
+
* Implemente o exemplo do Ping e Pong utilizando pthreads.
  
  
[https://www.dropbox.com/s/x7sxnpsh2k53lqp/booos-t1.tar.gz Aqui] há um projeto do Eclipse Luna pré-configurado com os gabaritos para o t1.
 
  
==== Resumo do trabalho ====
+
== 17/09 e 18/09: Comunicação entre processos ==
*'''O que é?''' Classe Task para nosso SO.
+
* Notas de aula ([[Arquivo:SOP29005-parte3.pdf]])
*'''Duração estimada:''' 6 horas.
 
*'''Dependências:''' nenhum.
 
*'''Entrega:''' Até 11/09, por email, apenas arquivos Task.h, Task.cc e Task_Test.cc (se modificado).
 
  
 +
===Comunicação entre processos: 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.
 +
Abaixo há um exemplo de programa criando um pipe e compartilhando os descritores entre dois processos (criados via ''fork()'').
 +
<syntaxhighlight lang=c>
 +
#include <unistd.h> 
 +
#include <fcntl.h>
 +
#include <stdio.h>
  
== 27/08: Atividades em laboratório: acompanhamento de projetos ==
+
char *message = "This is a message!!!" ;
 
 
== 28/08: Algoritmos de escalonamento ==
 
 
 
* Notas de aula ([[Arquivo:SOP29005-parte2.pdf]])
 
* Capítulo 5 do livro do Silberschatz.
 
  
 +
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) ;
 +
    }
 +
}
 +
</syntaxhighlight>
  
== 03/09: Algoritmos de escalonamento / Revisão para prova ==
+
*'''Desafio 1: construa um “pipeline”'''. Crie um programa que conecta 4 processos através de 3 pipes. Utilize ''fork()'' para criar vários processos.
 
 
== 04/09: Listas de exercícios / Revisão para prova ==
 
 
 
== 10/09: Prova 0 - Processos ==
 
 
 
== 11/09: Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades ==
 
  
* Conclusão do conteúdo da aula anterior
+
*'''Desafio 2: 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:
 +
:<syntaxhighlight lang=bash>
 +
$ FileCopy entrada.txt copia.txt
 +
</syntaxhighlight>
 +
: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.
  
=== Introdução a POSIX Threads ===
 
  
A biblioteca POSIX Threads, ou simplesmente ''pthreads'', é parte do padrão POSIX para programar utilizando threads. O padrão POSIX.1c define a API para criação e manipulação de threads. Esta API é encontrada na maioria dos sistemas baseados no Unix, como Linux, Mac OS X, Solaris, entre outros. Também existem alternativas adaptando a API para sistemas Windows, como a pthreads-w32.
+
===Comunicação entre processos: Memória Compartilhada===
 +
Outra maneira de compartilhar dados entre processos é utilizando memória compartilhada. Nestes casos, o sistema operacional precisa ser utilizado para "mapear" blocos de memória no espaço de endereçamento de cada processo (veremos mais tarde como isto é feito). Sistemas UNIX diponibilizam uma série de '''SystemCalls''' para compartilhar memória entre processos:
 +
*''int shmget(key_t key, size_t size, int shmflg)'': retorna o identificador de um segmento de memória compartilhado associado a uma chave de identificação (key). Caso a chave solicitada não exista e a flag IPC_CREAT está especificada, um novo segmento de memória compartilhada é criado, com o tamanho size.
 +
*''void *shmat(int shmid, const void *shmaddr, int shmflg)'': anexa/mapeia o segmento de memória especificado por shmid (obtido através de um ''shmget'') no endereço shmaddr do espaço de endereçamento do processo.
 +
*''int shmdt(const void *shmaddr)'': desfaz o mapeamento do segmento de memória compartilhada que está mapeado em shmaddr.
 +
*''int shmctl(int shmid, int cmd, struct shmid_ds *buf)'': realiza a ação de controle indicada por cmd sobre o segmento de memória compartilhada identificado pelo shmid. Caso o cmd utilizado necessite de parâmetros, estes são passados em buf.
  
A API da pthreads inclui métodos para criar, manipular e destruir threads, além de outras estruturas de dados para sincronizar as threads, incluindo implementações de mutexes e variáveis de condição.
+
O programa abaixo cria um novo segmento de memória compartilhada em um sistema UNIX. A função ''ftok'' é utilizada para gerar uma chave única de identificação do segmento de memória (veja "man ftok"). O programa ipcs pode ser utilizado para verificar os segmentos de memória compartilhada disponíveis no sistema.
 
 
A API POSIX ''semaphore'', utilizada para sincronização de processos ou threads, também funciona em conjunto com a ''pthreads'', embora sua implementação esteja definida em outro padrão, o POSIX.1b.
 
 
 
Programas em C/C++ que utilizarão a ''pthreads'' devem incluir o cabeçalho ''pthread.h'':
 
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
#include <pthread.h>
+
#include <stdio.h>
</syntaxhighlight>
+
#include <stdlib.h>
 
+
#include <string.h>
 
+
#include <sys/types.h>
==== Principais elementos da API ====
+
#include <sys/ipc.h>
 
+
#include <sys/shm.h>
* pthread_t (struct)
+
:Estrutura que armazena dados/atributos de uma pthread.
+
#define SHM_SIZE 1024  /* make it a 1K shared memory segment */
 
+
* pthread_create
+
int main(void)
: Função que cria uma thread, incializando-a e deixando-a pronta para executar. O código abaixo apresenta um exemplo simples de um programa utilizando pthreads.
+
{
 +
 +
      key_t key;
 +
      int shmid;
 +
      char *data;
 +
      int mode;
 +
 +
      /* make the key: */
 +
      if ((key = ftok("/tmp", 'X')) == -1) {
 +
            perror("ftok");
 +
            exit(1);
 +
      }
 +
 +
      /* create the shared memory segment: */
 +
      if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT )) == -1) {
 +
            perror("shmget");
 +
            exit(1);
 +
      }
 +
 +
      return(0);
 +
}
 +
</syntaxhighlight>
 +
 
 +
O programa abaixo manipula o segmento criado pelo programa anterior. Perceba que o programa utiliza a função ''ftok'' com os mesmos parâmetros passados na criação. O sistema operacional utiliza esta chava única para identificar o segmento de memória a ser utilizado. Se o programa abaixo é executado com um string como parâmetro (ex.: "./shm_test abc123"), ele escreve este parâmetro no segmento de memória. Caso seja executado sem parâmetros (ex.: "./shm_test"), o programa imprime o conteúdo da memória compartilhada.
  
 
<syntaxhighlight lang=c>
 
<syntaxhighlight lang=c>
 +
#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  /* make it a 1K shared memory segment */
 +
 +
int main(int argc, char *argv[])
 +
{
 +
 +
      key_t key;
 +
      int shmid;
 +
      char *data;
 +
      int mode;
 +
 +
 +
      /* make the key: */
 +
      if ((key = ftok("/tmp", 'X')) == -1) {
 +
            perror("ftok");
 +
            exit(1);
 +
      }
 +
 +
 +
      /* connect to the segment.
 +
      NOTE: There's no IPC_CREATE. What happens if you place IPC_CREATE here? */
 +
      if ((shmid = shmget(key, SHM_SIZE, 0644)) == -1) {
 +
            perror("shmget");
 +
            exit(1);
 +
      }
 +
 +
 +
    /* attach to the segment to get a pointer to it: */
 +
        data = shmat(shmid, (void *)0, 0);
 +
        if (data == (char *)(-1)) {
 +
            perror("shmat");
 +
            exit(1);
 +
        }
 +
 +
 +
        /* read or modify the segment, based on the command line: */
 +
        if (argc == 2) {
 +
            printf("writing to segment: \"%s\"\n", argv[1]);
 +
            strncpy(data, argv[1], SHM_SIZE);
 +
        } else
 +
            printf("segment contains: \"%s\"\n", data);
 +
 +
 +
        /* detach from the segment: */
 +
        if (shmdt(data) == -1) {
 +
            perror("shmdt");
 +
            exit(1);
 +
        }
 +
 +
      return(0);
 +
}
 +
</syntaxhighlight>
  
#include <pthread.h>
+
*'''Desafio1: destrua um shm.''' Crie um programa que destrua o shm utilizado nos programas anteriores. Para isso utilize ''shmctl'' com o parâmetro apropriado (veja "man shmctl").
 
 
pthread_t threads[2];
 
 
 
void *thread_func(void *arg) {
 
    // ...
 
}
 
 
 
int main(int argc, char **argv) {
 
    int i;
 
    for(i = 0; i < 2; i++) {
 
        pthread_create(&(threads[i]), NULL, thread_func,NULL);
 
    }
 
    for(i = 0; i < 2; i++) {
 
        pthread_join(threads[i], NULL);
 
    }
 
}
 
 
 
</syntaxhighlight>
 
 
 
 
 
* pthread_join
 
: Bloqueia execução de uma thread até que outra thread termine. Similar à chamada de sistema ''wait'' usada em processos Unix.
 
 
 
* pthread_exit
 
: Encerra a execução de uma thread. A chamada a esta função por uma thread gera a liberação de outras threads que estejam, eventualmente, bloqueadas nela por uma chamada ''pthread_join''.
 
 
 
O código abaixo apresenta um programa completo utilizando pthreads, onde threads recebem argumentos.
 
 
 
<syntaxhighlight lang=c>
 
 
 
#include <stdlib.h>
 
#include <stdio.h>
 
#include <pthread.h>
 
 
 
typedef struct {
 
    int idx, length;
 
} thread_arg, *ptr_thread_arg;
 
 
 
pthread_t threads[2];
 
 
 
void *thread_func(void *arg) {
 
    ptr_thread_arg targ = (ptr_thread_arg) arg;
 
    int i;
 
 
 
    for(i = targ->idx; i < (targ->idx + targ->length); i++)
 
        printf(“Thread %d – value %d\n”, pthread_self(), i);
 
}
 
 
 
int main(int argc, char **argv) {
 
    thread_arg arguments[2];
 
    int i;
 
    for(i = 0; i < 2; i++) {
 
        arguments[i].idx = i * 10;
 
        arguments[i].length = 10;
 
        pthread_create(&(threads[i]), NULL, thread_func, &(arguments[i]));
 
    }
 
    for(i = 0; i < 2; i++) {
 
        pthread_join(threads[i], NULL);
 
    }
 
}
 
  
 +
<!--
 +
<syntaxhighlight lang=c>
 +
#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  /* make it a 1K shared memory segment */
 +
 +
int main(void)
 +
{
 +
      key_t key;
 +
      int shmid;
 +
      char *data;
 +
      int mode;
 +
 +
      /* make the key: */
 +
      if ((key = ftok("/tmp", 'X')) == -1) {
 +
            perror("ftok");
 +
            exit(1);
 +
      }
 +
 +
      /* connect to memory segment: */
 +
      if ((shmid = shmget(key, SHM_SIZE, 0644)) == -1) {
 +
            perror("shmget");
 +
            exit(1);
 +
      }
 +
 +
    /* delete he segment */
 +
      if( shmctl(shmid, IPC_RMID, NULL) == -1) {
 +
            perror("shmctl");
 +
            exit(1);
 +
      }
 +
 +
      return(0);
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
-->
  
Ao compilar um programa com pthreads é necessário "linkar" com a biblioteca. Para isso, deve ser usando a opção -lpthread com o gcc.
+
== 24/09: Coordenação de processos ==
  
<syntaxhighlight lang=bash>
+
* Sincronização entre processos ([[Arquivo:SOP29005-parte4.pdf]])
gcc ... -lpthread
+
** Algoritmos de sincronização
</syntaxhighlight>
+
** Exclusão Mútua
 +
** Semáforos
  
==== Exercícios ====
+
*Curiosidade: [http://research.microsoft.com/en-us/um/people/mbj/mars_pathfinder/authoritative_account.html A inversão de prioridades na Mars Pathfinder]
  
* Implemente o exemplo do Ping e Pong utilizando pthreads.
 
  
 +
== 25/09: Laboratório: Impasses e Problemas clássicos de coordenação ==
  
 +
* Sincronização entre processos ([[Arquivo:SOP29005-parte4.pdf]])
  
=== t2: Escalonador FCFS e por prioridades ===
+
=== POSIX pthread mutex ===
  
Você irá construir um despachante de tarefas baseado em duas entidades: uma tarefa ''dispatcher'', responsável pelo controle geral, e uma função ''choose_next'', responsável por determinar qual a próxima tarefa a executar a cada troca de contexto. A figura abaixo ilustra o funcionamento geral do sistema (fonte Prof. Maziero/UTFPR):
+
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.
  
[[Arquivo:Dispatcher.png]]
+
<syntaxhighlight lang=c>
 +
#ifndef __mutex_h
 +
#define __mutex_h
  
Para isto, uma nova classe será adicionada ao nosso sistema: ''Scheduler''. Como nosso escalonador é também um processo, ele herda de Task. Uma função estática chamada ''dispatcher'' implementa o comportamento de escalonamento.
+
#include <pthread.h>
 
 
[[Arquivo:Booos-t3.png|600px]]
 
 
 
Abaixo, o esqueleto C++ da classe ''Scheduler'':
 
<syntaxhighlight lang=cpp>
 
/*
 
* Scheduler.h
 
*
 
*  Created on: Mar 21, 2014
 
*      Author: arliones
 
*/
 
 
 
#ifndef SCHEDULER_H_
 
#define SCHEDULER_H_
 
 
 
#include <Task.h>
 
#include <Queue.h>
 
 
 
namespace BOOOS {
 
 
 
class Scheduler : public Task {
 
friend class Task;
 
 
 
protected:
 
Scheduler();
 
  
 +
class Mutex
 +
{
 
public:
 
public:
enum SchedulerType {
+
    Mutex() {}
SCHED_FCFS,
+
    ~Mutex() {}
SCHED_PRIORITY
 
};
 
  
virtual ~Scheduler();
+
    void lock() { pthread_mutex_lock(&mut); }
 +
    bool try_lock() { return (pthread_mutex_trylock(&mut) == 0); } // true when succeeds.
 +
    void unlock() { pthread_mutex_unlock(&mut); }
  
static void init();
+
private:
 
+
    pthread_mutex_t mut;
static void dispatcher(void*);
 
 
 
static Scheduler * self() { return __dispatcher; }
 
 
 
protected:
 
virtual Task * choose_next();
 
 
 
static Scheduler * __dispatcher;
 
 
};
 
};
  
} /* namespace BOOOS */
+
#endif
  
#endif /* SCHEDULER_H_ */
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Neste trabalho, os seguintes métodos precisam ser implementados:
+
=== POSIX Semaphores ===
*''Scheduler()'': Um construtor que inicializa um Scheduler utilizando o construtor da Task, passando a função ''dispatcher'' como entry point. Importante aqui verificar que um Scheduler é um tipo especial de tarefa: ela está sempre pronta (''ready''), nunca fica bloqueada. '''Dica:''' Para permitir esta identificação, crie um estado extra em Task.h, chamado SCHEDULER, e utilize este estado para diferenciar a Scheduler de uma tarefa normal quando utilizando o ''pass_to'';
+
 
*''init()'': ele precisa inicializar a tarefa __dispatcher;
+
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):
*''dispatcher(void*)'': esta função implementa o comportamento do escalonador. O pseudo-código abaixo apresenta o comportamento do ''__dispatcher'' de modo simplificado:
+
*''sem_init'': inicializa um semáforo;
<syntaxhighlight lang=c>
+
*''sem_destroy'': destroy um semáforo;
void dispatcher()
+
*''sem_wait'': implementa a operação ''p'';
{
+
*''sem_post'': implementa a operação ''v''.
    while(userTasks > 0)
+
Para utilizar estas funções é necessário linkar o programa à librt ou à libpthread (''-lrt'' ou ''-lpthread''). A classe C++ abaixo abstrai estas operações:
    {
+
<syntaxhighlight lang=cpp>
      next = choose_next() ;  // escolher a próxima Task* a executar
+
#ifndef __semaphore_h
      if(next)
+
#define __semaphore_h
      {
 
          ... // ações antes de lancar a tarefa "next", se houverem
 
          self()->pass_to(next); // transfere controle para a tarefa "next"
 
          ... // ações apos retornar da tarefa "next", se houverem
 
      }
 
    }
 
    exit(0) ; // encerra a tarefa dispatcher
 
}
 
</syntaxhighlight>
 
  
*''choose_next()'': este método '''virtual''' é o responsável por implementar a política de escalonamento empregada. Neste trabalho utilizaremos uma política FCFS (''First-Come First-Served''). '''Dica:''' pense na relação entre esta política e a fila que implementamos - esta função deve ser extremamente simples!
+
#include <semaphore.h>
  
Aplicações de testes estão disponíveis [https://www.dropbox.com/s/bxsaiwjbl7ay2d8/booos-t2-tests.tar.gz aqui]. Algumas outras observações:
+
class Semaphore
* Como nosso sistema está ficando mais complexo, o lib/BOOOS.h e lib/BOOOS.cc estão sendo utilizados para configurar o sistema. Ele tem uma função ''init'' que chamará os ''inits'' dos outros componentes. Ele também tem parâmetros de configuração do escalonamento (política, preempção e envelhecimento de prioridades). Baixe estes arquivos [https://www.dropbox.com/s/8ikbwgaig6axxoy/booos-t2-config.tar.gz aqui].
+
{
* O arquivo Scheduler.h disponibilizado está completo, ou seja, você não precisa modificá-lo, a não ser que queira.
+
public:
* Serão necessárias algumas mudanças na classe Task:
+
    Semaphore(int i = 1) { sem_init(&sem, 0, i); }
** Novo estado: SCHEDULER
+
    ~Semaphore() { sem_destroy(&sem); }
** Novo método ''yield()'' que transfere a execução da tarefa corrente para o escalonador. '''Dica:''' utilize o ''pass_to''.
 
** A fila de tarefas prontas (_ready) deve ser um atributo de classe (static) de ''Task''. Tasks devem ser incluídas na fila quando criadas e removidas quando destruídas.
 
** A classe Scheduler é ''friend'' da classe Task. Isto significa que Scheduler pode manipular os atributos protegidos de Task.
 
  
 +
    void p() { sem_wait(&sem); }
 +
    void v() { sem_post(&sem); }
  
==== Algoritmos de escalonamento ====
+
    operator int()
 +
    {
 +
        int ret;
 +
        sem_getvalue(&sem, &ret);
 +
        return ret;
 +
    }
  
A implementação do ''dispatcher'' descrito acima já deve gerar uma política de escalonamento implícita. Que política é esta?
+
private:
 +
    sem_t sem;
 +
};
  
Agora, a política de escalonamento inicial deve ser modificada para escalonar tarefas por prioridades. O diagrama abaixo apresenta as modificações necessárias para que o sistema escalone por prioridades.
+
#endif
 +
</syntaxhighlight>
  
[[Arquivo:Booos-prio.png|600px]]
+
Exemplo de uso do operator:
 +
<syntaxhighlight lang=cpp>
 +
Semaphore sem;
 +
cout << (int)sem << endl;
 +
</syntaxhighlight>
  
# ''Queue::Element::_rank'': nosso elemento terá um rank genérico pelo qual a fila pode ser mantida em ordem. O valor padrão deste atributo é zero. O atributo é privado e deve ter métodos getter e setter (''rank()'' e ''rank(int r)'');
+
=== POSIX Threads ===
# ''Queue::insert_ordered(Element* elem)'': nossa fila agora deve ter um método que a mantém ordenada pelo valor do ''_rank'' de cada elemento;
+
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):
# ''Task::nice(int p)'': nossa Task terá um método chamado ''nice'' que configura a prioridade da tarefa ajustando o valor de seu ''_rank''. O escalonador ''DEVE'' utilizar prioridades no estilo UNIX, ou seja, com valores entre -20 e 20. ''Curiosidade:'' abra um terminal e digite '''man nice''';
+
*''pthread_create'': cria uma thread;
# ''SCHEDULER_TYPE'': parâmetro de configuração do sistema que define o tipo escalonador utilizado. ''Dica:'' utilize este parâmetro para decidir se o sistema de utilizar ''Queue::insert'' ou ''Queue::insert_ordered''.
+
*''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''). A classe C++ abaixo abstrai estas operações:
 +
<syntaxhighlight lang=cpp>
 +
#ifndef __thread_h
 +
#define __thread_h
  
 +
#include <pthread.h>
 +
#include <signal.h>
  
==== Resumo do trabalho ====
+
class Thread
*'''O que é?''' Classe implementando um escalonadores para nosso sistema.
+
{
*'''Duração estimada:''' 8 horas.
+
public:
*'''Dependências:''' Queue e Task.
+
    Thread(int ( * const entry)(int), int arg) {
*'''Entrega:''' Até 02/10, por email.
+
if(pthread_create(&thread, 0, (void*(*)(void*))entry, (void *)arg))
 
+
    thread = 0;
== 17/09: Comunicação entre processos ==
+
    }
* Notas de aula ([[Arquivo:SOP29005-parte3.pdf]])
+
    ~Thread() { pthread_kill(thread, SIGKILL); }
  
===Comunicação entre processos: Troca de mensagens===
+
    int join(int * status) { return pthread_join(thread, (void **)status); }
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 é:
+
    friend void exit(int status = 0) { pthread_exit((void *) status); }
*'''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.
 
Abaixo há um exemplo de programa criando um pipe e compartilhando os descritores entre dois processos (criados via ''fork()'').
 
<syntaxhighlight lang=c>
 
#include <unistd.h> 
 
#include <fcntl.h>
 
#include <stdio.h>
 
  
char *message = "This is a message!!!" ;
+
private:
 +
    pthread_t thread;
 +
};
  
main()
+
#endif
{
 
    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) ;
 
    }
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
*'''Desafio 1: construa um “pipeline”'''. Crie um programa que conecta 4 processos através de 3 pipes. Utilize ''fork()'' para criar vários processos.
+
=== Problemas clássicos de sincronização ===
  
*'''Desafio 2: 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:
+
==== Produtor/Consumidor ====
:<syntaxhighlight lang=bash>
+
O problema clássico Produtor/Consumidor consiste em dois fluxos de execução (threads/processos), sendo que um dos fluxos (consumidor) só pode executar a partir do momento em que seus dados de entrada foram produzidos pelo outro fluxo (produtor).
$ FileCopy entrada.txt copia.txt
+
*[http://cs.uttyler.edu/Faculty/Rainwater/COSC3355/Animations/processsync.htm Veja esta simulação]
</syntaxhighlight>
+
*[http://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem Veja esta descrição do problema]
: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.
 
  
 +
*''DESAFIO'': O programa abaixo implementa um produtor/consumidor utilizando semáforos para sincronização. Contudo, as chamadas para as operações ''v'' e ''p'' foram removidas, conforme comentários no código. Corrija este programa.
  
===Comunicação entre processos: Memória Compartilhada===
+
<syntaxhighlight lang=cpp>
Outra maneira de compartilhar dados entre processos é utilizando memória compartilhada. Nestes casos, o sistema operacional precisa ser utilizado para "mapear" blocos de memória no espaço de endereçamento de cada processo (veremos mais tarde como isto é feito). Sistemas UNIX diponibilizam uma série de '''SystemCalls''' para compartilhar memória entre processos:
+
#include <iostream>
*''int shmget(key_t key, size_t size, int shmflg)'': retorna o identificador de um segmento de memória compartilhado associado a uma chave de identificação (key). Caso a chave solicitada não exista e a flag IPC_CREAT está especificada, um novo segmento de memória compartilhada é criado, com o tamanho size.
+
#include "thread.h"
*''void *shmat(int shmid, const void *shmaddr, int shmflg)'': anexa/mapeia o segmento de memória especificado por shmid (obtido através de um ''shmget'') no endereço shmaddr do espaço de endereçamento do processo.
+
#include "semaphore.h"
*''int shmdt(const void *shmaddr)'': desfaz o mapeamento do segmento de memória compartilhada que está mapeado em shmaddr.
+
 
*''int shmctl(int shmid, int cmd, struct shmid_ds *buf)'': realiza a ação de controle indicada por cmd sobre o segmento de memória compartilhada identificado pelo shmid. Caso o cmd utilizado necessite de parâmetros, estes são passados em buf.
+
using namespace std;
 +
 
 +
const int DELAY = 10000000;
 +
const int REP = 5;
  
O programa abaixo cria um novo segmento de memória compartilhada em um sistema UNIX. A função ''ftok'' é utilizada para gerar uma chave única de identificação do segmento de memória (veja "man ftok"). O programa ipcs pode ser utilizado para verificar os segmentos de memória compartilhada disponíveis no sistema.
+
Semaphore empty(1);
 +
Semaphore full(0);
  
<syntaxhighlight lang=c>
+
int producer(int n)
#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  /* make it a 1K shared memory segment */
 
 
int main(void)
 
 
{
 
{
+
    cout << "Producer was born!\n";
      key_t key;
 
      int shmid;
 
      char *data;
 
      int mode;
 
 
      /* make the key: */
 
      if ((key = ftok("/tmp", 'X')) == -1) {
 
            perror("ftok");
 
            exit(1);
 
      }
 
 
      /* create the shared memory segment: */
 
      if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT )) == -1) {
 
            perror("shmget");
 
            exit(1);
 
      }
 
 
      return(0);
 
}
 
</syntaxhighlight>
 
  
O programa abaixo manipula o segmento criado pelo programa anterior. Perceba que o programa utiliza a função ''ftok'' com os mesmos parâmetros passados na criação. O sistema operacional utiliza esta chava única para identificar o segmento de memória a ser utilizado. Se o programa abaixo é executado com um string como parâmetro (ex.: "./shm_test abc123"), ele escreve este parâmetro no segmento de memória. Caso seja executado sem parâmetros (ex.: "./shm_test"), o programa imprime o conteúdo da memória compartilhada.
+
    // Faltam, no laço abaixo:
 +
    //  - uma chamada para empty.p()
 +
    //  - uma chamada para full.v()
 +
    for(int i = 0; i < REP; i++) {
 +
cout << "Producing ...\n";
 +
for(int i = 0; i < DELAY * 10; i++);
 +
cout << "Storing ...\n";
 +
for(int i = 0; i < DELAY; i++);
 +
    }
 +
 
 +
    return n;
 +
}
  
<syntaxhighlight lang=c>
+
int consumer(int n)
#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  /* make it a 1K shared memory segment */
 
 
int main(int argc, char *argv[])
 
 
{
 
{
+
    cout << "Consumer was born!\n";
      key_t key;
+
 
      int shmid;
+
    // Faltam, no laço abaixo:
      char *data;
+
    // - uma chamada para full.p()
      int mode;
+
    //  - uma chamada para empty.v()
+
    for(int i = 0; i < REP; i++) {
+
cout << "Retrieving ...\n";
      /* make the key: */
+
for(int i = 0; i < DELAY * 10; i++);
      if ((key = ftok("/tmp", 'X')) == -1) {
+
cout << "Consuming ...\n";
            perror("ftok");
+
for(int i = 0; i < DELAY; i++);
            exit(1);
+
    }
      }
+
 
+
    return n;
+
}
      /* connect to the segment.
+
 
      NOTE: There's no IPC_CREATE. What happens if you place IPC_CREATE here? */
+
int main()
      if ((shmid = shmget(key, SHM_SIZE, 0644)) == -1) {
+
{
            perror("shmget");
+
    cout << "The Producer x Consumer Problem\n";
            exit(1);
+
 
      }
+
    Thread prod(&producer, REP);
+
    Thread cons(&consumer, REP);
+
 
    /* attach to the segment to get a pointer to it: */
+
    int status;
        data = shmat(shmid, (void *)0, 0);
+
    prod.join(&status);
        if (data == (char *)(-1)) {
+
    if(status == REP)
            perror("shmat");
+
cout << "Producer went to heaven!\n";
            exit(1);
+
    else
        }
+
cout << "Producer went to hell!\n";
 
 
        /* read or modify the segment, based on the command line: */
 
        if (argc == 2) {
 
            printf("writing to segment: \"%s\"\n", argv[1]);
 
            strncpy(data, argv[1], SHM_SIZE);
 
        } else
 
            printf("segment contains: \"%s\"\n", data);
 
 
 
        /* detach from the segment: */
 
        if (shmdt(data) == -1) {
 
            perror("shmdt");
 
            exit(1);
 
        }
 
 
      return(0);
 
}
 
</syntaxhighlight>
 
  
*'''Desafio1: destrua um shm.''' Crie um programa que destrua o shm utilizado nos programas anteriores. Para isso utilize ''shmctl'' com o parâmetro apropriado (veja "man shmctl").
+
    cons.join(&status);
 
+
    if(status == REP)
<!--
+
cout << "Consumer went to heaven!\n";
<syntaxhighlight lang=c>
+
    else
#include <stdio.h>
+
cout << "Consumer went to hell!\n";
#include <stdlib.h>
+
 
#include <string.h>
+
    return 0;
#include <sys/types.h>
+
}
#include <sys/ipc.h>
+
</syntaxhighlight>
#include <sys/shm.h>
+
 
+
==== Jantar dos Filósofos ====
#define SHM_SIZE 1024  /* make it a 1K shared memory segment */
+
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.
+
*[http://www.doc.ic.ac.uk/~jnm/concurrency/classes/Diners/Diners.html Veja esta simulação]
int main(void)
+
*[http://en.wikipedia.org/wiki/Dining_philosophers_problem Veja esta descrição do problema]
{
+
 
      key_t key;
+
*''DESAFIO'': O 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. Corrija este programa.
      int shmid;
+
 
      char *data;
+
<syntaxhighlight lang=cpp>
      int mode;
+
#include <iostream>
+
#include "thread.h"
      /* make the key: */
+
#include "semaphore.h"
      if ((key = ftok("/tmp", 'X')) == -1) {
+
 
            perror("ftok");
+
using namespace std;
            exit(1);
+
 
      }
+
const int DELAY = 10000000;
+
const int ITERATIONS = 5;
      /* connect to memory segment: */
+
 
      if ((shmid = shmget(key, SHM_SIZE, 0644)) == -1) {
+
Semaphore chopstick[5];
            perror("shmget");
 
            exit(1);
 
      }
 
 
    /* delete he segment */
 
      if( shmctl(shmid, IPC_RMID, NULL) == -1) {
 
            perror("shmctl");
 
            exit(1);
 
      }
 
 
      return(0);
 
}
 
</syntaxhighlight>
 
-->
 
  
 +
int philosopher(int n)
 +
{
 +
    cout << "Philosopher " << n << " was born!\n";
  
== 24/09: Coordenação de processos ==
+
    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
  
* Sincronização entre processos ([[Arquivo:SOP29005-parte4.pdf]])
+
    // Foram removidos do laço abaixo:
** Algoritmos de sincronização
+
    //  - uma chamada para chopstick[first].p()
** Exclusão Mútua
+
    //  - uma chamada para chopstick[second].p()
** Semáforos
+
    //  - 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++);
  
*Curiosidade: [http://research.microsoft.com/en-us/um/people/mbj/mars_pathfinder/authoritative_account.html A inversão de prioridades na Mars Pathfinder]
+
cout << "Philosopher " << n << " eating ...\n";
 +
for(int i = 0; i < DELAY; i++);
 +
    }
  
 +
    return n;
 +
}
  
== 25/09: Laboratório: Impasses e Problemas clássicos de coordenação ==
+
int main()
 +
{
 +
    cout << "The Dining-Philosophers Problem\n";
  
* Sincronização entre processos ([[Arquivo:SOP29005-parte4.pdf]])
+
    Thread * phil[5];
 +
    for(int i = 0; i < 5; i++)
 +
phil[i] = new Thread(&philosopher, i);
  
=== POSIX pthread mutex ===
+
    int status;
 
+
    for(int i = 0; i < 5; i++) {
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):
+
phil[i]->join(&status);
*''pthread_mutex_lock'': acessa um mutex.
+
if(status == i)
*''pthread_mutex_trylock'': tenta acessar um mutex (retorna valor indicando sucesso ou falha no lock).
+
    cout << "Philosopher " << i << " went to heaven!\n";
*''pthread_mutex_unlock'': libera um mutex.
+
else
 +
    cout << "Philosopher " << i << " went to hell!\n";
 +
    }
  
<syntaxhighlight lang=c>
+
    return 0;
#ifndef __mutex_h
+
}
#define __mutex_h
+
</syntaxhighlight>
  
#include <pthread.h>
 
  
class Mutex
+
== 01/10 a 08/10: Laboratório: programação concorrente ==
{
 
public:
 
    Mutex() {}
 
    ~Mutex() {}
 
  
    void lock() { pthread_mutex_lock(&mut); }
+
=== Exercício 1 ===
    bool try_lock() { return (pthread_mutex_trylock(&mut) == 0); } // true when succeeds.
 
    void unlock() { pthread_mutex_unlock(&mut); }
 
  
private:
+
O programa abaixo cria 5 threads, e cada uma destas threads atualiza uma variável global (memória compartilhada).
    pthread_mutex_t mut;
 
};
 
  
#endif
+
<syntaxhighlight lang=cpp>
 +
#include <iostream>
 +
#include "thread.h"
  
</syntaxhighlight>
+
#define NUM_THREADS 5
  
=== POSIX Semaphores ===
+
using namespace std;
  
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):
+
int saldo = 1000;
*''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''). A classe C++ abaixo abstrai estas operações:
 
<syntaxhighlight lang=cpp>
 
#ifndef __semaphore_h
 
#define __semaphore_h
 
  
#include <semaphore.h>
+
int AtualizaSaldo(int n)
 
 
class Semaphore
 
 
{
 
{
public:
+
int meu_saldo = saldo;
    Semaphore(int i = 1) { sem_init(&sem, 0, i); }
+
int novo_saldo = meu_saldo + n*100;
    ~Semaphore() { sem_destroy(&sem); }
+
cout << "Novo saldo = " << novo_saldo << endl;
 +
saldo = novo_saldo;
 +
}
  
    void p() { sem_wait(&sem); }
+
int main()
    void v() { sem_post(&sem); }
+
{
 +
Thread * threads[NUM_THREADS];
  
    operator int()
+
for(int t = 0; t < NUM_THREADS; t++)
    {
+
threads[t] = new Thread(&AtualizaSaldo, t+1);
        int ret;
 
        sem_getvalue(&sem, &ret);
 
        return ret;
 
    }
 
  
private:
+
int status;
    sem_t sem;
+
for(int t = 0; t < NUM_THREADS; t++) {
};
+
threads[t]->join(&status);
 +
cout << "Thread " << t << " terminou com status " << status << "." << endl;
 +
}
  
#endif
+
cout << "Saldo final é " << saldo << "." << endl;
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Exemplo de uso do operator:
 
<syntaxhighlight lang=cpp>
 
Semaphore sem;
 
cout << (int)sem << endl;
 
</syntaxhighlight>
 
  
=== POSIX Threads ===
+
# Compile este programa. Você precisará da classe Thread implementada na aula passada.
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):
+
# Execute este programa várias vezes. Ele funciona? Será que ele gera as saídas esperadas?
*''pthread_create'': cria uma thread;
+
# Identifique as '''seções críticas''' do programa.
*''pthread_kill'': força a terminação de uma thread;
+
# Corrija o programa utilizando '''mutex'''. Utilize a classe Mutex implementada na aula passada.
*''pthread_join'': sincroniza o final de uma thread (qual a diferença/semelhança com o ''wait'' que usamos para processos?);
+
# 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.
*''pthread_exit'': finaliza uma thread.
+
# Modifique a função ''AtualizaSaldo()'' para reduzir o tempo de lock do programa.
Para utilizar estas funções é necessário linkar o programa à libpthread (''-lpthread''). A classe C++ abaixo abstrai estas operações:
+
# Modifique o programa para usar um semáforo binário ao invés de um mutex em sua solução. Utilize a classe Semaphore da aula passada.
 +
 
 +
=== Exercício 2 ===
 +
 
 +
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:
 +
# Criar N threads, uma para somar os valores de cada linha.
 +
# Receber o resultado do somatório de cada linha e gerar o somatório total da matriz.
 +
# Analise o programa: há problemas de sincronização que precisam ser resolvidos? Se sim, resolva-os.
 +
 
 
<syntaxhighlight lang=cpp>
 
<syntaxhighlight lang=cpp>
#ifndef __thread_h
+
#include <iostream>
#define __thread_h
+
#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];
  
#include <pthread.h>
 
#include <signal.h>
 
  
class Thread
+
/* thread function; it sums the values of the matrix in the row */
 +
int SumValues(int i)
 
{
 
{
public:
+
int n = i; /* number of row */
    Thread(int ( * const entry)(int), int arg) {
+
int total = 0; /* the total of the values in the row */
if(pthread_create(&thread, 0, (void*(*)(void*))entry, (void *)arg))
+
int j;
    thread = 0;
+
for (j = 0; j < M; j++) /* sum values in the "n" row */
    }
+
total += matrix[n][j];
    ~Thread() { pthread_kill(thread, SIGKILL); }
+
cout << "The total in row" << n << " is " << total << "." << endl;
 +
/* terminate a thread and return a total in the row */
 +
exit(total);
 +
}
  
    int join(int * status) { return pthread_join(thread, (void **)status); }
+
int main(int argc, char *argv[])
    friend void exit(int status = 0) { pthread_exit((void *) status); }
+
{
 +
int i, j;
 +
int total = 0; /* the total of the values in the matrix */
  
private:
+
/* initialize the matrix */
    pthread_t thread;
+
for (i = 0; i < N; i++)
};
+
for (j = 0; j < M; j++)
 +
matrix[i][j] = i * M + j;
  
#endif
+
/* 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;
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Problemas clássicos de sincronização ===
 
  
==== Produtor/Consumidor ====
+
=== Exercício 3 ===
O problema clássico Produtor/Consumidor consiste em dois fluxos de execução (threads/processos), sendo que um dos fluxos (consumidor) só pode executar a partir do momento em que seus dados de entrada foram produzidos pelo outro fluxo (produtor).
 
*[http://cs.uttyler.edu/Faculty/Rainwater/COSC3355/Animations/processsync.htm Veja esta simulação]
 
*[http://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem Veja esta descrição do problema]
 
  
*''DESAFIO'': O programa abaixo implementa um produtor/consumidor utilizando semáforos para sincronização. Contudo, as chamadas para as operações ''v'' e ''p'' foram removidas, conforme comentários no código. Corrija este programa.
+
Corrija os problemas de sincronização do programa abaixo.
  
 
<syntaxhighlight lang=cpp>
 
<syntaxhighlight lang=cpp>
 
#include <iostream>
 
#include <iostream>
 +
#include <fstream>
 +
#include <uuid/uuid.h>
 +
#include <unistd.h>
 +
#include <errno.h>
 +
 
#include "thread.h"
 
#include "thread.h"
#include "semaphore.h"
 
  
 
using namespace std;
 
using namespace std;
  
const int DELAY = 10000000;
+
const int PRODS = 100;
const int REP = 5;
+
const int REP = 10;
 +
 
 +
const int CONS = 20;
 +
 
 +
const int BUF_SIZE = 35;
 +
 
 +
Thread * prods[PRODS];
 +
Thread * cons[CONS];
 +
 
 +
uuid_t buffer[BUF_SIZE];
 +
static int prod_pos = 0;
 +
static int cons_pos = 0;
  
Semaphore empty(1);
+
bool finished = false;
Semaphore full(0);
 
  
 
int producer(int n)
 
int producer(int n)
 
{
 
{
    cout << "Producer was born!\n";
+
cout << "Producer was born!" << endl;
 +
 
 +
int rep = REP;
  
    // Faltam, no laço abaixo:
+
char fname[36+1];
    //  - uma chamada para empty.p()
+
 
    //  - uma chamada para full.v()
+
while(rep--)
    for(int i = 0; i < REP; i++) {
+
{
cout << "Producing ...\n";
+
if(++prod_pos == BUF_SIZE) prod_pos = 0;
for(int i = 0; i < DELAY * 10; i++);
+
uuid_generate(buffer[prod_pos]);
cout << "Storing ...\n";
+
uuid_unparse(buffer[prod_pos], fname);
for(int i = 0; i < DELAY; i++);
+
 
    }
+
string name(fname,sizeof(uuid_t)*2 + 4);
 +
ofstream file(name.c_str());
 +
file << name;
 +
file.close();
 +
}
  
    return n;
+
exit(REP);
 
}
 
}
  
 
int consumer(int n)
 
int consumer(int n)
 
{
 
{
    cout << "Consumer was born!\n";
+
cout << "Consumer was born!" << endl;
 +
 
 +
char fname[36+1];
 +
int consumed = 0;
  
    // Faltam, no laço abaixo:
 
    //  - uma chamada para full.p()
 
    //  - uma chamada para empty.v()
 
    for(int i = 0; i < REP; i++) {
 
cout << "Retrieving ...\n";
 
for(int i = 0; i < DELAY * 10; i++);
 
cout << "Consuming ...\n";
 
for(int i = 0; i < DELAY; i++);
 
    }
 
  
    return n;
+
while(true)
}
+
{
 +
if(finished) exit(consumed);
  
int main()
+
consumed++;
{
 
    cout << "The Producer x Consumer Problem\n";
 
  
    Thread prod(&producer, REP);
+
if(++cons_pos == BUF_SIZE) cons_pos = 0;
    Thread cons(&consumer, REP);
+
uuid_unparse(buffer[cons_pos], fname);
  
    int status;
+
{
    prod.join(&status);
+
ifstream file(fname);
    if(status == REP)
+
if(!file.good()) continue;
cout << "Producer went to heaven!\n";
+
string str;
    else
+
file >> str;
cout << "Producer went to hell!\n";
+
cout << "Consumed: " << str << endl;
 +
}
  
    cons.join(&status);
+
if(remove(fname)) cerr << "Error: " << errno << endl;
    if(status == REP)
+
}
cout << "Consumer went to heaven!\n";
 
    else
 
cout << "Consumer went to hell!\n";
 
  
    return 0;
+
exit(consumed);
 
}
 
}
</syntaxhighlight>
 
  
==== Jantar dos Filósofos ====
+
int main()
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.
+
{
*[http://www.doc.ic.ac.uk/~jnm/concurrency/classes/Diners/Diners.html Veja esta simulação]
+
    cout << "Massive Producer x Consumer Problem\n";
*[http://en.wikipedia.org/wiki/Dining_philosophers_problem Veja esta descrição do problema]
 
  
*''DESAFIO'': O 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. Corrija este programa.
+
    // Create
 +
    for(int i = 0; i < PRODS; i++)
 +
    prods[i] = new Thread(&producer, i);
 +
    for(int i = 0; i < CONS; i++)
 +
    cons[i] = new Thread(&consumer, i);
  
<syntaxhighlight lang=cpp>
+
    // Join
#include <iostream>
+
    int status = 0;
#include "thread.h"
+
    int produced = 0;
#include "semaphore.h"
+
    int consumed = 0;
 +
    for(int i = 0; i < PRODS; i++)
 +
    {
 +
    prods[i]->join(&status);
 +
    produced += status;
 +
    }
  
using namespace std;
+
    finished = true;
  
const int DELAY = 10000000;
+
    for(int i = 0; i < CONS; i++)
const int ITERATIONS = 5;
+
    {
 +
    cons[i]->join(&status);
 +
    consumed += status;
 +
    }
  
Semaphore chopstick[5];
+
    cout << "Total produced: " << produced << endl;
 +
    cout << "Total consumed: " << consumed << endl;
  
int philosopher(int n)
+
     return 0;
{
+
}
     cout << "Philosopher " << n << " was born!\n";
 
  
    int first = (n < 4)? n : 0; // left for phil 0 .. 3, right for phil 4
+
</syntaxhighlight>
    int second = (n < 4)? n + 1 : 4; // right for phil 0 .. 3, left for phil 4
 
  
    // Foram removidos do laço abaixo:
+
Utilize o script abaixo para executar o programa. O script cria um diretório em /tmp/test onde são salvos os arquivos criados pelos produtores. Para ter certeza de que todos os arquivos foram adequadamente consumidos (i.e., deletados), o diretório /tmp/test deve estar vazio após a execução do programa.
    //  - 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";
+
<syntaxhighlight lang=bash>
for(int i = 0; i < DELAY; i++);
+
#!/bin/bash
    }
 
  
    return n;
+
export myrun_DIR=`pwd`
}
+
export myrun_TEST=/tmp/test
 +
 
 +
rm -rf $myrun_TEST
 +
mkdir $myrun_TEST
 +
cd $myrun_TEST
 +
$myrun_DIR/multi_prod_cons
 +
cd $myrun_DIR
 +
</syntaxhighlight>
 +
 
 +
Problemas de sincronização a serem considerados:
 +
#Limite de tamanho do buffer: os produtores só podem produzir se houver espaço no buffer - nunca pode haver mais que BUF_SIZE arquivos na pasta de troca de arquivos.
 +
#Uso da posição do buffer: verificar se a posição consumida já foi de fato produzida.
 +
#Consumo: verificar se todos os arquivos produzidos foram consumidos.
 +
#Sistema de arquivos: verificar se os arquivos estão sendo sincronizados adequadamente no sistema de arquivos (ver ''man 2 sync'').
  
int main()
 
{
 
    cout << "The Dining-Philosophers Problem\n";
 
  
    Thread * phil[5];
+
== 09/10: Laboratório: programação concorrente ==
    for(int i = 0; i < 5; i++)
 
phil[i] = new Thread(&philosopher, i);
 
  
    int status;
+
== 15/10: Encaminhamento para palestra MCC ==
    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;
+
== 16/10: Revisão e correção de listas de exercícios ==
}
 
</syntaxhighlight>
 
  
 +
== 22/10: Prova 1 - Comunicação e coordenação de processos ==
  
== 01/10 a 08/10: Laboratório: programação concorrente ==
+
== 23/10: Gerenciamento de memória: Introdução ==
  
=== Exercício 1 ===
+
* [[Arquivo:SOP29005-parte5.pdf]]
  
O programa abaixo cria 5 threads, e cada uma destas threads atualiza uma variável global (memória compartilhada).
+
== 29/10 e 30/10: Gerenciamento de memória ==
  
<syntaxhighlight lang=cpp>
+
* [[Arquivo:SOP29005-parte5.pdf]]
#include <iostream>
 
#include "thread.h"
 
  
#define NUM_THREADS 5
+
=== Atividade prática ===
  
using namespace std;
+
Escreva um programa de computador que, dada a configuração de um sistema de paginação e um endereço de entrada, forneça informações sobre o endereço dado no referido sistema. Mais detalhes abaixo:
  
int saldo = 1000;
+
* Entradas do programa:
 +
** Largura do endereço em bits
 +
** Tamanho das páginas em bytes
 +
** Arquivo com tabela de páginas
 +
** Endereço a ser traduzido
  
int AtualizaSaldo(int n)
+
* Saídas do programa:
{
+
** Número de frames no sistema
int meu_saldo = saldo;
+
** Número da página (endereço lógico)
int novo_saldo = meu_saldo + n*100;
+
** Número do frame (endereço físico) (Hit ou Page Fault?)
cout << "Novo saldo = " << novo_saldo << endl;
+
** Deslocamento
saldo = novo_saldo;
 
}
 
  
int main()
+
A tabela de páginas estará em um arquivo em modo texto contendo um mapeamento por linha, como o abaixo. Observe que o arquivo contém os números de página ou frame, e não endereços.
{
+
<syntaxhighlight lang=text>
Thread * threads[NUM_THREADS];
+
0-10
 +
1-9
 +
2-20
 +
3-37
 +
4-1
 +
5-4
 +
6-7
 +
7-6
 +
</syntaxhighlight>
 +
 
 +
A classe abaixo pode ser utilizada para processar o arquivo de entrada. Ela utiliza um map da STL para fazer o mapeamento. Você precisará implementar o método get_frame desta classe.
  
for(int t = 0; t < NUM_THREADS; t++)
+
<syntaxhighlight lang=cpp>
threads[t] = new Thread(&AtualizaSaldo, t+1);
+
/*
 +
* MapFile.h
 +
*
 +
*  Created on: Oct 29, 2014
 +
*      Author: arliones
 +
*/
  
int status;
+
#ifndef MAPFILE_H_
for(int t = 0; t < NUM_THREADS; t++) {
+
#define MAPFILE_H_
threads[t]->join(&status);
 
cout << "Thread " << t << " terminou com status " << status << "." << endl;
 
}
 
  
cout << "Saldo final é " << saldo << "." << endl;
+
#include <string>
}
+
#include <map>
</syntaxhighlight>
+
#include <fstream>
 +
#include <stdlib.h>
 +
#include <iostream>
  
 +
using namespace std;
  
# Compile este programa. Você precisará da classe Thread implementada na aula passada.
+
class MapFile {
# Execute este programa várias vezes. Ele funciona? Será que ele gera as saídas esperadas?
+
MapFile() {}
# Identifique as '''seções críticas''' do programa.
+
public:
# Corrija o programa utilizando '''mutex'''. Utilize a classe Mutex implementada na aula passada.
+
MapFile(string filename)
# 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.
+
{
# Modifique a função ''AtualizaSaldo()'' para reduzir o tempo de lock do programa.
+
ifstream file(filename.c_str());
# Modifique o programa para usar um semáforo binário ao invés de um mutex em sua solução. Utilize a classe Semaphore da aula passada.
+
string line;
 +
char line_c[256];
 +
unsigned int page, frame, delimiter;
 +
while(!file.eof()) {
 +
file.getline(line_c,256); line = string(line_c);
 +
delimiter = line.find('-');
 +
page = atoi(line.substr(0,delimiter+1).c_str());
 +
frame = atoi(line.substr(delimiter+1).c_str());
 +
_pt.insert(make_pair(page,frame));
 +
}
  
=== Exercício 2 ===
+
}
  
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:
+
virtual ~MapFile() {}
# Criar N threads, uma para somar os valores de cada linha.
 
# Receber o resultado do somatório de cada linha e gerar o somatório total da matriz.
 
# Analise o programa: há problemas de sincronização que precisam ser resolvidos? Se sim, resolva-os.
 
  
<syntaxhighlight lang=cpp>
+
// Returns the number of the frame or -1 if not found
#include <iostream>
+
unsigned int get_frame(unsigned int page)
#include "thread.h"
+
{
 +
//TODO
 +
}
  
/* number of matrix columns and rows */
+
void print_page_table()
#define M 5
+
{
#define N 10
+
cout << "Page Table:" << endl;
 +
map<unsigned int, unsigned int>::iterator mit = _pt.begin();
 +
for(; mit != _pt.end(); ++mit)
 +
cout << mit->first << " - " << mit->second << endl;
 +
}
  
using namespace std;
+
private:
 
+
map<unsigned int, unsigned int> _pt;
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;
 
}
 
</syntaxhighlight>
 
 
 
 
 
=== Exercício 3 ===
 
 
 
Corrija os problemas de sincronização do programa abaixo.
 
 
 
<syntaxhighlight lang=cpp>
 
#include <iostream>
 
#include <fstream>
 
#include <uuid/uuid.h>
 
#include <unistd.h>
 
#include <errno.h>
 
 
 
#include "thread.h"
 
 
 
using namespace std;
 
 
 
const int PRODS = 100;
 
const int REP = 10;
 
 
 
const int CONS = 20;
 
 
 
const int BUF_SIZE = 35;
 
 
 
Thread * prods[PRODS];
 
Thread * cons[CONS];
 
 
 
uuid_t buffer[BUF_SIZE];
 
static int prod_pos = 0;
 
static int cons_pos = 0;
 
 
 
bool finished = false;
 
 
 
int producer(int n)
 
{
 
cout << "Producer was born!" << endl;
 
 
 
int rep = REP;
 
 
 
char fname[36+1];
 
 
 
while(rep--)
 
{
 
if(++prod_pos == BUF_SIZE) prod_pos = 0;
 
uuid_generate(buffer[prod_pos]);
 
uuid_unparse(buffer[prod_pos], fname);
 
 
 
string name(fname,sizeof(uuid_t)*2 + 4);
 
ofstream file(name.c_str());
 
file << name;
 
file.close();
 
}
 
 
 
exit(REP);
 
}
 
 
 
int consumer(int n)
 
{
 
cout << "Consumer was born!" << endl;
 
 
 
char fname[36+1];
 
int consumed = 0;
 
 
 
 
 
while(true)
 
{
 
if(finished) exit(consumed);
 
 
 
consumed++;
 
 
 
if(++cons_pos == BUF_SIZE) cons_pos = 0;
 
uuid_unparse(buffer[cons_pos], fname);
 
 
 
{
 
ifstream file(fname);
 
if(!file.good()) continue;
 
string str;
 
file >> str;
 
cout << "Consumed: " << str << endl;
 
}
 
 
 
if(remove(fname)) cerr << "Error: " << errno << endl;
 
}
 
 
 
exit(consumed);
 
}
 
 
 
int main()
 
{
 
    cout << "Massive Producer x Consumer Problem\n";
 
 
 
    // Create
 
    for(int i = 0; i < PRODS; i++)
 
    prods[i] = new Thread(&producer, i);
 
    for(int i = 0; i < CONS; i++)
 
    cons[i] = new Thread(&consumer, i);
 
 
 
    // Join
 
    int status = 0;
 
    int produced = 0;
 
    int consumed = 0;
 
    for(int i = 0; i < PRODS; i++)
 
    {
 
    prods[i]->join(&status);
 
    produced += status;
 
    }
 
 
 
    finished = true;
 
 
 
    for(int i = 0; i < CONS; i++)
 
    {
 
    cons[i]->join(&status);
 
    consumed += status;
 
    }
 
 
 
    cout << "Total produced: " << produced << endl;
 
    cout << "Total consumed: " << consumed << endl;
 
 
 
    return 0;
 
}
 
  
 +
#endif /* MAPFILE_H_ */
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Utilize o script abaixo para executar o programa. O script cria um diretório em /tmp/test onde são salvos os arquivos criados pelos produtores. Para ter certeza de que todos os arquivos foram adequadamente consumidos (i.e., deletados), o diretório /tmp/test deve estar vazio após a execução do programa.
+
Abaixo, exemplos de execução do programa desejado:
 
 
 
<syntaxhighlight lang=bash>
 
<syntaxhighlight lang=bash>
#!/bin/bash
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim
 
+
Usage: ./paging_sim ADDR_LEN PAGE_SIZE MAP_FILE ADDRESS
export myrun_DIR=`pwd`
 
export myrun_TEST=/tmp/test
 
 
 
rm -rf $myrun_TEST
 
mkdir $myrun_TEST
 
cd $myrun_TEST
 
$myrun_DIR/multi_prod_cons
 
cd $myrun_DIR
 
</syntaxhighlight>
 
 
 
Problemas de sincronização a serem considerados:
 
#Limite de tamanho do buffer: os produtores só podem produzir se houver espaço no buffer - nunca pode haver mais que BUF_SIZE arquivos na pasta de troca de arquivos.
 
#Uso da posição do buffer: verificar se a posição consumida já foi de fato produzida.
 
#Consumo: verificar se todos os arquivos produzidos foram consumidos.
 
#Sistema de arquivos: verificar se os arquivos estão sendo sincronizados adequadamente no sistema de arquivos (ver ''man 2 sync'').
 
 
 
 
 
== 09/10: Laboratório: programação concorrente ==
 
 
 
=== t3: Main, join, exit ===
 
 
 
==== Tarefa main====
 
 
 
Desde o início temos forjado uma tarefa main na inicialização de Task. Contudo, não demos ainda toda a atenção necessária a esta tarefa. Neste trabalho, caso ainda não tenha sido feito, vocês devem extender o sistema de vocês para atender aos seguintes requisitos:
 
*O ponto de partida deve ser o sistema implementado para o t2;
 
*O programa principal (main) deverá ser tratado como uma tarefa, sendo escalonável da mesma forma que as demais tarefas instanciadas;
 
*Todas as tarefas poderão ser escalonadas a partir de sua criação, inclusive o main;
 
<!--
 
*Seu sistema deverá funcionar corretamente com o código abaixo (atenção ao exit ao final da main):
 
 
 
<syntaxhighlight lang=cpp>
 
#include <iostream>
 
#include <queue>
 
#include <sstream>
 
#include <BOOOS.h>
 
#include <Scheduler.h>
 
 
 
#define ASSERT(x,y) if(!(x)) return y;
 
 
 
using namespace std;
 
using namespace BOOOS;
 
  
Task *pang, *peng, *ping, *pong, *pung;
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 16 1024 page_table.txt 5687
 +
Frames in the system: 64
 +
Requested page: 5
 +
Requested frame: 4
 +
Offset: 567
  
void function(void * arg) {
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 16 1024 page_table.txt 10578
int i;
+
Frames in the system: 64
 +
Requested page: 10
 +
Requested frame: Page Fault
 +
Offset: 338
  
Task::self()->nice(2*Task::self()->tid());
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4096 page_table.txt 8632
 +
Frames in the system: 1048576
 +
Requested page: 2
 +
Requested frame: 20
 +
Offset: 440
  
for(i=0; i<10; i++) {
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4096 page_table.txt 68723
cout << (char*)arg << " " << i << endl;
+
Frames in the system: 1048576
Timer::delay_ticks(25);
+
Requested page: 16
}
+
Requested frame: Page Fault
cout << (char*)arg << " End" << endl;
+
Offset: 3187
Task::self()->exit(0);
 
}
 
  
int main() {
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4194304 page_table.txt 354
 +
Frames in the system: 1024
 +
Requested page: 0
 +
Requested frame: 10
 +
Offset: 354
  
BOOOS_Configuration::SCHEDULER_TYPE = Scheduler::SCHED_PRIORITY;
+
arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4194304 page_table.txt 43554432
BOOOS_Configuration::SCHEDULER_PREEMPT = true;
+
Frames in the system: 1024
BOOOS_Configuration::SCHEDULER_AGING = true;
+
Requested page: 10
BOOOS::init();
+
Requested frame: Page Fault
 +
Offset: 1611392
  
cout << "Main Start" << endl;
 
 
pang = new Task(function, 1, (char*)"\tPang");
 
peng = new Task(function, 1, (char*)"\t\tPeng");
 
ping = new Task(function, 1, (char*)"\t\t\tPing");
 
pong = new Task(function, 1, (char*)"\t\t\t\tPong");
 
pung = new Task(function, 1, (char*)"\t\t\t\t\tPung");
 
 
while(Task::count() > 2) {
 
Task::self()->nice(20);
 
Task::self()->yield();
 
}
 
 
cout << "Main End" << endl;
 
 
Task::self()->exit(0);
 
 
return 0;
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
-->
 
 
  
==== Operador Join ====
+
Dicas: utilize variáveis ''unsigned'' do tipo adequado em seus cálculos. Você provavelmente precisará das funções ''log2'' e ''exp2'' da libmath (include math.h).
  
O objetivo deste projeto é construir um método de sincronização denominado ''join'' que permite que uma tarefa espere a conclusão de outra tarefa, de forma similar à chamada POSIX ''wait'' para processos. Para isso, um método com a seguinte assinatura deve ser adicionado à classe ''Task'':
+
== 12/11 e 13/11: Memória Virtual ==
  
<syntaxhighlight lang=cpp>
+
* [[Arquivo:SOP29005-parte5.pdf]]
int join();
 
</syntaxhighlight>
 
  
Ao chamar ''task->join()'', a tarefa atual (''Task::self()'') deve ser suspensa até a conclusão da tarefa ''task''. Quando a tarefa ''task'' terminar, isto é, chamar o método ''exit()'', a tarefa suspensa deve ser colocada de volta à fila de tarefas prontas. Caso a tarefa ''task'' não exista, ou já tenha encerrado, esta chamada deve retornar imediatamente, sem suspender a tarefa atual. Lembre-se que mais de uma tarefa pode aguardar pela mesma tarefa concluir, e que todas devem ser "acordadas" quando isto acontecer.
+
== 19/11 e 20/11: Sistemas de arquivos ==
  
O valor de retorno do método ''join'' é o código de encerramento da tarefa ''task'', ou seja, o valor passado como parâmetro à chamada do método ''exit()'', ou, -1 caso a tarefa indicada não exista.
+
* [[Arquivo:SOP29005-parte6.pdf]]
  
<!--
 
Seu sistema deve funcionar com o programa exemplo a seguir.
 
  
<syntaxhighlight lang=cpp>
+
== 26/11: Revisão para prova ==
#include <iostream>
 
#include <queue>
 
#include <sstream>
 
#include <BOOOS.h>
 
#include <Scheduler.h>
 
 
#define ASSERT(x,y) if(!(x)) return y;
 
 
using namespace std;
 
using namespace BOOOS;
 
 
Task *pang, *peng, *ping, *pong, *pung;
 
 
void function(void * arg) {
 
int i;
 
 
Task::self()->nice(2*Task::self()->tid());
 
 
for(i=0; i<10; i++) {
 
cout << (char*)arg << " " << i << endl;
 
Timer::delay_ticks(25);
 
}
 
cout << (char*)arg << " End" << endl;
 
Task::self()->exit(2*Task::self()->tid());
 
}
 
 
int main() {
 
 
BOOOS_Configuration::SCHEDULER_TYPE = Scheduler::SCHED_PRIORITY;
 
BOOOS_Configuration::SCHEDULER_PREEMPT = true;
 
BOOOS_Configuration::SCHEDULER_AGING = true;
 
BOOOS::init();
 
 
cout << "Main Start" << endl;
 
 
pang = new Task(function, 1, (char*)"\tPang");
 
peng = new Task(function, 1, (char*)"\t\tPeng");
 
ping = new Task(function, 1, (char*)"\t\t\tPing");
 
pong = new Task(function, 1, (char*)"\t\t\t\tPong");
 
pung = new Task(function, 1, (char*)"\t\t\t\t\tPung");
 
 
while(Task::count() > 2) {
 
Task::self()->nice(20);
 
Task::self()->yield();
 
}
 
 
Task * Main = Task::self();
 
int result = 0;
 
  
cout << "Main wait pang... ";
+
* Dúvidas / Lista de exercícios.
result = Main->join(pang);
+
* Aula será no
cout << "Result: " << result << endl;
 
  
cout << "Main wait peng... ";
+
== 27/11: P2 (gerenciamento de memória) ==
result = Main->join(peng);
 
cout << "Result: " << result << endl;
 
  
cout << "Main wait ping... ";
 
result = Main->join(ping);
 
cout << "Result: " << result << endl;
 
  
cout << "Main wait pong... ";
+
== 03/12: Sistemas de arquivos: estudos de caso ==
result = Main->join(pong);
 
cout << "Result: " << result << endl;
 
  
cout << "Main wait pung... ";
+
* [[Arquivo:SOP29005-parte6.pdf]]
result = Main->join(pung);
 
cout << "Result: " << result << endl;
 
  
 +
== 04/12: Gerenciamento de entrada e saída: introdução / Atividade em laboratório: construção de módulo para Linux ==
  
cout << "Main End" << endl;
+
* [[Arquivo:SOP29005-parte7.pdf]]
Main->exit(0);
 
  
return 0;
+
== 10/12: Revisão e correção de listas de exercícios ==
}
 
</syntaxhighlight>
 
  
==== Resumo do trabalho ====
+
== 11/12: P3 (sistemas de arquivo e gerenciamento de entrada e saída) ==
*'''O que é?''' Mecanismo de sincronização de tarefas.
 
*'''Duração estimada:''' 4 horas.
 
*'''Dependências:''' t0 a t6.
 
*'''Entrega:''' Até 23/05, por email.
 
-->
 

Edição atual tal como às 13h11min de 23 de janeiro de 2015

EngTel: Sistemas Operacionais - 2014-2

Plano de ensino

Cronograma de Atividades - Planejado

Cronograma de Atividades - Ajustado

Aula Data Horas Conteúdo Recursos
1 31/7 2 Apresentação da Disciplina. Visão geral de funções, responsabilidades e estruturas de um SO Lab. Informática
2 6/8 2 Atividades em laboratório: Introdução ao Linux e GCC Lab. Informática
3 7/8 2 Arquitetura de sistemas operacionais e modelos de programação Lab. Informática
4 13/8 2 Gerência de tarefas; contextos, processos e threads Lab. Informática
5 14/8 2 Atividades em laboratório: API POSIX – fork/wait – t0: biblioteca de filas Lab. Informática
6 20/8 2 Escalonamento de tarefas Lab. Informática
7 21/8 2 Atividades em laboratório: Estrutura de processos (a verdadeira história do Hello World) – t1: troca de contexto e tarefas cooperativas Lab. Informática
8 27/8 2 Atividade em laboratório: acompanhamento de projetos Lab. Informática
9 28/8 2 Algoritmos de escalonamento Lab. Informática
10 3/9 2 Algoritmos de escalonamento / Revisão para prova Lab. Informática
11 4/9 2 Revisão e correção de listas de exercícios Lab. Informática
12 10/9 2 P0 (introdução e gerência de tarefas) Lab. Informática
13 11/9 2 Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades Lab. Informática
14 17/9 2 Comunicação entre processos: Troca de mensagens e Memória Compartilhada Lab. Informática
15 18/9 2 Atividade em laboratório: programação com pipes e API shm – t3: Preempção e compartilhamento de tempo Lab. Informática
16 24/9 2 Coordenação entre processos Lab. Informática
17 25/9 2 Laboratório: Problemas clássicos de coordenação; impasses Lab. Informática
18 1/10 2 Laboratório: Problemas clássicos de coordenação; impasses Lab. Informática
19 2/10 2 Laboratório: Problemas clássicos de coordenação; impasses Lab. Informática
20 8/10 2 Laboratório: Problemas clássicos de coordenação; impasses Lab. Informática
21 9/10 2 Laboratório: Problemas clássicos de coordenação; impasses Lab. Informática
22 15/10 2 Encaminhamento para palestra MCC Lab. Informática
23 16/10 2 Revisão e correção de listas de exercícios Lab. Informática
24 22/10 2 P1 (comunicação e coordenação de tarefas) Lab. Informática
25 23/10 2 Gerenciamento de memória: Introdução Lab. Informática
26 29/10 4 Gerenciamento de memória: paginação e segmentação. Lab. Informática
27 30/10 2 Atividade em laboratório: alocação de memória. Lab. Informática
28 5/11 2 Aula adiantada em 29/out Lab. Informática
29 6/11 0 Aula adiada: viagem do professor Lab. Informática
30 12/11 2 Gerenciamento de memória: memória virtual Lab. Informática
31 13/11 2 Gerenciamento de memória: memória virtual Lab. Informática
32 19/11 2 Revisão e correção de listas de exercícios Lab. Informática
33 20/11 2 P2 (gerenciamento de memória) Lab. Informática
34 26/11 2 Sistemas de arquivos: introdução e controle de acesso Lab. Informática
35 27/11 2 Sistemas de arquivos: estudos de caso e gerenciamento de memória secundária Lab. Informática
36 3/12 2 Gerenciamento de entrada e saída: introdução / Atividade em laboratório: construção de módulo para Linux Lab. Informática
37 4/12 2 Revisão e correção de listas de exercícios Lab. Informática
38 10/12 2 P3 (sistemas de arquivo e gerenciamento de entrada e saída) Lab. Informática
TOTAL 76

Notas

Matrícula P0 P1 P2 P3 T0 T1 Final
122001838-4 A A B B A B A
122001432-0 A B B A A B A
122005026-1 A B A B A B A
122006899-3 B A C A A C B
122001913-5 B A A B A C B

Material de aula

Slides

Listas de exercícios

As listas de exercícios são compostas por exercícios selecionados do livro do Silberschatz, 8a edição. Há 10 volumes deste livro na biblioteca do campus, sendo suficiente para toda a turma deste semestre.

SILBERSCHATZ, Abraham; GALVIN, Peter; GAGNE, Greg. Fundamentos de sistemas operacionais. 8. ed. Rio de Janeiro: LTC, 2011. 515 p., il. ISBN 9788521617471.

Exercícios selecionados:

  • Parte 1
    • Capítulo 1: 1-3, 6-8, 10, 13, 14, 17, 22, 23, 25.
    • Capítulo 2: 1-8, 12, 13, 15, 17, 22, 25.
  • Parte 2
    • Capítulo 3: 1, 3, 6-10, 13, 15
    • Capítulo 4: 1, 4, 7, 8, 10-13
    • Capítulo 5: 1-3, 5, 6, 9, 10, 13-15, 21
  • Parte 3
    • Capítulo 6: 1, 2 (utilizar semáforos POSIX), 6, 8, 11-15, 18, 20, 21, 25, 29, 39.
  • Parte 4
    • Capítulo 8: 1-6, 9-21, 23.
    • Capítulo 9: 1-8, 14-16, 19-23, 28.
  • Parte 5
    • Capítulo 10: 1-20
    • Capítulo 11: 1-7
    • Capítulo 12: 1-7, 13-14 (desafio).

Projeto

Esta disciplina utiliza um projeto contínuo, no qual os alunos desenvolvem um aplicativo que emula um sistema operacional no espaço de usuário no Linux. Este projeto é chamado de BOOOS - Basic Object Oriented Operating System.

Diário de Aulas

31/07: Apresentação da Disciplina. Visão geral de funções, responsabilidades e estruturas de um SO

06/08: Laboratório: Linux e GCC/G++


Herança

Classes em C++ podem ser estendidas, criando novas classes que retêm as característica da classe-base. Este processo, conhecido como herança, envolve uma classe-base e uma classe derivada: a classe derivada herda os membros da classe-base, sobre os quais ela pode adicionar seus próprios membros.

Por exemplo, imaginemos uma série de classes para descrever dois tipos de polígonos: retângulos e triângulos. Estes dois polígonos têm certas propriedades em comum, como os valores necessários para calcular suas áreas: ambos podem ser descritos simplificadamente com uma altura e uma largura. (ou base).

Isto pode ser representado no formato de classes como uma classe Polygon (polígono) da qual podemos derivar duas outras classes: Rectangle e Triangle:

Inheritance.png

A classe Polygon poderia conter membros comuns a ambos os tipos de polígonos. Em nosso caso: largura e altura (width e height). E as classes Rectangle e Triangle poderiam ser as classes derivadas, com características específicas que diferenciam um tipo de polígono de outro.

Classes que são derivadas de outras herdam todos os membros acessíveis da classe-base. Isto significa que se a classe-base inclui um membro A e nós derivamos uma classe dela, e esta nova classe possui um membro B, a classe derivada conterá ambos os membros A e B.

No C++, a relação de herança de duas classes é declarada na classe derivada. A definição de classes derivadas usa a seguinte sintaxe:

class classe_derivada : public classe_base
{ /*...*/ };

O código acima define uma classe com nome classe_derivada, que herda publicamente a classe com nome classe_base. A palavra reservada public pode ser substituído por protected ou private, dependendo do tipo de herança desejado. Este delimitador de acesso limita o nível de acesso aos membros herdados da classe-base: os membros com um nível de acesso mais acessível são herdados com o nível declarado na herança, enquanto os membros com níveis de acesso iguais ou mais restritivos mantém, na classe derivada, os níveis de restrição declarados na classe-base.

#include <iostream>
using namespace std;

class Polygon {
  protected:
    int width, height;
  public:

    virtual int area() = 0;

    void set_values (int a, int b)
      { width=a; height=b;}
 };

class Rectangle: public Polygon {
  public:
    int area ()
      { return width * height; }
 };

class Triangle: public Polygon {
  public:
    int area ()
      { return width * height / 2; }
  };
  
int main () {
  Rectangle rect;
  Triangle trgl;
  rect.set_values (4,5);
  trgl.set_values (4,5);
  cout << rect.area() << '\n';
  cout << trgl.area() << '\n';
  return 0;
}
  • Experimento 1: compile e execute o exemplo acima.
  • Experimento 2: substitua, no exemplo acima, uma herança pública por herança protegida ou privada e verifique o que acontece.

Espaços de nomes

Espaços de nome, ou namespaces, permite agrupar entidades como classes, objetos e funções sob um mesmo nome. Deste modo o escopo global pode ser dividido em "sub-escopos", cada um com seu próprio nome.

A sintaxe para uso de um namespace em C++ é dada abaixo, onde identifier é o nome do sob o qual as entidades serão declaradas e, no local do comentário, seria registrado o conjunto de classes, objetos e funções incluídos no namespace:

namespace identifier
{
  /* entities... */
}

Por exemplo, o código abaixo as variáveis a e b são inteiros normais declarados dentro do namespace myNamespace.

namespace myNamespace
{
  int a, b;
}

Estas variáveis podem ser acessadas normalmente por classes ou funções declaradas dentro do mesmo namespace. Para serem acessadas de fora do namespace, contudo, elas precisam ser adequadamente qualificadas utilizando o operador de escopo (::). Por exemplo, para utilizar as variáveis acima de fora do myNamespace, elas devem ser qualificadas como:

myNamespace::a
myNamespace::b

Espaços de nomes podem ser bastante úteis para evitar colisão de identificadores:

// namespaces
#include <iostream>
using namespace std;

namespace foo
{
  int value() { return 5; }
}

namespace bar
{
  const double pi = 3.1416;
  double value() { return 2*pi; }
}

int main () {
  cout << foo::value() << '\n';
  cout << bar::value() << '\n';
  cout << bar::pi << '\n';
  return 0;
}
  • Experimento 1: compile, execute, e entenda o código do exemplo acima.
  • Experimento 2: crie, dentro do namespace bar, uma função que acesse a função value do namespace foo.

Criando bibliotecas

Uma biblioteca é uma coleção de objetos, assim como uma biblioteca tradicional é uma coleção de livros. Quando construindo seu programa, você pode utilizar, no gcc, uma ou mais bibliotecas, de modo que o gcc utilizará os objetos nestas bibliotecas para completar seu programa. Por exemplo, todas as funções da biblioteca padrão C (como printf e exit) estão em uma biblioteca C, geralmente na pasta lib/libc.a da sua instalação GCC. Quando você faz a ligação do seu programa, o GCC adiciona ao binário os objetos da biblioteca C necessários, baseando-se nas chamadas de funções do seu programa. Importante perceber que apenas as funções/objetos utilizados são ligados ao programa, não gerando desperdício de tempo e espaço.

Para fazer usa própria biblioteca, você precisa, primeiro, compilar cada um dos arquivos-fonte, gerando um conjunto de arquivos-objeto. Aqui utilizaremos, como exemplo, o código do exercício-exemplo da aula anterior.

g++ -c pessoa.cc
g++ -c aluno.cc

A seguir, você utilizará o comando ar para criar uma biblioteca contendo os arquivos-objeto criados.

ar rvs mylib.a pessoa.o aluno.o

Cada uma das letras em rvs especifica um parâmetro para o ar. r significa substituir objetos com mesmo nome na biblioteca pelos novos passados pela linha de comando. Como a biblioteca está inicialmente vazia, isto significa o mesmo que adicionar novos objetos à biblioteca. Há também opções para extrair e remover objetos da biblioteca. A opção v significa verbose, ou seja, pede que o programa ar imprima na tela as ações sendo tomadas durante sua execução. Finalmente, a opção s diz ao ar para criar uma tabela de símbolos, que é um recurso extra que o GCC precisa para utilizar uma biblioteca.

Para utilizar a biblioteca, simplesmente adicione ela ao comando de ligação do gcc como se fosse outro objeto:

g++ main.cc mylib.a -o main

É importante listar as bibliotecas na ordem correta. Durante a ligação, o GCC "puxa" apenas os objetos que sabe necessitar até o momento. Isto que dizer que primeiro é necessário alimentar ao GCC os arquivos-objeto que dependem de uma biblioteca (no exemplo, o main.cc), e por fim as bibliotecas que completam esta dependência.

  • Experimento 1: pegue o código-fonte da aula anterior e gere a biblioteca mylib.a utilizando os comandos acima.
  • Experimento 2: modifique o arquivo makefile da aula anterior para trabalhar com a biblioteca.
  • Experimento 3: modifique a assinatura de algum método das classes Pessoa ou Aluno e verifique o que acontece.

07/08: Arquitetura de sistemas operacionais e modelos de programação

  • Apresentação sobre histórico visão geral e estruturas básicas de um SO (Arquivo:SOP29005-parte1.pdf)
  • Capítulo 2 do livro do Silberschatz

13/08: Gerência de tarefas; contextos, processos e threads

14/08: Atividades em laboratório: API POSIX – fork/wait

Roteiro de exercícios: gerenciamento de processos

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

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());
        execl("/bin/ls","ls", (char*) NULL);
        perror("execl falhou!");
        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
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.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\n", getpid(), k);
        exit(0);
    }
    else // Este é o processo pai
    {
        wait(&status);
        k += 10;
        printf("processo pai\t pid: %d\t K: %d\n", getpid(), k);
        exit(0);
    }
    k += 10;
    printf("processo %d\t K: %d\n", getpid(), k);
    exit(0);
}
arliones@socrates:~/tmp$ gcc ex2.c -o ex2
arliones@socrates:~/tmp$ ./ex2 
processo 18425	 antes do fork
processo 18425	 depois do fork
processo 18426	 depois do fork
processo filho	 pid: 18426	 K: 1000
processo pai	 pid: 18425	 K: 10
arliones@socrates:~/tmp$
  • Modificação no código: comentar linhas 22 e 29
arliones@socrates:~/tmp$ gcc ex2.c -o ex2
arliones@socrates:~/tmp$ ./ex2 
processo 32342	 antes do fork
processo 32342	 depois do fork
processo 32343	 depois do fork
processo filho	 pid: 32343	 K: 1000
processo 32343	 K: 1010
processo pai	 pid: 32342	 K: 10
processo 32342	 K: 20
arliones@socrates:~/tmp$
  • Analise os resultados e busque entender a diferença.


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.


20/08: Escalonamento de tarefas

21/08: Atividades em laboratório: Estrutura de processos


Troca de contexto em nível de usuário

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);
}

27/08: Atividades em laboratório: acompanhamento de projetos

28/08: Algoritmos de escalonamento


03/09: Algoritmos de escalonamento / Revisão para prova

04/09: Listas de exercícios / Revisão para prova

10/09: Prova 0 - Processos

11/09: Atividades em laboratório: pthreads – t2: escalonamento FIFO e por prioridades

  • Conclusão do conteúdo da aula anterior

Introdução a POSIX Threads

A biblioteca POSIX Threads, ou simplesmente pthreads, é parte do padrão POSIX para programar utilizando threads. O padrão POSIX.1c define a API para criação e manipulação de threads. Esta API é encontrada na maioria dos sistemas baseados no Unix, como Linux, Mac OS X, Solaris, entre outros. Também existem alternativas adaptando a API para sistemas Windows, como a pthreads-w32.

A API da pthreads inclui métodos para criar, manipular e destruir threads, além de outras estruturas de dados para sincronizar as threads, incluindo implementações de mutexes e variáveis de condição.

A API POSIX semaphore, utilizada para sincronização de processos ou threads, também funciona em conjunto com a pthreads, embora sua implementação esteja definida em outro padrão, o POSIX.1b.

Programas em C/C++ que utilizarão a pthreads devem incluir o cabeçalho pthread.h:

#include <pthread.h>


Principais elementos da API

  • pthread_t (struct)
Estrutura que armazena dados/atributos de uma pthread.
  • pthread_create
Função que cria uma thread, incializando-a e deixando-a pronta para executar. O código abaixo apresenta um exemplo simples de um programa utilizando pthreads.
#include <pthread.h>

pthread_t threads[2];

void *thread_func(void *arg) {
    // ...
}

int main(int argc, char **argv) {
    int i;
    for(i = 0; i < 2; i++) {
        pthread_create(&(threads[i]), NULL, thread_func,NULL);
    }
    for(i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }
}


  • pthread_join
Bloqueia execução de uma thread até que outra thread termine. Similar à chamada de sistema wait usada em processos Unix.
  • pthread_exit
Encerra a execução de uma thread. A chamada a esta função por uma thread gera a liberação de outras threads que estejam, eventualmente, bloqueadas nela por uma chamada pthread_join.

O código abaixo apresenta um programa completo utilizando pthreads, onde threads recebem argumentos.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

typedef struct {
    int idx, length;
} thread_arg, *ptr_thread_arg;

pthread_t threads[2];

void *thread_func(void *arg) {
    ptr_thread_arg targ = (ptr_thread_arg) arg;
    int i;

    for(i = targ->idx; i < (targ->idx + targ->length); i++)
        printf(Thread %d  value %d\n, pthread_self(), i);
}

int main(int argc, char **argv) {
    thread_arg arguments[2];
    int i;
    for(i = 0; i < 2; i++) {
        arguments[i].idx = i * 10;
        arguments[i].length = 10;
        pthread_create(&(threads[i]), NULL, thread_func, &(arguments[i]));
    }
    for(i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }
}

Ao compilar um programa com pthreads é necessário "linkar" com a biblioteca. Para isso, deve ser usando a opção -lpthread com o gcc.

gcc ... -lpthread

Exercícios

  • Implemente o exemplo do Ping e Pong utilizando pthreads.


17/09 e 18/09: Comunicação entre processos

Comunicação entre processos: 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.

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>

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) ;
    }
}
  • Desafio 1: construa um “pipeline”. Crie um programa que conecta 4 processos através de 3 pipes. Utilize fork() para criar vários processos.
  • Desafio 2: 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.


Comunicação entre processos: Memória Compartilhada

Outra maneira de compartilhar dados entre processos é utilizando memória compartilhada. Nestes casos, o sistema operacional precisa ser utilizado para "mapear" blocos de memória no espaço de endereçamento de cada processo (veremos mais tarde como isto é feito). Sistemas UNIX diponibilizam uma série de SystemCalls para compartilhar memória entre processos:

  • int shmget(key_t key, size_t size, int shmflg): retorna o identificador de um segmento de memória compartilhado associado a uma chave de identificação (key). Caso a chave solicitada não exista e a flag IPC_CREAT está especificada, um novo segmento de memória compartilhada é criado, com o tamanho size.
  • void *shmat(int shmid, const void *shmaddr, int shmflg): anexa/mapeia o segmento de memória especificado por shmid (obtido através de um shmget) no endereço shmaddr do espaço de endereçamento do processo.
  • int shmdt(const void *shmaddr): desfaz o mapeamento do segmento de memória compartilhada que está mapeado em shmaddr.
  • int shmctl(int shmid, int cmd, struct shmid_ds *buf): realiza a ação de controle indicada por cmd sobre o segmento de memória compartilhada identificado pelo shmid. Caso o cmd utilizado necessite de parâmetros, estes são passados em buf.

O programa abaixo cria um novo segmento de memória compartilhada em um sistema UNIX. A função ftok é utilizada para gerar uma chave única de identificação do segmento de memória (veja "man ftok"). O programa ipcs pode ser utilizado para verificar os segmentos de memória compartilhada disponíveis no sistema.

#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  /* make it a 1K shared memory segment */
 
int main(void)
{
 
       key_t key;
       int shmid;
       char *data;
       int mode;
 
       /* make the key: */
       if ((key = ftok("/tmp", 'X')) == -1) {
            perror("ftok");
            exit(1);
       }
 
       /* create the shared memory segment: */
       if ((shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT )) == -1) {
            perror("shmget");
            exit(1);
       }
 
       return(0);
}

O programa abaixo manipula o segmento criado pelo programa anterior. Perceba que o programa utiliza a função ftok com os mesmos parâmetros passados na criação. O sistema operacional utiliza esta chava única para identificar o segmento de memória a ser utilizado. Se o programa abaixo é executado com um string como parâmetro (ex.: "./shm_test abc123"), ele escreve este parâmetro no segmento de memória. Caso seja executado sem parâmetros (ex.: "./shm_test"), o programa imprime o conteúdo da memória compartilhada.

#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  /* make it a 1K shared memory segment */
 
int main(int argc, char *argv[])
{
 
       key_t key;
       int shmid;
       char *data;
       int mode;
 
 
       /* make the key: */
       if ((key = ftok("/tmp", 'X')) == -1) {
            perror("ftok");
            exit(1);
       }
 
 
       /* connect to the segment.
      NOTE: There's no IPC_CREATE. What happens if you place IPC_CREATE here? */
       if ((shmid = shmget(key, SHM_SIZE, 0644)) == -1) {
            perror("shmget");
            exit(1);
       }
 
 
    /* attach to the segment to get a pointer to it: */
        data = shmat(shmid, (void *)0, 0);
        if (data == (char *)(-1)) {
            perror("shmat");
            exit(1);
        }
 
 
        /* read or modify the segment, based on the command line: */
        if (argc == 2) {
            printf("writing to segment: \"%s\"\n", argv[1]);
            strncpy(data, argv[1], SHM_SIZE);
        } else
            printf("segment contains: \"%s\"\n", data);
 
 
        /* detach from the segment: */
        if (shmdt(data) == -1) {
            perror("shmdt");
            exit(1);
        }
 
       return(0);
}
  • Desafio1: destrua um shm. Crie um programa que destrua o shm utilizado nos programas anteriores. Para isso utilize shmctl com o parâmetro apropriado (veja "man shmctl").


24/09: Coordenação de processos


25/09: Laboratório: Impasses e Problemas clássicos de coordenação

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.
#ifndef __mutex_h
#define __mutex_h

#include <pthread.h>

class Mutex
{
public:
    Mutex() {}
    ~Mutex() {}

    void lock() { pthread_mutex_lock(&mut); }
    bool try_lock() { return (pthread_mutex_trylock(&mut) == 0); } // true when succeeds.
    void unlock() { pthread_mutex_unlock(&mut); }

private:
    pthread_mutex_t mut;
};

#endif

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). A classe C++ abaixo abstrai estas operações:

#ifndef __semaphore_h
#define __semaphore_h

#include <semaphore.h>

class Semaphore
{
public:
    Semaphore(int i = 1) { sem_init(&sem, 0, i); }
    ~Semaphore() { sem_destroy(&sem); }

    void p() { sem_wait(&sem); }
    void v() { sem_post(&sem); }

    operator int()
    {
        int ret;
        sem_getvalue(&sem, &ret);
        return ret;
    }

private:
    sem_t sem;
};

#endif

Exemplo de uso do operator:

Semaphore sem;
cout << (int)sem << endl;

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). A classe C++ abaixo abstrai estas operações:

#ifndef __thread_h
#define __thread_h

#include <pthread.h>
#include <signal.h>

class Thread
{
public:
    Thread(int ( * const entry)(int), int arg) {
	if(pthread_create(&thread, 0, (void*(*)(void*))entry, (void *)arg))
	    thread = 0;
    }
    ~Thread() { pthread_kill(thread, SIGKILL); }

    int join(int * status) { return pthread_join(thread, (void **)status); }
    friend void exit(int status = 0) { pthread_exit((void *) status); }

private:
    pthread_t thread;
};

#endif

Problemas clássicos de sincronização

Produtor/Consumidor

O problema clássico Produtor/Consumidor consiste em dois fluxos de execução (threads/processos), sendo que um dos fluxos (consumidor) só pode executar a partir do momento em que seus dados de entrada foram produzidos pelo outro fluxo (produtor).

  • DESAFIO: O programa abaixo implementa um produtor/consumidor utilizando semáforos para sincronização. Contudo, as chamadas para as operações v e p foram removidas, conforme comentários no código. Corrija este programa.
#include <iostream>
#include "thread.h"
#include "semaphore.h"

using namespace std;

const int DELAY = 10000000;
const int REP = 5;

Semaphore empty(1);
Semaphore full(0);

int producer(int n)
{
    cout << "Producer was born!\n";

    // Faltam, no laço abaixo:
    //  - uma chamada para empty.p()
    //  - uma chamada para full.v()
    for(int i = 0; i < REP; i++) {
	cout << "Producing ...\n";
	for(int i = 0; i < DELAY * 10; i++);
	cout << "Storing ...\n";
	for(int i = 0; i < DELAY; i++);
    }

    return n;
}

int consumer(int n)
{
    cout << "Consumer was born!\n";

    // Faltam, no laço abaixo:
    //  - uma chamada para full.p()
    //  - uma chamada para empty.v()
    for(int i = 0; i < REP; i++) {
	cout << "Retrieving ...\n";
	for(int i = 0; i < DELAY * 10; i++);
	cout << "Consuming ...\n";
	for(int i = 0; i < DELAY; i++);
    }

    return n;
}

int main()
{
    cout << "The Producer x Consumer Problem\n";

    Thread prod(&producer, REP);
    Thread cons(&consumer, REP);

    int status;
    prod.join(&status);
    if(status == REP)
	cout << "Producer went to heaven!\n";
    else
	cout << "Producer went to hell!\n";

    cons.join(&status);
    if(status == REP)
	cout << "Consumer went to heaven!\n";
    else
	cout << "Consumer went to hell!\n";

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

  • DESAFIO: O 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. Corrija este programa.
#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;
}


01/10 a 08/10: Laboratório: programação concorrente

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 "thread.h"

#define NUM_THREADS 5

using namespace std;

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()
{
	Thread * threads[NUM_THREADS];

	for(int t = 0; t < NUM_THREADS; t++)
		threads[t] = new Thread(&AtualizaSaldo, t+1);

	int status;
	for(int t = 0; t < NUM_THREADS; t++) {
		threads[t]->join(&status);
		cout << "Thread " << t << " terminou com status " << status << "." << endl;
	}

	cout << "Saldo final é " << saldo << "." << endl;
}


  1. Compile este programa. Você precisará da classe Thread implementada na aula passada.
  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. Utilize a classe Mutex implementada na aula passada.
  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 a função AtualizaSaldo() para reduzir o tempo de lock do programa.
  7. Modifique o programa para usar um semáforo binário ao invés de um mutex em sua solução. Utilize a classe Semaphore da aula passada.

Exercício 2

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


Exercício 3

Corrija os problemas de sincronização do programa abaixo.

#include <iostream>
#include <fstream>
#include <uuid/uuid.h>
#include <unistd.h>
#include <errno.h>

#include "thread.h"

using namespace std;

const int PRODS = 100;
const int REP = 10;

const int CONS = 20;

const int BUF_SIZE = 35;

Thread * prods[PRODS];
Thread * cons[CONS];

uuid_t buffer[BUF_SIZE];
static int prod_pos = 0;
static int cons_pos = 0;

bool finished = false;

int producer(int n)
{
	cout << "Producer was born!" << endl;

	int rep = REP;

	char fname[36+1];

	while(rep--)
	{
		if(++prod_pos == BUF_SIZE) prod_pos = 0;
		uuid_generate(buffer[prod_pos]);
		uuid_unparse(buffer[prod_pos], fname);

		string name(fname,sizeof(uuid_t)*2 + 4);
		ofstream file(name.c_str());
		file << name;
		file.close();
	}

	exit(REP);
}

int consumer(int n)
{
	cout << "Consumer was born!" << endl;

	char fname[36+1];
	int consumed = 0;


	while(true)
	{
		if(finished) exit(consumed);

		consumed++;

		if(++cons_pos == BUF_SIZE) cons_pos = 0;
		uuid_unparse(buffer[cons_pos], fname);

		{
			ifstream file(fname);
			if(!file.good()) continue;
			string str;
			file >> str;
			cout << "Consumed: " << str << endl;
		}

		if(remove(fname)) cerr << "Error: " << errno << endl;
	}

	exit(consumed);
}

int main()
{
    cout << "Massive Producer x Consumer Problem\n";

    // Create
    for(int i = 0; i < PRODS; i++)
    	prods[i] = new Thread(&producer, i);
    for(int i = 0; i < CONS; i++)
    	cons[i] = new Thread(&consumer, i);

    // Join
    int status = 0;
    int produced = 0;
    int consumed = 0;
    for(int i = 0; i < PRODS; i++)
    {
    	prods[i]->join(&status);
    	produced += status;
    }

    finished = true;

    for(int i = 0; i < CONS; i++)
    {
    	cons[i]->join(&status);
    	consumed += status;
    }

    cout << "Total produced: " << produced << endl;
    cout << "Total consumed: " << consumed << endl;

    return 0;
}

Utilize o script abaixo para executar o programa. O script cria um diretório em /tmp/test onde são salvos os arquivos criados pelos produtores. Para ter certeza de que todos os arquivos foram adequadamente consumidos (i.e., deletados), o diretório /tmp/test deve estar vazio após a execução do programa.

#!/bin/bash

export myrun_DIR=`pwd`
export myrun_TEST=/tmp/test

rm -rf $myrun_TEST
mkdir $myrun_TEST
cd $myrun_TEST
$myrun_DIR/multi_prod_cons
cd $myrun_DIR

Problemas de sincronização a serem considerados:

  1. Limite de tamanho do buffer: os produtores só podem produzir se houver espaço no buffer - nunca pode haver mais que BUF_SIZE arquivos na pasta de troca de arquivos.
  2. Uso da posição do buffer: verificar se a posição consumida já foi de fato produzida.
  3. Consumo: verificar se todos os arquivos produzidos foram consumidos.
  4. Sistema de arquivos: verificar se os arquivos estão sendo sincronizados adequadamente no sistema de arquivos (ver man 2 sync).


09/10: Laboratório: programação concorrente

15/10: Encaminhamento para palestra MCC

16/10: Revisão e correção de listas de exercícios

22/10: Prova 1 - Comunicação e coordenação de processos

23/10: Gerenciamento de memória: Introdução

29/10 e 30/10: Gerenciamento de memória

Atividade prática

Escreva um programa de computador que, dada a configuração de um sistema de paginação e um endereço de entrada, forneça informações sobre o endereço dado no referido sistema. Mais detalhes abaixo:

  • Entradas do programa:
    • Largura do endereço em bits
    • Tamanho das páginas em bytes
    • Arquivo com tabela de páginas
    • Endereço a ser traduzido
  • Saídas do programa:
    • Número de frames no sistema
    • Número da página (endereço lógico)
    • Número do frame (endereço físico) (Hit ou Page Fault?)
    • Deslocamento

A tabela de páginas estará em um arquivo em modo texto contendo um mapeamento por linha, como o abaixo. Observe que o arquivo contém os números de página ou frame, e não endereços.

0-10
1-9
2-20
3-37
4-1
5-4
6-7
7-6

A classe abaixo pode ser utilizada para processar o arquivo de entrada. Ela utiliza um map da STL para fazer o mapeamento. Você precisará implementar o método get_frame desta classe.

/*
 * MapFile.h
 *
 *  Created on: Oct 29, 2014
 *      Author: arliones
 */

#ifndef MAPFILE_H_
#define MAPFILE_H_

#include <string>
#include <map>
#include <fstream>
#include <stdlib.h>
#include <iostream>

using namespace std;

class MapFile {
	MapFile() {}
public:
	MapFile(string filename)
	{
		ifstream file(filename.c_str());
		string line;
		char line_c[256];
		unsigned int page, frame, delimiter;
		while(!file.eof()) {
			file.getline(line_c,256); line = string(line_c);
			delimiter = line.find('-');
			page = atoi(line.substr(0,delimiter+1).c_str());
			frame = atoi(line.substr(delimiter+1).c_str());
			_pt.insert(make_pair(page,frame));
		}

	}

	virtual ~MapFile() {}

	// Returns the number of the frame or -1 if not found
	unsigned int get_frame(unsigned int page)
	{
		//TODO
	}

	void print_page_table()
	{
		cout << "Page Table:" << endl;
		map<unsigned int, unsigned int>::iterator mit = _pt.begin();
		for(; mit != _pt.end(); ++mit)
			cout << mit->first << " - " << mit->second << endl;
	}

private:
	map<unsigned int, unsigned int> _pt;
};

#endif /* MAPFILE_H_ */

Abaixo, exemplos de execução do programa desejado:

arliones@socrates:~/workspace/paging_sim$ ./paging_sim
Usage: ./paging_sim ADDR_LEN PAGE_SIZE MAP_FILE ADDRESS

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 16 1024 page_table.txt 5687
Frames in the system: 64
Requested page: 5
Requested frame: 4
Offset: 567

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 16 1024 page_table.txt 10578
Frames in the system: 64
Requested page: 10
Requested frame: Page Fault
Offset: 338

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4096 page_table.txt 8632
Frames in the system: 1048576
Requested page: 2
Requested frame: 20
Offset: 440

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4096 page_table.txt 68723
Frames in the system: 1048576
Requested page: 16
Requested frame: Page Fault
Offset: 3187

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4194304 page_table.txt 354
Frames in the system: 1024
Requested page: 0
Requested frame: 10
Offset: 354

arliones@socrates:~/workspace/paging_sim$ ./paging_sim 32 4194304 page_table.txt 43554432
Frames in the system: 1024
Requested page: 10
Requested frame: Page Fault
Offset: 1611392

Dicas: utilize variáveis unsigned do tipo adequado em seus cálculos. Você provavelmente precisará das funções log2 e exp2 da libmath (include math.h).

12/11 e 13/11: Memória Virtual

19/11 e 20/11: Sistemas de arquivos


26/11: Revisão para prova

  • Dúvidas / Lista de exercícios.
  • Aula será no

27/11: P2 (gerenciamento de memória)

03/12: Sistemas de arquivos: estudos de caso

04/12: Gerenciamento de entrada e saída: introdução / Atividade em laboratório: construção de módulo para Linux

10/12: Revisão e correção de listas de exercícios

11/12: P3 (sistemas de arquivo e gerenciamento de entrada e saída)