Mudanças entre as edições de "SOP-EngTel (página)"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
Linha 13: Linha 13:
  
 
[http://wiki.sj.ifsc.edu.br/index.php?title=SOP-EngTel_(p%C3%A1gina)&oldid=123789 Clique aqui] para acessar as edições antigas desta disciplina.
 
[http://wiki.sj.ifsc.edu.br/index.php?title=SOP-EngTel_(p%C3%A1gina)&oldid=123789 Clique aqui] para acessar as edições antigas desta disciplina.
 
= Material de aula =
 
 
== Slides ==
 
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte1.pdf Introdução a Sistemas Operacionais]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte2.pdf Escalonamento de Processos]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte3.pdf Comunicação entre Processos]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte4.pdf Coordenação de Processos (Programação Concorrente)]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte5.pdf Gerenciamento de Memória]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte6.pdf Gerenciamento de Arquivos]
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte7.pdf Gerenciamento de Entrada e Saída]
 
 
== 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.
 
 
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:
 
 
*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.
 
*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
 
*Capítulo 6: 1, 2 (utilizar semáforos POSIX), 6, 8, 11-15, 18, 20, 21, 25, 29, 39.
 
*Capítulo 8: 1-6, 9-21, 23.
 
*Capítulo 9: 1-8, 14-16, 19-23, 28.
 
*Capítulo 10: 1-20
 
*Capítulo 11: 1-7
 
*Capítulo 12: 1-7, 13-14 (desafio).
 
*Capítulo 13: 1, 4, 5, 6.
 
 
=Conteúdo=
 
 
{{collapse top| bg=lightyellow | expandir=true | Unidade 01: Introdução}}
 
== Unidade 01: Introdução ==
 
 
=== Apresentação do Curso ===
 
*[[SOP-EngTel_(Plano_de_Ensino)| Plano de Ensino]]
 
*[[Cronograma de atividades (STE-EngTel) | Cronograma]]
 
*Outros cursos de sistemas operacionais nos quais este curso se baseia:
 
**[http://www.lisha.ufsc.br/teaching/os/ Sistemas Operacionais - Ciências da Computação UFSC]
 
**[http://dainf.ct.utfpr.edu.br/~maziero/doku.php/so:start Sistemas Operacionais - Engenharia da Computação UTFPR]
 
<!--*[[BOOOS - Basic Object Oriented Operating System]]-->
 
 
=== Visão geral de funções, responsabilidades e estruturas de um SO ===
 
* [https://www.youtube.com/watch?v=7LGKgdWtrqI Revolution OS]: documentário sobre Linux e software livre
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte1.pdf Apresentação sobre histórico visão geral e estruturas básicas de um SO]
 
* Capítulo 1 do livro do Silberschatz
 
 
=== Arquitetura de sistemas operacionais e modelos de programação ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte1.pdf Apresentação sobre histórico visão geral e estruturas básicas de um SO]
 
* Capítulo 2 do livro do Silberschatz
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Unidade 02: Processos}}
 
== Unidade 02: Processos ==
 
 
=== Gerência de tarefas; contextos, processos e threads ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte2.pdf Apresentação sobre Gerenciamento de Processos]
 
* Capítulo 3 do livro do Silberschatz
 
 
=== Escalonamento de tarefas ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte2.pdf Apresentação sobre Escalonamento de Processos]
 
* [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.
 
* Estudo de caso: escalonador do Linux.
 
** [https://en.wikipedia.org/wiki/O(n)_scheduler Escalonador antigo O(n)].
 
** [https://en.wikipedia.org/wiki/O(1)_scheduler Escalonador do kernel 2.6 O(1)].
 
** [https://en.wikipedia.org/wiki/Completely_Fair_Scheduler Escalonador atual O(log(n))].
 
** [https://www.cs.columbia.edu/~smb/classes/s06-4118/l13.pdf Slides da University of Columbia sobre o mecanismo de escalonamento do Linux].
 
 
=== Comunicação entre Processos ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte3.pdf Apresentação sobre Comunicação entre Processos]
 
* Capítulo 3 do livro do Silberschatz.
 
 
=== Coordenação de processos ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte4.pdf Apresentação sobre Coordenação de Processos]
 
* Capítulos 6 e 7 do livro do Silberschatz.
 
* Curiosidade: [http://research.microsoft.com/en-us/um/people/mbj/mars_pathfinder/authoritative_account.html A inversão de prioridades na Mars Pathfinder]
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Unidade 03: Memória}}
 
== Unidade 03: Memória==
 
 
=== Introdução ao Gerenciamento de Memória ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte5.pdf Apresentação sobre Gerenciamento de Memória]
 
* Capítulo 8 do livro do Silberschatz.
 
 
=== Memória Principal ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte5.pdf Apresentação sobre Gerenciamento de Memória]
 
