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 profundamente o funcionamento de sistemas operacionais similares ao Unix, especificamente o trabalho com processos, diversos escalonadores, memória e outros componentes semelhantes que constituem um sistema operacional moderno. Você pode ver 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:
E você também pode acessar meu canal em =)
Execução do programa
O que acontece quando algum programa está em execução? O programa iniciado executa uma tarefa simples — ele executa instruções. A cada segundo, milhões e até bilhões de instruções são extraídas pelo processador da memória RAM, que por sua vez as decodifica (por exemplo, reconhecendo a qual tipo pertencem essas instruções) e as executa. Isso pode incluir a soma de dois números, acesso à memória, verificação de condições, chamada de funções e assim por diante. Após a conclusão da execução de uma instrução, o processador passa para a próxima. E assim, instrução por instrução, elas são executadas até que o programa seja encerrado.
Este exemplo, naturalmente, é uma simplificação — na verdade, para acelerar o trabalho do processador, o hardware moderno permite a execução de instruções fora de ordem, calcular resultados potenciais, executar instruções simultaneamente e outras artimanhas similares.
Modelo de Computação de Von Neumann
A forma simplificada de trabalho que descrevemos é semelhante ao modelo de computação de Von Neumann. Von Neumann é um dos pioneiros dos sistemas computacionais, e também um dos autores da teoria dos jogos. Durante a execução do programa, ocorrem muitos outros eventos, e outros processos e lógicas paralelas operam, cuja principal finalidade é simplificar a inicialização, operação e manutenção do sistema.
Há um conjunto de software responsável pela facilidade de execução de programas (ou mesmo permitindo a execução simultânea de vários programas), que permite que os programas compartilhem a mesma memória e interajam com diferentes dispositivos. Este conjunto de software é, em essência, o que chamamos de sistema operacional, cuja principal função é monitorar se o sistema opera corretamente e eficientemente, além de garantir a facilidade de gerenciamento desse sistema.
Sistema operacional
Sistema operacional, ou SO, é um conjunto de programas inter-relacionados, projetados para gerenciar os recursos do computador e organizar a interação do usuário com o computador..
O SO alcança sua eficiência principalmente através da técnica mais importante — a técnica de virtualização.. O SO interage com os recursos físicos (como processador, memória, disco e semelhantes) e os transforma em uma forma mais geral, com maiores capacidades e mais fácil de usar. Portanto, para uma compreensão geral, pode-se comparar muito grosseiramente o sistema operacional a uma máquina virtual.
Para permitir que os usuários emitam comandos para o sistema operacional e, dessa forma, utilizem as capacidades da máquina virtual (como: executar um programa, alocar memória, acessar arquivos e assim por diante), o sistema operacional fornece uma interface conhecida como API (interface de programação de aplicativos) à qual podem ser feitas chamadas (call). Um sistema operacional típico permite centenas de chamadas de sistema.
E finalmente, como a virtualização permite que vários programas funcionem (compartilhando assim a CPU) e acessem simultaneamente suas instruções e dados (dividindo assim a memória), bem como acessando os discos (compartilhando dispositivos de entrada e saída), 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 é gerenciar esses recursos, fazendo isso de maneira eficiente, justa ou, ao contrário, dependendo da tarefa para a qual foi projetado.
Virtualização da CPU
Vamos considerar o seguinte programa:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Ele não realiza ações especiais; essencialmente, tudo o que faz é chamar a função spin(), cuja tarefa é verificar ciclicamente o tempo e retornar depois que um segundo se passou. Assim, ele repete infinitamente a string que o usuário passou como argumento.
Vamos executar este programa e passar o caráter “A” como argumento. O resultado não é especialmente interessante — o sistema simplesmente executa o programa que periodicamente imprime na tela o caráter “A”.
Agora vamos tentar uma variante em que várias instâncias do mesmo programa estão em execução, mas imprimindo letras diferentes, para que fique mais claro. Nesse caso, o resultado será um pouco diferente. Apesar de termos um único processador, o programa é executado simultaneamente. Como isso é possível? Isso acontece porque o sistema operacional, com a ajuda das capacidades do hardware, cria uma ilusão. A ilusão de que o sistema possui vários processadores virtuais, transformando um processador físico em uma quantidade teoricamente infinita e, assim, permitindo que os programas pareçam estar sendo executados simultaneamente. Essa ilusão é chamada de Virtualização de CPU.
Esse cenário levanta muitas perguntas, por exemplo, se vários programas desejam ser iniciados ao mesmo tempo, qual deles será realmente executado? Essa questão é gerenciada por “políticas” do sistema operacional. As políticas são utilizadas em muitos lugares do sistema operacional e respondem a perguntas como essa, além de serem mecanismos básicos implementados pelo sistema operacional. Daí a função do sistema operacional como gerente de recursos.
Virtualização de memória
Agora, vamos considerar a memória. O modelo físico da memória em sistemas modernos é representado como um array de bytes. Para ler da memória, é necessário especificar o endereço da célula, para acessar. Para gravar ou atualizar dados, é necessário também especificar os dados e o endereço da célula onde gravá-los.
As acessos à memória ocorrem constantemente durante a execução do programa. O programa armazena em memória toda a sua estrutura de dados e a acessa, executando várias instruções. As instruções, por sua vez, também estão armazenadas na memória, portanto, o acesso a ela ocorre também a cada solicitação da próxima instrução.
Chamada malloc()
Vamos considerar o seguinte programa que aloca uma área de memória usando uma chamada 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. (https://youtu.be/jnlKRnoT1m0):

O programa realiza várias ações. Primeiro, aloca uma certa quantidade de memória (linha 7), em seguida, imprime o endereço da célula alocada (linha 9), grava zero no primeiro slot da memória alocada. Depois, o programa entra em um loop, no qual incrementa o valor gravado na memória pelo endereço da variável 'p'. Ele também imprime o identificador do próprio processo. O identificador do processo é único para cada processo em execução. Ao executar várias cópias, nos deparamos com um resultado interessante: No primeiro caso, se não fizermos nada e simplesmente executarmos várias cópias, os endereços serão diferentes. Mas isso não se encaixa em nossa teoria! Exato, pois em distribuições modernas, a função de randomização de memória está ativada por default. Se a desativarmos, teremos o resultado esperado — os endereços de memória de dois programas rodando simultaneamente coincidirão.

Assim, resulta que dois programas independentes operam com seus próprios espaços de endereço privados, que, por sua vez, são mapeados pelo sistema operacional na memória física.. Portanto, o uso de endereços de memória dentro de um programa não afetará outros, e cada programa parece ter seu próprio pedaço de memória física, totalmente alocado à sua disposição. A realidade, no entanto, é que a memória física é um recurso compartilhado, cuja gestão é realizada pelo sistema operacional.
Consistência
Outro tema importante no âmbito dos sistemas operacionais é a consistência. Este termo é utilizado quando se fala sobre problemas no sistema que podem surgir ao trabalhar com muitas coisas simultaneamente dentro de um único programa. Problemas de consistência ocorrem até mesmo no próprio sistema operacional. Nos exemplos anteriores com virtualização de memória e de CPU, entendemos que o SO gerencia muitas coisas ao mesmo tempo — iniciando o primeiro processo, depois o segundo e assim por diante. Como se tornou evidente, esse comportamento pode levar a alguns problemas. Por exemplo, programas multithread modernos enfrentam tais dificuldades.
Vamos considerar o seguinte programa:

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

Ao fornecer um número, por exemplo, 100000, esperamos ver a saída 200000. No entanto, ao executar o número 100000 várias vezes, não apenas não veremos a resposta correta, mas também obteremos respostas incorretas diferentes. O mistério 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, em seguida, gravar o número de volta. Como todas essas instruções não são realizadas de forma atômica (todas ao mesmo tempo), coisas estranhas podem acontecer. Este problema é chamado em programação race condition — condição de corrida. Quando forças desconhecidas em um momento incerto podem impactar a execução de suas operações.
Fonte: habr.com
