Sistemas Operacionais - AULA 2
 

A carga do Sistema Operacional

Em Computadores mais complexos, o SO costuma está em uma memória não volátil, o processo de copiar esse sistema, ou partes dele, para a memória e transferir o controle da CPU é o que chamo aqui de carga do SO. maneira como o Controle da CPU é transferido para o Sistema Operacional pode variar de acordo com o hardware em questão. Aqui vamos explicar o processo de boot de um sistema baseado na arquitetura intel x86.

BIOS, CMOS ,POST, Bootloader

Quando um computador é ligado, via hardware, o controle da CPU é transferido para BIOS (Binary Input Output System), esse é o primeiro programa que executa em um computador deste tipo. Esse software (firmware) fica localizado em uma memória não volátil conhecida como CMOS e durante o processo de inicialização, o conteúdo da CMOS é transferido para uma localização na memória. A primeira instrução executada pelo computador está no endereço 0xFFFF0 (1048560), essa instrução é justamente um salto para o endereço inicial do código da BIOS. A BIOS faz, dentre outras coisas, o chamado POST (Power On Self Test), que é uma verificação da saúde dos componentes de hardware. Após a verificação, a BIOS vai procurar um o Bootloader em um MBR. Uma das configurações que é feita na BIOS é a sequência de inicialização, ou seja, em quais "discos" e em qual sequência o bootloader deve ser procurado. O Bootloader é o sistema responsável pela carga do sistema operacional e fica armazenado nos primeiros 440 bytes do primeiro setor do disco ou partição.

A BIOS vai carregar para a memória o MBR que estiver assinado, , um MBR é considerado assinado, ou seja, o dispositivo é considerado inicializável, se os 2 ultmos bytes do primeiro setor do disco em questão, forem respectivamente 0xAA55. O bootloader é copiado para o endereço 0000:7C00 e controle da CPU é tranferido para o Bootloader.

Em ultima instancia o Bootloader faz a carga do sistema operacional na memória e transfere o controle para a mesmo. Isso encerra a carga do SO. Abaixo um esquema de um MBR.

000-439Código do Bootloader440 bytes
440-445Nulo6 bytes
446-509Tabela de Partições64 bytes
510-511Assinatura 0xAA552 bytes

Tabela de Partições

A figura abaixo mostra como a tabela de partições está inserida no Master Boot Sector (Setor 0, Trilha 0).

Refências:

Booting - Wikipedia

Master Boot Record - Wikipedia

Por Dentro do Processo de Inicialização do Linux - M. Tim Jones

Conceitos - Processos

Processos vs Programas

A definição clássica diz que um processo é um programa em execução. Podemos dizer que um processo é uma instância de um programa. O programa é estático, e normalmente está em alguma memória não volátil (disco), já o processo é dinâmico, e além do código executável, é composto de variáveis, pilha, etc. Ou seja no processo os dados podem variar ao longo do tempo. Um programa pode ser inicializado várias vezes, gerando vários processos totalmente distintos entre si.

Espaço de Endereçamento

É o conjunto da memória acessível ao processo, guarda o executável, as variáveis e a pilha, e normalmente começa de zero e vai até um numero específico. Nos sistemas operacionais mais modernos esse espaço de endereço é continuo, porém a sua organização física na RAM é fragmentada, para suportar isso existem os conceitos de Memória Real e Virtual.

Tabela de Processos

É a estrutura onde é guardada as informações sobre todos os processos que estão na memória do computador, nesta estrutura tem informações sobre estados do processo e contexto. Normalmente implementado através de uma tabela ou lista encadeada.

Registradores e Contexto de Processo

Em um sistema multiprogramado, processos podem se alternar no uso da CPU, um processo quando retorna a CPU ele deve continuar de onde ele estava quando deixou a CPU em sua execução anterior, para garantir isso, é preciso salvar o chamado contexto do processo, que é basicamente é constituido dos registradores de uso geral, status, program counter a pilha de execução, dentre outros.

Hierarquia de Processos

Apos a carga do SO esse inicia o primeiro processo, esse processo é resposável pela carga de outros processos e estes podem chamar outros processos. Quando um processo é criado um dos seus atributos é o identificador do processo que o criou, neste caso chamado de processo pai.

Esse tipo de relação gera a chamada hieraquia de processos, onde os processos dos sistema podem ser organizados em forma de arvore.

Atributos de Processos

Quando um processo é iniciado ele é registrado no sistema operacional, em uma estrutura conhecida como tabela de processos, neste cadastro o processo recebe certos atributos que podem ser usados para que tanto o SO como seu o administrador possam gereciá-los. São exemplos de atributos: o PID (Process ID), ID do usuário que o criou, comando/programa que originou o processo, etc.

Compilação x Interpretação