* Capítulo 8 do livro do Silberschatz.
 
 
=== Memória Virtual ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte5.pdf Apresentação sobre Gerenciamento de Memória]
 
* Capítulo 9 do livro do Silberschatz.
 
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Unidade 04: Armazenamento}}
 
== Unidade 04: Armazenamento ==
 
 
=== Interface do Sistema de Arquivos ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte6.pdf Apresentação sobre Gerenciamento de Arquivos]
 
* Capítulo 10 do livro do Silberschatz.
 
 
=== Implementação do Sistema de Arquivos ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte6.pdf Apresentação sobre Gerenciamento de Arquivos]
 
* Capítulo 11 do livro do Silberschatz.
 
 
=== Estrutura de Armazenamento em Massa ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte6.pdf Apresentação sobre Gerenciamento de Arquivos]
 
* Capítulo 12 do livro do Silberschatz.
 
 
=== Gerenciamento de Entrada e Saída ===
 
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte7.pdf Apresentação sobre Gerenciamento de Entrada e Saída]
 
* Capítulo 13 do livro do Silberschatz.
 
 
{{collapse bottom}}
 
 
=Laboratórios=
 
 
{{collapse top| bg=lightyellow | expandir=true | GCC/G++ no Linux}}
 
== GCC/G++ no Linux ==
 
 
;Referências
 
* Referência sobre C++: http://www.cplusplus.com/
 
* Livros sobre C++:
 
** [http://www.stroustrup.com/4th.html The C++ Programming Language (4th Edition)]
 
** [http://www.deitel.com/books/cpphtp5/ C++ How to Program, Fifth Edition]
 
* Tutorial C++
 
** [http://www.horstmann.com/ccj2/ccjapp3.html Moving from Java to C++]
 
* Exercício-exemplo feito em aula. [[http://docente.ifsc.edu.br/arliones.hoeller/sop/my_first_cpp_program.tgz código-fonte]]
 
 
;Herança
 
* http://www.cplusplus.com/doc/tutorial/inheritance/
 
 
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:
 
 
[[Arquivo: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:
 
 
<syntaxhighlight lang=cpp>
 
class classe_derivada : public classe_base
 
{ /*...*/ };
 
</syntaxhighlight>
 
 
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.
 
 
<syntaxhighlight lang=cpp>
 
#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;
 
}
 
</syntaxhighlight>
 
 
*'''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.
 
 
*'''Leitura extra - Polimorfismo:''' http://www.cplusplus.com/doc/tutorial/polymorphism/
 
 
;Espaços de nomes
 
* http://www.cplusplus.com/doc/tutorial/namespaces/
 
 
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'':
 
 
<syntaxhighlight lang=cpp>
 
namespace identifier
 
{
 
  /* entities... */
 
}
 
</syntaxhighlight>
 
 
Por exemplo, o código abaixo as variáveis ''a'' e ''b'' são inteiros normais declarados dentro do ''namespace'' ''myNamespace''.
 
 
<syntaxhighlight lang=cpp>
 
namespace myNamespace
 
{
 
  int a, b;
 
}
 
</syntaxhighlight>
 
 
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:
 
 
<syntaxhighlight lang=cpp>
 
myNamespace::a
 
myNamespace::b
 
</syntaxhighlight>
 
 
Espaços de nomes podem ser bastante úteis para evitar colisão de identificadores:
 
 
<syntaxhighlight lang=cpp>
 
// 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;
 
}
 
</syntaxhighlight>
 
 
*'''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''.
 
 
*'''Leitura extra - Visibilidade de nomes em C++:''' http://www.cplusplus.com/doc/tutorial/namespaces/
 
 
;Criando bibliotecas
 
* http://www.delorie.com/djgpp/doc/ug/larger/archives.html
 
 
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 [http://docente.ifsc.edu.br/arliones.hoeller/sop/my_first_cpp_program.tgz exercício-exemplo da aula anterior].
 
 
<syntaxhighlight lang=bash>
 
g++ -c pessoa.cc
 
g++ -c aluno.cc
 
</syntaxhighlight>
 
 
A seguir, você utilizará o comando ''ar'' para criar uma biblioteca contendo os arquivos-objeto criados.
 
 
<syntaxhighlight lang=bash>
 
ar rvs mylib.a pessoa.o aluno.o
 
</syntaxhighlight>
 
 
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:
 
 
<syntaxhighlight lang=cpp>
 
g++ main.cc mylib.a -o main
 
</syntaxhighlight>
 
 
É 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.
 
 
*'''Leitura extra - uso da C++ Standard Template Library:''' http://www.yolinux.com/TUTORIALS/LinuxTutorialC++STL.html
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Processos no Linux}}
 
== Processos no Linux ==
 
 
 
;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
 
<syntaxhighlight lang=c>
 
// 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());
 
        exit(0);
 
    }
 
    else // Este é o processo pai
 
    {
 
        wait(&status);
 
        printf("processo pai\t pid: %d\t pid pai: %d\n", getpid(), getppid());
 
        exit(0);
 
    }
 
}
 
</syntaxhighlight>
 
 
<syntaxhighlight lang=bash>
 
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$
 
</syntaxhighlight>
 
 
* Exemplo 2: processos pai e filho compartilham código, mas não dados.
 
<syntaxhighlight lang=c>
 
// 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);
 
}
 
</syntaxhighlight>
 
 
<syntaxhighlight lang=bash>
 
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$
 
</syntaxhighlight>
 
 
* Modificação no código: comentar linhas 22 e 29
 
 
<syntaxhighlight lang=bash>
 
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$
 
</syntaxhighlight>
 
 
* 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.
 
 
;Exercício status/wait
 
 
O ''status'' passado como parâmetro à função ''wait(&status)'' é, na verdade, o mecanismo de retorno de resultado do ''wait/waitpid''. Ao retornar, esta variável contém informações sobre o resultado da execução do processo filho. Por exemplo, se um processo terminou normalmente (i.e., chamou ''exit''), o comando ''WIFEXITED(status)'' retorna ''true''. Este comando retorna ''false'' se o processo foi abortado (e.g., ''segmentation fault'') ou morto (e.g., ''kill''). Investigue no manual do wait no Linux (''man wait'') o funcionamento do comando WEXITSTATUS(status), e use-o para modificar o exercício anterior para calcular o 5!, sendo que cada processo pode executar apenas uma multiplicação.
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Threads de aplicação}}
 
 
== Threads de aplicação ==
 
 
O Linux, através da API POSIX, oferece um conjunto de funções que permite às aplicações manipular contextos, facilitando a vida do programador que quer implementar tarefas "simultâneas" dentro de um único processo, ou seja, threads. As seguintes funções e tipos estão disponíveis:
 
