SOP29005-2020-1
AULA 1 - Dia 11/02/2020
Objetivos/Conteúdos
- Apresentação do Plano de Ensino
- Objetivos e Conteúdo Programático da Disciplina (ver SIGAA)
- Forma de Avaliação (ver SIGAA)
- Introdução a Sistemas Operacionais (Cap1. do Silberschatz)
- O que faz um sistema operacional (1.1)
- Organização e Arquitetura de um Sistema Computacional (1.2 e 1.3)
- Importância da Interrupção e Timers (1.2.1)
- Estrutura de Armazenamento (1.2.2)
- Estrutura de IO (1.2.3)
- Estrutura e Operações de um Sistema Operacional (1.4 e 1.5)
Material de Referência
- Apresentação sobre histórico visão geral e estruturas básicas de um SO
- Slides Silberschatz Oitava Edição
- Tradução Slides Silberschatz Cap1
- Tradução Slides Silberschatz Cap2
- Tradução Slides Silberschatz Cap3
- Tradução Slides Silberschatz Cap4
- Livro do Prof.Maziero
- Université Nice Sophia Antipolis
Arliones:
Leitura Recomendada
- Cap.1 do Silberschatz principalmente:
- 1.1 a 1.9
Exercícios Práticos de Apoio a Aula
Na sequência. com fins motivacionais, são apresentados alguns exercícios ilustrando conceitos de processos, arquivos e permissionamento.
-
Comando para ver todos os processos no linux:
$ ps aux
-
Colocar 2 processos se executando no contexto de um terminal e verificar número dos processos e então "destruí-los":
$ yes > /dev/null $ yes > /dev/null $ ps $ kill -9 <pid_yes> $ kill -9 <pid_yes>
Observe que dois processos foram criados a partir do programa "yes". Os processos associados ao terminal são visualizados e então destruídos. Tente destruir também o interpretador de comando (shell) associado ao terminal.
-
Criar um terminal adicional
$ xterm
Ir no terminal criado e listar os processos que se executam associados a este terminal. Verificar qual o dispositivo de entrada e saída associado a ele. Voltar ao terminal original e enviar uma mensagem. Destruir o terminal criado:
$ echo Alo Terminal > /dev/pts/2 $ kill -9 <pid_terminal>
-
Comunicação entre processos:
$ cat /etc/passwd | grep home | wc -l
-
Retirando a permissão de leitura de um arquivo em nível de usuário proprietário:
$ echo Alo Mundo > alfa.txt $ ls -l alfa.txt $ cat alfa.txt $ chmod u-r alfa.txt $ cat alfa.txt $ chmod u+r alfa.txt $ cat alfa.txt
AULA 2 - Dia 14/02/2020
Objetivos/Conteúdos
- Estruturas do Sistema Operacional (cap.2)
- Serviços do Sistema Operacional (2.1)
- Interfaces com Usuários (2.2)
- Chamadas do Sistema (2.3)
- Tipos de Chamadas de Sistema (2.4) ver Fig.2.8
- Chamadas de Controle de Processos
Material de Referência
- [http://docente.ifsc.edu.br/arliones.hoeller/sop/slides/SOP29005-parte1.pdf Apresentação sobre histórico visão geral
- [3]
Leitura Recomendada
- Cap.2 do Silberschatz principalmente:
- 2.1 a 2.8
Exercícios
- Estudar e executar o código em http://cs.lmu.edu/~ray/notes/gasexamples/ Detalhes das chamadas na arquitetura x86_64 ver em https://lwn.net/Articles/604287/
- Estudar e executar o código usando a função (em conformidade com a API POSIX) write() para o hello world:
#include <unistd.h> main() { write(1,"Alo Mundo\n",10); }
- Use o comando strace para verificar todas as chamadas de sistema dos programas acima.
-
DESAFIO 1: Estude a seção "Mixing C and Assembly Language" da http://cs.lmu.edu/~ray/notes/gasexamples/ e construa uma função meu_hello_world() usando o código em assembly do exercício inicial. Estude como poderia disponibilizar esta e outras funções de interface (a sua API) em uma biblioteca. Note que esta função deve ser chamada da forma:
main() { meu_hello_world(); }
Gere o assembly do código em C e discuta a diferença entre uma chamada de função e uma chamada de sistema.
-
DESAFIO 2: Estude o link http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ e melhore a função meu_hello_world para suportar uma mensagem adicional da forma:
#include <string.h> main() { char *p="Tudo bem com vocês?"; meu_hello_world(p, strlen(p)); }
Solução:
Solução modificado de http://cs.lmu.edu/~ray/notes/gasexamples/ .global meu_hello_world .text meu_hello_world: push %rdi # salva primeiro parâmetro push %rsi # salva segundo parâmetro mov $1, %rax # system call 1 é write mov $1, %rdi # file handle 1 é stdout mov $message, %rsi # endereço da string mov $13, %rdx # número de bytes da string syscall # chamada ao sistema pop %rdx # o que havia sido passado em rsi (número de bytes) é colocado em rdx pop %rsi # o que havia sido colocado em rdi (endereço da string) é colocado em rsi mov $1, %rax # system call 1 is write mov $1, %rdi # file handle 1 é stdout syscall # nova chamada ao sistema ret message: .ascii "Hello, World\n"
AULA 3 - Dia 18/02/2020
Objetivos/Conteúdos
- Interfaces com Usuários (2.2)
- Chamadas do Sistema (2.3)
- Tipos de Chamadas de Sistema (2.4) ver Fig.2.8
- Chamadas de Controle de Processos
AULA 4 - Dia 21/08/2020
Objetivos
- PARTE 2A: Gerenciamento de Processos (cap.3)
- 3.1.Conceito de Processo
- 3.2.Escalonamento de Processos
- 3.3.Operações sobre Processos (Laboratório Operações sobre Processos no Linux: Fork/Exec/Wait)
Material de Referência
- Slides do Cap.3 - Silberschatz
- Laboratório Fork/Exec/Wait
Exercícios de Demonstração
- Executar em um terminal o comando top. Em um outro terminal executar:
yes > /dev/null & yes > /dev/null &
- Parar um dos processos yes
kill -STOP <pid_yes>
- Continuar o processo
kill -CONT <pid_yes>
- Destruir todos
killall yes
AULA 5 - Dia 28/02/2020
Objetivos/Conteúdos
- Gerenciamento de Processos
- Conceito de Processo (3.1)
- O processo (3.1.1)
- Estado do Processo (3.1.2)
- Bloco de Controle de Processo (3.1.3)
- Threads (3.1.4)
- Scheduling (3.2)
- Filas de Scheduling (3.2.1)
- Schedulers
- Mudança de Contexto (3.2.3)
- Operações Sobre Processos (3.3)
- Conceito de Processo (3.1)
Slides desta aula
- Apresentação sobre Gerenciamento de Processos
- Capítulo 3 do livro do Silberschatz
Laboratório em Sala
https://wiki.sj.ifsc.edu.br/index.php/SOP29005-2019-1#Processos_no_Linux_-_Modificado_por_Eraldo
Exercícios de Demonstração
Processos no Linux Processos no Linux - Modificado por Eraldo
Processos no Linux
- Exercícios propostos pelo Prof.Arliones e Modificados por Eraldo
- 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> #include <sys/wait.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 // ex2: fork/wait "compartilhando" dados #include <sys/types.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/wait.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 \t endereço K: %p\n", getpid(), k, &k); exit(0); } else // Este é o processo pai { wait(&status); // espera o filho terminar k += 10; printf("processo pai\t pid: %d\t K: %d \t endereço K: %p\n", getpid(), k, &k); exit(0); } k += 1000; printf("FIM processo %d\t K: %d\n", getpid(), k); exit(0); }
processo 17056 antes do fork processo 17056 depois do fork processo 17057 depois do fork processo filho pid: 17057 K: 1000 endereço K: 0x7ffd8923e318 processo pai pid: 17056 K: 10 endereço K: 0x7ffd8923e318
- Modificação no código: comentar linhas 23 e 30
- Analise os resultados e busque entender a diferença.
- Perguntas e desafios.:
- Analisando os valores impressos de k pode-se dizer que os dados são compartilhados entre os dois processos?
- O endereço de k impresso é o mesmo nos dois processos. ISto não contradiz a afirmação anterior?
- Substitua o k por uma área criada dinâmicamente com o malloc (área de HEAP). Verifique se esta área é ou não compartilhada pelos processos.
- 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.
- Use o comando pstree para verificar a árvore de processos criada.
Solução /* ex3: Excrever um programa C que cria uma arvore 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 sao 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.
- /
- include <sys/types.h>
- include <stdlib.h>
- include <stdio.h>
- include <unistd.h>
- include <sys/wait.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 { pid = fork(); if(pid == -1) { perror("fork falhou!"); exit(-1); } else if(pid == 0) // Este é o filho do filho { sleep(1); printf("Eu sou o processo C (PID %d), filho de %d\n", getpid(), getppid()); exit (0); } else { wait(&status); sleep(1); printf("Eu sou o processo B (PID %d), filho de %d\n", getpid(), getppid()); exit(0); } } else // Este é o processo pai { wait(&status); sleep(1); printf("Eu sou o processo A (PID %d), filho de %d\n", getpid(), getppid()); exit(0); }
}
</syntaxhighlight>
AULA 6 - Dia 03/03/2020
Objetivos/Conteúdos
- Processos
- Exercícios Fork/Exec
AULA 7 - Dia 06/03/2020
Objetivos/Conteúdos
- Processos
- Exercícios Fork/Exec
- Memória Compartilhada
Cap.3 - Seção 3.4 e 3.5
AULA 8 - Dia 10/03/2020
Objetivos/Conteúdos
- Memória Compartilhada
- Troca de Msgs
- Laboratório de Pipes
AULA 9 - Dia 10/03/2020
Objetivos/Conteúdos
- Questionário de REvisão
- Conceito de Threads (cap.4)
- Caracterização do contexto e chaveamento de contexto do thread
- Laboratório de Thread de Applicação
- Slides sobre Threads:
Questionário - Processos
- Conceitue processo. Descreva e explique cada uma das seções de um processo na memória.
- Faça um diagrama e explique os possíveis estados de um processo no sistema. O que produz a transição entre estados?
- O que é um Bloco de Controle de Processo? Quais informações ficam armazenadas neste bloco?
- Faça um diagrama mostrando a alternância entre dois processos na CPU. Indique o procedimento de salvamento e restauração do estado do PCB.
- Explique o que é multiprogramação e escalonamento de processos.
- O que é a fila de processos prontos?
- Diferencie um processo limitado por IO de um processo limitado por CPU.
- Diferencie escalonamento de longo prazo de escalonamento de curto prazo.
- Discuta o papel da interrupção na troca de contexto entre processos.
- Explique como é o processo de criação de novos processos no UNIX/Linux através da chamada fork. O que acontece com a memória do novo processo?
- Explique como funciona a chamada exec no UNIX/Linux
- Explique como funciona a chamada wait no UNIX/Linux e como é possível retornar valores de um filho para o pai.
- Faça um esqueleto de um programa UNIX/Linux que ao se tornar processo cria 3 filhos e espera pela execução dos mesmos.
Questionário - Comunicação entre Processos
- Diferenciar processos independentes de processos cooperantes.
- Enumere e explique quatro razões para cooperação entre processos.
- A velocidade de processamento é uma das razões da cooperação entre processos. Discuta se um hardware com apenas uma CPU pode usufruir desta característica.
- Descreva os dois principais mecanismos de cooperação entre processos. Discuta a situação em que é possível usar a memória compartilhada e/ou a troca de mensagens.
- A memória compartilhada requer chamada ao sistema na sua operação? Discuta.
- Por que o processo de troca de mensagem deve ser mais lento que a memória compartilhada?
- Apresente em pseudocódigo uma proposta de funcionamento do problema do produtor consumidor usando memória compartilhada.
- A construção de uma solução para o problema produtor consumidor pode ser facilitado pelo uso da memória compartilhada? Discuta.
- Quais as duas operações básicas ma troca de mensagens? O que poderia motivar o uso de mensagens do tamanho fixo? Esta abordagem torna mais complexa a vida dos programadores?
- Enumere três métodos de implementação de uma lógica de ligação (link) entre processos.
- Explique o que é a comunicação direta em troca de mensagens. Descreva como são as duas primitivas de comunicação direta para esta abordagem e quais as 3 propriedades seguidas por esta comunicação.
- Descreva a variante assimétrica da troca direta de mensagens. O que muda em termos de primitiva de comunicação?
- Descreva o problema de hard-coding associado a nomeação direta e como pode ser contornado através da nomeação/comunicação indireta?
- O que é uma caixa postal? Como o Posix identifica uma caixa postal no caso de fila de mensagens?
- Dois processos podem usar mais do que uma caixa postal para se comunicar? Qual a condição para que isto ocorra?
- Descreva as duas operações básicas para a troca de mensagens com Caixa Postal.
- Descreva na comunicação indireta via Caixa Postal as 3 propriedades básicas.
- A questão do compartilhamento de caixas postais por múltiplos processos podem causar um problema na operação da recepção. Descreva quais as 3 possibilidades para contornar este problema.
- De que forma a caixa postal estando no espaço de endereçamento de um processo (propriedade) afeta a recepção por mensagens? Descreva.
- Quais as operações sobre uma caixa postal criada no espaço do sistema operacional deverão ser disponíveis? Descreva.
- A transmissão de mensagem pode ser com e sem bloqueio. Descreva as 4 possibilidades (síncrona/assíncrona).
- Discuta as 3 possibilidades de armazenamento de mensagens em buffer. Em que condições um remetente pode ser bloqueado?
Laboratório Threads Aplicação
Ver https://wiki.sj.ifsc.edu.br/index.php/SOP29005-2019-1#Threads_de_aplica.C3.A7.C3.A3o
Slides para esta aula
- Apresentação sobre Gerenciamento de Processos
- Slides do Capítulo 4 do livro do Silberschatz
AULA 10 ?? - REMOTA - Dia 20/03/2020
Objetivos/Conteúdos
- Webconf
- Revisão de Threads
- Laboratório de Thread de Applicação
Slides para esta aula
- Apresentação sobre Gerenciamento de Processos
- Slides do Capítulo 4 do livro do Silberschatz (
- Tradução Slides Silberschatz Cap4)
Threads de aplicação Threads de aplicação
O Linux, através da API POSIX, oferece um conjunto de funções que permite às aplicações manipular contextos, facilitando a vida do programador que quer implementar tarefas "simultâneas" dentro de um único processo, ou seja, threads. As seguintes funções e tipos estão disponíveis:
- getcontext(&a): salva o contexto na variável a;
- setcontext(&a): restaura um contexto salvo anteriormente na variável a;
- swapcontext(&a,&b): salva o contexto atual na variável a e restaura o contexto salvo anteriormente na variável b;
- makecontext(&a, ...): ajusta parâmetros internos do contexto salvo em a;
- ucontext_t: as variáveis a e b são do tipo ucontext_t. Este tipo armazena um contexto.
Busque mais informações sobre estas funções utilizando o programa manpage do Linux (ex.: man getcontext).
Estude o código no arquivo pingpong.c abaixo e explique seu funcionamento.
#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í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.
- Exercício 3: O que acontece se um dos threads é colocado para dormir? Todos os demais threads param a sua execução.
- Exercício 4: Note que um thread somente deixa a execução para outro thread de forma explícita. Será que é possível realizar um escalonamento de threads de forma similar ao que o kernel faz com os processos? Ver http://www.cplusplus.com/forum/unices/6452/
AULA 11 ?? - REMOTA - Dia 27/03/2020
Objetivos/Conteúdos
- Rever Threads em nível de Aplicação
- Escalonamento de Threads de Aplicação
- Exemplo: Escalalonador semi-automático
Ainda Threads - Um escalonador semi-automático" Ainda Threads - Um escalonador semi-automático usando sinais
Neste laboratório serão aprimorados os threads em nível de aplicação vistos na aula anterior. Usaremos sinais do Unix/Linux para escalonar os threads via handlers.
Exemplo 1:
No Linux/Unix o kernel ou um processo podem enviar sinais para outros processos. Trata-se de uma notificação de que um evento ocorreu. O processo pode ter tratadores (handlers) para estes sinais de forma a tratá-los de forma assíncrona. Pode-se fazer uma analogia com a interrupção por hardware de um programa em execução. Um tratador da interrupção trata a causa da mesma (evento) e no final do tratador (handler) é restaurado o contexto do programa interrompido. Por exemplo, quando um filho se encerra, o pai recebe do kernel o sinal SIGCHLD.
No exemplo abaixo utilizamos o sinail USR1 de uso geral (disponível para o usuário). Associamos um handler a este sinal (sig_handler). O programa sendo executado, fica em loop no main(). Usando um outro terminal podemos enviar um sinal e verificar o efeito do mesmo.
#include<stdio.h> #include <stdlib.h> #include<signal.h> #include<unistd.h> void sig_handler(int signo) { int x; printf("Turma de SOP: recebido SIGUSR1\n"); } int main(void) { int x; if (signal(SIGUSR1, sig_handler) == SIG_ERR) { printf("\nProblemas com SIGUSR1\n"); exit(-1); } // Loop eterno dormindo 1s while(1) { sleep(1); } return 0; }
Fazer no terminal:
ps aux
anotar o pid do processo e enviar o sinal:
kill -USR1 pid
Exemplo 2:
O programa anterior é aprimorado para começar a executar um thread Ping similar a aula anterior. Ainda não existe escalonamento dos threads. Somente o thread ping é executado. O sinal USR1 pode ser enviado para o processo interrompendo a execução do thread.
#include<stdio.h> #include <stdlib.h> #include<signal.h> #include<unistd.h> #include <ucontext.h> #define STACKSIZE 32768 /* tamanho de pilha das threads */ #define PING_ID 1 /* VARIÁVEIS GLOBAIS */ ucontext_t cPing, cPong, cMain; int curr_thread; /* Handler para tratar o sinal */ void sig_handler(int signo) { printf("Turma de SOP: recebido SIGUSR1\n"); } /* Funções-comportamento das Tarefas */ void f_ping(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void f_pong(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void preparar_contexto_ping() { char *stack; 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"); } void preparar_contexto_pong() { char *stack; 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"); } int main(void) { int x; printf ("Main INICIO\n"); preparar_contexto_ping(); preparar_contexto_pong(); if (signal(SIGUSR1, sig_handler) == SIG_ERR) { printf("\nProblemas com SIGUSR1\n"); exit(-1); } curr_thread=PING_ID; swapcontext(&cMain, &cPing); // loop eterno que nunca é executado while(1) { sleep(1); } return 0; }
Enviar o sinal USR1 para ver o efeito.
Exemplo 3:
Neste exemplo preparamos o handler para chavear o contexto de ping para pong e vice-versa. A cada USR1 recebido será realizada a troca de contexto.
#include<stdio.h> #include <stdlib.h> #include<signal.h> #include<unistd.h> #include <ucontext.h> #define STACKSIZE 32768 /* tamanho de pilha das threads */ #define PING_ID 1 #define PONG_ID 2 /* VARIÁVEIS GLOBAIS */ ucontext_t cPing, cPong, cMain; int curr_thread; /* Handler para tratar o sinal */ void sig_handler(int signo) { printf("Turma de SOP: recebido SIGUSR1\n"); if (curr_thread==PING_ID) { curr_thread=PONG_ID; swapcontext(&cPing, &cPong); } else { curr_thread=PING_ID; swapcontext(&cPong, &cPing); } } /* Funções-comportamento das Tarefas */ void f_ping(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void f_pong(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void prepara_contexto_ping() { char *stack; 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"); } void prepara_contexto_pong() { char *stack; 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"); } int main(void) { int x; printf ("Main INICIO\n"); prepara_contexto_ping(); prepara_contexto_pong(); if (signal(SIGUSR1, sig_handler) == SIG_ERR) { printf("\nProblemas com SIGUSR1\n"); exit(-1); } curr_thread=PING_ID; swapcontext(&cMain, &cPing); // A long long wait so that we can easily issue a signal to this process while(1) { sleep(1); } return 0; }
Exercício: Use o comando htop para verificar como estes threads são vistos pelo SOP.
Exercício: Tente analisar as pilhas dos threads e verificar sob qual pilha o handler se executa.
Exercício 4:
Neste ponto associamos um timer ao handler com o apoio do sinal ALRM. Agora os threads são escalonados automaticamente.
#include<stdio.h> #include <stdlib.h> #include<signal.h> #include<unistd.h> #include <ucontext.h> #include <sys/time.h> #define STACKSIZE 32768 /* tamanho de pilha das threads */ #define PING_ID 1 #define PONG_ID 2 #define TIME_SLICE 5 /* VARIÁVEIS GLOBAIS */ ucontext_t cPing, cPong, cMain; int curr_thread; /* Handler para tratar o sinal */ void sig_handler(int signo) { printf("SOP da Turma 2020-1: recebido SIGALRM\n"); alarm(TIME_SLICE); if (curr_thread==PING_ID) { curr_thread=PONG_ID; swapcontext(&cPing, &cPong); } else { curr_thread=PING_ID; swapcontext(&cPong, &cPing); } } /* Funções-comportamento das Tarefas */ void f_ping(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void f_pong(void * arg) { int i=0; printf("%s iniciada\n", (char *) arg); for (;;) { printf("%s %d\n", (char *) arg, i++); sleep(1); } printf("%s FIM\n", (char *) arg); } void preparar_contexto_ping() { char *stack; 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"); } void preparar_contexto_pong() { char *stack; 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"); } void preparar_handler() { if (signal(SIGALRM, sig_handler) == SIG_ERR) { printf("\nProblemas com SIGUSR1\n"); exit(-1); } alarm(TIME_SLICE); } int main(void) { int x; printf ("Main INICIO\n"); preparar_contexto_ping(); preparar_contexto_pong(); preparar_handler(); curr_thread=PING_ID; //ajusta primeiro thread swapcontext(&cMain, &cPing); //nunca mais volta... return 0; }
Exercício Proposto 5
Refinar o handler de chaveamento de threads para que permita escalonar N f_pings, onde N será passado na linha de comando. Sugestão: criar uma fila ou vetor de identificadores de threads.
AULA 12 ?? - REMOTA - Dia 27/03/2020
Objetivos/Conteúdos
- Conceito de Preempção
- Critérios de Escalonamento
- Algoritmos de Escalonamento
- FCFS
- Menor-Job-Primeiro
- Prioridades
- Round Robin
Slides USados mna Aula
- Slides do Livro do Professor Maziero e do Prof.Arliones
Referências
- Cap.51, 5.2 e 5.3 do Livro de Silberschatz
- Slides Silberschatz
- Apresentação sobre Gerenciamento de Processos
- Cap.6 do Livro do Prof.Maziero (http://wiki.inf.ufpr.br/maziero/doku.php?id=socm:start)
- https://www.geeksforgeeks.org/vector-in-cpp-stl/
AULA 13 ?? - REMOTA - Dia 14/04/2020
Objetivos/Conteúdos
- Exemplo do Escalonador RR
Código de Exemplo
/** User-level threads example. Orion Sky Lawlor, olawlor@acm.org, 2005/2/18 (Public Domain) */ #include <stdio.h> #include <stdlib.h> #include <ucontext.h> /* for makecontext/swapcontext routines */ #include <queue> /* C++ STL queue structure */ #include <vector> #include<signal.h> #include<unistd.h> #include <ucontext.h> #include <sys/time.h> #define TIME_SLICE 1 typedef void (*threadFn)(void); class thread_cb { // bloco de controle do thread int id_thread; public: ucontext_t contexto; //metodos que manipulam o thread control block thread_cb(threadFn p, int id) //construtor { int stackLen=4*1024; char *stack=new char[stackLen]; getcontext(&contexto); contexto.uc_stack.ss_sp=stack; contexto.uc_stack.ss_size=stackLen; contexto.uc_stack.ss_flags=0; makecontext(&contexto,p,0); id_thread = id; }; ucontext_t *get_context() { return &contexto; }; }; std::queue<class thread_cb *> ready_pool; // fila de pronto int id_thread = 0; int id1,id2,id3; class thread_cb *curr_thread=NULL; int add_thread(threadFn func) //criar thread e retornar o id do thread criad { class thread_cb *p = new thread_cb(func, ++id_thread); ready_pool.push(p); return id_thread; } void dispatcher(ucontext_t *old_task, ucontext_t *new_task) { if (old_task!=NULL) swapcontext(old_task, new_task); else setcontext(new_task); } void scheduler_rr() { class thread_cb *next,*last; if(curr_thread!=NULL) { printf("Aqui\n"); ready_pool.push(curr_thread); last=curr_thread; next=ready_pool.front(); ready_pool.pop(); curr_thread=next; dispatcher(last->get_context(), curr_thread->get_context()); } else { next=ready_pool.front(); ready_pool.pop(); curr_thread = next; dispatcher(NULL, next->get_context()); } } void sig_handler(int signo) { printf("SOP da Turma 2019-2: recebido SIGALRM\n"); alarm(TIME_SLICE); if (ready_pool.empty()) { printf("Nothing more to run!\n"); exit(0); } scheduler_rr(); } void preparar_handler() { if (signal(SIGALRM, sig_handler) == SIG_ERR) { printf("\nProblemas com SIGUSR1\n"); exit(-1); } alarm(TIME_SLICE); } void runA(void) { int x=1; for (;;) { x++; printf("Thread A x=%d\n", x); } } void runB(void) { int x=1; for (;;) { x++; printf("Thread B x=%d\n", x); if(x>90000000L) yield_thread(); // abandonar a CPU } } void runC(void) { int x=1; for (;;) { x++; printf("Thread B x=%d\n", x); if (x>1000000L) //consultar um timer global del_thread(id2); } } main() { id1 = add_thread(runA); id2 = add_thread(runB); id3 = add_thread(runC); preparar_handler(); for(;;); }