Existem duas formas de se fazer a tradução da linguagem de nível N (mais alto) para a de nível N-1 (mais baixo), compilação e interpretação. No processo de compilação, a tradução do código é feita por completo em uma única vez, gerando o código na linguagem N-1, chamado código objeto. Já no processo de interpretação a tradução do código é feita em tempo de execução do programa, para isso o código de linguagem N é executado com o auxílio de um software conhecido como interpretador.

Conceitos - Memória

Endereçamento

A memória pode ser vista como um array de posições para armazenamento de dados e instruções. Esse conceito foi introduzido por Von Neumann. O espaço de memória ocupado por um processo é chamado de espaço de endereçamento, e é só nesta área que o processo poderá atuar. Aqui é importante lembrar que as arquiteturas de 32 Bits só permitem o endereçamento de 4GB por parte do processo. Isso também é um limite para o SO. Para quebrar esse limite na arquitetura IA32 a Intel criou uma estensão para seus processadores chamada de PAE (Physical Address Extension), com essa extensão os limites de 4Gb para o proceso continua, mas o SO consegue agora gerenciar até 64GB de memória.

Fragmentação

Uma das finalidades do SO é gerenciar memória, entenda-se por isso prover memória para processos e também gerenciar porções livres da memória. Para o SO gerenciar cada posição de memória seria muito caro em termos de processamento e memória. Por isso os sistemas optam por dividir a memória em blocos, e é esses blocos que serão alocados para os processos. Em algumas situções o processo não conseguirár usar todo espaço do bloco, provocando disperdício de memória, a isso chamamos de fragmentação interna. Em outros casos quando o processo é obrigado a ocupar um espaço continuo na memória, podem surgir áreas de memória entre processos que sejam tão pequenas que não poderão ser ocupadas por nenhum processo, o que também gera disperdício, a esse tipo de situação chamamos de fragmentação externa.

Memória Virtual

Para evitar que os programadores tenham que trabalhar diretamente sobre a memória física, foi criado o conceito de memória virtual. Sendo assim independente da área de memória onde o processo foi alocado, o espaço de endereçamento do processo sempre vai de zero até um valor que específico. A tradução do endereço virtual para o real é sempre feito com o apoio de hardware específico.

Proteção

O espaço de endereçamento de um processo deve ser protegido de acessos por parte de outros processos. Em geral o sistema operacional conta com o apoio do hardware para prover esse tipo proteção. Quando um processo tenta acessar uma memória que está fora do seu espaço de endereçamento o hardware gera uma (exceção)interrupção e o processo é terminado imediatamente.

Interpretador de Comandos

A função básica do shell é tornar o uso do sistema operacional mais fácil para os usuários. Basicamente o usuário digita um comando no prompt do shell, e este traduz aquela solicitação em uma requisição para o sistema operacional.

Existem três paradigmas mais usados para usabilidade dos shells. Alguns interpretadores de comandos se apresentam na forma de menu, e o usuário escolhe a operação dentre as opções existente nestes menus. Outros interpretadores podem ser acionados através de comandos digitados em um prompt de comando, esses interpretadores são conhecidos como interpretadores de linha de comando ou CLI (command line interface). Por fim existem os interpretadores capazes de tratar cliques de mouse, esses são os interpretadores gráficos. Por exemplo, no Windows: experimente matar o processo explorer.exe, você verá que não será possível interagir mais com o Windows. Sendo assim podemos dizer que o explorer.exe é um espécie de interpretador de comandos gráficos (cliques de mouse).

Um dos principais usos do interpretador de comandos é o lançamento de aplicações. Em geral, cada comando corresponde a um arquivo no sistema onde está o código de máquina da aplicação. Porém alguns dos comandos disponíveis para uso não são aplicações e tem a sua programação feita internamente dentro do interpretador, esses comandos são conhecidos como comandos internos do interpretador. Todo sistema operacional possui o comando CD nos interpretadores, mas nenhum deles tem um “CD.EXE”.

Em geral os interpretadores permitem a execução em lote de programas através de arquivos, que são conhecidos como batches. Um batch é um arquivo contendo uma sequencia de comandos válidos para o interpretador de comandos. Esses comandos podem ser aplicações ou comandos internos. Alguns interpretadores possuem comandos internos que permitem programar desvios de fluxo (if) e laços (for e while) com base no código de retorno gerado ao final da execução dos comandos. A grande parte dos interpretadores presentes no sistemas operacionais possui uma linguagem de programação (Linguagem Script), os programas escritos nessas linguagens não precisam ser compilados, e são traduzidos para linguagem de máquina pelo interpretador em tempo de execução.