*'''getcontext(&a)''': salva o contexto na variável '''a''';
 
*'''setcontext(&a)''': restaura um contexto salvo anteriormente na variável '''a''';
 
*'''swapcontext(&a,&b)''': salva o contexto atual na variável '''a''' e restaura o contexto salvo anteriormente na variável '''b''';
 
*'''makecontext(&a, ...)''': ajusta parâmetros internos do contexto salvo em '''a''';
 
*'''ucontext_t''':  as variáveis '''a''' e '''b''' são do tipo '''ucontext_t'''. Este tipo armazena um contexto.
 
 
Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext).
 
 
Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.
 
<syntaxhighlight lang=c>
 
#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);
 
}
 
</syntaxhighlight>
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Trocas de mensagens com pipes}}
 
== Trocas de mensagens com pipes ==
 
 
;Troca de mensagens
 
Um mecanismo disponibilizado por sistemas UNIX para troca de mensagens entre processos é o PIPE. Pipes são mecanismos de comunicação indireta onde mensagens são trocadas através de ''mailboxes''. Cada ''mailbox'' possui um identificador único, permitindo que processos identifiquem o canal de comunicação entre eles. O fluxo de mensagens em um Pipe é:
 
*'''unidirecional''': sobre um mesmo pipe, apenas um processo envia mensagens e um processo recebe mensagens;
 
*'''FIFO''': as mensagens são entregues na ordem de envio;
 
*'''não-estruturado''': não há estrutura pré-definida para o formato da mensagem.
 
No UNIX, pipes são inicializados através da '''SystemCall''' ''pipe'', que possui a seguinte sintaxe:
 
*''int pipe(int pipefd[2])'': ''pipe'' inicializa um novo pipe no sistema e retorna, no array pipefd, os descritores identificando cada uma das pontas do pipe. A primeira posição do array, i.e. pipefd[0], recebe o descritor que pode ser aberto apenas para leitura, enquanto a segunda posição do array, i.e. pipefd[1], recebe o descritor que pode ser aberto apenas para escrita. A função retorna zero no caso de sucesso, ou -1 se ocorrer erro.
 
