Mudanças entre as edições de "SOP-EngTel 2018 1"
Linha 196: | Linha 196: | ||
+ | {{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. |
Edição das 16h28min de 23 de fevereiro de 2018
Sistemas Operacionais
- Professor: André D'Amato
- Encontros: Segundas às 7:30 e sextas às 09:40 no Laboratório de Redes II.
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.
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.
Conteúdo
Unidade 01: Introdução |
---|
Unidade 01: IntroduçãoApresentação do CursoVisão geral de funções, responsabilidades e estruturas de um SO
Arquitetura de sistemas operacionais e modelos de programação
|
Unidade 02: Processos |
---|
Unidade 02: ProcessosGerência de tarefas; contextos, processos e threads
Escalonamento de tarefas
Comunicação entre Processos
Coordenação de processos
|
Unidade 03: Memória |
---|
Unidade 03: MemóriaIntrodução ao Gerenciamento de Memória
Memória Principal
Memória Virtual
|
Unidade 04: Armazenamento |
---|
Unidade 04: ArmazenamentoInterface do Sistema de Arquivos
Implementação do Sistema de Arquivos
Estrutura de Armazenamento em Massa
Gerenciamento de Entrada e Saída
|
Laboratórios
Um Exemplo de Uso "API Padrão POSIX" |
---|
Um Exemplo de Uso "API Padrão POSIX"
Qual o tamanho limite da memória que você conseguiu alocar?
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 + sizeof(size_t);
}
int meu_free(void* addr) {
return munmap(addr - sizeof(size_t), (size_t) addr);
}
int soma(int *N1, int *N2){
return (*N1+*N2);
}
int main(int argc, char* argv[]) {
int* numero1 = meu_malloc(sizeof(int));
int* numero2 = meu_malloc(sizeof(int));
*numero1 = 10;
*numero2 = 20;
int resultado = soma(numero1, numero2);
printf("\n\n O resultado da soma é %d \n\n",resultado);
meu_free(numero1);
meu_free(numero2);
return 0;
}
|
Processos no Linux |
---|
Processos no Linux
// 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$
// 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$
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$
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.
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. |