SOP-EngTel 2018 2

De MediaWiki do Campus São José
Revisão de 12h14min de 6 de agosto de 2018 por 127.0.0.1 (discussão)
Ir para navegação Ir para pesquisar

Sistemas Operacionais

  • Professor: André D'Amato
  • Encontros: Segundas às 15:40 no LabCad e quintas às 13:30 no LabSid.
  • Atendimento paralelo: a definir.


Conteúdo

Unidade 01: Introdução

Unidade 01: Introdução

Visão geral de funções, responsabilidades e estruturas de um SO

Arquitetura de sistemas operacionais e modelos de programação


Laboratórios

Um Exemplo de Uso "API Padrão POSIX"

Um Exemplo de Uso "API Padrão POSIX"

Referências


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


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

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

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

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

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

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


void* meu_malloc(size_t tamanho) {
  void* addr = mmap(0,                      // addr
                    tamanho,   // len
                    PROT_READ | PROT_WRITE,  // prot
                    MAP_ANON | MAP_PRIVATE, // flags
                    -1,                     // filedes
                    0);                     // off
  *(size_t*)addr = tamanho;
  return addr;
}

int meu_free(void* addr) {
  return munmap(addr - sizeof(size_t), (size_t) addr);
}


int soma(int *N1, int *N2){

return (*N1+*N2);

}


int main(int argc, char* argv[]) {
  
  int* numero1 = meu_malloc(sizeof(int));
  int* numero2 = meu_malloc(sizeof(int)); 
  

  *numero1 = 10;
  *numero2 = 20;	

  int resultado = soma(numero1, numero2);

  printf("\n\n O resultado da soma é %d \n\n",resultado);	  	
  
  meu_free(numero1);
  meu_free(numero2);

  return 0;
}


Processos no Linux (Atividade 1)

Processos no Linux

Entregar um relatório impresso sobre a sua solução para o problema descrito. O relatório deve conter as seguintes seções:

    • Resumo;
    • Introdução;
    • Conceitos;
    • Problema;
    • Solução (Diagramas e código fonte);
    • Conclusão.


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


Exemplos POSIX utilizando fork/wait/exec
  • Exemplo 1: fork/wait básico
// ex1: fork/wait básico
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
    int pid, status;
    pid = fork();

    if(pid == -1) // fork falhou
    {
        perror("fork falhou!");
        exit(-1);
    }
    else if(pid == 0) // Este é o processo filho
    {
        printf("processo filho\t pid: %d\t pid pai: %d\n", getpid(), getppid());
        exit(0);
    }
    else // Este é o processo pai
    {
        wait(&status);
        printf("processo pai\t pid: %d\t pid pai: %d\n", getpid(), getppid());
        exit(0);
    }
}
arliones@socrates:~/tmp$ gcc ex1.c -o ex1 
arliones@socrates:~/tmp$ ./ex1 
processo filho	 pid: 27858	 pid pai: 27857
processo pai	 pid: 27857	 pid pai: 5337
arliones@socrates:~/tmp$
  • Exemplo 2: processos pai e filho compartilham código, mas não dados.
// ex2: fork/wait "compartilhando" dados
#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.

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.