















































Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Gerencia de Processadores de alto rendimento
Tipologia: Resumos
1 / 55
Esta página não é visível na pré-visualização
Não perca as partes importantes!
















































Definição de processos e threads e da forma como esses elementos devem ser estruturados para a construção de sistemas eficientes.
Atualmente, até os mais simples dispositivos contam com a capacidade de multiprocessamento. Os sistemas operacionais, acompanhando o rápido desenvolvimento da tecnologia, fornecem mecanismos que possibilitam a construção de sistemas concorrentes que buscam tirar o máximo proveito desta capacidade. Saber explorar esta possibilidade é fundamental para a formação de profissionais habilitados a resolverem os desafios demandados por sistemas que buscam alto desempenho.
Antes de iniciar o conteúdo deste tema, é desejável ter acesso a um computador (ou máquina virtual) com Linux instalado. Para os exemplos deste tema, foi utilizado o Ubuntu Desktop 20.04 LTS.
Descrever os conceitos de processos
Compreender como ocorre a construção de programas concorrentes
Identificar o mecanismo de comunicação entre processos
Comparar as diferentes formas de escalonamento
Fonte: Shutterstock/whiteMocca
Para compilar um programa chamado prog.c basta entrar no shell, no diretório onde se encontra o programa, e executar o comando:
gcc prog.c
Será criado um arquivo executável de nome a.out.
Para compilar um programa e escolher o nome do arquivo executável que será gerado, utilize o parâmetro -o. Por exemplo, para compilar o programa prog.c e gerar como saída um arquivo executável prog , utilize o comando:
gcc prog.c -o prog
Para executar o programa prog que acabou de ser compilado, execute o comando:
./prog
MODELO DE PROCESSO
Os primeiros sistemas permitiam a execução de apenas um programa de cada vez, que deveria ter o controle completo do sistema e acesso a todos os seus recursos. Os sistemas atuais permitem que diversos programas sejam carregados na memória e executados simultaneamente.
Essa evolução tornou necessário um controle maior na divisão de tarefas dos vários programas, resultando na noção de processo.
Em um sistema multiprogramável , a unidade central de processamento (UCP) alterna entre processos, dedicando um pouco de seu tempo a cada um, dando a ilusão de paralelismo. Este esquema costuma ser chamado de pseudoparalelismo.
SISTEMA MULTIPROGRAMÁVEL
Sistema que permite a execução de mais de um programa ao mesmo tempo.
Neste modelo, todo software executado no computador é organizado em processos sequenciais , também chamado de processos. O modelo de processos foi desenvolvido para tornar o paralelismo mais fácil de tratar.
Um processo é um programa em execução, incluindo os valores atuais dos registradores e variáveis, assim como seu espaço de endereçamento. Um programa por si só não é um processo, mas uma
entidade passiva. Um processo é uma entidade ativa, com um contador de instruções e um conjunto de registradores a ele associado.
ESPAÇO DE ENDEREÇAMENTO
Conjunto de endereços de memória que um processo pode acessar.
CONTADOR DE INSTRUÇÕES
Registrador de uma unidade central de processamento que indica qual é a posição atual na sequência de execução de um processo. Dependendo dos detalhes da arquitetura, ele armazena o endereço da instrução que está sendo executada ou o endereço da próxima instrução.
Embora dois processos possam estar associados a um mesmo programa, são duas sequências de execução distintas.
Conceitualmente, cada processo tem sua própria UCP. Com a UCP alternando entre os processos, a velocidade com que um processo executa não será uniforme.
Fonte: Yduqs/Acervo pessoal Exemplo de sistema de tempo compartilhado com 4 processos concorrentes.
MULTIPROCESSADOR
Sistema com mais de um processador nos quais os processadores compartilham uma memória comum.
Em todos esses casos, um novo processo é criado por outro já existente, executando uma chamada de sistema de criação de processo. O que esse processo faz é executar uma chamada de sistema para criar o processo.
No Linux, a chamada de sistema mais comum para a criação de processos é a fork(). Essa chamada cria um processo idêntico ao processo que a chamou. Após a fork() , os dois processos, o pai e o filho, têm a mesma imagem de memória, as mesmas variáveis de ambiente e os mesmos arquivos abertos.
O código a seguir, em Linguagem C, exemplifica a criação de um novo processo com a chamada de sistema fork().
#include #include #include
int main() { int resultado, pid, ppid; resultado = fork(); if (resultado < 0) printf("Algo deu errado!!!\n"); pid = getpid(); if (resultado == 0) { ppid = getppid(); printf("Eu sou o processo filho, meu PID é %d e meu pai tem PID=%d.\n", pid, ppid); } if (resultado > 0) {
printf("Eu sou o processo pai, meu PID é %d e meu filho tem PID=%d.\n", pid, resultado); waitpid(resultado, NULL, 0); } }
Quando fork() é executada, o resultado do processamento é armazenado na variável “resultado”. O processo pai (que fez a chamada) receberá na variável “resultado” o PID do processo filho, enquanto o processo filho receberá na variável “resultado” o valor 0. Assim, para saber quem é o processo pai e quem é o processo filho, é necessário verificar o valor em “resultado”.
PID
Abreviação de Process ID (identificação do processo). É um valor que identifica um processo de forma única no sistema operacional.
De início, verifica-se a ocorrência de algum erro na criação do processo filho. Caso tenha ocorrido um erro, “resultado” receberá um valor negativo.
A seguir, o comando “pid = getpid();” obtém o PID do processo corrente.
Então, o processo verifica o valor de “resultado”. Se for zero, é o processo filho que está executando e, nesse, caso o processo executa “ppid = getppid();” para obter o PID do processo pai. Se o valor de “resultado” for maior que zero, significa que quem está executando é o processo pai. Ambos (pai e filho) mostram na tela os respectivos PID.
Por fim, o processo pai executa a chamada de sistema “waitpid(resultado, NULL, 0);” para, antes de terminar, aguardar o término da execução do filho.
Quando o fork() é utilizado para a criação de um processo que executará o código de outro programa, a chamada de sistema execve() deve ser utilizada para que o processo filho mude sua imagem de
O processo também pode terminar quando descobre um erro fatal. Ele pode solicitar ao usuário uma nova entrada de dados ou simplesmente encerrar sua execução de maneira similar à saída normal.
Saída por erro (voluntária)
Outra razão para o término é um erro causado pelo processo, muitas vezes decorrente de um erro de programa. Exemplos incluem executar uma instrução ilegal, referenciar uma memória não existente ou dividir por zero.
Morto por outro processo (involuntário)
A quarta razão pela qual um processo pode ser finalizado é a execução de uma chamada de sistema dizendo ao sistema operacional para matar outro processo. Para um processo matar outro, é necessário autorização.
Em alguns sistemas, quando um processo é finalizado, todos os processos que ele criou são finalizados também. Nem o Linux nem o Windows funcionam desta forma.
HIERARQUIA DE PROCESSOS
Em alguns sistemas, quando um processo cria outro, o processo pai e o processo filho continuam associados. O processo filho pode criar mais processos, formando uma hierarquia de processos. No Linux, um processo e todos os seus filhos e demais descendentes formam um grupo de processos.
No Linux, um processo especial chamado systemd (ou init , dependendo da versão) está presente na imagem de inicialização do sistema. É o primeiro processo a ser executado e é responsável por iniciar a execução dos demais processos do sistema operacional. Cada um desses processos pode iniciar mais processos. Desse modo, todos os processos no Linux pertencem a uma única árvore, com o systemd (ou init ) em sua raiz.
Fonte: Shutterstock/jivacore
A imagem a seguir exemplifica a árvore de processos em um sistema Linux por meio da execução do comando “ pstree ”.
systemd-+-ModemManager---2[{ModemManager}] |-NetworkManager---2[{NetworkManager}] |-VBoxService---8[{VBoxService}] |-accounts-daemon---2[{accounts-daemon}] |-acpid |-avahi-daemon---avahi-daemon |-colord---2[{colord}] |-cron |-cups-browsed---2[{cups-browsed}] |-cupsd |-dbus-daemon |-gdm3-+-gdm-session-wor-+-gdm-wayland-ses-+-gnome-session-b---3[{gnome-session-b}] | | | -2*[{gdm-wayland-ses}] | |-2[{gdm-session-wor}] | -2*[{gdm3}] |-2*[kerneloops] |-login---bash---pstree |-networkd-dispat |-polkitd---2*[{polkitd}] |-rsyslogd---3*[{rsyslogd}] |-rtkit-daemon---2*[{rtkit-daemon}] |-snapd---8*[{snapd}] |-switcheroo-cont---2*[{switcheroo-cont}] |-systemd---(sd-pam) |-systemd-+-(sd-pam) | |-at-spi-bus-laun-+-dbus-daemon | |-3[{at-spi-bus-laun}] | |-at-spi2-registr---2[{at-spi2-registr}] | |-dbus-daemon | |-gjs---4[{gjs}] | |-gnome-keyring-d---3[{gnome-keyring-d}] | |-gnome-session-b---3[{gnome-session-b}] | |-gnome-session-c---{gnome-session-c} | |-gnome-shell-+-Xwayland | | |-ibus-daemon-+-ibus-engine-sim---2[{ibus-engine-sim}] | | | |-ibus-memconf---2*[{ibus-memconf}]
|-whoopsie---2*[{whoopsie}] `-wpa_supplicant
Eventualmente, um processo que está em execução necessita parar momentaneamente sua execução por estar aguardando uma informação ainda não disponível ou porque já foi executado por muito tempo e precisa liberar a UCP para outro processo.
Um processo pode transitar por diferentes estados, que dependem do sistema operacional. De forma geral, podemos dizer que um processo pode estar nos estados novo, executando, pronto, bloqueado ou terminado.
Fonte: Yduqs/Acervo pessoal
Quando um processo é criado, ele se inicia no estado novo. O processo já existe, mas ainda precisam ser tomadas algumas providências pelo sistema operacional para que o processo possa iniciar sua execução. Quando tudo está pronto, o processo faz a transição 1 e muda para o estado pronto.
O processo reúne todas as condições para ser executado quando está no estado pronto , faltando apenas que o processador fique disponível para sua execução. Quando um processador fica disponível e o processo é selecionado para execução, ele faz a transição 2 e vai para o estado executando.
No estado executando , o processo tem suas instruções executadas pelo processador e três coisas podem acontecer:
processamento do processador de um computador. Nesse caso, ocorre a transição 4, e o processo vai para o estado bloqueado.
O processo que vai para o estado bloqueado permanece nele até que seja concluída a operação que aguardava. Quando isso ocorre, o processo passa pela transição 5 e vai para o estado pronto até ser novamente selecionado para execução.
O estado terminado é para os processos que não serão mais executados. Quando está neste estado, o sistema operacional deve providenciar a desalocação dos recursos que ainda estejam alocados ao processo. Somente após a desalocação de todos os recursos, o processo deixa de existir no sistema.
Para implementar o modelo de processos, o Linux mantém uma tabela de processos, com uma entrada por processo. Esta entrada é chamada de Bloco de Controle de Processo – BCP ( Process Control Block – PCB) e contém todas as informações do processo. Algumas entradas do BCP são:
Estado do processo
Prioridade do processo
Número do processo
Registradores da UCP
Informações relativas ao gerenciamento de memória
Informações de contabilidade
Informações sobre operações de E/S
Essa visão dá origem ao seguinte modelo:
Fonte: Yduqs/
O nível mais baixo do sistema operacional é o escalonador (também conhecido como agendador ). Ele cuida do gerenciamento de interrupções e dos detalhes de como iniciar e parar processos. Também costuma ser muito pequeno.
Os processos no Linux comportam-se como processos sequenciais tradicionais. É um sistema operacional multiusuário/multitarefa, que permite a execução simultânea de diversos processos, que podem pertencer a diferentes usuários. Mesmo que haja apenas um usuário logado no sistema, é comum a existência de diversos daemons em execução.
Fonte: Shutterstock/Paolo De Gasperis
Um exemplo é o daemon de impressão, responsável por fazer a alocação da impressora e controlar o envio de trabalhos de impressão. É uma parte importante do sistema, pois permite o compartilhamento da impressora por diversos processos, possivelmente pertencentes a diferentes usuários.
A forma usual para criação de processos no Linux ocorre por meio da chamada de sistema fork() , conforme você já estudou neste tema.
O processo filho recebe uma cópia exata do espaço de endereçamento do processo pai. Todos os valores de variáveis e demais objetos em memória serão idênticos, porém alterações realizadas por um dos processos em qualquer conteúdo de memória não afetarão o outro.
Arquivos que foram abertos antes da chamada fork() permanecem abertos para o processo pai e para o processo filho. As alterações realizadas por qualquer um destes processos se tornam imediatamente disponíveis para o outro. Processos são identificados por um número inteiro conhecido como PID (identificação do processo).
Algumas chamadas de sistema do Linux para o gerenciamento de processos são:
Chamada de sistema
Descrição
fork() Cria um processo filho idêntico ao processo pai. Para o processo pai, retorna o PID do processo filho e, para o processo filho, retorna o valor 0.
waitpid() Espera até que o processo filho passado como parâmetro termine sua execução.
execve()
Substitui a imagem de execução de um processo, fazendo com que, no lugar do processo corrente, seja executado o código do programa passado como parâmetro.
exit() Termina a execução do processo e retorna como^ status^ o valor passado como parâmetro.
Faça o download do documento Comandos do Shell , com os principais comandos para Linux.
1. Uma das formas de criação de subprocessos no Linux ocorre por meio da chamada de sistema fork(). Assinale a alternativa verdadeira em relação à chamada de sistema fork().
A alternativa "B " está correta.
A chamada de sistema fork() cria um subprocesso que é cópia exata do processo pai, mas que compartilha com ele apenas os arquivos abertos. Portanto, não há compartilhamento do espaço de endereçamento com o processo pai. Para executar o código de outro programa, é necessário utilizar a chamada de sistema execve() após a chamada de sistema fork().
2. É comum um processo transitar por vários estados durante sua vida em um sistema operacional. Sobre os estados de um processo, é correto afirmar que:
A alternativa "D " está correta.
Um processo no estado bloqueado está aguardando a ocorrência de um evento que fará com que ele possa prosseguir sua execução. Quando ocorrer este evento, o processo deverá ir obrigatoriamente para a fila de processos prontos, onde aguardará a liberação de uma UCP antes de poder entrar em execução.
Compreender como ocorre a construção de programas concorrentes
Quando um processo (processo pai) cria um outro processo, ele é conhecido como subprocesso ou processo filho. O subprocesso, por sua vez, pode criar outros subprocessos.
A utilização de subprocessos permite dividir uma aplicação em partes que podem trabalhar de forma concorrente. Imagine, por exemplo:
Um servidor web que aceite requisições de clientes da internet e coloque as requisições em uma fila. Uma forma simples de implementar este servidor seria criar um processo que pegue a primeira requisição da fila, processe a requisição e devolva o resultado do processamento ao cliente que a solicitou. Após isso, ele pegaria a próxima requisição e faria o mesmo trabalho.
O problema com essa solução é que ela não aproveita a capacidade de multiprocessamento dos sistemas atuais. Como existe apenas um processo em execução, somente um dos processadores do sistema é utilizado para atendimento das requisições. Além disso, se houver várias requisições complexas e demoradas e uma requisição simples, como uma pequena página HTML, entrar no final da fila, esta requisição mais simples, que poderia ser respondida rapidamente, será atendida somente depois que todas as demais forem processadas.
Fonte: Shutterstock/Adhil Jayan
A utilização de subprocessos resolve bem estes problemas. Se o servidor, no lugar de responder sequencialmente a cada requisição, criar um subprocesso para cada uma delas, tirará proveito da capacidade de multiprocessamento do sistema. Como cada requisição será tratada por um processo diferente , as requisições serão espalhadas pelos processadores do sistema, aproveitando sua capacidade de multiprocessamento. Além disso, como as requisições serão tratadas por diferentes processos, elas serão executadas concorrentemente.
Dessa forma, uma requisição simples, como a solicitação de uma página HTML, poderá ser iniciada e respondida rapidamente, ainda que existam outras requisições complexas solicitadas anteriormente e que elas ainda estejam sendo processadas.
O trecho de código abaixo em Linguagem C exemplifica a parte de um servidor web simples que cria subprocessos para atendimento das requisições: