Introdução aos sistemas operacionais
Olá Habr! Quero chamar sua atenção para uma série de artigos - traduções de uma literatura interessante em minha opinião - OSTEP. Este artigo discute profundamente o trabalho de sistemas operacionais semelhantes a unix, ou seja, trabalha com processos, vários agendadores, memória e outros componentes similares que compõem o sistema operacional moderno. O original de todos os materiais que você pode ver
aqui . Observe que a tradução foi feita de maneira não profissional (muito livremente), mas espero ter mantido o significado geral.
O trabalho de laboratório sobre este assunto pode ser encontrado aqui:
Outras partes:
E você pode olhar para o meu canal no
telegrama =)
Trabalho do programa
O que acontece quando um programa funciona? A execução de programas faz uma coisa simples: ele executa instruções. A cada segundo, milhões e até possivelmente bilhões de instruções são extraídas pelo processador da RAM, por sua vez, as decodificam (por exemplo, reconhece a que tipo essas instruções pertencem) e são executadas. Pode ser a adição de dois números, acesso à memória, verificação de condições, mudança de funções e assim por diante. Após a conclusão de uma instrução, o processador continua a executar outra. E assim, instrução por instrução, elas são executadas até que o programa seja concluído.
Este exemplo é naturalmente considerado simplificado - na verdade, para acelerar o processador, o hardware moderno permite executar instruções fora do prazo, calcular possíveis resultados, seguir instruções ao mesmo tempo e truques semelhantes.
Modelo de computação de Von Neumann
A forma simplificada de trabalho descrita por nós é semelhante ao modelo de cálculos de Von Neumann.
Von Neumann é um dos pioneiros em sistemas de computador, ele também é um dos autores da teoria dos jogos . Enquanto o programa está em execução, ocorrem vários outros eventos, muitos outros processos e a lógica de terceiros funcionam, cujo principal objetivo é simplificar o lançamento, a operação e a manutenção do sistema.
Existe um conjunto de softwares responsável pela simplicidade da execução de programas (ou mesmo por permitir a execução de vários programas ao mesmo tempo), que permite que os programas compartilhem a mesma memória e interajam com diferentes dispositivos. Esse conjunto de software (software) é chamado essencialmente de sistema operacional e suas tarefas incluem monitorar o funcionamento correto e eficiente do sistema, além de garantir a facilidade de gerenciamento desse sistema.
Sistema operacional
Um sistema operacional, abreviação de SO, é um complexo de programas interconectados, projetados para gerenciar os recursos do computador e organizar a interação de um usuário com um computador .
O sistema operacional atinge sua eficácia principalmente através da técnica mais importante - a técnica de
virtualização . O sistema operacional interage com um recurso físico (processador, memória, disco e similares) e o transforma em uma forma mais geral, mais poderosa e fácil de usar. Portanto, para um entendimento geral, você pode comparar muito bem o sistema operacional com uma máquina virtual.
Para permitir que os usuários forneçam instruções ao sistema operacional e, assim, usar os recursos de uma máquina virtual (como: iniciar um programa, alocar memória, acessar um arquivo etc.), o sistema operacional fornece uma interface chamada
API (interface de programação de aplicativos) e à qual Você pode fazer chamadas. Um sistema operacional típico torna possível fazer centenas de chamadas do sistema.
E, finalmente, como a virtualização permite que muitos programas funcionem (compartilhando a CPU) e, ao mesmo tempo, acessando suas instruções e dados (compartilhando a memória) e acessando discos (compartilhando dispositivos de E / S) ), o sistema operacional também é chamado de gerenciador de recursos. Cada processador, disco e memória é um recurso do sistema e, portanto, uma das funções do sistema operacional passa a ser a tarefa de gerenciar esses recursos, de maneira eficiente, honesta ou, inversamente, dependendo da tarefa para a qual esse sistema operacional foi projetado.
Virtualização de CPU
Considere o seguinte programa:
(https://www.youtube.com/watch?v=zDwT5fUcki4)

Ele não executa nenhuma ação especial; na verdade, tudo o que faz é chamar a função
spin (), cuja tarefa é alternar o tempo e retornar após um segundo. Assim, ele repete infinitamente a sequência que o usuário passou como argumento.
Execute este programa e passe o símbolo "A" para ele como argumento. O resultado não é muito interessante - o sistema simplesmente executa um programa que exibe periodicamente o símbolo "A".
Agora vamos tentar a opção quando muitas instâncias do mesmo programa estiverem em execução, mas exibindo letras diferentes, para que seja mais compreensível. Nesse caso, o resultado será um pouco diferente. Apesar de termos um processador, o programa é executado simultaneamente. Como isso acontece? Mas acontece que o sistema operacional, não sem a ajuda dos recursos de hardware, cria uma ilusão. A ilusão de que existem vários processadores virtuais no sistema, transformando um processador físico em uma quantidade teoricamente infinita e, assim, permitindo que programas pareçam ser executados simultaneamente. Essa ilusão é chamada de
virtualização da CPU .
Essa imagem levanta muitas questões, por exemplo, se vários programas desejam iniciar simultaneamente, qual será lançado? As "políticas" do sistema operacional são responsáveis por esta pergunta. As políticas são usadas em muitos locais do sistema operacional e respondem a perguntas semelhantes e também são os mecanismos básicos que o sistema operacional implementa. Daí o papel do sistema operacional como um gerenciador de recursos.
Virtualização de memória
Agora vamos olhar para a memória.
O modelo de memória física nos sistemas modernos é representado como uma matriz de bytes . Para ler da memória, você deve especificar o
endereço da célula para acessá-la. Para registrar ou atualizar dados, você também deve especificar os dados e o endereço da célula onde os gravar.
Os acessos à memória ocorrem constantemente durante a operação do programa. Um programa armazena na memória toda a sua estrutura de dados e a acessa seguindo várias instruções. Enquanto isso, as instruções também são armazenadas na memória, por isso também são acessadas para cada solicitação da próxima instrução.
Ligar para malloc ()
Considere o seguinte programa que aloca uma região de memória usando a chamada
malloc () (https://youtu.be/jnlKRnoT1m0):

O programa faz algumas coisas. Primeiro, ele aloca uma certa quantidade de memória (linha 7), depois exibe o endereço da célula selecionada (linha 9), grava zero no primeiro slot da memória alocada. A seguir, o programa entra em um ciclo no qual incrementa o valor registrado na memória no endereço na variável “p”. Ele também exibe o identificador do processo em si.
O ID do processo é exclusivo para cada processo em execução . Após o lançamento de várias cópias, encontraremos um resultado interessante: no primeiro caso, se você não fizer nada e apenas iniciar várias cópias, os endereços serão diferentes. Mas isso não se enquadra na nossa teoria! É verdade, pois nas distribuições modernas a função de randomização da memória é ativada por padrão. Se você desativá-lo, obtemos o resultado esperado - os endereços de memória de dois programas em execução simultâneos coincidirão.
Como resultado, verifica-se que dois programas independentes funcionam com seus próprios espaços de endereço privados, que por sua vez são exibidos pelo sistema operacional na memória física . Portanto, o uso de endereços de memória em um programa não afetará os outros de nenhuma maneira e parece que cada programa possui sua própria parte da memória física, que está inteiramente à sua disposição. A realidade, no entanto, é que a memória física é um recurso compartilhado gerenciado pelo sistema operacional.
Coerência
Outro tópico importante nos sistemas operacionais é a
consistência . Esse termo é usado quando se trata de problemas no sistema que podem ocorrer ao trabalhar com várias coisas ao mesmo tempo no mesmo programa. Problemas de consistência surgem mesmo no próprio sistema operacional. Nos exemplos anteriores, com virtualização de memória e processador, percebemos que o sistema operacional gerencia muitas coisas ao mesmo tempo - inicia o primeiro processo, depois o segundo e assim por diante. Como se viu, esse comportamento pode levar a alguns problemas. Assim, por exemplo, programas modernos multithread enfrentam essas dificuldades.
Considere o seguinte programa:

O programa na função principal cria dois threads usando a chamada
Pthread_create () . Neste exemplo, um encadeamento pode ser pensado como uma função em execução no mesmo espaço de memória ao lado de outras funções, e o número de funções ativadas simultaneamente é claramente mais de uma. Neste exemplo, cada thread inicia e executa a função
worker () ,
que ,
por sua vez, simplesmente incrementa a variável.Execute este programa com o argumento 1000. Como você deve ter adivinhado, o resultado deve ser 2000, pois cada thread incrementou a variável 1000 vezes. No entanto, nem tudo é tão simples. Vamos tentar executar o programa com o número de repetições em uma ordem de magnitude mais.

Ao inserir um número, por exemplo, 100000, esperamos ver 200000 na saída.No entanto, executando o número 100000 várias vezes, não apenas veremos a resposta correta, mas também obteremos respostas incorretas diferentes. A resposta está no fato de que, para aumentar o número, são necessárias três operações - extrair o número da memória, incrementar e depois escrever o número. Como todas essas instruções não são implementadas atomicamente (todas ao mesmo tempo), coisas estranhas podem acontecer. Esse problema é chamado de programação de
condição de corrida -
condição de corrida . Quando forças desconhecidas em um momento desconhecido podem afetar o desempenho de qualquer uma de suas operações.