A execução do programa ocorre em duas fases: primeiro colocava-se a máquina em modo de carga de programa (program load) e executava-se a leitura das instruções de máquina para o local apropriado da memória; depois carregava-se o endereço da primeira instrução do programa no registrador PC (program counter) e mandava-se iniciar o ciclo fetch-execute a partir daí (program run). Após o término da execução do programa todo o processo tinha que ser repetido para a execução do próximo programa.
Estes dois processos (programação e execução) eram complicados e muito suscetíveis a erros. A solução foi dar um passo de abstração à frente. Na área da programação isto foi feito com o surgimento das primeiras linguagens Assembly, e na execução surgiram os primeiros sistemas operacionais.
Linguagens Assembly
A grande novidade das linguagns Assembly (ou linguagens de segunda geração - sendo as linguagens de máquina a primeira geração) foi permitir que os programadores fizessem uso de referências simbólicas e mnemônicas aos elementos do programa (instruções e operandos). Vejamos um exemplo simples (ver IBM z/Architecture Principles of Operation). Suponhamos que o programador deseje executar a seguinte sequência de operações
- Carregar no registrador de propósito geral número 2 os 32 bits (4 bytes) localizados a partir do endereço contido no registrador de propósito geral número 12;
- Crregar no registrador de propósito geral número 3 os 32 bits localizados a partir do endereço contido no registrador de propósito geral número 12 mais 32 bits;
- Somar os conteúdos dos registradores de propósito geral números 2 e 3 (considerados como números inteiros representados em formato binário com sinal);
- Armazenar o resultado da soma nos 32 bits localizados a partir do endereço contido no registrador de propósito geral número 12 mais 64 bits.
5820C0005830C0041A235020C008
A mesma sequência de quatro instruções na linguagem Assembly desta arquitetura poderiam ser codificadas da seguinte forma:
L R2,0(R0,R12) R2=addr(R12)
L R3,4(R0,R12) R3=addr(R12)+4
AR R2,R3 R2=R2+R3
ST R2,8(R0,R12) addr(R12+8)=R2
Bem melhor. Mas ainda dá pra melhorar. Se o programador adotar nomes simbólicos para as localizações de memória apontadas a partir do endereço contido no registrador de propósito geral 12 (usando instruções Assembly do tipo define storage - mnemônico DS) a sequência de instruções poderia ser da seguinte forma:
USING *,R12 R12=baseaddr
...
PARCELA1 DS F 4 bytes
PARCELA2 DS F 4 bytes
SOMA DS F 4 bytes
...
L R2,PARCELA1 R2=PARCELA1
L R3,PARCELA2 R3=PARCELA2
AR R2,R3 R2=R2+R3
ST R2,SOMA SOMA=R2
Ótimo, só que este avanço não vem de graça. Para gerar a sequência de instruções de máquina correspondente às instruções simbólicas Assembly é necessário um programa de tradução denominado Assembler. O programa escrito pelo programador usando instruções Assembly é denominado programa fonte, que é utilizado como entrada pelo programa Assembler, gerando como saída o programa equivalente em linguagem de máquina, denominado programa objeto.
A prática mostrou que quase todos os programas possuiam seções de código virtualmente idênticas (ex.: rotinas para executar operações de entrada/saída de dados). Além disso, programas comumente compartilhavam descrições de estruturas de dados. A partir destes fatos introduziu-se na linguagem Assembly a possibilidade de incorporar trechos do programa fonte a partir de bibliotecas de código fonte, bem como incorporar sequências já traduzidas de código objeto como parte do programa. Assim o processo de construção de um programa executável passou a ser um processo de duas etapas, como mostrado na figura abaixo.
Este modelo geral para a criação dos programas executáveis persiste até hoje. A única diferença é que o programa fonte agora é escrito em outras linguagens, mais abstratas (chamadas de terceira geração), e o programa responsável pela tradução do programa fonte para o programa objeto é chamado compilador.
Os compiladores, a propósito, tornaram-se uma forma muito conveniente para garantir que os programas executáveis utilizassem adequadamente os serviços de um novo elemento que surgiu nesta época: o sistema operacional.
A primeira motivação para a existência de programas especializados em supervisionar o ambiente de execução dos programas foi a criação da abstração de arquivos, que dá aos programadores primitivas padronizadas para executar operações de entrada e saída de dados (ex.: ler/gravar em fita magnética; imprimir; etc.). Em termos modernos chamaríamos isto de uma API (application programming interface).
Logo também foi notado que o modelo de execução de um programa de cada vez era altamente ineficiente (a diferença entre o tempo de execução de instruções pelo processador e o tempo para execução de operações de entrada/saída de dados cria grandes "buracos" de inatividade do processador). A solução para isto foi criar uma nova camada de abstração entre os programas e a máquina, permitindo que o tempo do processador e a memória pudessem ser compartilhados, criando para cada programa a ilusão de possuir o controle exclusivo da máquina;
Com isso passamos a ter duas "classes" programas em execução: os programas de controle, ou de supervisão, responsáveis pela administração do ambiente virtual de execução; e os programas de aplicação, voltados para a solução dos problemas dos usuários e que executam "encaixotados" no ambiente virtual de execução administrado pelos programas de sistema. Os programas de controle formam o núcleo (kernel) do sistema operacional, e fornecem aos programas de aplicação uma API comumente conhecida como chamadas de sistema (system calls), que inclui a API de arquivos e outras funções básicas (ex.: solicitar execução de programa).
Cada programa em execução gerenciado pelos programas de controle (incluindo aí as estruturas de dados que descrevem o estado do programa de aplicação para os programas de controle) é chamado um processo. desta forma o modelo operacional da máquina, do kernel do sistema operacional e dos processos é como mostrado na figura abaixo.
Nos próximos artigos desta série vamos examinar mais de perto como o kernel executa as funções de compartilhamento do tempo do processador e da memória. Até breve.
A prática mostrou que quase todos os programas possuiam seções de código virtualmente idênticas (ex.: rotinas para executar operações de entrada/saída de dados). Além disso, programas comumente compartilhavam descrições de estruturas de dados. A partir destes fatos introduziu-se na linguagem Assembly a possibilidade de incorporar trechos do programa fonte a partir de bibliotecas de código fonte, bem como incorporar sequências já traduzidas de código objeto como parte do programa. Assim o processo de construção de um programa executável passou a ser um processo de duas etapas, como mostrado na figura abaixo.
Este modelo geral para a criação dos programas executáveis persiste até hoje. A única diferença é que o programa fonte agora é escrito em outras linguagens, mais abstratas (chamadas de terceira geração), e o programa responsável pela tradução do programa fonte para o programa objeto é chamado compilador.
Os compiladores, a propósito, tornaram-se uma forma muito conveniente para garantir que os programas executáveis utilizassem adequadamente os serviços de um novo elemento que surgiu nesta época: o sistema operacional.
Sistemas Operacionais
A primeira motivação para a existência de programas especializados em supervisionar o ambiente de execução dos programas foi a criação da abstração de arquivos, que dá aos programadores primitivas padronizadas para executar operações de entrada e saída de dados (ex.: ler/gravar em fita magnética; imprimir; etc.). Em termos modernos chamaríamos isto de uma API (application programming interface).
Logo também foi notado que o modelo de execução de um programa de cada vez era altamente ineficiente (a diferença entre o tempo de execução de instruções pelo processador e o tempo para execução de operações de entrada/saída de dados cria grandes "buracos" de inatividade do processador). A solução para isto foi criar uma nova camada de abstração entre os programas e a máquina, permitindo que o tempo do processador e a memória pudessem ser compartilhados, criando para cada programa a ilusão de possuir o controle exclusivo da máquina;
Com isso passamos a ter duas "classes" programas em execução: os programas de controle, ou de supervisão, responsáveis pela administração do ambiente virtual de execução; e os programas de aplicação, voltados para a solução dos problemas dos usuários e que executam "encaixotados" no ambiente virtual de execução administrado pelos programas de sistema. Os programas de controle formam o núcleo (kernel) do sistema operacional, e fornecem aos programas de aplicação uma API comumente conhecida como chamadas de sistema (system calls), que inclui a API de arquivos e outras funções básicas (ex.: solicitar execução de programa).
Cada programa em execução gerenciado pelos programas de controle (incluindo aí as estruturas de dados que descrevem o estado do programa de aplicação para os programas de controle) é chamado um processo. desta forma o modelo operacional da máquina, do kernel do sistema operacional e dos processos é como mostrado na figura abaixo.
Nos próximos artigos desta série vamos examinar mais de perto como o kernel executa as funções de compartilhamento do tempo do processador e da memória. Até breve.
Nenhum comentário:
Postar um comentário