As primitivas send/receive para uso de um pipe no UNIX são implementadas por '''SystemCalls''' read/write, conforme segue:
 
*''ssize_t read(int fd, void *buf, size_t count)'': “puxa” dados do pipe identificado pelo descritor fd. Os dados recebidos são os apontados pelo ponteiro buf, sendo count a quantidade máxima de bytes a serem recebidos. A função retorna o número de bytes recebidos.
 
*''ssize_t write(int fd, const void *buf, size_t count)'': “empurra” dados no pipe identificado pelo descritor fd. Os dados transmitidos são os apontados pelo ponteiro buf, sendo count a quantidade de bytes a serem transmitidos. A função retorna o número de bytes transmitidos.
 
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>
 
#include <string.h>
 
 
char *message = "This is a message!!!" ;
 
 
main()
 
{
 
    char buf[1024] ;
 
    int fd[2];
 
    pipe(fd);    /*create pipe*/
 
    if (fork() != 0) { /* I am the parent */
 
        write(fd[1], message, strlen (message) + 1) ;
 
    }
 
    else { /*Child code */
 
        read(fd[0], buf, 1024) ;
 
        printf("Got this from MaMa!!: %s\n", buf) ;
 
    }
 
}
 
</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.
 
 
*'''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.
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Memória compartilhada entre processos}}
 
 
== Memória compartilhada entre processos ==
 
 
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.
 
 
<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);
 
      }
 
 
      /* 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>
 
#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>
 
 
*'''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").
 
 
<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>
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | POSIX Threads}}
 
 
== 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'':
 
 
<syntaxhighlight lang=c>
 
#include <pthread.h>
 
</syntaxhighlight>
 
 
;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.
 
 
<syntaxhighlight lang=c>
 
 
#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);
 
    }
 
}
 
 
</syntaxhighlight>
 
 
 
* pthread_join
 
: Bloqueia execução de uma thread até que outra thread termine, implementando um comportamento 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 pthread 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>
 
 
Ao compilar um programa com pthreads é necessário "linkar" com a biblioteca ''libpthread''. Para isso, deve ser usando a opção -lpthread com o gcc.
 
 
<syntaxhighlight lang=bash>
 
gcc ... -lpthread
 
</syntaxhighlight>
 
 
;Exercícios
 
 
* Implemente o Algoritmo de Peterson para garantir que as threads do exemplo acima imprimam de forma alternada:
 
Thread d571a700 – value 0
 
Thread d4f19700 – value 10
 
Thread d571a700 – value 1
 
Thread d4f19700 – value 11
 
Thread d571a700 – value 2
 
Thread d4f19700 – value 12
 
Thread d571a700 – value 3
 
Thread d4f19700 – value 13
 
Thread d571a700 – value 4
 
Thread d4f19700 – value 14
 
Thread d571a700 – value 5
 
Thread d4f19700 – value 15
 
Thread d571a700 – value 6
 
Thread d4f19700 – value 16
 
Thread d571a700 – value 7
 
Thread d4f19700 – value 17
 
Thread d571a700 – value 8
 
Thread d4f19700 – value 18
 
Thread d571a700 – value 9
 
Thread d4f19700 – value 19
 
 
<!--
 
* Implemente o exemplo do Ping e Pong utilizando pthreads.
 
-->
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Programação concorrente}}
 
 
== Programação concorrente ==
 
 
;POSIX Threads
 
A API POSIX disponibiliza uma biblioteca de threads chamada pthread. As threads são implementadas pela estrutura ''pthread_t'', e manipuladas pelas funções (acesse as man-pages das chamadas para maiores detalhes):
 
*''pthread_create'': cria uma thread;
 
*''pthread_kill'': força a terminação de uma thread;
 
*''pthread_join'': sincroniza o final de uma thread (qual a diferença/semelhança com o ''wait'' que usamos para processos?);
 
*''pthread_exit'': finaliza uma thread.
 
Para utilizar estas funções é necessário linkar o programa à libpthread (''-lpthread''). A classe C++ abaixo abstrai estas operações:
 
<syntaxhighlight lang=cpp>
 
#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() {}
 
 
    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
 
</syntaxhighlight>
 
 
 
;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.
 
 
<syntaxhighlight lang=c>
 
#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
 
 
</syntaxhighlight>
 
 
;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:
 
<syntaxhighlight lang=cpp>
 
#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
 
</syntaxhighlight>
 
 
Exemplo de uso do operator:
 
<syntaxhighlight lang=cpp>
 
Semaphore sem;
 
cout << (int)sem << endl;
 
</syntaxhighlight>
 
 
 