Após a digitação de um comando, em geral uma saída (resultado) é gerada na tela. A medida que o usuário digita mais comandos essa saída vai desaparecendo, para dar lugar as novas saídas geradas, de forma que aquele resultado não estará mais disponível. Para evitar isso, o interpretadores pussuem o recurso de redirecionamento, com ele é possível direcionar as saídas (padrão e erro) de um comando para um arquivo colocando os caracteres > ou >> entre o comando e o arquivo de saída.

Outra funcionalidade disponível nos interpretadores é acoplar as saídas de um comando na entrada de outro, esse recurso é conhecido como pipe e pode ser utilizando o caractere '|' entre os comandos.

Chamadas de Sistemas

Conceitos

Uma chamada de sistema é a forma como o sistema operacional oferece os seus serviços. Em geral quando se faz um programa, existem certas instruções que o programa em execução, aqui chamado de processo, pode executar, como por exemplo operar com posições de memória pertencentes ao seu espaço de endereçamento. Exemplo: um processo pode perfeitamente somar o conteúdo de duas posições de memória e armazenar numa terceira sem precisar do Sistema Operacional, porém se esse processo precisa armazenar isso em um arquivo, mandar via rede ou simplesmente mostrar na tela, ele vai precisar do Sistema operacional para isso.

Como usar uma chamada de sistema?

Para usar as chamadas de sistema oferecidas por um sistema operacional é necessário conhecer a API ou SPI do sistema operacional que define quais chamadas estão disponíveis e quais são os parâmetros que devem ser passados. Os desenvolvedores de sistemas operacionais devem disponiblizar a documentação desta API, se ele deseja que as pessoas façam programas para o seu sistema. Aqui você encontra a documentação da API do Windows e Linux.

Para explicar esses conceitos utilizaremos GNU/Linux, porém eles se aplicam a qualquer sistema operacional. Cada uma destas chamadas recebe parâmetros que devem ser colocados em registradores específicos. Para exemplificar imagine o programa que tem as seguintes atribuições: ler o conteúdo de um arquivo e exibi-lo na tela. Em termos de chamadas de sistema, o programa deve ser organizado da seguinte forma: Abrir o arquivo (Open), Ler o seu conteúdo (Read) e Escrever na saida (write), Fechar o arquivo (close) e por fim, terminar o processo (exit). No GNU/Linux essas chamadas de sistemas tem os seguintes números:

A chamada Open apresenta os parâmetros Arquivo e Modo, onde Arquivo é o nome do arquivo que será aberto e modo é como o arquivo será aberto. O código a seguir ilustra o uso desta chamada usando a linguagem assembly:

section .data

sys_open: equ 5 ; sys_open = 5
f: db "file.txt",0 ; f = "file.txt"
f_id: dd 0 ; f_id = 0
o_rdonly: equ 0
section .text
global _start

_start:

mov eax, sys_open ; move a chamada para o registrador eax
mov ebx, f ; move o nome do arquivo para o registrador ebx
mov ecx, o_rdonly ; move o modo de abertura para ecx
int 80h ; chama o SO
mov dword [f_id], eax ; O retorno (resultado) da chamada é armazenado em f_id (file descriptor)

Padrão POSIX

Organização de Sistemas Operacionais (Tipos de Projeto)

Monolitico: Esse tipo de projeto caracteriza-se por todo o código do sistema operacional rodar com o processador em modo supervisor. Isso torna, em tese, a execução das atividades mais eficiente, pois todas as operações tem acesso a instruções privilégiadas não necessitando repassar requisições deste tipo a um outro módulo (microkernel). Os críticos deste tipo de projeto citam a segurança e a falta de organização como desvantegem deste tipo de projeto. Um exemplo clássico deste projeto é o Linux.

Microkernel: Esse projeto seria o contraponto ao projeto monolítico. Os procedimentos do sistema operacional são agrupados em bibliotecas e apenas uma pequena porção do código será executada em modo supervisor. Isso proporciona uma execução mais segura do SO, porém a eficiencia das tarefas básicas do SO podem ser prejudicadas haja vista que procedimentos privilégiados terão que ser repassados para o microkernel. Olhando o relacionamento dos componentes do SO Windows, pode-se concluir que trata-se de um projeto microkernel (Carece de referência).

cliente Servidor: Pode ser encarado como um SO distribuido, ou seja, diferentes componentes do SO ficariam em diferentes hosts distribuidos geográficamente, não consegui exemplos.

Exokernel: Esse projeto é usado para escrever os hypervisors, usados na Virtualização de Sistemas Operacionais. A IBM criou essa possiblidade na decada de 70 e isso voltou muito forte nos ultimos anos. A idéia principal deste projeto é ter um kernel realmente mínimo com o objetivo de prover interface para que vários kernels possam compartilhar o mesmo hardawre.

Leitura recomendada:

Tipos de Estrutura Para SO - UFRN - Vicente Neto e Roberta Coelho