Mudanças entre as edições de "SOP-EngTel 2018 1"
(→Notas) |
|||
(96 revisões intermediárias por 2 usuários não estão sendo mostradas) | |||
Linha 4: | Linha 4: | ||
*'''Professor:''' [[André D'Amato]] | *'''Professor:''' [[André D'Amato]] | ||
*'''Encontros:''' Segundas às 7:30 e sextas às 09:40 no Laboratório de Redes II. | *'''Encontros:''' Segundas às 7:30 e sextas às 09:40 no Laboratório de Redes II. | ||
− | + | *'''Atendimento paralelo:''' Quarta 9:30 às 10:30 na sala telecomunicações 1. | |
*[[SOP-EngTel_2018_1_plano| Plano de Ensino]] | *[[SOP-EngTel_2018_1_plano| Plano de Ensino]] | ||
*[[SOP2018_1_Cronograma | Cronograma]] | *[[SOP2018_1_Cronograma | Cronograma]] | ||
+ | *'''COMUNICADOs -> AULAS DE REPOSIÇÂO!!!!!!!''' | ||
+ | {| border=1 | ||
+ | ! Dia 23/06 | ||
+ | ! Dia 30/06 | ||
+ | ! PROVA DE RECUPERAÇÂO | ||
+ | |- | ||
+ | | 9:40 ás 11:30 || 9:40 ás 11:30 || DIA 09/07 ás 9:40 | ||
+ | |} | ||
+ | = Notas = | ||
− | = | + | {| border=1 |
− | + | ! Nome | |
− | + | ! Atividade 1 | |
− | + | ! Atividade 2 | |
− | * | + | ! Atividade 3 |
+ | ! Atividade 4 | ||
+ | ! Atividade 5 | ||
+ | ! Prova 1 | ||
+ | ! Prova 2 | ||
+ | ! Média Trabalhos * 0,3 | ||
+ | ! Média Provas * 0,7 | ||
+ | ! Final | ||
+ | |- | ||
+ | | Allex || 10 || 10 ||0 || 5 || 5 || 5,25(7,34) || 6,2 || 1,8 || 4,74 || 6,5 | ||
+ | |- | ||
+ | | Ameliza || 6,5 || 0 || 5||0 || 0 || 2,75(8,34) || 7,2 || 0,4 || 5,44 || 5,8 | ||
+ | |- | ||
+ | | Douglas || 9,0 || 10 || 0||0 || 5 || 6,0(8,34) || 8,3 || 1,4 || 5,8 || 7,2 | ||
+ | |- | ||
+ | | Filipe || 10 || 10 || 5|| 4|| 10 || 8,0 || 9,8 || 2,3 || 6,2 || 8,5 | ||
+ | |- | ||
+ | | Francisco || 0 || 0 ||0 ||0 || 0 || 0 || 0 || 0|| 0 || 0 | ||
+ | |- | ||
+ | | Gabriel || 0 || 0 ||0 || 0 || 0 || 7,25 || 0(3,6) || 0 || 3,8 || 3,8 | ||
+ | |- | ||
+ | | Joseane || 10 || 10 || 5|| 4 || 10 || 3,0(5,34) || 7,7 || 2,3 || 4,6 || 6,9 | ||
+ | |- | ||
+ | | Marcone || 10 || 10 || 6 || 0|| 5 || 5,75 || 7,7 || 1,9 || 4,7 ||6,6 | ||
+ | |- | ||
+ | | Thiago || 0 || 0 ||0 || 0|| 0 || 6,75 || 0 || 0 || 2,4 || 2,4 | ||
+ | |- | ||
+ | | Yan || 7 || 10 || 7,5|| 6,5 || 8,5 || 6,5 || 7,5 || 2,4 || 4,9 || 7,3 | ||
+ | |- | ||
+ | |} | ||
Linha 26: | Linha 64: | ||
Exercícios selecionados: | Exercícios selecionados: | ||
− | *Capítulo 1: 1-3, 6-8, | + | *Capítulo 1: 1.1-1.3, 1.6-1.8, 1.13, 1.14, 1.17, 1.22, 1.23, 1.25. |
+ | *Capítulo 2: 2.6-2.8, 2.12, 2.13, 2.17, 2.21, 2.26. | ||
+ | *Capítulo 3: 3.1, 3.3, 3.6, 3.7, 3.8, 3.10. | ||
+ | *Capítulo 4: 4.5, 4.10 | ||
+ | *Capítulo 5: 5.2, 5.3, 5.12 | ||
+ | |||
+ | '''Programa do exercício 3.10''' | ||
+ | <syntaxhighlight lang=cpp> | ||
+ | #include <sys/types.h> | ||
+ | #include <stdio.h> | ||
+ | #include <unistd.h> | ||
+ | |||
+ | |||
+ | |||
+ | int main(){ | ||
+ | |||
+ | pid_t pid, pid1; /* cria um processo-filho*/ | ||
+ | |||
+ | pid = fork(); | ||
+ | if (pid < 0) { /* um erro ocorreu */ | ||
+ | fprintf(stderr, "Fork Failed"); | ||
+ | return 1; | ||
+ | } | ||
+ | |||
+ | else if (pid == 0) { /* processo-filho */ | ||
+ | pid1 = getpid(); | ||
+ | printf("child: pid = %d \n",pid); /* A */ | ||
+ | printf("child: pid1 = %d \n",pid1); /* B */ | ||
+ | }else { /* processo-pai */ | ||
+ | pid1 = getpid(); | ||
+ | printf("parent: pid = %d \n",pid); /* C */ | ||
+ | printf("parent: pid1 = %d \n",pid1); /* D */ | ||
+ | wait(NULL); | ||
+ | } | ||
+ | return 0; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
=Conteúdo= | =Conteúdo= | ||
Linha 35: | Linha 109: | ||
=== Apresentação do Curso === | === Apresentação do Curso === | ||
− | *[[SOP- | + | *[[SOP-EngTel_2018_1_plano| Plano de Ensino]] |
− | *[[ | + | *[[SOP2018_1_Cronograma | Cronograma]] |
=== Visão geral de funções, responsabilidades e estruturas de um SO === | === Visão geral de funções, responsabilidades e estruturas de um SO === | ||
Linha 53: | Linha 127: | ||
=== Gerência de tarefas; contextos, processos e threads === | === Gerência de tarefas; contextos, processos e threads === | ||
− | * [http://docente.ifsc.edu.br/ | + | * [http://docente.ifsc.edu.br/andre.damato/sop2018/SOP2018-parte2.pdf Apresentação sobre Gerenciamento de Processos] |
* Capítulo 3 do livro do Silberschatz | * Capítulo 3 do livro do Silberschatz | ||
Linha 113: | Linha 187: | ||
* [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte7.pdf Apresentação sobre 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. | * Capítulo 13 do livro do Silberschatz. | ||
+ | |||
+ | {{collapse bottom}} | ||
+ | |||
+ | =Laboratórios= | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | Um Exemplo de Uso "API Padrão POSIX"}} | ||
+ | == Um Exemplo de Uso "API Padrão POSIX" == | ||
+ | |||
+ | ;Referências | ||
+ | * Referência http://man7.org/linux/man-pages/man2/mmap.2.html | ||
+ | |||
+ | |||
+ | 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. | ||
+ | |||
+ | <syntaxhighlight lang=cpp> | ||
+ | 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). | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | |||
+ | <syntaxhighlight lang=cpp> | ||
+ | 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; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | |||
+ | {{collapse bottom}} | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | 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 | ||
+ | <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 (Atividade 2)}} | ||
+ | |||
+ | == Threads de aplicação == | ||
+ | |||
+ | 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. | ||
+ | |||
+ | |||
+ | 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> | ||
+ | |||
+ | *Exercício 1: Primeiramente compile e execute o código. Agora estude o código e entenda completamente o seu funcionamento. Explique em DETALHES o código, comentando todas as linhas. Na seção de diagrama do seu relatório, desenhe um diagrama de funcionamento do código para mostrar exatamente como acontece a troca de contexto entre as threads. | ||
+ | *Exercício 2: Acrescente um procedimento '''f_new''' que receba 4 strings como parâmetros e imprima todas na tela. Antes do final da execução do main faça uma mudança de contexto para chamar o procedimento criado. | ||
+ | |||
+ | |||
+ | {{collapse bottom}} | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | Exercícios sobre pipe e memória compartilhada}} | ||
+ | == PIPE e SH_MEMORY == | ||
+ | |||
+ | ;Referências | ||
+ | * http://man7.org/linux/man-pages/man2/pipe.2.html | ||
+ | * http://man7.org/linux/man-pages/man2/shmget.2.html | ||
+ | * http://man7.org/linux/man-pages/man3/ftok.3.html | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | *'''Experimento 1:''' Complete o código a seguir para implementar um processo que escreva no pipe fd a mensagem de "hello pipe", e envie esta mensagem para outro processo escrever na tela. | ||
+ | |||
+ | |||
+ | <syntaxhighlight lang=cpp> | ||
+ | #include <stdio.h> | ||
+ | #include <unistd.h> | ||
+ | #include <sys/types.h> | ||
+ | #include <string.h> | ||
+ | #include <stdlib.h> | ||
+ | |||
+ | int main(void) | ||
+ | { | ||
+ | int fd[2], pipe_ret, filho; | ||
+ | char string[] = "Hello, pipe!\n"; | ||
+ | char buffer[20]; | ||
+ | |||
+ | pipe(fd); | ||
+ | |||
+ | if((filho = fork()) == -1) | ||
+ | { | ||
+ | perror("fork"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if(filho == 0) | ||
+ | { | ||
+ | /*Mandar string para a extremidade do pipe*/ | ||
+ | |||
+ | exit(0); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | |||
+ | /*Ler a mensagem no pipe*/ | ||
+ | |||
+ | printf("Recebi este texto %s", buffer); | ||
+ | } | ||
+ | |||
+ | return(0); | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | *'''Experimento 2:''' Complete o código a seguir para que os processos pai e filho possam compartilhar um segmento de memória. O filho escreve no segmento e o pai imprime na tela o conteúdo da mensagem. | ||
+ | |||
+ | |||
+ | <syntaxhighlight lang=cpp> | ||
+ | |||
+ | #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 | ||
+ | |||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | key_t key; | ||
+ | int shmid; | ||
+ | char *segmento; | ||
+ | int modo,filho; | ||
+ | |||
+ | |||
+ | /* Criar a chave: */ | ||
+ | if (key == -1) | ||
+ | { | ||
+ | perror("ftok"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | |||
+ | /*Criar o segmento */ | ||
+ | |||
+ | |||
+ | |||
+ | if (shmid == -1) { | ||
+ | perror("shmget"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | /*Vincula o segmento de memória á variável segmento*/ | ||
+ | segmento = shmat(shmid, (void *)0, 0); | ||
+ | if (segmento == (char *)(-1)) { | ||
+ | perror("shmat"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if((filho = fork()) == -1) | ||
+ | { | ||
+ | perror("fork"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if(filho == 0) //Código do filho | ||
+ | { | ||
+ | |||
+ | |||
+ | |||
+ | exit(0); | ||
+ | } | ||
+ | else //Código do pai | ||
+ | { | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | /* detach from the segment: */ | ||
+ | if (shmdt(segmento) == -1) { | ||
+ | perror("shmdt"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | <!-- | ||
+ | <code> | ||
+ | |||
+ | #include <stdio.h> | ||
+ | #include <unistd.h> | ||
+ | #include <sys/types.h> | ||
+ | #include <string.h> | ||
+ | #include <stdlib.h> | ||
+ | |||
+ | int main(void) | ||
+ | { | ||
+ | int fd[2], pipe_ret, filho; | ||
+ | char string[] = "Hello, pipe!\n"; | ||
+ | char buffer[20]; | ||
+ | |||
+ | pipe(fd); | ||
+ | |||
+ | if((filho = fork()) == -1) | ||
+ | { | ||
+ | perror("fork"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if(filho == 0) | ||
+ | { | ||
+ | /*Mandar string para a extremidade do pipe*/ | ||
+ | write(fd[1], string, (strlen(string)+1)); | ||
+ | exit(0); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | |||
+ | /* Read in a string from the pipe */ | ||
+ | pipe_ret = read(fd[0], buffer, (20*sizeof(char))); | ||
+ | printf("Recebi este texto %s", buffer); | ||
+ | } | ||
+ | |||
+ | return(0); | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | <code> | ||
+ | |||
+ | #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 | ||
+ | |||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | key_t key; | ||
+ | int shmid; | ||
+ | char *segmento; | ||
+ | int modo,filho; | ||
+ | |||
+ | |||
+ | key = ftok("teste_sh.txt", 'A'); /*O arquivo deve existir de verdade*/ | ||
+ | if (key == -1) | ||
+ | { | ||
+ | perror("ftok"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | |||
+ | shmid = shmget(key, SHM_SIZE, (0644 | IPC_CREAT)); | ||
+ | if (shmid == -1) { | ||
+ | perror("shmget"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | |||
+ | segmento = shmat(shmid, (void *)0, 0); | ||
+ | if (segmento == (char *)(-1)) { | ||
+ | perror("shmat"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if((filho = fork()) == -1) | ||
+ | { | ||
+ | perror("fork"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if(filho == 0) | ||
+ | { | ||
+ | |||
+ | printf("Filho escrevendo no segmento compartilhado\n\n"); | ||
+ | strncpy(segmento, "mensagem compartilhada", SHM_SIZE); | ||
+ | |||
+ | exit(0); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | wait(filho); | ||
+ | printf("Mensagem para o pai: %s\n", segmento); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | if (shmdt(segmento) == -1) { | ||
+ | perror("shmdt"); | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | --> | ||
+ | |||
+ | {{collapse bottom}} | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | Trocas de mensagens com pipes (Atividade 3)}} | ||
+ | == 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> | ||
+ | |||
+ | *'''Exercício 1: construa um “pipeline”'''. Crie um programa que conecta 4 processos através de 3 pipes. Utilize ''fork()'' para criar vários processos. | ||
+ | |||
+ | *'''Exercício 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 | Exercício (Algoritmo de Peterson)}} | ||
+ | == Exercício (Algoritmo de Peterson) == | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | '''Exercício 1''': Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize o algoritmo de '''Peterson''' visto em aula. '''Utilize memória compartilhada''' para comunicação entre os processos. | ||
+ | |||
+ | <syntaxhighlight lang=c> | ||
+ | |||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/ipc.h> | ||
+ | #include <sys/shm.h> | ||
+ | |||
+ | main() | ||
+ | { | ||
+ | |||
+ | |||
+ | if (fork() != 0) { /* I am the parent */ | ||
+ | int i; | ||
+ | |||
+ | for(i = 0;i < 10;i=i+2){ | ||
+ | printf("Processo pai %d \n", i); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | else { /*Child code */ | ||
+ | int i; | ||
+ | for(i = 1;i < 10;i=i+2){ | ||
+ | printf("Processo filho %d \n", i); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | exit(0); | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | '''Exercício 2''': Considerando o exercício anterior faça a mesma sincronização, no entanto desta vez utilize a modelagem em software do TSL. | ||
+ | * Em sua experiência, depois de testar diversas vezes as execuções de suas soluções baseadas no algoritmo de '''Peterson''' e '''Tsl''', qual sua opinião sobre as abordagens? | ||
+ | Explique seu raciocínio. | ||
+ | |||
+ | |||
+ | |||
+ | {{collapse bottom}} | ||
+ | {{collapse top| bg=lightyellow | expandir=true | Exercício (Semáforos)}} | ||
+ | == Exercício (Semáforos) == | ||
+ | |||
+ | |||
+ | '''Exercício 1''': Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize '''Semáforos''' de acordo com a implementação em '''semaforo.h'''. '''Utilize memória compartilhada''' para comunicação entre os processos. | ||
+ | |||
+ | <syntaxhighlight lang=c> | ||
+ | |||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/ipc.h> | ||
+ | #include <sys/shm.h> | ||
+ | |||
+ | main() | ||
+ | { | ||
+ | |||
+ | |||
+ | if (fork() != 0) { /* I am the parent */ | ||
+ | int i; | ||
+ | |||
+ | for(i = 0;i < 10;i=i+2){ | ||
+ | printf("Processo pai %d \n", i); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | else { /*Child code */ | ||
+ | int i; | ||
+ | for(i = 1;i < 10;i=i+2){ | ||
+ | printf("Processo filho %d \n", i); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | exit(0); | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | |||
+ | SEMAFORO.H | ||
+ | |||
+ | |||
+ | <syntaxhighlight lang=c> | ||
+ | |||
+ | |||
+ | |||
+ | int criar_semaforo(int val, int chave) | ||
+ | { | ||
+ | int semid ; | ||
+ | |||
+ | union semun { | ||
+ | int val; | ||
+ | struct semid_ds *buf ; | ||
+ | ushort array[1]; | ||
+ | } arg_ctl ; | ||
+ | |||
+ | key_t ft = ftok("/tmp", chave); | ||
+ | |||
+ | semid = semget(ft,1,IPC_CREAT|IPC_EXCL|0666); | ||
+ | if (semid == -1) { | ||
+ | semid = semget(ft,1,0666); | ||
+ | if (semid == -1) { | ||
+ | perror("Erro semget()"); | ||
+ | exit(1) ; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | arg_ctl.val = val; //valor de início | ||
+ | if (semctl(semid,0,SETVAL,arg_ctl) == -1) { | ||
+ | perror("Erro inicializacao semaforo"); | ||
+ | exit(1); | ||
+ | } | ||
+ | return(semid) ; | ||
+ | } | ||
+ | |||
+ | void P(int semid){ | ||
+ | |||
+ | struct sembuf *sops = malloc(10*sizeof(int)); | ||
+ | sops->sem_num = 0; | ||
+ | sops->sem_op = -1; | ||
+ | sops->sem_flg = 0; | ||
+ | semop(semid, sops, 1); | ||
+ | free(sops); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | void V(int semid){ | ||
+ | |||
+ | struct sembuf *sops = malloc(10*sizeof(int)); | ||
+ | sops->sem_num = 0; | ||
+ | sops->sem_op = 1; | ||
+ | sops->sem_flg = 0; | ||
+ | semop(semid, sops, 1); | ||
+ | free(sops); | ||
+ | |||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | void sem_delete(int semid) | ||
+ | { | ||
+ | |||
+ | if (semctl(semid,0,IPC_RMID,0) == -1) | ||
+ | perror("Erro na destruicao do semaforo"); | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | {{collapse bottom}} | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | Programação concorrente (Atividade 4)}} | ||
+ | |||
+ | == Programação concorrente (Atividade 4) == | ||
+ | |||
+ | ;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); | ||
+ | |||
+ | |||
+ | |||
+ | cout << "Saldo final é " << saldo << "." << endl; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | # Compile este programa. Você precisará da classe Thread. | ||
+ | # 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 | Softwares básicos, caso Hello Word! (Atividade 5)}} | ||
+ | |||
+ | == Softwares básicos, caso Hello Word! == | ||
+ | |||
+ | O objetivo do experimento de hoje é pesquisar e entender os processos de atribuição de endereços de programas realizados em tempo de compilação pelos softwares básicos como: compilador, linker, e assembler. Sendo assim, neste experimento vamos utilizar os seguintes softwares para criação e análise de código: | ||
+ | |||
+ | *'''GCC''': compilador para gerar código objeto a partir de um código de programa escrito na linguagem c; | ||
+ | *'''GNU Linker (LD)''': Para vincular os códigos (módulos) objetos do programa; | ||
+ | *[https://linux.die.net/man/1/ld| Linker ] | ||
+ | *'''GNU Assembler''': Para gerar o código executável a partir do código objeto; | ||
+ | *[https://linux.die.net/man/1/as | assembler] | ||
+ | * '''OBJDUMP''': Para mostrar informações do código; | ||
+ | *[https://linux.die.net/man/1/objdump | Objdump ] | ||
+ | |||
+ | |||
+ | A seguir segue descrito o programa a ser utilizado no exercício 1: | ||
+ | |||
+ | <syntaxhighlight lang=cpp> | ||
+ | #include <stdio.h> | ||
+ | int main() | ||
+ | { | ||
+ | printf("Hello, World!"); | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | Trata-se do programa hello word, este programa apenas exibe uma mensagem na tela. No entanto, vamos analisar como são as etapas confecção do executável a partir deste código ''simples''. | ||
+ | |||
+ | Exercício 1: | ||
+ | * Compile o programa hello word, e o transforme em código objeto utilizando o programa GCC. Para esta tarefa execute o seguinte comando: | ||
+ | <code> | ||
+ | gcc -o hello hello.c | ||
+ | </syntaxhighlight> | ||
+ | * Agora abra o código objeto utilizando o programa OBJDUMP. | ||
+ | <code> | ||
+ | objdump -D hello | ||
+ | </syntaxhighlight> | ||
+ | * Identifique quais são as seções de código obtidas a partir do '''hello.c'''. | ||
+ | * Pesquise e entenda o significado destas seções de código. | ||
+ | * Faça uma análise e identifique o endereço de memória que o programa ''hello world'' vai ser carregado. | ||
+ | * Este código objeto é relocável? Justifique sua resposta. | ||
+ | * Agora gere o código assembly do hello world. | ||
+ | <code> | ||
+ | gcc -S hello.c | ||
+ | </syntaxhighlight> | ||
+ | *Agora gere o código executável utilizando o programa '''AS''' e o programa '''LD'''. | ||
+ | <code> | ||
+ | as -o hello.o hello.s | ||
+ | </syntaxhighlight> | ||
+ | * Como a etapa de linkagem para a construção do código executável está sendo executada sem o auxílio do '''GCC''', necessitamos vincular manualmente as bibliotecas necessárias. Para criar apropriadamente o executável vamos precisar das bibliotecas '''ld-linux-x86-64.so.2''', '''crt1.o''', '''crti.o''', '''crtn.o'''. | ||
+ | * Para descobrir suas respectivas localizações use o comando '''LOCATE'''. Por exemplo: | ||
+ | <code> | ||
+ | |||
+ | locate crti.o | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | ou | ||
+ | |||
+ | <code> | ||
+ | |||
+ | find /usr/ -name crti* | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | * Agora que já sabemos a localização das bibliotecas necessárias vamos vincular essas a nossa aplicação. | ||
+ | <code> | ||
+ | ld --dynamic-linker /caminho/ld-linux-x86-64.so.2 /caminho/crt1.o /caminho/crti.o /caminho/crtn.o hello.o -lc -o hello.exe | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | * Agora abra o código objeto utilizando o programa OBJDUMP. | ||
+ | * Faça uma análise e identifique o endereço de memória em que o programa ''hello world'' inicializa suas estruturas na memória. | ||
+ | * Dica! | ||
+ | <code> | ||
+ | objdump -D -s -j .init hello.exe | ||
+ | </syntaxhighlight> | ||
+ | * Houve diferença entre os endereços do programa executável em relação ao código objeto? Explique. | ||
+ | * Explique as principais diferenças entre o arquivo objeto .o e o executável final. (dica utilize o objdump para fazer essa análise) | ||
+ | {{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}} | ||
+ | |||
+ | {{collapse top| bg=lightyellow | expandir=true | Lista de exercícios}} | ||
+ | |||
+ | == Lista de exercícios == | ||
+ | |||
+ | Lista de exercícios referente ao dia 22/06 [http://docente.ifsc.edu.br/andre.damato/sop2018/ListaCOPA.pdf | Lista de exercícios]. | ||
{{collapse bottom}} | {{collapse bottom}} |
Edição atual tal como às 18h10min de 9 de julho de 2018
Sistemas Operacionais
- Professor: André D'Amato
- Encontros: Segundas às 7:30 e sextas às 09:40 no Laboratório de Redes II.
- Atendimento paralelo: Quarta 9:30 às 10:30 na sala telecomunicações 1.
- COMUNICADOs -> AULAS DE REPOSIÇÂO!!!!!!!
Dia 23/06 | Dia 30/06 | PROVA DE RECUPERAÇÂO |
---|---|---|
9:40 ás 11:30 | 9:40 ás 11:30 | DIA 09/07 ás 9:40 |
Notas
Nome | Atividade 1 | Atividade 2 | Atividade 3 | Atividade 4 | Atividade 5 | Prova 1 | Prova 2 | Média Trabalhos * 0,3 | Média Provas * 0,7 | Final |
---|---|---|---|---|---|---|---|---|---|---|
Allex | 10 | 10 | 0 | 5 | 5 | 5,25(7,34) | 6,2 | 1,8 | 4,74 | 6,5 |
Ameliza | 6,5 | 0 | 5 | 0 | 0 | 2,75(8,34) | 7,2 | 0,4 | 5,44 | 5,8 |
Douglas | 9,0 | 10 | 0 | 0 | 5 | 6,0(8,34) | 8,3 | 1,4 | 5,8 | 7,2 |
Filipe | 10 | 10 | 5 | 4 | 10 | 8,0 | 9,8 | 2,3 | 6,2 | 8,5 |
Francisco | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Gabriel | 0 | 0 | 0 | 0 | 0 | 7,25 | 0(3,6) | 0 | 3,8 | 3,8 |
Joseane | 10 | 10 | 5 | 4 | 10 | 3,0(5,34) | 7,7 | 2,3 | 4,6 | 6,9 |
Marcone | 10 | 10 | 6 | 0 | 5 | 5,75 | 7,7 | 1,9 | 4,7 | 6,6 |
Thiago | 0 | 0 | 0 | 0 | 0 | 6,75 | 0 | 0 | 2,4 | 2,4 |
Yan | 7 | 10 | 7,5 | 6,5 | 8,5 | 6,5 | 7,5 | 2,4 | 4,9 | 7,3 |
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.1-1.3, 1.6-1.8, 1.13, 1.14, 1.17, 1.22, 1.23, 1.25.
- Capítulo 2: 2.6-2.8, 2.12, 2.13, 2.17, 2.21, 2.26.
- Capítulo 3: 3.1, 3.3, 3.6, 3.7, 3.8, 3.10.
- Capítulo 4: 4.5, 4.10
- Capítulo 5: 5.2, 5.3, 5.12
Programa do exercício 3.10
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid, pid1; /* cria um processo-filho*/
pid = fork();
if (pid < 0) { /* um erro ocorreu */
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0) { /* processo-filho */
pid1 = getpid();
printf("child: pid = %d \n",pid); /* A */
printf("child: pid1 = %d \n",pid1); /* B */
}else { /* processo-pai */
pid1 = getpid();
printf("parent: pid = %d \n",pid); /* C */
printf("parent: pid1 = %d \n",pid1); /* D */
wait(NULL);
}
return 0;
}
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 (Atividade 1) |
---|
Processos no LinuxEntregar um relatório impresso sobre a sua solução para o problema descrito. O relatório deve conter as seguintes seções:
// 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. |
Threads de aplicação (Atividade 2) |
---|
Threads de aplicaçãoEntregar um relatório impresso sobre a sua solução para o problema descrito. O relatório deve conter as seguintes seções:
Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext). Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento. #include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#define STACKSIZE 32768 /* tamanho de pilha das threads */
/* VARIÁVEIS GLOBAIS */
ucontext_t cPing, cPong, cMain;
/* Funções-comportamento das Tarefas */
void f_ping(void * arg) {
int i;
printf("%s iniciada\n", (char *) arg);
for (i=0; i<4; i++) {
printf("%s %d\n", (char *) arg, i);
swapcontext(&cPing, &cPong);
}
printf("%s FIM\n", (char *) arg);
swapcontext(&cPing, &cMain);
}
void f_pong(void * arg) {
int i;
printf("%s iniciada\n", (char *) arg);
for (i=0; i<4; i++) {
printf("%s %d\n", (char *) arg, i);
swapcontext(&cPong, &cPing);
}
printf("%s FIM\n", (char *) arg);
swapcontext(&cPong, &cMain);
}
/* MAIN */
int main(int argc, char *argv[]) {
char *stack;
printf ("Main INICIO\n");
getcontext(&cPing);
stack = malloc(STACKSIZE);
if(stack) {
cPing.uc_stack.ss_sp = stack ;
cPing.uc_stack.ss_size = STACKSIZE;
cPing.uc_stack.ss_flags = 0;
cPing.uc_link = 0;
}
else {
perror("Erro na criação da pilha: ");
exit(1);
}
makecontext(&cPing, (void*)(*f_ping), 1, "\tPing");
getcontext(&cPong);
stack = malloc(STACKSIZE);
if(stack) {
cPong.uc_stack.ss_sp = stack ;
cPong.uc_stack.ss_size = STACKSIZE;
cPong.uc_stack.ss_flags = 0;
cPong.uc_link = 0;
}
else {
perror("Erro na criação da pilha: ");
exit(1);
}
makecontext (&cPong, (void*)(*f_pong), 1, "\tPong");
swapcontext(&cMain, &cPing);
swapcontext(&cMain, &cPong);
printf("Main FIM\n");
exit(0);
}
|
Exercícios sobre pipe e memória compartilhada |
---|
PIPE e SH_MEMORY
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int fd[2], pipe_ret, filho;
char string[] = "Hello, pipe!\n";
char buffer[20];
pipe(fd);
if((filho = fork()) == -1)
{
perror("fork");
exit(1);
}
if(filho == 0)
{
/*Mandar string para a extremidade do pipe*/
exit(0);
}
else
{
/*Ler a mensagem no pipe*/
printf("Recebi este texto %s", buffer);
}
return(0);
}
#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
int main(int argc, char *argv[])
{
key_t key;
int shmid;
char *segmento;
int modo,filho;
/* Criar a chave: */
if (key == -1)
{
perror("ftok");
exit(1);
}
/*Criar o segmento */
if (shmid == -1) {
perror("shmget");
exit(1);
}
/*Vincula o segmento de memória á variável segmento*/
segmento = shmat(shmid, (void *)0, 0);
if (segmento == (char *)(-1)) {
perror("shmat");
exit(1);
}
if((filho = fork()) == -1)
{
perror("fork");
exit(1);
}
if(filho == 0) //Código do filho
{
exit(0);
}
else //Código do pai
{
}
/* detach from the segment: */
if (shmdt(segmento) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
|
Trocas de mensagens com pipes (Atividade 3) |
---|
Trocas de mensagens com pipes
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 é:
No UNIX, pipes são inicializados através da SystemCall pipe, que possui a seguinte sintaxe:
As primitivas send/receive para uso de um pipe no UNIX são implementadas por SystemCalls read/write, conforme segue:
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>
#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) ;
}
}
|
Exercício (Algoritmo de Peterson) |
---|
Exercício (Algoritmo de Peterson)Exercício 1: Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize o algoritmo de Peterson visto em aula. Utilize memória compartilhada para comunicação entre os processos. #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
main()
{
if (fork() != 0) { /* I am the parent */
int i;
for(i = 0;i < 10;i=i+2){
printf("Processo pai %d \n", i);
}
}
else { /*Child code */
int i;
for(i = 1;i < 10;i=i+2){
printf("Processo filho %d \n", i);
}
}
exit(0);
}
Explique seu raciocínio.
|
Exercício (Semáforos) |
---|
Exercício (Semáforos)Exercício 1: Sincronize o código a seguir, de maneira que o processo pai imprima apenas os números impares e o processo filho os números pares. Para isso utilize Semáforos de acordo com a implementação em semaforo.h. Utilize memória compartilhada para comunicação entre os processos. #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
main()
{
if (fork() != 0) { /* I am the parent */
int i;
for(i = 0;i < 10;i=i+2){
printf("Processo pai %d \n", i);
}
}
else { /*Child code */
int i;
for(i = 1;i < 10;i=i+2){
printf("Processo filho %d \n", i);
}
}
exit(0);
}
SEMAFORO.H
int criar_semaforo(int val, int chave)
{
int semid ;
union semun {
int val;
struct semid_ds *buf ;
ushort array[1];
} arg_ctl ;
key_t ft = ftok("/tmp", chave);
semid = semget(ft,1,IPC_CREAT|IPC_EXCL|0666);
if (semid == -1) {
semid = semget(ft,1,0666);
if (semid == -1) {
perror("Erro semget()");
exit(1) ;
}
}
arg_ctl.val = val; //valor de início
if (semctl(semid,0,SETVAL,arg_ctl) == -1) {
perror("Erro inicializacao semaforo");
exit(1);
}
return(semid) ;
}
void P(int semid){
struct sembuf *sops = malloc(10*sizeof(int));
sops->sem_num = 0;
sops->sem_op = -1;
sops->sem_flg = 0;
semop(semid, sops, 1);
free(sops);
}
void V(int semid){
struct sembuf *sops = malloc(10*sizeof(int));
sops->sem_num = 0;
sops->sem_op = 1;
sops->sem_flg = 0;
semop(semid, sops, 1);
free(sops);
}
void sem_delete(int semid)
{
if (semctl(semid,0,IPC_RMID,0) == -1)
perror("Erro na destruicao do semaforo");
}
|
Programação concorrente (Atividade 4) |
---|
Programação concorrente (Atividade 4)
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):
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() {}
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
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):
#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
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):
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;
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);
cout << "Saldo final é " << saldo << "." << endl;
}
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:
#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;
}
|
Problemas clássicos de coordenação de processos |
---|
Problemas clássicos de coordenação de processos
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).
#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;
}
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.
#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;
}
|
Softwares básicos, caso Hello Word! (Atividade 5) |
---|
Softwares básicos, caso Hello Word!O objetivo do experimento de hoje é pesquisar e entender os processos de atribuição de endereços de programas realizados em tempo de compilação pelos softwares básicos como: compilador, linker, e assembler. Sendo assim, neste experimento vamos utilizar os seguintes softwares para criação e análise de código:
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
Trata-se do programa hello word, este programa apenas exibe uma mensagem na tela. No entanto, vamos analisar como são as etapas confecção do executável a partir deste código simples. Exercício 1:
|
Permissões de sistema de arquivos no Linux |
---|
Permissões de sistema de arquivos no LinuxNeste 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 deste roteiro do Prof. Maziero da UTFPR. |
Lista de exercícios |
---|
Lista de exercíciosLista de exercícios referente ao dia 22/06 | Lista de exercícios. |