;Exercício 1
 
 
O programa abaixo cria 5 threads, e cada uma destas threads atualiza uma variável global (memória compartilhada).
 
 
<syntaxhighlight lang=cpp>
 
#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;
 
}
 
</syntaxhighlight>
 
 
 
# Compile este programa. Você precisará da classe Thread implementada na aula passada.
 
# Execute este programa várias vezes. Ele funciona? Será que ele gera as saídas esperadas?
 
# Identifique as '''seções críticas''' do programa.
 
# Corrija o programa utilizando '''mutex'''. Utilize a classe Mutex implementada na aula passada.
 
# 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 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>
 
#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;
 
}
 
</syntaxhighlight>
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Problemas clássicos de coordenação de processos}}
 
 
== Problemas clássicos de coordenação de processos ==
 
 
;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).
 
*[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 1'': 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, garantindo a coerência da variável compartilhada ''buffer''.
 
 
*''DESAFIO 2'': Após resolver a sincronização no acesso ao buffer, utilize um Mutex para resolver a concorrência no acesso ao ''cout'' no programa abaixo.
 
 
<syntaxhighlight lang=cpp>
 
#include <iostream>
 
#include "thread.h"
 
#include "semaphore.h"
 
 
using namespace std;
 
 
const int REP = 5;
 
char buffer;
 
 
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()
 
    char data = -1;
 
    for(int i = 0; i < REP; i++) {
 
 
cout << "Producing ...\n";
 
data = (char) i + 0x61;
 
 
buffer = data;
 
cout << "Stored... " << data << endl;
 
 
    }
 
 
    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()
 
    char data = -1;
 
    for(int i = 0; i < REP; i++) {
 
 
cout << "Retrieving ...\n";
 
data = buffer;
 
 
cout << "Consumed... " << data << endl;
 
 
    }
 
 
    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;
 
}
 
</syntaxhighlight>
 
 
;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.
 
*[http://www.doc.ic.ac.uk/~jnm/concurrency/classes/Diners/Diners.html Veja esta simulação]
 
*[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. Re-insira as operações no código e analise a solução. Esta modificação é suficiente para garantir que não haverá deadlock? Se sim, mostre o porque. Se não, proponha uma solução completa.
 
 
<syntaxhighlight lang=cpp>
 
#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;
 
}
 
</syntaxhighlight>
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Tradução de endereços com paginação}}
 
 
== Tradução de endereços com paginação ==
 
 
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
 
** Endereço físico
 
 
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>
 
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.
 
 
<syntaxhighlight lang=cpp>
 
/*
 
* 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_ */
 
</syntaxhighlight>
 
 
Abaixo, exemplos de execução do programa desejado:
 
<syntaxhighlight lang=bash>
 
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: 0x237 (567)
 
Logical Address:  0x1637 (5687)
 
Physical Address: 0x1237 (4663)
 
 
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: 0x152 (338)
 
Logical Address:  0x2952 (10578)
 
Physical Address: 0xfffffd52 (4294966610)
 
 
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: 0x1b8 (440)
 
Logical Address:  0x21b8 (8632)
 
Physical Address: 0x141b8 (82360)
 
 
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: 0xc73 (3187)
 
Logical Address:  0x10c73 (68723)
 
Physical Address: 0xfffffc73 (4294966387)
 
 
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: 0x162 (354)
 
Logical Address:  0x162 (354)
 
Physical Address: 0x2800162 (41943394)
 
 
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: 0x189680 (1611392)
 
Logical Address:  0x2989680 (43554432)
 
Physical Address: 0xffd89680 (4292384384)
 
 
</syntaxhighlight>
 
 
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).
 
 
{{collapse bottom}}
 
 
{{collapse top| bg=lightyellow | expandir=true | Permissões de sistema de arquivos no Linux}}
 
 
== Permissões de sistema de arquivos no Linux ==
 
 
Neste estudo de caso são realizados alguns exercícios práticos que permitem verificar como o sistema de arquivos é organizado no Linux.
 
Acesse o estudo de caso através [http://wiki.inf.ufpr.br/maziero/doku.php?id=unix:permissoes_em_arquivos deste roteiro] do Prof. Maziero da UTFPR.
 
 
{{collapse bottom}}
 

Edição das 15h52min de 23 de fevereiro de 2017

Sistemas Operacionais

  • Professor: Arliones Hoeller
  • Encontros: Quartas e sextas às 7:30 no Laboratório de Redes II.

A partir de 2017, o conteúdo deste curso está sendo publicado aos alunos via Moodle.

Clique aqui para acessar as edições antigas desta disciplina.