Introdução aos sistemas operacionais
Olá, Habr! Quero apresentar a vocês uma série de artigos traduzidos de uma literatura que considero interessante — OSTEP. Este material explora a fundo o funcionamento dos sistemas operacionais semelhantes ao Unix, particularmente — o trabalho com processos, vários escalonadores, memória e outros componentes que compõem um sistema operacional moderno. Você pode olhar o original de todo o material aqui . Peço que considerem que a tradução foi feita de forma não profissional (de maneira bastante livre), mas espero ter mantido o sentido geral.
Os trabalhos de laboratório sobre este assunto podem ser encontrados aqui:
- original:
- original:
- minha adaptação pessoal:
Partes adicionais:
E você também pode acessar meu canal em =)
Vamos considerar a abstração mais fundamental que o sistema operacional oferece aos usuários: o processo. A definição de um processo é bastante simples — é um programa em execução. Um programa por si só é uma coisa inerte, armazenada no disco — é um conjunto de instruções e, possivelmente, alguns dados estáticos, aguardando o momento de ser iniciado. É o sistema operacional que pega esses bytes e os inicia, transformando o programa em algo útil.
Na maioria das vezes, os usuários desejam executar mais de um programa ao mesmo tempo, por exemplo, você pode abrir em seu laptop um navegador, um jogo, um reprodutor de mídia, um editor de texto e assim por diante. De fato, um sistema típico pode executar dezenas, até centenas de processos simultaneamente. Esse fato torna o sistema mais fácil de usar, você nunca precisa se preocupar se o CPU está livre, você simplesmente inicia os programas.
Daí surge o problema: como criar a ilusão de múltiplos CPUs? Como o sistema operacional pode gerar a ilusão de um número quase infinito de CPUs, mesmo que você tenha apenas um CPU físico?
O sistema operacional cria essa ilusão por meio da virtualização do CPU. Ao iniciar um processo, depois interrompê-lo, iniciar outro e assim por diante, o sistema operacional pode manter a ilusão de que existem vários CPUs virtuais, embora na verdade haja apenas um ou alguns processadores físicos. Essa técnica é chamada de partição de recursos do CPU por tempo. Essa técnica permite que os usuários executem quantos processos simultâneos quiserem. O preço dessa solução é a performance – uma vez que, se o CPU é compartilhado por vários processos, cada processo será processado mais lentamente.
Para implementar a virtualização do CPU, especialmente para fazê-lo de maneira eficaz, o sistema operacional necessita de suporte tanto de baixo nível quanto de alto nível. O suporte de baixo nível é chamado de mecanismos — são métodos ou protocolos de baixo nível que implementam a parte necessária da funcionalidade. Um exemplo dessa funcionalidade é a troca de contexto, que permite ao sistema operacional parar um programa e iniciar outro no processador. Essa partição por tempo é implementada em todos os sistemas operacionais modernos.
No topo desses mecanismos, existe uma lógica incorporada no sistema operacional, sob a forma de "políticas". Política é um algoritmo de tomada de decisão do sistema operacional. Tais políticas, por exemplo, determinam qual programa deve ser iniciado (da lista de comandos) em primeiro lugar. Assim, por exemplo, essa tarefa será resolvida por uma política chamada política de agendamento (scheduling policy) e, na escolha da decisão, será guiada por dados como: histórico de execução (qual programa foi executado por mais tempo no último minuto), que carga este processo está gerando (quais tipos de programas foram iniciados), métricas de desempenho (se o sistema foi otimizado para interação ou para largura de banda) e assim por diante.
Abstração: processo
A abstração de um programa em execução, realizada pelo sistema operacional, é o que chamamos de processo. Como já mencionado anteriormente, um processo é simplesmente um programa em execução em um dado instante no tempo. Um programa com o qual podemos obter informações consolidadas de diversos recursos do sistema e que são acessados ou afetados por esse programa durante sua execução.
Para entender os componentes de um processo, precisamos compreender os estados do sistema: o que um programa pode ler ou alterar durante sua operação. Em qualquer momento, é necessário entender quais elementos do sistema são importantes para a execução do programa.
Um dos elementos óbvios do estado do sistema que o processo inclui é a memória. Instruções são armazenadas na memória. Os dados que o programa lê ou escreve também são armazenados na memória. Assim, a memória que um processo pode endereçar (o chamado espaço de endereçamento) é parte do processo.
Os registradores também fazem parte do estado do sistema. Muitas instruções são orientadas a alterar o valor dos registradores ou ler seus valores e, assim, os registradores também se tornam uma parte importante do funcionamento do processo.
É importante notar que o estado da máquina também é formado por alguns registradores especiais. Por exemplo, IP — ponteiro de instrução é o ponteiro para a instrução que o programa está executando no momento. Existe também ponteiro da pilha e o correspondente ponteiro de quadro, que são usados para gerenciar: parâmetros de funções, variáveis locais e endereços de retorno.
Por fim, os programas frequentemente acessam a ROM (memória apenas de leitura). Essas informações sobre “I/O” (entrada e saída) devem incluir uma lista de arquivos que estão abertos pelo processo no momento.
API do Processo
Para melhorar a compreensão do funcionamento do processo, vamos examinar exemplos de chamadas de sistema que devem ser incluídas em qualquer interface do sistema operacional. Essas APIs estão, de uma forma ou de outra, disponíveis em qualquer SO.
● Criar (criação): O SO deve ter algum método que permita criar novos processos. Quando você digita um comando no terminal ou executa um aplicativo dando um duplo clique no ícone, uma chamada é enviada ao SO para criar um novo processo e, em seguida, iniciar o programa especificado.
● Remoção: Como há uma interface para criar um processo, o SO também deve fornecer a capacidade de forçar a exclusão de um processo. A maioria dos programas naturalmente iniciará e terminará por conta própria à medida que forem executados. Caso contrário, o usuário gostaria de ter a possibilidade de encerrá-los, portanto, a interface para interromper um processo seria útil.
● Aguardar (espera): Às vezes, é útil esperar pela conclusão de um processo, então algumas interfaces são fornecidas que permitem essa espera.
● Controle Diversificado (controle diverso): Além de encerrar e esperar por um processo, existem outros métodos de controle variados. Por exemplo, a maioria dos SOs fornece a possibilidade de congelar um processo (interromper sua execução por um período) e, em seguida, retomar (continuar a execução).
● Status (estado): Existem várias interfaces para obter informações sobre o status de um processo, como a duração de sua operação ou em que estado ele se encontra atualmente.

Criação do Processo: detalhes
Uma das coisas interessantes é como os programas se transformam em processos. Especialmente, como o SO inicia e executa o programa. Como um processo é criado exatamente.
Em primeiro lugar, o sistema operacional deve carregar o código do programa e os dados estáticos na memória (no espaço de endereçamento do processo). Os programas geralmente estão armazenados em disco ou em uma unidade de estado sólido em algum formato executável. Assim, o processo de carregamento do programa e dos dados estáticos na memória exige que o sistema operacional tenha a capacidade de ler esses bytes do disco e posicioná-los em algum lugar na memória.
Nos sistemas operacionais mais antigos, o processo de carregamento era feito de maneira apressada (eagerly), ou seja, isso significa que o código era carregado na memória por completo antes da execução do programa. Sistemas operacionais modernos fazem isso de forma preguiçosa (lazily), ou seja, carregando pedaços de código ou dados apenas quando são necessários para o programa durante sua execução.
Depois que o código e os dados estáticos são carregados na memória, o sistema operacional precisa realizar algumas outras tarefas antes de iniciar o processo. Uma quantidade de memória deve ser alocada para a pilha. Os programas utilizam a pilha para variáveis locais, parâmetros de funções e endereços de retorno.. O sistema operacional aloca essa memória e a entrega ao processo. A pilha também pode ser alocada com alguns argumentos, especificamente preenchendo os parâmetros da função main(), como um array argc e argv.
O sistema operacional também pode alocar uma quantidade de memória para o heap do programa. O heap é utilizado pelos programas para dados que são dinamicamente alocados por solicitação explícita.. Os programas solicitam esse espaço chamando a função para alocação de memória pode falhar se precisar da memória RAM atualmente ocupada pelo ARC. Mas há boas razões para usar o cache próprio, pelo menos agora. e o liberam explicitamente, chamando a função free(). O heap é necessário para estruturas de dados como: listas encadeadas, tabelas hash, árvores e outras. Inicialmente, uma pequena quantidade de memória é alocada para o heap, mas com o tempo, à medida que o programa opera, o heap pode solicitar uma quantidade maior de memória através da chamada de API de biblioteca malloc(). O sistema operacional está envolvido no processo de alocação de mais memória para ajudar a satisfazer esses pedidos.
O sistema operacional também executará tarefas de inicialização, especialmente aquelas relacionadas à entrada e saída. Por exemplo, em sistemas UNIX, cada processo, por padrão, tem 3 descritores de arquivo abertos: para o fluxo padrão de entrada, saída e erro. Esses descritores permitem que os programas leiam a entrada do terminal e também enviem informações para a tela.
Assim, ao carregar o código e os dados estáticos na memória, criar e inicializar a pilha, além de realizar outras tarefas relacionadas à execução de operações de entrada e saída, o sistema operacional prepara o ambiente para a execução do processo. No final, resta uma última tarefa: iniciar a execução do programa através de seu ponto de entrada, chamado de função main(). Ao entrar na execução da função main(), o sistema operacional transfere o controle da CPU para o novo processo, permitindo assim que o programa comece a ser executado.
Estado do processo
Agora que temos uma certa compreensão do que é um processo e como ele é criado, vamos listar os estados do processo nos quais ele pode se encontrar. Na forma mais simples, um processo pode estar em um desses estados:
● Em execução. No estado em execução, o processo está sendo executado na CPU. Isso significa que as instruções estão sendo processadas.
● Pronto. No estado de pronto, o processo está pronto para iniciar, mas por algum motivo o sistema operacional não o executa naquele momento.
● Bloqueado. No estado bloqueado, o processo está realizando algumas operações que o impedem de estar pronto para execução até que ocorra algum evento. Um exemplo comum é quando um processo inicializa uma operação de IO, tornando-se bloqueado, permitindo que outro processo utilize a CPU.

Esses estados podem ser imaginados como um gráfico. Como podemos ver na imagem, o estado do processo pode mudar entre RUNNING e READY a critério do sistema operacional. Quando o estado do processo muda de READY para RUNNING, isso significa que o processo foi agendado. No sentido oposto, é retirado da programação. No momento em que o processo se torna BLOCKED, por exemplo, ao inicializar uma operação de IO, o sistema operacional o manterá nesse estado até que um certo evento ocorra, como a conclusão do IO. Nesse momento, ele transita para o estado READY e, possivelmente, instantaneamente para o estado RUNNING, se assim decidir o sistema operacional.
Vamos dar uma olhada em um exemplo de como dois processos transitam por esses estados. Para começar, suponhamos que ambos os processos estejam em execução e cada um utilize apenas a CPU. Nesse caso, seus estados serão os seguintes.

No exemplo a seguir, o primeiro processo solicita IO após algum tempo de execução e muda para o estado BLOQUEADO, permitindo que outro processo seja iniciado (FIG 1.4). O SO vê que o processo 0 não está utilizando o CPU e inicia o processo 1. Durante a execução do processo 1, o IO é concluído e o status do processo 0 muda para PRONTO. Por fim, o processo 1 é finalizado, e ao término dele, o processo 0 é iniciado, executado e conclui sua tarefa.

Estrutura de dados
O SO é um programa e, como qualquer outro programa, possui algumas estruturas de dados-chave que rastreiam várias informações relevantes. Para rastrear o estado de cada processo, o SO mantém uma certa lista de processos para todos os processos no estado PRONTO, além de algumas informações adicionais para acompanhar os processos que estão sendo executados no momento. Além disso, o SO deve acompanhar também os processos bloqueados. Após a conclusão do IO, o SO deve despertar o processo necessário e mudá-lo para o estado pronto para execução.
Assim, por exemplo, o SO deve preservar o estado dos registradores da CPU. No momento em que o processo é interrompido, o estado dos registradores é salvo no espaço de endereço do processo, e ao continuar sua execução, os valores dos registradores são restaurados, continuando assim a execução desse processo.
Além dos estados pronto, bloqueado e em execução, existem outros estados. Às vezes, ao ser criado, um processo pode ter o estado INIT. Finalmente, um processo pode ser colocado no estado FINAL quando já foi concluído, mas as informações sobre ele ainda não foram limpas. Em sistemas UNIX, esse estado é chamado de processo zumbi. Esse estado é útil em casos em que o processo pai deseja saber o código de retorno do filho, por exemplo, geralmente 0 sinaliza uma conclusão bem-sucedida, e 1 uma falha, no entanto, programadores podem criar códigos de saída adicionais, sinalizando diferentes problemas. Ao finalizar, o processo pai faz a última chamada de sistema, como wait(), para aguardar a conclusão do processo filho e sinalizar ao SO que pode limpar quaisquer dados associados ao processo finalizado.

Pontos-chave da palestra:
● Processo — a principal abstração de um programa em funcionamento em um sistema operacional. A qualquer momento, um processo pode ser descrito pelo seu estado: o conteúdo da memória em seu espaço de endereçamento, o conteúdo dos registradores do processador, incluindo o ponteiro de instrução e o ponteiro de pilha, além de informações sobre I/O, como arquivos abertos que estão sendo lidos ou escritos.
● API do Processo consiste em chamadas que programas podem fazer em relação a processos. Normalmente, são chamadas de criação, exclusão ou outras.
● Um processo está em um dos muitos estados, incluindo running, ready, blocked. Diferentes eventos, como agendamento, exclusão do agendamento ou espera, podem fazer a transição do estado do processo de um para outro.
● Lista de Processos contém informações sobre todos os processos no sistema. Cada entrada nela é chamada de bloqueio de controle de processo, que na realidade é uma estrutura que contém todas as informações necessárias sobre um processo específico.
Fonte: habr.com
