Prévia do material em texto
Sistemas Operacionais
Marcio Quirino - 1
SUMÁRIO
Caminho do Brilho: Saiba Quais Conteúdos Você Irá Aprender Conosco........................................... 8
Onboarding ......................................................................................................................................... 8
Conceitos Básicos de SO ...................................................................................................................... 9
Descrição ............................................................................................................................................ 9
Propósito ............................................................................................................................................. 9
Introdução ........................................................................................................................................... 9
Conceitos ............................................................................................................................................ 9
Histórico dos sistemas computacionais ........................................................................................... 11
Primeira geração ...................................................................................................................................... 11
Segunda geração ..................................................................................................................................... 12
Terceira geração ...................................................................................................................................... 13
Quarta geração ........................................................................................................................................ 13
MS Windows e UNIX ........................................................................................................................ 14
Windows ................................................................................................................................................... 14
MS-DOS ............................................................................................................................................... 14
MS Windows 1.0 .................................................................................................................................. 14
Windows NT ......................................................................................................................................... 14
Windows 2000 ...................................................................................................................................... 14
Windows XP ......................................................................................................................................... 14
Outras versões ..................................................................................................................................... 14
Unix .......................................................................................................................................................... 14
1965 ..................................................................................................................................................... 14
1969 ..................................................................................................................................................... 15
1973 ..................................................................................................................................................... 15
1991 ..................................................................................................................................................... 15
Tipos de Sistemas Operacionais ..................................................................................................... 15
Sistemas Monoprogramáveis ................................................................................................................... 15
Sistemas Multiprogramáveis ou Multitarefa .............................................................................................. 16
Sistemas com múltiplos processadores .......................................................................................... 18
Sistemas fortemente acoplados ............................................................................................................... 18
SMP - Symmetric Multiprocessors ....................................................................................................... 18
NUMA - Non-Uniform Memory Access ................................................................................................. 18
Sistemas fracamente acoplados .............................................................................................................. 19
Outras classificações dos sistemas operacionais ........................................................................... 19
Sistemas em lote ...................................................................................................................................... 19
Sistemas de processamento de transações ............................................................................................. 19
Sistemas de tempo compartilhado ........................................................................................................... 19
Sistema de tempo real crítico ................................................................................................................... 20
Sistema de tempo real não crítico ............................................................................................................ 20
Sistemas Operacionais
Marcio Quirino - 2
Software proprietário ................................................................................................................................ 20
Software livre (free software).................................................................................................................... 20
Software de código aberto (open source) ................................................................................................ 20
Estrutura do SO: Kernel, System Calls, Modos de Acesso............................................................. 21
System Calls ............................................................................................................................................ 22
Modos de acesso ..................................................................................................................................... 26
Modo kernel ou supervisor ................................................................................................................... 26
Modo de acesso usuário ...................................................................................................................... 26
Arquiteturas de kernel .............................................................................................................................. 26
Arquitetura monolítica (mono-kernel) ................................................................................................... 26
Arquitetura de camadas ....................................................................................................................... 27
Máquina virtual ou virtual machine (VM) .............................................................................................. 28
Arquitetura microkernel ........................................................................................................................ 29
Exonúcleo ............................................................................................................................................. 29
Conceitos ..........................................................................................................................................30
Estrutura de diretórios no Linux ....................................................................................................... 31
Comandos do Linux ......................................................................................................................... 32
Comandos básicos para uso via terminal ................................................................................................. 33
Considerações finais ........................................................................................................................ 35
Referências....................................................................................................................................... 35
Explore+ ........................................................................................................................................... 35
Processos e Gerencia de Processador ............................................................................................... 36
Definição ........................................................................................................................................... 36
Propósito ........................................................................................................................................... 36
Preparação ....................................................................................................................................... 36
Introdução ......................................................................................................................................... 36
Conceitos .......................................................................................................................................... 36
Modelo de processo ................................................................................................................................. 37
Criação e encerramento de processos ............................................................................................ 38
Hierarquia de processos .......................................................................................................................... 40
Estados de processos ...................................................................................................................... 42
Mudança de contexto ............................................................................................................................... 43
Contexto de hardware .......................................................................................................................... 43
Contexto de software............................................................................................................................ 44
Espaço de endereçamento................................................................................................................... 44
Processos no Linux .......................................................................................................................... 44
Comandos do shell ........................................................................................................................... 44
Comandos para controle de processos ........................................................................................... 46
ps ............................................................................................................................................................. 46
Sistemas Operacionais
Marcio Quirino - 3
top ............................................................................................................................................................ 46
<Ctrl> + <C> ............................................................................................................................................. 46
<Ctrl> + <Z> ............................................................................................................................................. 47
jobs ........................................................................................................................................................... 47
bg ............................................................................................................................................................. 47
fg .............................................................................................................................................................. 47
kill ............................................................................................................................................................. 47
killall ......................................................................................................................................................... 47
killall5 ....................................................................................................................................................... 47
nohup ....................................................................................................................................................... 47
pstree ....................................................................................................................................................... 47
Exemplos: ............................................................................................................................................. 48
Subprocesso ..................................................................................................................................... 49
Threads ............................................................................................................................................. 50
Tipos de processos .................................................................................................................................. 53
Processos e Threads No Linux ........................................................................................................ 53
Processos de aplicações concorrentes ........................................................................................... 54
Condição de corrida ......................................................................................................................... 54
Região crítica ........................................................................................................................................... 55
Semáforos ................................................................................................................................................ 55
Utilização de semáforo na solução de condição de corrida ..................................................................... 57
ANEXO I ............................................................................................................................................... 57
ANEXO II .............................................................................................................................................. 58
Monitores .......................................................................................................................................... 60
Sincronização no Linux .................................................................................................................... 61
Tratamento de sinais em Linux ................................................................................................................ 63
Exemplo para tratamento de sinais ...................................................................................................... 64
Escalonamento ................................................................................................................................. 64
Escalonamento First In First Out (FIFO) ..................................................................................................65
Escalonamento Shortest Job First (SJF) .................................................................................................. 65
Escalonamento Shortest Remaining Time Next (SRTN) .......................................................................... 66
Escalonamento Cooperativo .................................................................................................................... 66
Escalonamento Circular (Round Robin) ................................................................................................... 66
Escalonamento por Prioridade ................................................................................................................. 67
Escalonamento por Múltiplas Filas ........................................................................................................... 67
Escalonamento em Sistemas de Tempo Real.......................................................................................... 68
Escalonamento de Threads...................................................................................................................... 68
Escalonamento no Linux .................................................................................................................. 69
Considerações finais ........................................................................................................................ 70
Referências....................................................................................................................................... 70
Sistemas Operacionais
Marcio Quirino - 4
Explore+ ........................................................................................................................................... 71
Memória ............................................................................................................................................... 72
Descrição .......................................................................................................................................... 72
Propósito ........................................................................................................................................... 72
Introdução ......................................................................................................................................... 72
Gerência de memória ....................................................................................................................... 72
Como os processos enxergam a memória ............................................................................................... 74
Proteção de memória ............................................................................................................................... 75
Relocação ................................................................................................................................................ 77
Políticas de alocação ....................................................................................................................... 79
Gerenciamento de memória sem permuta ...................................................................................... 79
Alocação contígua .................................................................................................................................... 79
Overlay ..................................................................................................................................................... 79
Alocação particionada fixa ....................................................................................................................... 81
Alocação com partições variáveis ............................................................................................................ 82
Política Best-Fit ........................................................................................................................................ 84
Política First-Fit ........................................................................................................................................ 84
Política Worst-Fit ...................................................................................................................................... 85
Fragmentação externa e interna ...................................................................................................... 85
Gerenciamento de memória com permuta ...................................................................................... 86
Swap de memória .................................................................................................................................... 86
Gerenciamento de espaço livre ....................................................................................................... 87
Gerenciamento de memória com mapas de bits ...................................................................................... 87
Gerenciamento de memória com listas encadeadas ................................................................................ 88
Memória virtual ................................................................................................................................. 89
Paginação ......................................................................................................................................... 90
Controle de espaço livre ........................................................................................................................... 90
Proteção de memória ............................................................................................................................... 91
Paginação sob demanda .......................................................................................................................... 92
Políticas de paginação ............................................................................................................................. 92
Algoritmo de segunda chance .................................................................................................................. 93
Segmentação ........................................................................................................................................... 95
Gerenciamento de memória no Linux .............................................................................................. 96
Gerenciamento da memória física no Linux .................................................................................... 96
Memória virtual no Linux .................................................................................................................. 97
Regiões de memória virtual ...................................................................................................................... 97
Tempo de vida de um espaço de endereçamento virtual ......................................................................... 97
Permuta e paginação ............................................................................................................................... 98
Execução e carga de programas de usuário ................................................................................... 98
Sistemas Operacionais
Marcio Quirino - 5
Mapeamento de programas para a memória ........................................................................................... 98
Vinculação estática e dinâmica ................................................................................................................ 99
Utilitários e comandos para gerenciar a memória do sistema Linux ............................................ 100
Obtendo informações pela linha de comando ........................................................................................ 100
Utilitários para acesso à informação ......................................................................................................101
Htop .................................................................................................................................................... 101
Ksysguard .............................................................................................................................................. 103
Monitor do Sistema ................................................................................................................................ 104
Considerações finais ...................................................................................................................... 105
Referências..................................................................................................................................... 105
EXPLORE+ ..................................................................................................................................... 105
Sistema de Arquivos .......................................................................................................................... 106
Descrição ........................................................................................................................................ 106
Propósito ......................................................................................................................................... 106
Preparação ..................................................................................................................................... 106
Introdução ....................................................................................................................................... 106
Conceitos ........................................................................................................................................ 106
Arquivos .......................................................................................................................................... 107
Estrutura de Arquivos ............................................................................................................................. 108
Métodos de Acesso ................................................................................................................................ 109
Tipos de Arquivos ................................................................................................................................... 109
Diretórios ........................................................................................................................................ 110
Implementação do sistema de arquivos ........................................................................................ 111
Alocação Contígua ................................................................................................................................. 112
Alocação por Lista Encadeada ............................................................................................................... 113
Alocação por Lista Encadeada Utilizando Índice.................................................................................... 114
I-nodes (Alocação Indexada).................................................................................................................. 114
IMPLEMENTAÇÃO DE CACHE .................................................................................................... 115
O sistema de arquivos do Linux ..................................................................................................... 116
ext2 ................................................................................................................................................. 118
Journaling ....................................................................................................................................... 120
Disco Rígido ................................................................................................................................... 121
Partições ......................................................................................................................................... 121
Gerenciamento de partições em Linux ................................................................................................... 122
Formatação ............................................................................................................................................ 129
Formatação de partições em Linux ........................................................................................................ 129
Montagem do sistema de arquivos ................................................................................................ 130
Montagem de partições em Linux .......................................................................................................... 131
Outros comandos para gerenciamento de partições..................................................................... 135
Sistemas Operacionais
Marcio Quirino - 6
Conceitos ........................................................................................................................................ 136
Comandos para manipulação de diretórios ................................................................................... 137
Comandos para manipulação de arquivos .................................................................................... 139
Links simbólicos e hardlinks ........................................................................................................... 142
Editor de arquivos x processador de textos................................................................................... 143
vim .......................................................................................................................................................... 144
Principais comandos do vim ............................................................................................................... 144
Montagem de partições em Linux .......................................................................................................... 146
nano ....................................................................................................................................................... 150
gedit ....................................................................................................................................................... 150
Considerações finais ...................................................................................................................... 152
Referências..................................................................................................................................... 152
Explore+ ......................................................................................................................................... 153
Automatizando Tarefas no Linux ....................................................................................................... 154
Descrição ........................................................................................................................................ 154
Propósito ......................................................................................................................................... 154
Preparação ..................................................................................................................................... 154
Introdução ....................................................................................................................................... 154
Conceitos ........................................................................................................................................ 154
Ferramenta multiusuário ................................................................................................................155
Como configurar o cron de usuário ................................................................................................ 155
Regras e formatos do crontab........................................................................................................ 156
Exemplo de uma tarefa automática de backup ............................................................................. 158
A configuração de cron para o sistema ......................................................................................... 158
Conceitos ........................................................................................................................................ 159
O interpretador de shell .................................................................................................................. 159
A palavra mágica que identifica um script ..................................................................................... 159
Um script muito simples ................................................................................................................. 159
Linhas em branco e comentários ................................................................................................... 160
Incluindo textos na resposta do script............................................................................................ 161
Inserindo caracteres especiais no comando ‘echo’ ....................................................................... 162
Criando pausas na execução de um script .................................................................................... 162
Apagando todo o conteúdo do terminal ......................................................................................... 163
Terminando um script com valor de retorno .................................................................................. 163
Conceitos ........................................................................................................................................ 164
Atribuição de valores a uma variável ............................................................................................. 164
Referenciando variáveis ................................................................................................................. 164
Atribuição da saída de um comando a uma variável..................................................................... 165
Sistemas Operacionais
Marcio Quirino - 7
Passando parâmetros para o script ............................................................................................... 165
O operador lógico if ........................................................................................................................ 166
Comparadores numéricos .............................................................................................................. 166
Comparadores de cadeias de caracteres (strings)........................................................................ 168
Executando o script sem parâmetros ............................................................................................ 169
Realizando operações aritméticas em script ................................................................................. 170
Incrementando variáveis ................................................................................................................ 171
Operações com ponto flutuante ..................................................................................................... 171
Conceitos ........................................................................................................................................ 172
A estrutura de repetição while........................................................................................................ 173
Interferindo na execução de um loop............................................................................................. 174
A estrutura de repetição for ............................................................................................................ 175
Obtendo uma variável a partir de um arquivo texto....................................................................... 179
Considerações ao executar um script a partir do cron .................................................................. 179
Considerações finais ...................................................................................................................... 181
Referências..................................................................................................................................... 181
Explore+ ......................................................................................................................................... 181
Sistemas Operacionais
Marcio Quirino - 8
Sistemas Operacionais
Caminho do Brilho: Saiba Quais Conteúdos Você Irá
Aprender Conosco
Onboarding
É com grande entusiasmo que apresentamos a disciplina de Sistemas Operacionais, um dos pilares
essenciais para a sua formação e desenvolvimento profissional na área de Computação. Ao longo deste
percurso, você irá perceber como os conhecimentos adquiridos aqui são fundamentais para construir uma
carreira sólida e buscar oportunidades no mundo de trabalho.
A jornada que começamos a trilhar aqui abordará uma série de tópicos cruciais para sua formação
como profissional. Desde a evolução histórica dos sistemas operacionais até a aplicação prática de
comandos no Linux, cada passo desse caminho é uma conquista em direção ao sucesso profissional.
Ao compreender a evolução dos sistemas operacionais, você entenderá como chegamos aos
sistemas que utilizamos hoje, e isso lhe dará uma base sólida para acompanhar as constantes mudanças
nessa área dinâmica. Além disso, ao identificar os tipos de sistemas operacionais e entender a estrutura do
Sistema Operacional, incluindo o Kernel, system calls e modos de acesso, você estará equipado para lidar
com sistemas diversos e complexos.
Aqui, você também aprenderá sobre processos e programação concorrente, habilidades essenciais
em um ambiente de TI que exige eficiência e multitarefa. Compreenderá como os processos se comunicam
e como o escalonamento de tarefas é crucial para um sistema operacional eficaz.
A gestão de memória é outra área crítica em nossa jornada. Você descobrirá como a memória é
alocada e gerenciada, e como o Linux lida com esse aspecto fundamental. Além disso, entenderá como os
sistemas de arquivos funcionam e como usar ferramentas de gerenciamento no Linux para manipular
arquivos com eficiência.
Para a automação de tarefas, aprenderemos a criar scripts e agendamentos, tornando seu trabalho
mais eficiente e produtivo. O uso de variáveis de ambiente e estruturas de decisão em scripts permitirá que
você realize tarefas complexas de forma automatizada.
Em resumo, esta disciplina não apenas ampliará seus conhecimentos técnicos, mas também abrirá
portas para o mercado de trabalho em constante evolução. Valorize cada conquista e transformação do
saber, pois elas o aproximam do sucesso profissional que tanto almeja.
Lembre-se de que você está na universidade para crescer e se desenvolver. Cada conhecimento
adquirido aqui é uma ferramenta valiosa em sua jornada como profissional. Mantenha-se curioso,
comprometido e, acima de tudo, feliz com seu aprendizado, pois essa paixão pelo conhecimento é o que
diferencia os melhores profissionais.
Sistemas Operacionais
Marcio Quirino - 9
Conceitos Básicos de SO
Descrição
A conceituação dos elementos de um sistema operacional, desde a sua evolução histórica, passando
pela estrutura básica, até a exemplificação da instalação e utilização do sistema operacional Linux, sob o
ponto de vista do usuário.
Propósito
Compreender os conceitos básicos que formam os sistemas operacionaisé importante para a
solidificação do seu leque de conhecimentos fundamentais em sistemas de computação, possibilitando
expandir a percepção de mais detalhes relacionados aos assuntos que envolvem computadores.
Introdução
Qualquer sistema computacional é composto pelo conjunto de usuários, hardware e software. Dentre
os softwares que são utilizados, podemos separá-los nos aplicativos que são disponibilizados pelos usuários
e os sistemas operacionais.
Os sistemas operacionais (SO) oferecem uma interface entre os aplicativos e o hardware, tornando
a vida dos usuários e dos desenvolvedores mais simples. Além disso, o sistema operacional permite um uso
mais eficiente e eficaz do hardware.
Para conseguirmos reconhecer o papel do sistema operacional, iremos descobrir um pouco de como
eles surgiram e evoluíram junto aos próprios sistemas computacionais.
Iremos aprender os tipos de sistemas operacionais, bem como compreender a estrutura básica do
sistema operacional, composto, dentre outros elementos, do kernel, chamadas de sistemas e modos de
acesso ao núcleo.
Por fim, vamos verificar como é a utilização básica de um sistema operacional, aplicando conceitos
ao uso do SO Linux.
1. Evolução histórica dos SO
Descrever a evolução histórica dos Sistemas Operacionais.
Conceitos
Estamos prestes a realizar o nosso estudo em sistemas operacionais. Esse assunto é importante
para todos os profissionais que buscam exercer plenamente suas atividades nas diversas áreas da
computação, como administradores de sistemas, programadores de aplicações concorrentes, gerentes de
segurança e administradores de rede (devido aos sistemas operacionais nas redes de comunicação de
dados).
Vamos começar verificando quais são os componentes básicos de um sistema computacional (ou
sistema de computação ou S.C.):
1. Hardware
✓ Fornece recursos básicos de computação CPU, memória, dispositivos de E/S.
Sistemas Operacionais
Marcio Quirino - 10
2. Aplicativos
✓ Definem as maneiras como os recursos são usados, para resolver os problemas de
computação dos usuários, como compiladores, banco de dados, jogos de videogames,
programas comerciais e outros.
3. Usuários
✓ São as pessoas, máquinas ou outros computadores.
4. Sistema Operacional
✓ Controla e coordena o uso do hardware entre os vários programas de aplicação, para os
diversos usuários.
O sistema computacional é um sistema complexo demais. Não apenas para ser entendido nos
mínimos detalhes, como também para realizar a gerência de todos os seus componentes e usá-los de modo
otimizado. Por esse motivo é que os computadores possuem um software denominado sistema operacional.
Uma das definições possíveis para um sistema operacional (abreviado muitas vezes por SO, ou S.O.)
é ser constituído por um conjunto de rotinas de computação elaborado para propósitos específicos, de forma
semelhante com o que ocorre com um aplicativo de computador que utilizamos no dia a dia, por exemplo,
uma planilha eletrônica. Porém, há uma particularidade:
O sistema operacional se diferencia de um aplicativo de usuário ao atuar como um intermediário entre o usuário
e o hardware de um computador, tornando a utilização deste mais simples, rápida e segura.
Figura 1 – Sistemas Operacionais. Fonte: Rose Carson/Shutterstock.
Você já deve ter utilizado sistemas operacionais tais como o Windows, o Linux, o Mac OS X ou o
Android, mas as aparências podem enganar:
Atenção
O programa que serve para a interação dos usuários na realidade não faz parte do sistema operacional em si,
embora usem o SO para realizar seu trabalho. Na realidade, o que usuário utiliza diretamente é uma interface de
acesso ao sistema operacional. Essa interface pode ser baseada em texto (shell, ou interpretador de comandos), ou
baseado em interface gráfica com ícones (GUI - Graphical User Interface).
Em um computador, os programas podem ser executados em modo usuário ou kernel. No modo
usuário, os softwares têm acesso limitado ao hardware e normalmente estão os programas e aplicativos
utilizados diretamente pelos usuários.
O SO é o único programa executado em modo núcleo, ou Kernel. Significa que o SO possui o acesso completo
ao hardware e consegue executar qualquer instrução possível.
Além disso, o sistema operacional é um programa de controle que coordena a execução dos
programas do usuário e as operações dos dispositivos de E/S (entrada e saída, ou periféricos); e também é
um gerenciador de recursos de hardware, que gerencia e aloca as partes de todo um sistema complexo.
Sistemas Operacionais
Marcio Quirino - 11
Você sabia
Nos primeiros computadores, a programação era toda feita através de painéis físicos, exigindo do programador
um grande conhecimento do hardware, mas o hardware em si possuía pouca utilidade para o usuário devido à sua
complexidade.
O surgimento do sistema operacional permitiu que o hardware pudesse ser utilizado de forma mais eficiente,
levando em consideração que o SO consegue disponibilizar de forma mais eficiente os serviços aos usuários,
modularizando e abstraindo a visão do usuário.
O uso de softwares, incluindo o sistema operacional, possibilita oferecer ao usuário uma visão daquilo
que interessa a ele, ou seja, o software modulariza aquilo que o usuário vê e usa.
O uso de um SO também permite abstrair não apenas o hardware como também rotinas de outros
softwares, ou seja, é possível representar o funcionamento daquilo que está "abaixo" de um determinado
aplicativo ou sistema operacional. Essas visões do usuário, abaixo ou acima, que representam conjuntos de
serviços ou funções, podem ser representadas em um modelo chamado máquina de níveis, ou máquina de
camadas, conforme mostrado na figura 2.
Particularmente, a abstração do hardware, ou seja, qualquer camada acima do hardware, pode ser
chamada de máquina virtual.
Figura 2 – Máquina de níveis. Fonte: Adaptado de Machado, 2007.
Histórico dos sistemas computacionais
Um sistema operacional está intimamente ligado ao hardware do computador no qual ele é
executado, estendendo o conjunto de instruções do computador e gerenciando seus recursos. Assim, o
sistema operacional deve possuir grande conhecimento sobre o hardware, ao menos a partir do ponto de
vista do programador.
Os sistemas operacionais evoluíram gradualmente, e o processo temporal dessa evolução pode ser
dividido em fases.
A história dos sistemas operacionais é bastante relacionada à arquitetura de computadores. Sendo
assim, veremos brevemente as várias gerações de computadores com o objetivo de entender as primeiras
versões de sistemas operacionais.
Primeira geração
O período entre aproximadamente 1945 até 1955 corresponde ao surgimento da primeira geração
dos computadores. Nessa época, a programação era feita em painéis de programação (figura 3), e os
componentes de hardware eram mais primitivos, por exemplo, empregando válvulas (figura 4) na
composição dos circuitos lógicos.
Sistemas Operacionais
Marcio Quirino - 12
Figura 3 – Programação realizada em painéis físicos. Fonte: Produção Virtual UFPB.
Figura 4 – Válvula. Fonte: Produção Virtual UFPB.
Segunda geração
Na segunda geração de computadores, compreendida entre aproximadamente 1955 até 1965, houve
a adoção dos transistores no lugar das válvulas para a composição dos circuitos lógicos, e a aplicação de
sistemas de computação em lote, como exemplificado na figura 5. O significado das etapas da figura
encontra-se a seguir:
a) Os programadores levavam os cartões para o leitor de cartões.
b) O leitor de cartões gravava o lote de tarefas em fita.
c) O operador levava a fita para o computador (7094) processar as informações.
d) O computador executava o processamento.
e) O operador levava a fita de saída para um dispositivo capaz de ler o conteúdo e enviar para a
impressora.
f) A impressora gerava as saídas.Sistemas Operacionais
Marcio Quirino - 13
Figura 5 - Imagem ilustrativa do sistema em lote (batch) da segunda geração. Fonte: Adaptado de Tanenbaum, 2010.
Terceira geração
Foi na terceira geração, compreendida entre 1965 a 1980, que o conceito de sistema operacional foi
plenamente estabelecido, trazendo inovações como multiprocessamento, multiprogramação, time-sharing,
spooling e memória virtual. Foi nessa geração que surgiram também os circuitos integrados (CIs) e o sistema
operacional UNIX.
Quarta geração
Pode-se dizer que estamos na quarta geração de computadores, iniciada em 1980 e durando até o
momento no qual este material foi escrito. Entretanto, alguns autores podem chamar os sistemas atuais de
quinta geração.
Nesse período, surgiram uma série de novidades e eventos relacionados aos computadores e aos
sistemas operacionais, alguns dos quais exibidos na tabela 1.
• Surgimento dos computadores pessoais
• Integração em larga escala (LSI e VLSI) – Miniaturização e barateamento dos componentes
eletrônicos
• Intel produz seu primeiro microprocessador – Intel 4004
• Intel 8080 – Primeiro microcomputador
• 1976: Apple II (8 bits)
• Surgimento da Microsoft
• Redes distribuídas
• Protocolos de redes
• Redes locais
• SO intimamente relacionados com o software de rede
• Surgimento da linguagem Pascal e C
• Surgimento do IBM PC – Intel 8088 de 16 bits com DOS (Disk Operating System)
• Sistemas multiusuários foram impulsionados
• Protocolos da internet TCP/IP
• Surgimento das estações de trabalho
• 1982: Surgimento da Sun Microsystems e das primeiras estações RISC
• Surgimento das interfaces gráficas
• Avanços de hardware, software e telecomunicações
• Processadores e dispositivos de E/S mais rápidos e menores
• Integração em ultra larga escala (ULSI)
• Internet: Problemas de gerência, segurança e desempenho
• Arquitetura Cliente – Servidor
Sistemas Operacionais
Marcio Quirino - 14
• Software aberto (Open Source)
• Demanda cada vez maior de processamento
• Arquiteturas paralelas
• Processamento distribuído
• SO em celulares, tablets e outros
MS Windows e UNIX
Historicamente, existiram (e existem) diversos tipos e denominações de sistemas operacionais.
Dentre esses vários, dois se destacam por serem bastante conhecidos e utilizados, e por ainda exercerem
uma grande influência na evolução de todo o contexto tecnológico relacionado aos computadores: Windows
e Unix.
Windows
MS-DOS
A história do Windows começa com o MS-DOS (Disk Operating System), um sistema operacional de
16 bits monoprogramável, monousuário e com interface de linha de comando, lançado pela Microsoft em
1981 para o IBM-PC.
MS Windows 1.0
Em 1985, foi lançado o MS Windows 1.0, introduzindo uma interface gráfica para o MS-DOS. As
versões seguintes continuaram mantendo o MS-DOS como núcleo de SO (tais como Windows 3.0, Windows
95, Windows 98 e Windows Me).
Windows NT
Paralelo às versões baseadas no MS-DOS, o Windows NT foi lançado em 1993, sendo um sistema
de 32 bits, com multitarefa preemptiva, multithread, memória virtual e suporte a múltiplos processadores
simétricos, nas versões para desktop e servidores. O Windows NT possuía um núcleo completamente novo,
mas oferecia compatibilidade parcial com aplicações legadas dos sistemas baseados em MS-DOS, e
possuía a mesma interface gráfica das versões existentes.
Windows 2000
O Windows 2000 foi uma evolução do Windows NT 4, utilizando a mesma arquitetura interna, e novas
funções, tais como o plug and play e o serviço de diretórios Active Directory.
Windows XP
A partir do Windows XP, lançado em 2001, a Microsoft começou a descontinuar as famílias DOS-
Windows e Windows NT/2000, integrando as duas linhas de sistemas operacionais.
Outras versões
Desde então, outras versões foram lançadas direcionadas para desktops (Windows Vista, Windows
7, Windows 8, Windows 10) e servidores (Windows Server 2003, Windows Server 2008, Windows Server
2012, Windows Server 2016, Windows Server 2019).
Unix
O Unix veio de outra vertente dos sistemas operacionais. Como já comentado anteriormente, uma
das características da geração de computadores existentes na década de 1960 eram os sistemas batch.
1965
O MIT, a Bell Labs e a General Electric se uniram para desenvolver o MULTICS (MULTiplexed
Information and Computing Service), um esforço para desenvolver um verdadeiro sistema operacional de
tempo compartilhado.
Sistemas Operacionais
Marcio Quirino - 15
1969
Após a Bell Labs se retirar do projeto, Ken Thompson, que foi um dos pesquisadores envolvidos no
projeto do MULTICS, desenvolveu sua própria versão em Assembly para um minicomputador PDP-7, que
veio a se chamar UNICS (UNiplexed Information and Computing Service) e, mais para frente, Unix. Com o
objetivo de torná-lo mais fácil de ser portado para outras plataformas, Thompson desenvolveu a linguagem
B e reescreveu o código do sistema nessa nova linguagem. Thompson e Dennis Ritchie evoluíram a
linguagem, criando o C.
1973
O Unix foi reescrito em C e portado para um minicomputador PDP-11. Depois disso várias
universidades começaram a licenciar o Unix, como a Universidade de Berkeley, na Califórnia, que
desenvolveu sua própria versão do sistema, chamada BSD (Berkeley Software Distribution), com várias
melhorias, tais como a memória virtual, C shell, Fast File System, sockets, e o protocolo TCP/IP.
1991
O finlandês Linus Torvalds iniciou o desenvolvimento do Linux, baseado no Minix, uma variante do
Unix desenvolvido pelo professor Andrew Tanenbaum, da Holanda, com fins educacionais.
Atenção
O Linux começou a evoluir com a ajuda de vários programadores voluntários. Assim, várias versões do Unix
podem ser encontradas.
A mais importante tentativa de unificação das várias versões do Unix foi feita pelo IEEE através do seu comitê
POSIX (Portable Operating System Unix), que resultou em uma biblioteca padrão de chamadas e um conjunto de
utilitários que todo sistema Unix deveria oferecer.
2. Tipos de SO
Identificar os tipos de Sistemas Operacionais.
Tipos de Sistemas Operacionais
Os sistemas operacionais podem ser classificados em: Sistemas operacionais monoprogramáveis
/monotarefa; sistemas operacionais multiprogramáveis/multitarefa e sistemas com múltiplos processadores.
Figura 6 – Tipos de sistemas operacionais. Fonte: Adaptado de Machado, 2007.
Vejamos agora cada um deles:
Sistemas Monoprogramáveis
Nos sistemas monoprogramáveis (figura 7), também chamados de sistemas monotarefa, os
principais módulos computacionais (processador, memória e periféricos) ficam alocados exclusivamente
para a execução de um único programa, sendo que qualquer outra aplicação deve esperar para poder ser
processada.
Sistemas Operacionais
Marcio Quirino - 16
Figura 7 –Sistemas monoprogramáveis/monotarefa. Fonte: Adaptado de Machado, 2007.
Sistemas Multiprogramáveis ou Multitarefa
Nos sistemas multiprogramáveis (figura 8), ou multitarefa, os recursos computacionais passam a ser
compartilhados entre usuários e aplicações, permitindo que muitas aplicações compartilhem os mesmos
recursos.
Por exemplo, alguns aplicativos podem usar o processador enquanto um determinado programa
aguarda o desfecho de um comando de leitura/gravação em mídia de armazenamento.
Figura 8 – Sistemas multiprogramáveis/multitarefas. Fonte: Adaptado de Machado, 2007.
Os sistemas multiprogramáveis podem ser classificados em sistemas batch, de tempo compartilhado
ou de tempo real. O sistema operacional é responsável por gerenciar vários desses programas ao mesmo
tempo, conforme ilustrado na figura 9.
Sistemas Operacionais
Marcio Quirino - 17
Figura 9 – Classificação dos sistemas multiprogramáveis. Fonte: Adaptado de Machado, 2007.
Um dos problemas que ocorriam nos sistemas monoprogramáveis é que havia um desperdício na
utilização do processador (figura10). Por exemplo, enquanto uma leitura em disco era realizada, o
processador permanecia ocioso, e o tempo de espera até que a leitura em disco fosse realizada era
relativamente longo, pois as operações com dispositivos de E/S são muito lentas quando comparadas com
a velocidade do processador para a execução de instruções.
Figura 10 – Sistemas monoprogramáveis x sistemas multiprogramáveis. Fonte: Adaptado de Machado, 2007.
Em sistemas multiprogramáveis, o processador é utilizado por diversos programas, ou processos, de forma
concorrente, ou seja, quando um processo tem que fazer uma operação de E/S, outro processo pode utilizar o
processador.
A utilização da CPU como função da quantidade de processos carregados na memória
simultaneamente é chamada grau de multiprogramação. A figura 11 apresenta um gráfico representando a
utilização da CPU versus o grau de multiprogramação.
Figura 11 – Utilização da CPU como função do número de processos na memória. Fonte: Adaptado de Machado, 2007.
Atenção
Pode-se perceber através da análise da figura 11 que, para a curva de “80% de espera de E/S”, se os processos
passarem 80% do seu tempo esperando por dispositivos de E/S, pelo menos 10 processos devem estar na memória
simultaneamente, para que a CPU desperdice menos de 10%.
Sistemas Operacionais
Marcio Quirino - 18
Sistemas com múltiplos processadores
Os sistemas com múltiplos processadores possuem dois ou mais processadores atuando juntos,
oferecendo vantagens como:
1. Escalabilidade
✓ Aumenta a capacidade de processamento.
2. Disponibilidade
✓ Se um processador falhar, outro pode assumir a carga de trabalho.
3. Balanceamento de carga
✓ Distribuição da carga de trabalho entre os processadores.
Em sistemas com múltiplos processadores, os processadores podem se comunicar de duas
maneiras: Nos sistemas fortemente acoplados e sistemas fracamente acoplados.
Sistemas fortemente acoplados
Nos sistemas fortemente acoplados, os processadores compartilham somente a memória principal e
os periféricos são gerenciados por um único sistema operacional.
Figura 12 - Sistemas fortemente acoplados. Fonte: Adaptado de Machado, 2007.
Os sistemas fortemente acoplados podem ser:
Clique nas barras para ver as informações.
SMP - Symmetric Multiprocessors
• Os processadores acessam a memória uniformemente ao longo do tempo.
NUMA - Non-Uniform Memory Access
• Como existem conjuntos de processadores e memória principal, e um conjunto se conecta
aos outros por meio de uma rede de interconexão, o tempo de acesso dos processadores
à memória varia conforme a localização física.
Sistemas Operacionais
Marcio Quirino - 19
Sistemas fracamente acoplados
Já nos sistemas fracamente acoplados (figura 13), dois ou mais sistemas computacionais
independentes (cada um possui o seu próprio sistema operacional e gerencia seus próprios recursos, como
processador, memória e periféricos) estão conectados por linhas de comunicação.
Figura 13 - Sistemas fracamente acoplados. Fonte: Adaptado de Machado, 2007.
Os sistemas com múltiplos processadores também podem ser chamados "Sistemas Avançados de
Processamento". Eles podem ser constituídos de computadores com vários processadores, processadores
com vários núcleos e sistemas distribuídos. O S.O. deve estar adaptado para operar esses sistemas.
Outras classificações dos sistemas operacionais
Dentro desses mais de 50 anos de existência dos sistemas operacionais, existiu um desenvolvimento
de uma grande variedade deles, alguns dos quais não foram bem conhecidos.
Dentro dos tipos que existiram ao longo do tempo, um S.O. foi elaborado oferecendo um conjunto de
serviços suportados. Por exemplo, alguns dos serviços que podem ser especificamente oferecidos pelos
sistemas operacionais de computadores de grande porte são:
1. O processamento simultâneo de muitas tarefas.
2. O suporte ao manejo de grandes quantidades de E/S.
3. O oferecimento dos serviços de processamento em lote (batch), de processamento de
transações e de execução de tarefas em tempo compartilhado.
Quanto aos serviços citados em (3), temos:
Sistemas em lote
• O sistema em lote (batch) processa tarefas de rotina sem a presença interativa do usuário.
Por exemplo: Processamento de apólices de companhia de seguro; relatório de vendas
de uma cadeia de lojas.
Sistemas de processamento de transações
• Os sistemas de processamento de transações administram grandes quantidades de
pequenas requisições. Cada unidade de trabalho é pequena, mas o sistema precisa tratar
centenas ou milhares delas por segundo. Por exemplo: Processamento de verificações
em um banco ou em reservas de passagens aéreas.
Sistemas de tempo compartilhado
• Os sistemas de tempo compartilhado permitem que múltiplos usuários remotos executem
suas tarefas simultaneamente no computador. Por exemplo: Realização de consultas a
um banco de dados.
Já os serviços de sistemas de tempo real possuem o tempo como parâmetro fundamental. Por
exemplo: Linha de montagem de um carro.
Sistemas Operacionais
Marcio Quirino - 20
Os sistemas de tempo real podem ser dos tipos:
Sistema de tempo real crítico
• As ações precisam ocorrer em determinados instantes. Exemplo: Processos industriais,
aviônica, militares.
Sistema de tempo real não crítico
• O descumprimento de um prazo não causa dano permanente. Exemplo: Sistema de áudio
digital, multimídia, telefones digitais.
Comentário
Alguns tipos de sistemas operacionais, assim como algumas das funcionalidades específicas relacionadas a
cada tipo, podem deixar de existir, enquanto outros novos tipos e funcionalidades podem surgir, conforme acontece o
avanço das tecnologias.
Outra situação que acontece muitas vezes é certos tipos de SOs e de rotinas que já existiram em
algum momento no passado caírem em desuso e voltarem a ser utilizadas no tempo presente. Tanenbaum
(2010) diz que cada nova “espécie” de computador parece passar pelo mesmo desenvolvimento de seus
ancestrais, tanto no que se refere ao hardware quanto ao software. Muitas vezes, uma alteração tecnológica
torna alguma ideia obsoleta e ela desaparece rapidamente, mas outra mudança tecnológica pode reavivá-
la.
Exemplo
Um exemplo é o uso da memória cache nas CPUs quando se tornam mais velozes que as memórias. Se
alguma nova tecnologia de memória tornar as memórias mais velozes que a CPU, as memórias caches irão
desaparecer. E, no caso de uma nova tecnologia de CPU torná-las novamente mais rápidas que as memórias, as
memórias caches reaparecerão.
Para terminar, os sistemas operacionais variam quanto aos tipos de licenças, ou seja, o conjunto de
ações que o usuário do SO pode ou não fazer:
Software proprietário
• Licenciado sob direitos legais exclusivos – copyright.
• Usualmente o código-fonte total ou parcial não é disponibilizado para modificação por
qualquer pessoa, apenas a fabricante possui o acesso.
Software livre (free software)
• Software livre se refere à liberdade dos usuários executarem, copiarem, distribuírem,
estudarem, modificarem e aperfeiçoarem o software.
• Por exemplo, a licença GNU da Free Software Foundation (FSF) possui as seguintes
liberdades:
• Liberdade nº 0: Executar o programa, para qualquer propósito.
• Liberdade nº 1: Liberdade de estudar como o programa funciona, e adaptá-lo para as suas
necessidades. Acesso ao código-fonte é um pré-requisito para esta liberdade.
• Liberdade nº 2: Liberdade de redistribuir cópias de modo que você possa ajudar ao seu
próximo.
• Liberdade nº 3: Liberdade de aperfeiçoar o programa e liberar os seus aperfeiçoamentos,
de modo que toda a comunidade se beneficie. Acesso ao código-fonte é um pré-requisito
para esta liberdade.
Software de código aberto (open source)
• O código-fonte é disponibilizado por meio de uma licença de código aberto para
modificação ou melhoria por qualquer pessoa.
SistemasOperacionais
Marcio Quirino - 21
• Diferente das liberdades do software livre.
• O termo "código aberto" foi criado pela OSI (Open Source Initiative).
• Difere-se de um software livre por não respeitar as 4 liberdades definidas pela Free
Software Foundation (FSF), compartilhadas também pelo projeto Debian em "Debian Free
Software Guidelines (DFSG)".
• Qualquer licença de software livre é também uma licença de código aberto (Open Source),
mas o contrário nem sempre é verdade.
• Exemplos de licenças de código aberto:
✓ Apache License.
✓ MIT License.
✓ Mozilla Public License.
✓ Common Development and Distribution License.
✓ Eclipse Public License.
3. Estrutura do SO: Kernel, System Calls, Modos de Acesso
Compreender a estrutura do SO: Kernel, system calls, modos de acesso.
Estrutura do SO: Kernel, System Calls, Modos de Acesso
O sistema operacional é formado por um conjunto de rotinas que oferecem serviços aos usuários, às
suas aplicações e ao próprio sistema. Esse conjunto de rotinas é denominado núcleo do sistema ou kernel,
cuja localização na máquina de níveis está ilustrada na figura 14.
Figura 14 - Kernel do SO na máquina de níveis. Fonte: Adaptado de Machado, 2007.
Saiba mais
O sistema operacional funciona com algumas particularidades quanto à execução das rotinas:
• Tarefas não sequenciais, ou seja, as rotinas são executadas concorrentemente.
• Sem ordem predefinida.
• Eventos assíncronos.
• Existem eventos relacionados ao hardware e eventos relacionados às tarefas internas do próprio SO.
O núcleo do SO deve atuar considerando essas particularidades, e para isso implementa funções
como:
✓ Tratamento de interrupções e exceções.
✓ Criação e eliminação de processos e threads.
Sistemas Operacionais
Marcio Quirino - 22
✓ Sincronização e comunicação entre processos e threads.
✓ Escalonamento e controle de processos e threads.
✓ Gerência de memória.
✓ Gerência do sistema de arquivos.
✓ Gerência dos dispositivos de E/S.
✓ Suporte a redes locais e distribuídas.
✓ Contabilização do uso do sistema.
✓ Auditoria e segurança do sistema.
✓ Etc.
A execução das rotinas no sistema operacional é controlada pelo mecanismo denominado system
call (figura 15), ou chamada ao sistema, um mecanismo de proteção por software que garante a execução
somente de rotinas previamente autorizadas.
Figura 15 – System call. Fonte: Adaptado de Machado, 2007.
System Calls
Quando qualquer aplicação deseja chamar uma rotina do sistema operacional, ela aciona a system
call. O sistema operacional verifica se a aplicação possui os privilégios suficientes para a execução da rotina
desejada. Se não tiver, o sistema operacional impede o desvio para a rotina do sistema, e informa ao
programa de origem que a operação não será executada.
A system call é uma “porta de entrada” para o acesso ao núcleo do SO e aos seus serviços. Cada serviço
possui uma system call associada, e cada SO possui seu próprio conjunto de chamadas, com nomes, parâmetros e
formas de ativação específicas.
Por exemplo, a chamada ao sistema "read" (figura 16) possui três parâmetros:
Figura 16 – Chamada ao sistema "read". Fonte: Adaptado de Tanenbaum, 2010.
Os passos para a realização da system call "read" estão ilustrados na figura 17:
Sistemas Operacionais
Marcio Quirino - 23
Figura 17 - Passos para a realização da system call "read". Fonte: Adaptado de Tanenbaum, 2010.
Observe uma breve descrição a seguir:
1. Passos 1 a 3
✓ O programa que chama read primeiramente armazena os parâmetros na pilha.
2. Passos 4 e 5
✓ Segue-se para a chamada real à rotina de biblioteca (passo 4) que, em geral, coloca o
número da chamada de sistema em um local esperado pelo S.O., por exemplo, em um
registrador (o passo 5).
3. Passo 6
✓ É executada uma instrução TRAP para passar do modo usuário para o modo núcleo,
iniciando a execução em um determinado endereço no núcleo.
4. Passo 7
✓ O código do núcleo, iniciado após a instrução TRAP, verifica o número da chamada de
sistema e despacha para a rotina correta de tratamento dessa chamada.
5. Passo 8
✓ É a execução da rotina de tratamento da chamada de sistema.
6. Passos 9 e 10
✓ Após o término dessa rotina, pode-se retornar o controle para a rotina de biblioteca no
espaço do usuário, na instrução seguinte à instrução TRAP (passo 9), em geral do
mesmo modo no qual são feitas as chamadas de rotina (passo 10).
7. Passo 11
✓ O programa do usuário deve limpar a pilha. O programa agora está liberado para fazer
o que quiser.
Sistemas Operacionais
Marcio Quirino - 24
Em geral, para qualquer situação, o funcionamento da system call (figura 18) consiste em:
1. Realização da chamada ao sistema pela aplicação que deseja executar rotina(s).
2. Processamento da solicitação pelo kernel.
3. Retorno, para a aplicação solicitante, de uma resposta, com um estado de conclusão,
indicando se houve algum erro.
Figura 18 – Chamada a uma rotina de sistema. Fonte: Adaptado de Machado, 2007.
Exemplo
Um exemplo de conjunto de chamadas de sistema é o POSIX (Portable Operating System Interface for Unix),
padrão internacional ISO/IEC/IEEE 9945 que foi uma tentativa de padronizar as system calls.
O objetivo inicial foi unificar as diversas versões existentes do sistema operacional UNIX, permitindo um
aplicativo utilizando o padrão POSIX teoricamente poder ser executado em qualquer SO que possua suporte. É
incorporado, atualmente, pela maioria dos sistemas operacionais modernos.
Já o conjunto de chamadas de sistemas Win32 é suportada desde o Windows 95. Cada nova
versão do Windows traz novas chamadas que não existiam anteriormente, buscando não invalidar
os programas existentes.
A API Win32 possui um grande número de chamadas para gerenciar aspectos da interface gráfica
(como janelas, figuras geométricas, textos, fontes de caracteres, caixas de diálogos, menus etc.).
A API Win32 possui diferenças em relação ao POSIX.
Uma delas é o desacoplamento das chamadas de biblioteca e das chamadas reais ao sistema, o que
pode dificultar a identificação do que é uma chamada ao sistema, e do que é uma chamada de biblioteca no
espaço do usuário.
A API Win32 permite aos programadores acesso aos serviços do SO mesmo quando trabalhando
em uma versão diferente com chamadas ao sistema modificadas. As chamadas Win32 chegam a milhares.
A tabela 2 exibe algumas chamadas da API Win32 que correspondem, aproximadamente, às
chamadas do UNIX.
Sistemas Operacionais
Marcio Quirino - 25
UNIX Win32 Descrição
fork CreateProcess Cria um novo processo
waitpid WaitForSingleObject Pode esperar que um processo saia
execve (nenhuma) CreateProcess = fork + execve
exit ExitProcess Conclui a execução
open CreateFile Cria um arquivo ou abre um arquivo existente
close CloseHandle Fecha um arquivo
read ReadFile Lê dados a partir de um arquivo
write WriteFile Escreve dados em um arquivo
iseek SetFilePointer Move o ponteiro do arquivo
stat GetFileAttributesEx Obtém vários atributos do arquivo
mkdir CreateDirectory Cria um novo diretório
rmdir RemoveDirectory Remove um diretório vazio
link (nenhuma) Win32 não dá suporte a links
unlink DeleteFile Destrói um arquivo existente
mount (nenhuma) Win32 não dá suporte a links
umount (nenhuma) Win32 não dá suporte a links
chdir SetCurrentDirectory Altera o diretório de trabalho atual
chmod (nenhuma)
Win32 não dá suporte à segurança (embora o NT
suporte)
kill (nenhuma) Win32 não dá suporte a sinais
time GetLocalTime Obtém o tempo atual
Tabela 2 – Chamadas da API Win32 que correspondem aproximadamente às chamadas do POSIX. Fonte: Adaptado de
Tanenbaum, 2010.
Podemos classificar os tipos de chamadas ao sistema em: Chamadas para o gerenciamento de
processos, arquivos e diretórios. A Tabela 3 mostra alguns exemplos desses tipos de chamadas.
Gerenciamento de processosChamada Descrição
pid=fork() Cria um processo filho idêntico ao pai
pid=waitpid(pid, &statloc, options) Espera que um processo filho seja concluído
Gerenciamento de arquivos
Chamada Descrição
fd=open(file, how, ...0) Abre um arquivo para leitura, escrita ou ambos
s=close(fd) Fecha um arquivo aberto
n=read(fd, buffer, nbytes) Lê dados a partir de um arquivo em um buffer
Sistemas Operacionais
Marcio Quirino - 26
Gerenciamento do sistema de diretório e arquivo
Chamada Descrição
s=mkdir(name, mode) Cria um novo diretório
s=rmdir(name) Remove um diretório vazio
s=mount(special, name, flag) Monta um sistema de arquivos
Diversas
Chamada Descrição
s=chmod(name, mode) Altera os bits de proteção de um arquivo
seconds=time(&seconds) Obtém o tempo decorrido desde 1 de janeiro de 1970
Tabela 3 - Exemplos de tipos de chamadas ao sistema. Fonte: Adaptado de Tanenbaum, 2010.
Modos de acesso
As instruções executadas pelas aplicações podem ser dos tipos:
1. Privilegiadas
✓ Incluem as instruções que de algum modo podem comprometer o sistema.
2. Não privilegiadas
✓ São as instruções que não oferecem riscos ao sistema.
As instruções podem acessar o sistema de dois modos:
Modo kernel ou supervisor
• Uma aplicação possui o modo de acesso supervisor quando é constituída de instruções
privilegiadas. A aplicação pode executar o conjunto total de instruções. O modo kernel
também protege a área do SO localizada na memória.
Modo de acesso usuário
• Uma aplicação possui o modo de acesso usuário quando é constituída de instruções não
privilegiadas.
Quando a aplicação chama a rotina do SO através da System Call, o sistema verifica se ela tem os
privilégios necessários (autorizada pelo administrador do sistema). Se não possuir, o SO impede o desvio
para a rotina do sistema através do mecanismo de proteção por software.
Uma aplicação sempre deve executar com o processador em modo usuário.
Você sabia
Se uma aplicação tentar executar diretamente uma instrução privilegiada sem ser por intermédio de uma
chamada à rotina do sistema, um mecanismo de proteção por hardware garantirá a segurança do sistema. O hardware
do processador irá sinalizar com um erro.
Arquiteturas de kernel
Existem variações quanto à concepção da estrutura do núcleo do sistema operacional. As principais
arquiteturas dos sistemas operacionais são:
Arquitetura monolítica (mono-kernel)
• Podemos comparar a arquitetura de núcleo monolítico com uma aplicação composta por
vários módulos, que são compilados separadamente e depois linkados (ligados), constituindo
um grande e único programa executável, no qual os módulos podem interagir livremente. Uma
ilustração se encontra na figura 19. Nessa abordagem, o programa-objeto real do sistema
Sistemas Operacionais
Marcio Quirino - 27
operacional é construído compilando todas as rotinas individualmente e depois juntando todas
em um único arquivo-objeto usando o ligador (linker) do sistema. Todas as rotinas são visíveis
umas às outras, não existindo, em essência, uma ocultação de informação.
Figura 19 – Arquitetura monolítica. Fonte: Adaptado de Machado, 2007.
Arquitetura de camadas
• Nessa arquitetura, o sistema é dividido em várias camadas, que oferecem um conjunto de
funções. Cada camada oferece suas funções para a camada imediatamente superior. Possui
as vantagens de isolar as funções do SO, facilitando sua manutenção e depuração, e de criar
uma hierarquia de níveis de modo de acesso, protegendo as camadas mais internas. Possui
a desvantagem de poder ter o desempenho comprometido (a segregação implica em mais
rotinas para a comunicação entre as camadas).
Comentário
Atualmente, a maioria dos SO comerciais para workstations, em geral, usam duas camadas.
Figura 20 – Arquitetura de camadas.
Sistemas Operacionais
Marcio Quirino - 28
Figura 21 – Arquitetura de camadas. Fonte: Adaptado de Tanenbaum, 2010.
Máquina virtual ou virtual machine (VM)
• Cria um nível intermediário entre o hardware e o sistema operacional, chamado de
gerenciador de máquinas virtuais. Este nível possibilita a criação de máquinas virtuais
independentes, sendo que cada uma oferece uma cópia virtual do hardware (virtualização),
incluindo os modos kernel e usuário, as interrupções e dispositivos de E/S.
O termo máquina virtual também pode ser definido como a ocultação de certas características computacionais
através de uma camada de abstração.
Figura 22 – Máquina virtual. Fonte: Adaptado de Machado, 2007.
Comentário
Nos últimos anos, as máquinas virtuais voltaram a ser bastante utilizadas. Muitas organizações executavam
seus servidores em computadores separados às vezes com sistemas operacionais distintos. Com a virtualização, é
possível executar todos eles na mesma máquina sem que uma falha em um servidor afete os outros.
Quando os clientes alugam uma máquina virtual, uma mesma máquina física suporta muitas
máquinas virtuais simultaneamente, logo poderão executar os sistemas operacionais e softwares que
quiserem por uma parte do custo de um servidor dedicado. Pode-se usar a virtualização também para
executar dois ou mais sistemas operacionais ao mesmo tempo.
Para executar o software de máquina virtual em um computador, a CPU deve ser virtualizável.
Sistemas Operacionais
Marcio Quirino - 29
Você sabia
Em CPUs mais antigas, as tentativas de executar instruções não privilegiadas no modo usuário são ignoradas,
o que impossibilitou a existência de máquinas virtuais nesse hardware.
Essa situação deixou de existir ao substituir os hypervisors de tipo 1, executados diretamente no hardware,
pelos hypervisors de tipo 2, executados como programas aplicativos na camada superior do sistema operacional,
conhecida como sistema operacional hospedeiro. O hypervisor é o monitor de máquina virtual.
O hypervisor de tipo 2 instala o sistema operacional hóspede em um disco virtual, que é apenas um arquivo
grande no sistema de arquivos do sistema operacional hospedeiro. Quando o sistema operacional hóspede é
inicializado, faz o mesmo procedimento que é feito no verdadeiro hardware, iniciando algum processo subordinado e
depois uma interface gráfica.
Arquitetura microkernel
• Visa a tornar o núcleo do sistema operacional o menor e mais simples possível,
disponibilizando no núcleo apenas os serviços do sistema que oferecem funções específicas,
como gerência de arquivos, gerência de processos, gerência de memória e escalonamento.
Os demais processos são executados fora do núcleo.
É difícil de implementar na prática, sendo que usualmente é feita uma combinação híbrida, ou seja, do modelo
de camadas com a arquitetura microkernel.
Figura 23 – Arquitetura microkernel. Fonte: Adaptado de Machado, 2007.
Atenção
O princípio do microkernel é obter alta confiabilidade ao dividir o sistema operacional em pequenos módulos e
bem definidos. Apenas o micronúcleo pode ser executado no modo núcleo. Os outros módulos são executados como
processos de usuário comuns.
Quando, por exemplo, há um erro em um driver de dispositivo executado como um processo de
usuário separado, somente o componente correspondente é quebrado, enquanto que o sistema continua
funcionando inteiramente.
Exonúcleo
• No lugar da clonagem da máquina real, como é no caso das máquinas virtuais, uma estratégia
é dividi-la, ou seja, fornecer um subconjunto de recursos para cada usuário.
Sistemas Operacionais
Marcio Quirino - 30
Exemplo
Por exemplo, uma máquina virtual pode obter os blocos 0 a 1.023 do disco, outra os blocos 1.024 a 2.047 etc.
O exonúcleo é um programa em execução em modo núcleo na camada mais inferior, que aloca
recursos às máquinas virtuais e verifica as tentativas de uso para garantir que uma máquina não
tente usar recursos de outra.
A vantagem é que o exonúcleo poupa uma camada de mapeamento: Ao invés de cadamáquina
virtual pensar que possui seu próprio disco (com blocos indo de 0 até o limite), fazendo com que o hypervisor
mantenha tabelas para remapear os endereços de disco, com o exonúcleo, esse mapeamento não é mais
necessário.
Basta manter o registro de para qual máquina virtual foi atribuído qual recurso.
4. Arquitetura, instalação do Linux e comandos básicos
Analisar a arquitetura, instalação do Linux e comandos básicos
Conceitos
O Linux é um sistema operacional moderno e gratuito desenvolvido inicialmente em 1991 como um
kernel pequeno e autocontido por Linus Torvalds, com os objetivos de ser baseado nos padrões UNIX e
manter a compatibilidade com o mesmo. Sua história tem sido de colaboração por muitos usuários do mundo
inteiro se comunicando principalmente através da Internet.
O Linux é um sistema operacional dito “Unix-like”, por possuir comportamento similar ao do
sistema operacional Unix (multitarefa e multiusuário).
Saiba mais
Uma distribuição Linux é constituída por uma coleção de aplicativos e o kernel (núcleo) do sistema operacional.
As distribuições podem ser utilizadas por meio da instalação em disco no terminal do usuário, ou ainda por meio das
distribuições do tipo live (oferecendo recursos mais limitados), que geralmente são executados a partir de dispositivos
de armazenamento como discos removíveis, e carregados na memória RAM sem a necessidade de instalação no host.
Vamos agora ver como podemos instalar o Linux para darmos os primeiros passos nesse sistema
operacional. Para isso, iremos utilizar o Ubuntu, uma distribuição popular baseada no Debian, que servirá
como base para as demonstrações exibidas neste módulo, especificamente a versão "Ubuntu desktop".
Entretanto, você poderá optar por usar outras distribuições de sua escolha. O download poderá ser realizado
na página do Ubuntu.
Figura 24 – Logotipo do Ubuntu. Fonte: wikimedia.
Cada versão do Ubuntu recebe uma numeração e um codinome, que seguem a lógica mostrada na
tabela 4.
Denominação da versão 20. 04 LTS Focal Fossa
Significado Ano (2020) Mês (04 – abril)
Long-Term Support (Suporte de longo prazo - 5 anos de
suporte (atualizações))
Codinome
Tabela 4 – Exemplo de numeração e um codinome do Ubuntu. Fonte: O Autor.
Sistemas Operacionais
Marcio Quirino - 31
Faça o download do arquivo de imagem “iso" correspondente à versão atual do Ubuntu acessando
a página do Ubuntu, conforme mostrado na figura 25.
Figura 25 – Página para download do Ubuntu. Fonte: O Autor.
Dica
Você poderá melhorar a utilização do S.O. no VirtualBox instalando o “Adicionais para convidado”, acessando
o menu “Dispositivos” -> “Inserir Imagem de CD dos Adicionais para Convidado”. O “CD” será automaticamente
montado e deverá aparecer como execução automática.
Figura 26 – Adicionais para convidado do VirtualBox. Fonte: O Autor.
Estrutura de diretórios no Linux
O Linux possui uma estrutura única de pastas, que partem de uma única raiz, como ilustrado na
figura 27.
Todos os dispositivos, internos ou externos, fixos ou removíveis, são montados em algum ponto
abaixo da raiz.
Sistemas Operacionais
Marcio Quirino - 32
Figura 27 – Estrutura de pastas do Linux.
Atenção
Existem regras para nomes de arquivos e pastas. O Linux é case-sensitive, ou seja, as letras MAIÚSCULAS e
minúsculas fazem diferença no nome de um arquivo. Por exemplo, "texto", "Texto" e "TEXTO" são três arquivos
distintos. Quando for escrever um caminho de diretórios, use o separador "/". Exemplo: /tmp/Texto.txt.
As principais pastas e seus significados resumidos estão mostrados na tabela 5.
/ Pasta Raiz /usr Programas de Usuário
/bin Executáveis Binários /home Pasta Pessoal
/sbin Sistema Binário /boot Arquivos de Inicialização
/etc Arquivos de Configuração /lib Bibliotecas do Sistema
/dev Arquivos de Dispositivos /opt Aplicações Opcionais
/proc Informação de Processo /mnt Pasta de Montagem
/var Arquivos Variáveis /media Dispositivos Removíveis
/tmp Arquivos Temporários /srv Serviço de Dados
Tabela 5 – Diretórios e seus significados. Fonte: O Autor.
O Super Usuário é um usuário especial predefinido, com poderes especiais de administração. No
Windows é chamado de administrador (administrator), e no Linux (Unix) é denominado root.
Comandos do Linux
A seguir, serão mostrados os comandos básicos para o terminal do Linux. No Ubuntu Desktop, clique
no ícone "mostrar aplicativos" na barra da lateral esquerda, digite "term" na caixa de pesquisa, e abra o
terminal.
O terminal (ou prompt de comando) será aberto, conforme a figura 30. Veja o significado da linha de
comandos:
teste@teste-VirtualBox:~$
teste: Usuário
teste-VirtualBox: Nome da máquina
~$: Significa que o usuário está na sua pasta pessoal em /home ("/home/usuário")
Sistemas Operacionais
Marcio Quirino - 33
Figura 30 – Terminal. Fonte: O Autor.
Saiba mais
Veja uma lista com os Comandos básicos para uso via terminal.
Comandos básicos para uso via terminal
• pwd → Mostra o diretório no qual o usuário está no momento.
• sudo <comando> → Permite que o usuário execute comandos como ‘root’, sem ter que usar a
conta de ‘root’.
• su →Permite que o usuário entre no modo Super Usuário. Aconselha-se usar esse comando
somente quando for necessário, não sendo aconselhado usar o modo super usuário para
executar suas ações de rotina. Configure primeiramente usando o comando sudo passwd para
poder usar o comando su. A linha de comandos, então, ficará da seguinte maneira:
root@teste-VirtualBox:/#
root: Usuário ‘root’
teste-VirtualBox: Nome da máquina
/#: Usuário que possui nível de acesso privilegiado a partir da raiz do sistema
Depois que tiver realizado o procedimento que demandou o uso da conta ‘root’, volte para o
usuário padrão digitando exit.
• cd [opções] <caminho> → Navega entre as pastas do sistema.
a. cd .. ➔ Acessa um diretório de nível acima do atual.
b. cd / ➔ Vai para o diretório raiz.
c. cd ~ ➔ Vai para o seu diretório pessoal.
d. cd – ➔ Volta para o diretório no qual o usuário se encontrava antes de mudar.
• ls → Lista arquivos e diretórios. A opção –a mostra arquivos ocultos e a opção –l mostra mais
informações (detalhes).
Exemplo: “ls” ou “ls –l” ou “ls –a” ou “ls –la”
• Símbolo ~ → Representa o diretório pessoal (home) do usuário, também chamado de diretório
de login.
Exemplos:
a. ~teste → Caminho para a pasta pessoal do usuário ‘teste’
b. cd ~teste
(a) Acessa a pasta pessoal do usuário ‘usuário’.
c. ls ~teste/documentos
(a) Lista os arquivos na pasta ‘documentos’, contida na pasta pessoal do usuário.
• cat → Exibe o conteúdo de um arquivo na tela do terminal.
Exemplo: “cat /etc/resolv.conf”
Sistemas Operacionais
Marcio Quirino - 34
• head [opção] → Exibe o conteúdo do início de um arquivo.
a. -n → Exibe as n primeiras linhas
(a) Ex.: “head -3 /etc/hosts”
• tail → Exibe o conteúdo do final de um arquivo.
a. -n → Exibe as n primeiras linhas
(a) Exemplo: “tail -3 /etc/hosts”
• clear → Limpa a tela do terminal.
• cp → Copia arquivos.
a. Exemplo: “cp arquivo1 arquivo2”
(a) Observação: Origem e destino no mesmo diretório.
b. Ex.: “cp arquivo1 /dir/arquivo2”
(a) Obs.: Destino em diretório diferente.
c. Exemplo: “cp /dir/arquivo1 /dir/arquivo2”
(a) Observação: Origem e destino em diretórios diferentes.
• mv → Move arquivos.
a. Exs.: “mv /home/teste/resolv.conf /tmp/”
b. “mv /home/teste/resolv.conf /tmp/”
• rm → Remove arquivos/diretórios. Opção –rf remove de forma recursiva e forçada (muito cuidado
ao usar, pois poderá apagar tudo acidentalmente).
a. Exemplo: “rm /tmp/musica.mp3”
• mkdir → Cria um diretório.
a. Exemplo: “mkdir /home/teste/musicas”
• rmdir → Remove um diretório, apenas se ele estiver vazio.
a. Exemplo: “rmdir /home/teste/musicas”
• man <comando> → Exibe uma página de instruções para um comando ou arquivo de
configuração.a. Exemplo: “man head”.
• exit ou logout → Sai da sessão atual, ou realiza o logout do usuário.
a. Pode-se encerrar a sessão também com a combinação de teclas CTRL+D.
• reboot → Reinicia a máquina.
• poweroff ou halt → Desliga a máquina de forma segura.
• shutdown <opção> <quando> [Mensagem] → Reinicia ou desliga a máquina.
a. Exemplo: ‘shutdown –h +5 “Aviso: Servidor desligando”’: O servidor desligará em 5
minutos e será enviada a mensagem aos demais usuários conectados.
b. Lembre-se de usar “sudo”.
• date → Mostra a data e hora do sistema.
a. Exemplo: “sudo date 082013502020”: Altera a data e hora para 13:50h de 20/08/2020.
• history → Exibe o histórico de comandos já digitados no terminal.
• touch <nome> → Cria o arquivo <nome> vazio. Se o arquivo já existe, sua hora é atualizada.
• apt → Gerenciador de pacotes do Ubuntu, evolução do Apt-Get.
a. apt update ➔ Atualiza a lista de fontes.
b. apt install <nome_pacote> ➔ Instala um novo pacote.
c. apt remove <nome_pacote> ➔ Remove um pacote.
d. apt upgrade ➔ Atualiza todos os pacotes instalados.
e. apt full-upgrade➔ Atualiza o sistema todo para uma nova versão.
Alguns aplicativos são obtidos na forma de código-fonte aberto, implicando na necessidade de
compilação do código. Os passos gerais para compilação de código-fonte são:
✓ Realizar o download do arquivo-fonte (por exemplo, .tar.gz).
✓ Descompactar o arquivo-fonte.
Sistemas Operacionais
Marcio Quirino - 35
✓ Acessar o diretório contendo os arquivos do código-fonte.
✓ Executar os seguintes comandos:
./configure
make
make install
Considerações finais
O sistema operacional surgiu com o objetivo de tornar o uso do computador mais fácil e conveniente
para o usuário.
É muito importante o entendimento da ideia de abstração em sistemas de computação. O sistema
operacional é um conjunto de rotinas que tem o objetivo de abstrair as camadas inferiores e o hardware, no
modelo de máquina de camadas. Essas rotinas em geral constituem o núcleo do sistema operacional,
embora algumas rotinas possam ser executadas no espaço do usuário, conforme varia a estrutura do núcleo
do sistema operacional.
Existem (e existiram) diversos sistemas operacionais ao longo da história. Um dos mais conhecidos
e bastante utilizado é o Linux, que conta com uma enorme gama de distribuições à disposição dos usuários.
Referências
MACHADO, F. B.; MAIA, L. P. M. Arquitetura de sistemas operacionais. 4. ed. Rio de Janeiro: LTC,
2007.
MACHADO, F. B.; MAIA, L. P. M. Arquitetura de sistemas operacionais. 5. ed. Rio de Janeiro: LTC,
2013.
MACHADO, F. B.; MAIA, L. P. M. Arquitetura de sistemas operacionais – Material Suplementar para
Acompanhar. 5. ed. Rio de Janeiro: LTC, 2013.
SILBERSCHATZ, A. Fundamentos de sistemas operacionais – Princípios básicos. 1. ed. Rio de
Janeiro: LTC, 2013.
TANENBAUM, A. S. Sistemas Operacionais Modernos. 3. ed. São Paulo: Pearson, 2010.
TANENBAUM, A. S.; BOS, H. Sistemas Operacionais Modernos. 4. ed. São Paulo: Pearson, 2016.
Explore+
Para saber mais sobre os assuntos tratados neste tema, pesquise na internet:
Comunidade Open Source ‒ Open Source (Código Aberto).
GuiaFoca.
Linux From Scratch!
O Sistema Operacional GNU e o Movimento de Software Livre, GNU.
Open Source Initiative, Opensource.
Para saber mais sobre os assuntos tratados neste tema, pesquise na internet:
Crash Course Ciência da Computação, Episódio 2, YouTube.
Dentro do seu computador ‒ Bettina Bair, YouTube.
Sistemas Operacionais
Marcio Quirino - 36
Processos e Gerencia de Processador
Definição
Definição de processos e threads e da forma como esses elementos devem ser estruturados para a
construção de sistemas eficientes.
Propósito
Atualmente, até os mais simples dispositivos contam com a capacidade de multiprocessamento. Os
sistemas operacionais, acompanhando o rápido desenvolvimento da tecnologia, fornecem mecanismos que
possibilitam a construção de sistemas concorrentes que buscam tirar o máximo proveito desta capacidade.
Saber explorar esta possibilidade é fundamental para a formação de profissionais habilitados a resolverem
os desafios demandados por sistemas que buscam alto desempenho.
Preparação
Antes de iniciar o conteúdo deste tema, é desejável ter acesso a um computador (ou máquina virtual)
com Linux instalado. Para os exemplos deste tema, foi utilizado o Ubuntu Desktop 20.04 LTS.
Introdução
Neste tema, estudaremos os conceitos de processos e como eles são utilizados para tirar proveito
da capacidade de multiprocessamento dos equipamentos atuais. Para isso, veremos a criação de
subprocessos e como eles se comunicam. Estudaremos também o funcionamento dos threads, que são
linhas de execução concorrentes dentro de um processo, fornecendo ao programador/desenvolvedor os
mecanismos necessários para permitir a concorrência dentro de um processo.
Por fim, destacaremos os cuidados que devemos ter ao desenvolver sistemas que acessam dados
compartilhados e os mecanismos que permitem o desenvolvimento seguro de tais aplicações.
1. Conceitos de processos
Descrever os conceitos de processos
Conceitos
Antes de iniciar
Neste tema, você verá alguns exemplos de funcionamento de programas e comandos. Para o
desenvolvimento dos exemplos, foi utilizado o Sistema Operacional Ubuntu Desktop 20.04 LTS, porém você
pode utilizar o Linux de sua preferência. O esperado é que não haja diferença de utilização entre as várias
distribuições Linux.
Os programas utilizados como exemplo foram desenvolvidos em Linguagem C, uma linguagem
clássica para o desenvolvimento de sistemas operacionais. O objetivo dos exemplos é permitir que você
possa ver a utilização prática dos conceitos estudados no tema.
1. Instalando o compilador
✓ Para instalar o compilador de Linguagem C no Ubuntu, assim como em qualquer
distribuição Linux derivada do Debian, basta executar no shell os comandos:
sudo apt-get update
sudo apt-get install gcc
Sistemas Operacionais
Marcio Quirino - 37
2. Compilando
✓ Para compilar um programa chamado prog.c basta entrar no shell, no diretório onde se
encontra o programa, e executar o comando:
gcc prog.c
3. Compilando especificando a saída
✓ Para compilar um programa e escolher o nome do arquivo executável que será gerado,
utilize o parâmetro -o. Por exemplo, para compilar o programa prog.c e gerar como saída
um arquivo executável prog, utilize o comando:
gcc prog.c -o prog
4. Executando o programa
✓ Para executar o programa prog que acabou de ser compilado, execute o comando:
./prog
Modelo de processo
Os primeiros sistemas permitiam a execução de apenas um programa de cada vez, que deveria ter
o controle completo do sistema e acesso a todos os seus recursos. Os sistemas atuais permitem que
diversos programas sejam carregados na memória e executados simultaneamente.
Essa evolução tornou necessário um controle maior na divisão de tarefas dos vários programas,
resultando na noção de processo.
Em um sistema multiprogramável, a unidade central de processamento (UCP) alterna entre
processos, dedicando um pouco de seu tempo a cada um, dando a ilusão de paralelismo. Este esquema
costuma ser chamado de pseudoparalelismo.
Sistema multiprogramável
Sistema que permite a execução de mais de um programa ao mesmo tempo.
Neste modelo, todo software executado no computador é organizado em processos sequenciais,
também chamado de processos. O modelo de processos foi desenvolvido para tornar o paralelismo mais
fácil de tratar.
Um processo é um programa em execução, incluindo os valores atuais dos registradores e variáveis,
assim como seu espaço de endereçamento. Um programa por si só não é um processo, mas uma entidade
passiva. Um processo é uma entidade ativa, com um contador de instruções e um conjunto de
registradores a ele associado.
Espaço de endereçamento
Conjunto de endereços de memóriaque um processo pode acessar.
Contador de instruções
Registrador de uma unidade central de processamento que indica qual é a posição atual na sequência de
execução de um processo. Dependendo dos detalhes da arquitetura, ele armazena o endereço da instrução que está
sendo executada ou o endereço da próxima instrução.
Atenção
Embora dois processos possam estar associados a um mesmo programa, são duas sequências de execução
distintas.
Conceitualmente, cada processo tem sua própria UCP. Com a UCP alternando entre os processos,
a velocidade com que um processo executa não será uniforme.
Sistemas Operacionais
Marcio Quirino - 38
Exemplo de sistema de tempo compartilhado com 4 processos concorrentes.
Na figura anterior, vemos um exemplo de um sistema no qual são executados os processos A, B, C
e D. O processo A é executado até o instante t1, quando para, e o processo B é colocado em execução até
o instante t2. Os processos alternam suas execuções até que, no instante t4, o processo D para de executar
e o processo A retoma sua execução.
Como os processos alternam suas execuções de forma muito rápida, o usuário tem a ilusão de que
os processos estão realmente sendo executados ao mesmo tempo.
Criação e encerramento de processos
Sistemas operacionais precisam criar processos durante sua operação.
Quatro eventos principais fazem com que processos sejam criados:
1. Inicialização do sistema.
2. Execução de uma chamada de sistema de criação de processo por um processo em
execução.
3. Solicitação de um usuário para criar um processo.
4. Início de uma tarefa em lote.
Quando um sistema operacional é inicializado, uma série de processos são criados. Alguns são
processos de primeiro plano (interagem com usuários), enquanto outros operam no segundo plano
(background) e não estão associados a usuários em particular. Processos que ficam em segundo plano para
lidar com algumas atividades, como e-mail, páginas da web, notícias, impressão, são chamados de
daemons.
Além dos processos criados durante a inicialização do sistema, outros também podem ser criados.
Muitas vezes, um processo em execução emitirá chamadas de sistema para criar um ou mais processos
para ajudá-lo em seu trabalho. Criar processos novos é particularmente útil quando o trabalho a ser feito
pode ser facilmente formulado em termos de vários processos relacionados. Em um multiprocessador, por
exemplo, cada processo pode ser executado em uma UCP, fazendo com que a tarefa seja realizada mais
rapidamente.
Multiprocessador
Sistema com mais de um processador nos quais os processadores compartilham uma memória comum.
Em sistemas interativos, os usuários podem começar um programa digitando um comando ou
clicando duas vezes sobre um ícone. Cada uma dessas ações inicia um novo processo e executa o programa
selecionado.
Tarefas em lote costumam ser executadas em grandes sistemas. As tarefas são submetidas ao
sistema e, quando o sistema operacional tem os recursos necessários para executar outra tarefa, cria um
processo e executa a próxima tarefa a partir da fila de entrada.
Sistemas Operacionais
Marcio Quirino - 39
Atenção
Em todos esses casos, um novo processo é criado por outro já existente, executando uma chamada de sistema
de criação de processo. O que esse processo faz é executar uma chamada de sistema para criar o processo.
No Linux, a chamada de sistema mais comum para a criação de processos é a fork(). Essa chamada
cria um processo idêntico ao processo que a chamou. Após a fork(), os dois processos, o pai e o filho, têm
a mesma imagem de memória, as mesmas variáveis de ambiente e os mesmos arquivos abertos.
O código a seguir, em Linguagem C, exemplifica a criação de um novo processo com a chamada de
sistema fork().
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int resultado, pid, ppid;
resultado = fork();
if (resultado < 0)
printf("Algo deu errado!!!\n");
pid = getpid();
if (resultado == 0) {
ppid = getppid();
printf("Eu sou o processo filho, meu PID é %d e meu pai tem PID=%d.\n", pid,
ppid);
}
if (resultado > 0) {
printf("Eu sou o processo pai, meu PID é %d e meu filho tem PID=%d.\n", pid,
resultado);
waitpid(resultado, NULL, 0);
}
}
Quando fork() é executada, o resultado do processamento é armazenado na variável “resultado”. O
processo pai (que fez a chamada) receberá na variável “resultado” o PID do processo filho, enquanto o
processo filho receberá na variável “resultado” o valor 0. Assim, para saber quem é o processo pai e quem
é o processo filho, é necessário verificar o valor em “resultado”.
1. De início, verifica-se a ocorrência de algum erro na criação do processo filho. Caso tenha
ocorrido um erro, “resultado” receberá um valor negativo.
2. A seguir, o comando “pid = getpid();” obtém o PID do processo corrente.
3. Então, o processo verifica o valor de “resultado”. Se for zero, é o processo filho que está
executando e, nesse, caso o processo executa “ppid = getppid();” para obter o PID do
processo pai. Se o valor de “resultado” for maior que zero, significa que quem está
executando é o processo pai. Ambos (pai e filho) mostram na tela os respectivos PID.
4. Por fim, o processo pai executa a chamada de sistema “waitpid(resultado, NULL, 0);” para,
antes de terminar, aguardar o término da execução do filho.
Quando o fork() é utilizado para a criação de um processo que executará o código de outro programa,
a chamada de sistema execve() deve ser utilizada para que o processo filho mude sua imagem de memória
e execute o novo programa.
O código a seguir ilustra a utilização de execve(). Nele, o processo pai cria três processos filhos em
sequência. O primeiro coloca em execução uma calculadora, o segundo, o editor de textos gedit, e o terceiro,
o utilitário xeyes.
Sistemas Operacionais
Marcio Quirino - 40
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv, char* envp[]) {
int pid, i;
for (i=1; i<=3; i++) {
pid = fork();
if (pid < 0) {
printf("Algo deu errado!!!\n");
return 0;
}
if (pid == 0) { // Processo filho
if (i == 1)
execve("/usr/bin/xcalc", argv, envp);
if (i == 2)
execve("/usr/bin/gedit", argv, envp);
if (i == 3)
execve("/usr/bin/xeyes", argv, envp);
}
else // Processo pai
waitpid(pid, NULL, 0)
}
}
Após ter executado sua tarefa, o novo processo terminará devido a uma das seguintes condições:
Saída normal (voluntária)
A maioria dos processos termina por terem realizado o seu trabalho. Quando isto ocorre, o processo executa
uma chamada para dizer ao sistema operacional que ele terminou e para que o sistema possa tomar as providências
necessárias ao seu encerramento.
Erro fatal (involuntário)
O processo também pode terminar quando descobre um erro fatal. Ele pode solicitar ao usuário uma nova
entrada de dados ou simplesmente encerrar sua execução de maneira similar à saída normal.
Saída por erro (voluntária)
Outra razão para o término é um erro causado pelo processo, muitas vezes decorrente de um erro de programa.
Exemplos incluem executar uma instrução ilegal, referenciar uma memória não existente ou dividir por zero.
Morto por outro processo (involuntário)
A quarta razão pela qual um processo pode ser finalizado é a execução de uma chamada de sistema dizendo
ao sistema operacional para matar outro processo. Para um processo matar outro, é necessário autorização.
Em alguns sistemas, quando um processo é finalizado, todos os processos que ele criou são
finalizados também. Nem o Linux nem o Windows funcionam desta forma.
Hierarquia de processos
Em alguns sistemas, quando um processo cria outro, o processo pai e o processo filho continuam
associados. O processo filho pode criar mais processos, formando uma hierarquia de processos. No Linux,
um processo e todos os seus filhos edemais descendentes formam um grupo de processos.
No Linux, um processo especial chamado systemd (ou init, dependendo da versão) está presente na
imagem de inicialização do sistema. É o primeiro processo a ser executado e é responsável por iniciar a
execução dos demais processos do sistema operacional. Cada um desses processos pode iniciar mais
processos. Desse modo, todos os processos no Linux pertencem a uma única árvore, com o systemd (ou
init) em sua raiz.
Sistemas Operacionais
Marcio Quirino - 41
A imagem a seguir exemplifica a árvore de processos em um sistema Linux por meio da execução
do comando “pstree”.
systemd-+-ModemManager---2*[{ModemManager}]
|-NetworkManager---2*[{NetworkManager}]
|-VBoxService---8*[{VBoxService}]
|-accounts-daemon---2*[{accounts-daemon}]
|-acpid
|-avahi-daemon---avahi-daemon
|-colord---2*[{colord}]
|-cron
|-cups-browsed---2*[{cups-browsed}]
|-cupsd
|-dbus-daemon
|-gdm3-+-gdm-session-wor-+-gdm-wayland-ses-+-gnome-session-b---3*[{gnome-session-b}]
| | | `-2*[{gdm-wayland-ses}]
| | `-2*[{gdm-session-wor}]
| `-2*[{gdm3}]
|-2*[kerneloops]
|-login---bash---pstree
|-networkd-dispat
|-polkitd---2*[{polkitd}]
|-rsyslogd---3*[{rsyslogd}]
|-rtkit-daemon---2*[{rtkit-daemon}]
|-snapd---8*[{snapd}]
|-switcheroo-cont---2*[{switcheroo-cont}]
|-systemd---(sd-pam)
|-systemd-+-(sd-pam)
| |-at-spi-bus-laun-+-dbus-daemon
| | `-3*[{at-spi-bus-laun}]
| |-at-spi2-registr---2*[{at-spi2-registr}]
| |-dbus-daemon
| |-gjs---4*[{gjs}]
| |-gnome-keyring-d---3*[{gnome-keyring-d}]
| |-gnome-session-b---3*[{gnome-session-b}]
| |-gnome-session-c---{gnome-session-c}
| |-gnome-shell-+-Xwayland
| | |-ibus-daemon-+-ibus-engine-sim---2*[{ibus-engine-sim}]
| | | |-ibus-memconf---2*[{ibus-memconf}]
| | | `-2*[{ibus-daemon}]
| | `-6*[{gnome-shell}]
| |-goa-daemon---3*[{goa-daemon}]
| |-goa-identity-se---2*[{goa-identity-se}]
| |-gsd-a11y-settin---3*[{gsd-a11y-settin}]
| |-gsd-color---3*[{gsd-color}]
| |-gsd-keyboard---3*[{gsd-keyboard}]
| |-gsd-media-keys---3*[{gsd-media-keys}]
| |-gsd-power---3*[{gsd-power}]
| |-gsd-print-notif---2*[{gsd-print-notif}]
| |-gsd-printer---2*[{gsd-printer}]
| |-gsd-rfkill---2*[{gsd-rfkill}]
| |-gsd-smartcard---4*[{gsd-smartcard}]
| |-gsd-sound---3*[{gsd-sound}]
| |-gsd-usb-protect---3*[{gsd-usb-protect}]
| |-gsd-wacom---3*[{gsd-wacom}]
| |-gsd-wwan---3*[{gsd-wwan}]
| |-gsd-xsettings---3*[{gsd-xsettings}]
| |-gvfs-afc-volume---3*[{gvfs-afc-volume}]
| |-gvfs-goa-volume---2*[{gvfs-goa-volume}]
| |-gvfs-gphoto2-vo---2*[{gvfs-gphoto2-vo}]
| |-gvfs-mtp-volume---2*[{gvfs-mtp-volume}]
| |-gvfs-udisks2-vo---3*[{gvfs-udisks2-vo}]
| |-gvfsd---2*[{gvfsd}]
| |-gvfsd-fuse---5*[{gvfsd-fuse}]
| |-ibus-portal---2*[{ibus-portal}]
| |-ibus-x11---2*[{ibus-x11}]
| |-pulseaudio---3*[{pulseaudio}]
| |-tracker-miner-f---4*[{tracker-miner-f}]
| `-xdg-permission----2*[{xdg-permission-}]
Sistemas Operacionais
Marcio Quirino - 42
|-systemd-journal
|-systemd-logind
|-systemd-resolve
|-systemd-timesyn---{systemd-timesyn}
|-systemd-udevd
|-udisksd---4*[{udisksd}]
|-unattended-upgr---{unattended-upgr}
|-upowerd---2*[{upowerd}]
|-whoopsie---2*[{whoopsie}]
`-wpa_supplicant
Estados de processos
Eventualmente, um processo que está em execução necessita parar momentaneamente sua
execução por estar aguardando uma informação ainda não disponível ou porque já foi executado por muito
tempo e precisa liberar a UCP para outro processo.
Um processo pode transitar por diferentes estados, que dependem do sistema operacional. De forma
geral, podemos dizer que um processo pode estar nos estados novo, executando, pronto, bloqueado ou
terminado.
Quando um processo é criado, ele se inicia no estado novo. O processo já existe, mas ainda precisam
ser tomadas algumas providências pelo sistema operacional para que o processo possa iniciar sua
execução. Quando tudo está pronto, o processo faz a transição 1 e muda para o estado pronto.
O processo reúne todas as condições para ser executado quando está no estado pronto, faltando
apenas que o processador fique disponível para sua execução. Quando um processador fica disponível e o
processo é selecionado para execução, ele faz a transição 2 e vai para o estado executando.
No estado executando, o processo tem suas instruções executadas pelo processador e três coisas
podem acontecer:
1. Etapa 01
✓ A primeira delas é o processo ser executado por muito tempo. Então, o sistema
operacional interrompe momentaneamente a execução do processo para colocar outro
em seu lugar, ocorrendo a transição 3, que leva o processo de volta ao estado pronto,
no qual aguardará nova oportunidade de execução.
2. Etapa 02
✓ Pode ocorrer ainda de o processo solicitar uma operação de entrada e saída e ter de
aguardar a conclusão da operação, que costuma ser demorada quando comparada à
capacidade de processamento do processador de um computador. Nesse caso, ocorre
a transição 4, e o processo vai para o estado bloqueado.
3. Etapa 03
✓ Por último, o processo pode encerrar sua execução, realizando a transição 6 e indo para
o estado terminado.
O processo que vai para o estado bloqueado permanece nele até que seja concluída a operação que
aguardava. Quando isso ocorre, o processo passa pela transição 5 e vai para o estado pronto até ser
novamente selecionado para execução.
Sistemas Operacionais
Marcio Quirino - 43
Atenção
O estado terminado é para os processos que não serão mais executados. Quando está neste estado, o sistema
operacional deve providenciar a desalocação dos recursos que ainda estejam alocados ao processo. Somente após a
desalocação de todos os recursos, o processo deixa de existir no sistema.
Para implementar o modelo de processos, o Linux mantém uma tabela de processos, com uma
entrada por processo. Esta entrada é chamada de Bloco de Controle de Processo – BCP (Process Control
Block – PCB) e contém todas as informações do processo. Algumas entradas do BCP são:
✓ Estado do processo
✓ Prioridade do processo
✓ Número do processo
✓ Registradores da UCP
✓ Informações relativas ao gerenciamento de memória
✓ Informações de contabilidade
✓ Informações sobre operações de E/S
Essa visão dá origem ao seguinte modelo:
O nível mais baixo do sistema operacional é o escalonador (também conhecido como agendador).
Ele cuida do gerenciamento de interrupções e dos detalhes de como iniciar e parar processos. Também
costuma ser muito pequeno.
Um processo passa pelas várias filas de seleção durante sua execução. Cabe ao escalonador
selecionar processos destas filas e decidir qual será o próximo a ser executado.
O escalonador é chamado com muita frequência. Um processo pode ser executado por apenas
alguns milissegundos (ms) e ter de esperar por ter feito uma requisição de E/S. O escalonador costuma ser
chamado pelo menos uma vez a cada 100ms para realizar a troca de processos. Devido ao pequeno
intervalo de tempo entre as chamadas ao escalonador, sua execução deve ser bastante rápida, para que
não se gaste muito tempo de UCP com trabalho de gerência.
Mudança de contexto
Para transferir o controle da UCP de um processo a outro, é necessário guardar o estado do processo
em execução e carregar o estado do processo a entrar em execução. Esta tarefa é conhecida como mudança
de contexto (ou troca de contexto).
O tempo gasto na mudança de contexto varia, dependendo de fatores como velocidade da memória,
quantidade de registradores e existência de instruções especiais. Este tempo costuma variar de 1 a 1000
microssegundos.
O contexto de um processo pode ser dividido em três elementos básicos:
Contexto de hardware
• O contexto de hardware constitui-se basicamente do conteúdo dos registradores. No
momento em que o processo perde a UCP, o sistema salva suas informações. Ele é
fundamental para a implementaçãodos sistemas multiprogramáveis.
Sistemas Operacionais
Marcio Quirino - 44
Contexto de software
• O contexto de software especifica características do processo que influenciarão na execução
de um programa. Ele define basicamente três grupos de informações sobre um processo:
identificação, quotas e privilégios. A identificação define o processo para o sistema de forma
única, através de seu PID, UID e GID. Quotas são os limites de cada recurso que o sistema
operacional pode alocar, como número de arquivos abertos, quantidade de memória,
quantidade de subprocessos que podem ser criados etc. Privilégio é o que o processo pode
ou não fazer em relação ao sistema e outros processos.
Espaço de endereçamento
• O espaço de endereçamento é a área de memória do processo em que o programa será
executado e a área de memória onde os dados do processo serão armazenados. Cada
processo possui seu próprio espaço de endereçamento, que deve ser protegido dos demais.
Processos no Linux
Os processos no Linux comportam-se como processos sequenciais tradicionais. É um sistema
operacional multiusuário/multitarefa, que permite a execução simultânea de diversos processos, que podem
pertencer a diferentes usuários. Mesmo que haja apenas um usuário logado no sistema, é comum a
existência de diversos daemons em execução.
Atenção
Um exemplo é o daemon de impressão, responsável por fazer a alocação da impressora e controlar o envio de
trabalhos de impressão. É uma parte importante do sistema, pois permite o compartilhamento da impressora por
diversos processos, possivelmente pertencentes a diferentes usuários.
A forma usual para criação de processos no Linux ocorre por meio da chamada de sistema fork(),
conforme você já estudou neste tema.
O processo filho recebe uma cópia exata do espaço de endereçamento do processo pai. Todos os
valores de variáveis e demais objetos em memória serão idênticos, porém alterações realizadas por um dos
processos em qualquer conteúdo de memória não afetarão o outro.
Arquivos que foram abertos antes da chamada fork() permanecem abertos para o processo pai e
para o processo filho. As alterações realizadas por qualquer um destes processos se tornam imediatamente
disponíveis para o outro. Processos são identificados por um número inteiro conhecido como PID
(identificação do processo).
Algumas chamadas de sistema do Linux para o gerenciamento de processos são:
Chamada de sistema Descrição
fork()
Cria um processo filho idêntico ao processo pai. Para o processo pai, retorna o PID do processo filho e,
para o processo filho, retorna o valor 0.
waitpid() Espera até que o processo filho passado como parâmetro termine sua execução.
execve()
Substitui a imagem de execução de um processo, fazendo com que, no lugar do processo corrente, seja
executado o código do programa passado como parâmetro.
exit() Termina a execução do processo e retorna como status o valor passado como parâmetro.
Comandos do shell
O shell, interpretador de comandos, é um programa que recebe comandos pelo teclado e providencia
a execução desses comandos.
Sistemas Operacionais
Marcio Quirino - 45
Comandos são ordens passadas ao sistema operacional para executar umadeterminada tarefa.
Para colocar o shell em execução no Ubuntu, você deve abrir a lista de aplicativos clicando no botão
que fica no canto inferior esquerdo de sua tela. Procure pelo aplicativo Terminal e clique sobre ele. Será
aberta uma janela de texto com uma linha contendo o nome de seu usuário, o caractere @, o nome de seu
sistema, e a sequência :~$. Essa linha é conhecida como prompt.
Quando um usuário comum estiver utilizando o sistema, o shell apresenta, no final do prompt, o
caractere $. Quando o shell estiver em uso pela conta root (administrador do sistema), o final do prompt será
#.
Para executar um comando, é necessário que ele tenha permissão de execução e que esteja no
caminho de procura de arquivos (esteja no path).
O path é o caminho de procura dos arquivos executáveis. Ele é armazenado na variável de ambiente
PATH. Você pode ver o conteúdo desta variável com o comando:
echo $PATH
Um exemplo de retorno deste comando é:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/
usr/games:/usr/local/games:/snap/bin
Por exemplo, o caminho “/usr/local/sbin:/usr/local/bin:/usr/sbin” significa que, ao ser digitado um
comando, o interpretador de comandos iniciará a procura do comando no diretório “/usr/local/sbin”. Caso
não encontre o arquivo nesse diretório, procurará em “/usr/local/bin”, depois em “/usr/sbin” e assim por
diante. Caso o interpretador de comandos chegue até o último diretório do path e não encontre o arquivo
digitado, será mostrada uma mensagem de erro.
Ao digitar o nome de um programa ou comando e teclar Enter, o programa/comando será executado
e receberá um número de identificação (PID – Process Identification).
Todo o programa recebe também uma identificação de usuário (UID – User Identification) quando é
executado, que determina quais serão suas permissões de acesso durante sua execução. O programa
normalmente usa o UID do usuário que o executou, mas pode ser configurado pelo sistema para executar
como um usuário específico.
Comandos podem possuir uma ou mais opções. Entre o nome do comando e suas opções deve
haver, pelo menos, um espaço em branco. As opções são usadas para controlar como o comando será
executado.
Comandos podem ser internos ou externos. Comandos internos são comandos que estão localizados
dentro do interpretador de comandos (não ficam no disco). Eles são carregados na memória RAM do
computador com o interpretador de comandos.
Quando executa um comando, o interpretador de comandos primeiramente verifica se é um comando
interno. Caso não seja, é verificado se é um comando externo.
Comandos externos estão localizados no disco. Os comandos são procurados no disco de acordo
com o PATH do sistema e são executados assim que encontrados.
É possível consultar a lista de comandos já executados utilizando as setas para cima e para baixo
do teclado.
A tela pode ser rolada para frente e para trás ao pressionar a tecla Shift e clicando nas teclas Page
Up e Page Down.
Sistemas Operacionais
Marcio Quirino - 46
Programas podem executar em primeiro ou em segundo plano. Quando um programa está sendo
executado em primeiro plano (foreground), é necessário esperar o término da execução do programa para
executar outro. O prompt só é mostrado após o término da execução do programa.
Quando um programa está sendo executado em segundo plano (background), não é necessário
esperar o término de sua execução para executar um outro. Após iniciar um programa em background, é
mostrado um número PID (identificação do processo) e o prompt é liberado, permitindo a execução de outro
programa.
Para iniciar um programa em primeiro plano, basta digitar seu nome normalmente. Para iniciar um
programa em segundo plano, acrescente o caractere & ao final do comando.
Teste a utilização do terminal:
4. Execute o comando gedit.
5. Tente utilizar o terminal enquanto a janela do gedit estiver aberta.
6. Feche o gedit.
7. Execute o comando “gedit &”.
8. Tente utilizar o terminal enquanto a janela do gedit estiver aberta.
9. Feche o gedit.
Separando os comandos por “;” (ponto e vírgula), eles serão executados em sequência. Digite o
comando “gedit; xcalc; xeyes” e tecle Enter.
Comandos para controle de processos
ps
Mostra os processos em execução no sistema, a identificação do usuário que executou o processo,
a hora em que o processo foi iniciado etc.
Algumas opções:
✓ a → Processos de todos os usuários.
✓ x → Processos que não são controlados pelo terminal.
✓ u -> Mostra o nome de usuário que iniciou o processo e a hora em que o processo foi iniciado.
✓ m → Mostra a memória ocupada pelos processos.
✓ f → Mostra a árvore de execuçãode processos.
o Exemplo: ps -aux
top
Mostra continuamente informações sobre processos, utilização da UCP, uso da memória RAM, swap
etc.
Para encerrar a execução do top, deve ser pressionada a tecla q.
Algumas teclas úteis:
✓ espaço → Atualiza imediatamente a tela.
✓ h → Mostra a tela de ajuda.
✓ q -> Sai do programa.
✓ k -> Finaliza um processo. Semelhante ao comando kill. Será necessário fornecer o número
de identificação do processo (PID).
<Ctrl> + <C>
Encerra o processo que está sendo executado em primeiro plano.
Sistemas Operacionais
Marcio Quirino - 47
<Ctrl> + <Z>
Pausa a execução do processo que está em primeiro plano e mostra o número de seu job.
O prompt do shell é liberado.
O processo permanece na memória no ponto de processamento em que parou quando foi pausado.
Sua execução pode ser retomada com os comandos fg ou bg.
jobs
Mostra os processos e seus respectivos números de job, que estão parados ou executando em
segundo plano.
bg
Coloca um processo parado para executar em segundo plano. Para especificar o processo que
entrará em execução em segundo plano, deve ser passado ao comando seu número obtido com o comando
jobs.
fg
Coloca um processo parado ou executando em segundo plano para executar em primeiro plano.
Para especificar o processo que irá para o primeiro plano, deve ser passado ao comando seu número obtido
com o comando jobs. Caso não seja fornecido o número do job, ele irá para o primeiro plano o último
processo pausado.
kill
Envia sinais a processos. Caso seja usado sem parâmetros, o kill enviará um sinal de término ao
processo.
Comando: kill [opções] [sinal] [número]
Onde:
✓ número → Número de identificação do processo obtido com o comando ps.
✓ Também pode ser [%num], onde um é o número pelo comando jobs.
✓ sinal → Sinal que será enviado ao processo. Se omitido, envia o sinal 15 (SIGTERM).
Como opção pode ser passado ao comando a opção -9, que envia o sinal 9 (SIGKILL) ao processo.
Trata-se um sinal de destruição que faz com que o processo seja terminado imediatamente.
Somente o dono do processo ou o usuário root pode terminá-lo ou destruí-lo.
killall
Finaliza um ou mais processos e tem seu nome como base.
killall5
Envia sinal de finalização a todos os processos.
nohup
Executa o comando passado como parâmetro, ignorando os sinais de interrupção.
Ainda pode ser finalizado com kill e <Ctrl>+<C>, mas sobrevive ao fechamento do terminal.
pstree
Mostra a árvore de processos do sistema.
Sistemas Operacionais
Marcio Quirino - 48
Exemplos:
1. Encerrando processos com kill.
Abra um shell e coloque o gedit em execução com o comando:
gedit &
Execute o comando “ps -a” para listar os processos em execução e procure pela
linha correspondente ao programa gedit. Um exemplo de saída do comando é:
PID TTY TIME CMD
1495 tty2 00:00:55 Xorg
1522 tty2 00:00:00 gnome-session-b
3379 pts/0 00:00:02 gedit
3394 pts/0 00:00:00 ps
Para o exemplo acima, percebemos que o gedit possui PID com valor 3379. O PID de seu
processo deve ser outro. Verifique com atenção esse número.
Para eliminar o processo 3379, utilize o comando:
kill 3379
Repare que o processo gedit foi fechado.
2. Processos em primeiro plano e em segundo plano.
Abra um shell e coloque o gedit em execução com o comando:
xeyes
Será colocado em execução o aplicativo xeyes, um par de olhos que acompanham a posição
do cursor do mouse.
Mexa o mouse pela tela e veja que o processo está funcionando normalmente.
Em seguida, tente digitar algum comando no terminal. Você não conseguirá, pois o processo
xeyes está executando em primeiro plano, impedindo o acesso ao terminal.
Depois, pressione simultaneamente as teclas <Ctrl> e <Z>. Essa ação irá bloquear o
processo xeyes e librar o prompt. A partir daí, você poderá entrar com novos comandos que
o shell aceitará.
Processos bloqueados não realizam processamento. Mexa novamente o mouse e verá que
os olhos não acompanham o cursor.
Para recolocar o processo em execução, você pode utilizar o comando fg (traz o processo
para execução em primeiro plano) ou o comando bg (coloca o processo em execução em
segundo plano). Vamos tentar com o bg. Para isso, execute o comando:
bg
Mexa o mouse e tente utilizar o comando. Você verá que ambos estão funcionando, pois,
como o xeyes está executando em segundo plano, o terminal fica liberado.
Vamos manter o xeyes em execução.
Para colocar um processo em execução diretamente no segundo plano, você pode colocar
o caractere & ao final do comando. Execute o comando:
xcalc &
A calculadora abrirá diretamente no segundo plano e teremos os três processos funcionando
normalmente (shell, xeyes e calculadora).
Sistemas Operacionais
Marcio Quirino - 49
Execute o comando:
gedit
Volte ao terminal e pressione <Ctrl>+<Z>. Com isso, o gedit será bloqueado e o shell
liberado para execução.
Execute o comando:
jobs
Será apresentada uma saída como:
[1] Executando xeyes &
[2]- Executando xcalc &
[3]+ Parado gedit
Pela saída, podemos ver que os processos xeyes e xcalc estão executando em segundo
plano e que o processo gedit está bloqueado. Você pode colocar qualquer um deles
executando em segundo plano com o comando fg, bastar colocar o número do job (que
aparece entre colchetes) como parâmetro para o comando.
Para colocar, por exemplo, o gedit executando em primeiro plano, execute o comando:
fg 3
2. Construção de programas concorrentes
Compreender como ocorre a construção de programas concorrentes
Subprocesso
Quando um processo (processo pai) cria um outro processo, ele é conhecido como subprocesso ou
processo filho. O subprocesso, por sua vez, pode criar outros subprocessos.
A utilização de subprocessos permite dividir uma aplicação em partes que podem trabalhar de forma
concorrente. Imagine, por exemplo:
Exemplo
Um servidor web que aceite requisições de clientes da internet e coloque as requisições em uma fila. Uma
forma simples de implementar este servidor seria criar um processo que pegue a primeira requisição da fila, processe
a requisição e devolva o resultado do processamento ao cliente que a solicitou. Após isso, ele pegaria a próxima
requisição e faria o mesmo trabalho.
O problema com essa solução é que ela não aproveita a capacidade de multiprocessamento dos
sistemas atuais. Como existe apenas um processo em execução, somente um dos processadores do
sistema é utilizado para atendimento das requisições. Além disso, se houver várias requisições complexas
e demoradas e uma requisição simples, como uma pequena página HTML, entrar no final da fila, esta
requisição mais simples, que poderia ser respondida rapidamente, será atendida somente depois que todas
as demais forem processadas.
A utilização de subprocessos resolve bem estes problemas. Se o servidor, no lugar de responder
sequencialmente a cada requisição, criar um subprocesso para cada uma delas, tirará proveito da
capacidade de multiprocessamento do sistema. Como cada requisição será tratada por um processo
diferente, as requisições serão espalhadas pelos processadores do sistema, aproveitando sua capacidade
de multiprocessamento. Além disso, como as requisições serão tratadas por diferentes processos, elas
serão executadas concorrentemente.
Sistemas Operacionais
Marcio Quirino - 50
Cada requisição será tratada por um processo diferente
Um subprocesso é um processo completo. Então, cada subprocesso do servidor é, na realidade, um processo
independente.
Dessa forma, uma requisição simples, como a solicitação de uma página HTML, poderá ser iniciada
e respondida rapidamente, ainda que existam outras requisições complexas solicitadas anteriormente e que
elas ainda estejam sendo processadas.
O trecho de código abaixo em Linguagem C exemplifica a parte de um servidor web simples que cria
subprocessos para atendimento dasrequisições:
while (1) { // Loop infinito
req = pega_proxima_requisicao();
pid = fork();
if (pid == 0) { // Proceso filho
processa_requisicao(req);
exit(0);
}
}
Entenda o significado de cada um deles:
1. “pega_proxima_requisicao()”
✓ Neste código, a rotina “pega_proxima_requisicao()” é responsável por verificar se existe
alguma requisição enfileirada para atendimento. Se não houver, o processo pai fica
bloqueado até que chegue uma nova requisição, sem impactar no desempenho do
sistema. Enquanto isso, seus processos filhos continuam atendendo às requisições em
andamento.
2. “processa_requisicao()”
✓ A rotina “processa_requisicao()” é executada somente pelo processo filho, uma vez que
o comando if anterior faz essa verificação. Essa rotina fica encarregada do atendimento
à requisição que foi enviada ao servidor web. Depois do processamento, a chamada de
sistema “exit(0)” encerra o processo filho.
3. “while (1) {…}”
✓ Todo o código fica contido em um loop infinito “while (1) {…}”. Esse loop repetidamente
pega a próxima requisição da fila, cria um processo filho para atendimento da requisição
e retorna a aguardar uma nova requisição, enquanto o processo filho faz o
processamento da requisição que acabou de chegar.
O uso de subprocessos demanda consumo de diversos recursos do sistema. Sempre que um novo
processo é criado, o sistema deve alocar recursos (contexto de hardware, contexto de software e espaço de
endereçamento) para ele, além de consumir tempo de UCP. Além disso, cada processo possui seu próprio
BCP.
Threads
Na tentativa de diminuir o tempo gasto na criação/eliminação de processos, bem como economizar
recursos do sistema, foi introduzido o conceito de thread. Em um ambiente com múltiplos threads, não é
necessário haver vários processos para implementar aplicações concorrentes.
Threads compartilham o processador da mesma maneira que um processo. Cada thread possui seu
próprio conjunto de registradores (contexto de hardware), porém compartilha o mesmo espaço de
endereçamento com os demais threads do processo. No momento em que um thread perde a utilização do
processador, o sistema salva suas informações. Threads passam pelos mesmos estados que um processo.
A grande diferença entre subprocessos e threads é em relação ao espaço de endereçamento.
Sistemas Operacionais
Marcio Quirino - 51
1. Subprocessos possuem, cada um, espaços independentes e protegidos.
2. Threads, por outro lado, compartilham o mesmo espaço de endereçamento do processo,
sem nenhuma proteção, permitindo que um thread possa alterar dados de outro thread. São
desenvolvidos para trabalharem de forma cooperativa, voltados para desempenhar uma
tarefa em conjunto, e são conhecidos como processos leves.
A mudança de contexto entre threads em um mesmo processo exige uma alteração de um conjunto
de registradores, mas não é necessário nenhum outro trabalho, como gerenciamento de memória, por
exemplo, tornando-a mais leve que a mudança de contexto entre processos.
Quando múltiplos threads estão presentes no mesmo processo, alguns campos da tabela de
processos não ocorrem por processo, mas por thread.
Em alguns sistemas, os threads são gerenciados no espaço do usuário, sem o conhecimento do
sistema operacional. É o caso do pacote P-threads (POSIX). A comutação de threads é muito mais rápida
quando é feita no espaço do usuário pelo fato de não precisar fazer uma chamada ao kernel. Porém, quando
os threads são executados no espaço do usuário e um thread bloqueia, todo o processo é bloqueado pelo
kernel. Threads no nível do usuário também fazem com que o tempo dedicado a threads de diferentes
processos não seja distribuído de forma justa.
Atenção
Threads são muito úteis em sistemas com múltiplas UCP, nos quais o paralelismo real é possível. Elas
permitem que ocorram múltiplas execuções no mesmo ambiente, fazendo com que várias partes de um mesmo
processo estejam em execução concorrente em diferentes processadores.
O termo multithread também é usado para descrever a permissão de múltiplos threads no mesmo
processo. Algumas UCP oferecem suporte de hardware direto para multithread e permitem que
chaveamentos de threads aconteçam em uma escala de tempo de nanossegundos.
Processos com apenas um thread são conhecidos como monothread.
Além de compartilhar o espaço de endereçamento, os threads podem compartilhar o mesmo conjunto
de arquivos abertos, processos filhos, alarmes e sinais.
Atenção
É importante perceber que cada thread tem a sua própria pilha, que contém uma estrutura para cada rotina
chamada, mas ainda não retornada. Essa estrutura contém as variáveis locais da rotina e o endereço de retorno para
serem usados quando a chamada de rotina for encerrada.
No Linux, os threads são criados com a chamada de sistema clone(). Sua sintaxe é:
int clone(int (*fn)(void *), void *stack, int flags, void *arg)
Sistemas Operacionais
Marcio Quirino - 52
Os flags mais comuns são:
Flag
Comportamento
Quando utilizado Quando não utilizado
CLONE_VM Cria um thread. Cria um processo.
CLONE_FS
Compartilha as informações sobre o sistema
de arquivos.
Não compartilha informações sobre o sistema de arquivos.
CLONE_FILES Compartilha os descritores de arquivos. Copia os descritores de arquivos.
CLONE_SIGHAND Compartilha a tabela do tratador de sinais. Copia a tabela do tratador de sinais.
CLONE_PARENT
O novo thread tem o mesmo pai que o
chamador.
O chamador é o pai do novo thread.
SIGCHLD
O thread envia o sinal SIGCHLD ao pai quando
termina.
O thread não envia o sinal SIGCHLD ao pai quando termina.
Segue um exemplo de programa em Linguagem C que exemplifica a utilização de threads. Nele, são
criados três threads dentro de um processo. O thread principal termina sua execução somente quando o
último thread finaliza sua execução.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#define TAMANHO_PILHA 65536
#define _1SEGUNDO 1000000
int global = 0; // Variável global alterada pelos threads
static int funcaoThread(void *arg) {
int id = *((int *) arg); // Identificação de cada thread
int i;
printf("Iniciou thread [%d]\n", id);
for (i=0; i<3; i++) { // Loop no qual o thread altera a variável global
printf("Thread [%d] incrementou \"global\" para %d.\n", id, ++global);
usleep(_1SEGUNDO * (1+id/10.0));
}
printf("Saindo do thread [%d]\n", id);
}
int main () {
void *pilha;
int i, pid[3];
int id[3] = {1,2,3}; // Identificação a ser passada para cada thread
for (i=0; i<3; i++) { // Alocando espaço para a pilha de cada thread
if ((pilha = malloc(TAMANHO_PILHA)) == 0) {
perror("Erro na alocação da pilha.");
exit(1);
}
pid[i] = clone(funcaoThread,
pilha + TAMANHO_PILHA,
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD,
&(id[i])); // Criação de cada thread
}
printf("Thread principal aguardando demais threads terminarem.\n");
for (i=0; i < 3; i++)
if (waitpid(pid[i], 0, 0) == -1) { // Aguarda até o término do thread
perror("waitpid")
exit(2);
}
Sistemas Operacionais
Marcio Quirino - 53
printf("Thread principal terminando.\n\n");
}
Quando executado, o programa fornece a seguinte saída:
Thread principal aguardando demais threads terminarem.
Iniciou thread [3]
Thread [3] incrementou "global" para 1.
Iniciou thread [2]
Thread [2] incrementou "global" para 2.
Iniciou thread [1]
Thread [1] incrementou "global" para 3.
Thread [1] incrementou "global" para 4.
Thread [2] incrementou "global" para 5.
Thread [3] incrementou "global" para 6.
Thread [1] incrementou "global" para 7.
Thread [2] incrementou "global" para 8.
Thread [3] incrementou "global" para 9.
Saindo do thread [1]
Saindo do thread [2]
Saindo do thread [3]
Thread principal terminando.
Pela execução, vocêpode perceber que os valores das variáveis locais de cada thread não são
influenciados pela execução dos demais threads. Porém, quando a variável “global” é modificada por um
thread, esta alteração é vista imediatamente por todos os demais threads.
Tipos de processos
Existem basicamente dois tipos de processos relacionados ao tipo de processamento que executam.
Nos cards, a seguir, entenda cada um deles:
Os processos do tipo CPU-bound passam a maior parte do tempo no estado executando, realizando
poucas operações de E/S. Costumam ser encontrados em aplicações científicas.
Os processos do tipo I/O-bound passam a maior parte do tempo no estado bloqueado, por realizar
elevado número de operações de E/S. Costumam ser encontrados em aplicações comerciais. Processos
interativos também são exemplos deste tipo de processo.
Processos e Threads No Linux
O Linux pode duplicar um processo por meio da chamada de sistema fork(), mas também pode criar
threads pela chamada de sistema clone(). No entanto, ele não faz distinção entre processos e threads. Toda
entidade em execução, processo ou thread, será considerada como uma tarefa (task). Um processo com
um único thread será considerado como uma tarefa, e um processo com n threads terá n estruturas de
tarefas.
Atenção
As chamas de sistema fork() e clone() são bastante semelhantes. De fato, se clone() for invocada sem nenhum
flag passado como parâmetro, seu comportamento será idêntico ao fork().
O contexto de uma tarefa do Linux não é mantido em uma única estrutura, mas são criadas várias
estruturas independentes para cada contexto. Os dados de uma tarefa são acessados por meio de ponteiros
que apontam para cada estrutura de cada contexto. Dessa forma, o compartilhamento de informações entre
as tarefas fica facilitado, bastando que as tarefas apontem para as mesmas estruturas em memória.
A fim de manter a compatibilidade com demais sistemas UNIX, o Linux associa um PID diferente a
cada tarefa. Desta forma, diferentes threads de um processo terão diferentes PID.
Quando um subprocesso é criado, a princípio, o Linux deveria alocar memória para os diferentes
processos e copiar o conteúdo dos segmentos de memória do processo pai. Porém, copiar segmentos de
Sistemas Operacionais
Marcio Quirino - 54
memória requer processamento e toma tempo. Então, para ser mais eficiente, o Linux simplesmente faz
com que ambos (pai e filho) compartilhem o mesmo segmento de memória.
Se os processos se limitarem à operação de consulta nos segmentos, não haverá problema, mas,
se algum dos processos tentar escrever em um dos segmentos, neste momento, haverá a cópia do
segmento compartilhado (mecanismo conhecido como cópia na escrita – copy on write). Além de aumentar
o desempenho do sistema, esse mecanismo ajuda a diminuir o consumo de memória física.
3. Comunicação entre processos
Identificar o mecanismo de comunicação entre processos
Processos de aplicações concorrentes
O surgimento dos sistemas multiprogramáveis tornou possível criar aplicações nas quais diferentes
partes do código de um programa pudessem executar de forma concorrente. Tais aplicações são conhecidas
como aplicações concorrentes.
É comum que processos de aplicações concorrentes compartilhem recursos do sistema. Não
importam quais recursos são compartilhados, os problemas decorrentes serão os mesmos. O
compartilhamento de recursos entre processos pode gerar situações indesejáveis capazes de comprometer
o sistema.
Se dois processos concorrentes trocam informações através de um buffer, um deles só poderá gravar
dados caso o buffer não esteja cheio, enquanto um processo só poderá ler dados caso o buffer não esteja
vazio. Em qualquer caso, os processos deverão aguardar até que o buffer esteja pronto para as operações
de E/S.
As considerações anteriores levam a três questões:
1. Como um processo passa informações a outro?
2. Como garantir que dois processos não se interfiram?
3. Como realizar o sequenciamento quando um processo depende do outro?
É bastante comum que aplicações concorrentes necessitem trocar informações. Tal comunicação
pode se dar por meio de variáveis compartilhadas (memória compartilhada) ou por troca de mensagens.
Porém, independentemente do mecanismo de comunicação utilizado, é preciso que os processos possam
se manter sincronizados.
Atenção
Os mecanismos que garantem a comunicação entre processos concorrentes e o acesso a recursos
compartilhados são chamados mecanismos de sincronização. Os mesmos mecanismos se aplicam a threads.
Condição de corrida
Em alguns sistemas, processos que estão trabalhando em conjunto, muitas vezes, utilizam uma
memória comum, onde cada processo pode ler ou escrever. Este armazenamento compartilhado pode ser
feito na memória principal ou em um arquivo em disco.
Exemplo
Imagine um programa que atualize o saldo de um cliente após o lançamento de um débito ou um crédito em
um registro.
O trecho do programa que faz a atualização poderia ser:
Sistemas Operacionais
Marcio Quirino - 55
void atualiza_saldo(double valor, int conta) {
Registro registro;
registro = le_registro(conta);
registro.saldo = registro.saldo + valor;
grava_registro(registro, conta);
}
Exemplo
Suponha que os funcionários Carlos e Orlando, caixas do banco, solicitem a atualização de uma conta cujo
saldo é 500. Carlos faz uma operação de depósito de 100 e Orlando uma operação de saque de 200. Pode acontecer
de o processo de Carlos ler o saldo da conta (500) e, ao mesmo tempo, o processo de Orlando ler o mesmo valor.
Então, o processo de Carlos soma 100 e grava o registro com o valor 600. No entanto, o processo de Orlando já tem
o valor de saldo de 500, subtrai 200 e fica com saldo de 300. Como o processo de Orlando grava o registro por último,
o depósito não é computado, levando a uma inconsistência. O valor final das operações deveria ser 400, mas ficou
registrado 300.
Problemas como esses são conhecidos como condição de corrida, que ocorre quando dois ou mais
processos estão acessando dados compartilhados e o resultado do processamento depende de quem
executa e quando é executado.
Região crítica
Como evitar as condições de corrida?
A forma mais simples é impedir que dois ou mais processos acessem um mesmo recurso no mesmo
instante, impedindo que eles acessem o recurso compartilhado simultaneamente. Quando um processo
estiver acessando o recurso, os demais deverão esperar. A esta exclusividade de acesso dá-se o nome de
exclusão mútua, uma questão importante para o desenvolvimento de um sistema operacional.
A parte do programa que acessa a memória compartilhada é denominada seção crítica ou região
crítica (RC). Quando um processo é executado dentro de sua região crítica, nenhum outro processo pode
entrar lá.
{
......
Entra_Regiao_Critica();
Executa_Regiao_Critica();
Sai_Regiao_Critica();
......
}
Não é suficiente evitar que o processo seja interrompido dentro da região crítica. São quatro as
condições para uma boa solução:
1. Não pode haver mais de um processo simultaneamente dentro de suas regiões críticas.
2. Nenhuma suposição pode ser feita sobre a velocidade ou o número de UCP.
3. Nenhum processo que execute fora de sua região crítica pode bloquear outro processo.
4. Nenhum processo deve ter de esperar eternamente para entrar em sua região crítica
(starvation).
Para garantir a implementação da exclusão mútua, os processos envolvidos devem fazer acesso aos
recursos compartilhados de forma sincronizada.
Semáforos
Um semáforo é uma variável inteira que conta sinais enviados a ela. Associadas aos semáforos,
existem duas operações especiais: up e down.
Sistemas Operacionais
Marcio Quirino - 56
Semáforos
Semáforos e monitores são recursos para resolver o problema da condição de corrida.
1. A operação down decrementa o valor do semáforo se ele for maior que 0, senãoo processo
é bloqueado.
2. A operação up incrementa o valor do semáforo caso não haja processos que tenham sido
bloqueados pela operação down, senão um processo é desbloqueado.
No caso da exclusão mútua, as instruções down e up funcionam como protocolos para que um
processo possa entrar e sair de sua região crítica. O semáforo fica associado a um recurso compartilhado,
indicando quando o recurso está sendo acessado por um dos processos concorrentes. Se seu valor for
maior que 0, nenhum processo está utilizando o recurso. Caso contrário, o processo fica impedido de
acessar o recurso.
Sempre que deseja entrar na sua região crítica, um processo executa uma instrução down. Se o
semáforo for maior que 0, ele é decrementado de 1, e o processo que solicitou a operação pode executar
sua região crítica. Se uma instrução down é executada em um semáforo cujo valor seja 0, o processo que
solicitou a operação ficará no estado bloqueado em uma fila associada ao semáforo.
Quando o processo que está acessando o recurso sai de sua região crítica, ele executa uma
instrução up, incrementando o semáforo de 1 e liberando o acesso ao recurso. Se um ou mais processos
estiverem esperando, o sistema escolhe um processo na fila de espera e muda seu estado para pronto.
As operações up e down são realizadas pelo sistema operacional, que deve garantir que elas sejam executadas
atomicamente.
A correção para o caso anterior de atualização do saldo de uma conta, com a utilização de semáforos,
ficaria:
void atualiza_saldo(double valor, int conta) {
Registro registro;
down(mutex);
registro = le_registro(conta);
registro.saldo = registro.saldo + valor;
grava_registro(registro, conta);
up(mutex);
}
Com a utilização de semáforos, somente um dos processos estará dentro da região crítica em
determinado instante. Voltemos ao caso dos caixas Carlos e Orlando:
Exemplo
Ambos solicitaram a atualização de uma conta cujo saldo é 500. Com a utilização de semáforos, antes de o
processo de Carlos ler o saldo, é realizada a operação down(mutex). Supondo que não haja outro processo na região
crítica, o semáforo mutex permitirá a entrada do processo de Carlos. Quando o processo de Orlando tenta fazer sua
operação, também se chamará down(mutex), mas, como o processo de Carlos está na região crítica, o processo de
Orlando será bloqueado pelo semáforo. O processo de Carlos segue normalmente, faz o depósito de 100 e atualiza o
saldo para 600. Ao sair da região crítica, o processo de Carlos faz up(mutex), liberando o processo de Orlando, que
faz a leitura do saldo já atualizado (600). Assim, o processo faz o saque de 200 e atualiza o saldo para 400. Ao final, o
processo de Orlando faz up(mutex), liberando novamente a região crítica. Vemos que, com a utilização de semáforos,
não ocorre a inconsistência vista anteriormente.
Para conhecer os programas em Linguagem C para Linux que fazem operações concorrentes de
saque e depósito, consulte o documento Utilização de semáforo na solução de condição de corrida.
Sistemas Operacionais
Marcio Quirino - 57
Utilização de semáforo na solução de condição de corrida
A listagem do Anexo I mostra um programa com 2 threads que são executados concorrentemente.
O thread “funcaoDeposito” realiza uma operação de depósito de 100 em uma conta, enquanto o thread
“funcaoSaque” realiza um saque de 200 na mesma conta.
A função responsável por atualizar o saldo é a “atualiza_saldo”. De modo a provocar a situação de
haver dois threads dentro da região crítica, foi introduzida a chamada de sistema usleep(1000), que faz com
que a função bloqueie por 1ms. Esse tempo será suficiente para provocar a ocorrência da condição de
corrida no processo, em que os dois threads tentarão atualizar a mesma variável simultaneamente, fazendo
com que o resultado seja inconsistente.
Durante a execução, os dois threads solicitarão a atualização do saldo em “registro”. Como ambos
leem a varável antes de alterá-la, somente uma das alterações terá efeito.
O resultado do processamento do programa do Anexo I será:
Saldo antes das operações = 500
Iniciando operação [-200]
Iniciando operação [100]
Terminada operação [-200]
Terminada operação [100]
Saldo depois das operações = 600
Você pode perceber que é iniciada a operação de saque de 200 e, antes que ela termine, é iniciada
a operação de depósito de 100. O saldo antes das operações era 500 e o resultado do processamento
deveria ser 400. Porém, devido à condição de corrida, o resultado do processamento foi 600.
Para a solução deste problema, é necessária a definição de uma região crítica e sua proteção por
semáforo, de modo que possa haver somente um processo em execução dentro da região crítica.
Na listagem no Anexo II, você encontra o mesmo programa, com a inserção da região crítica
protegida por um semáforo de nome mutex (linhas 43 a 50). O semáforo impede que dois threads executem
concorrente dentro da região crítica, impedindo o acesso simultâneo ao saldo da conta.
O resultado do processamento do programa do Anexo II será:
Saldo antes das operações = 500
Iniciando operação [-200]
Terminada operação [-200]
Iniciando operação [100]
Terminada operação [100]
Saldo depois das operações = 400
Pela saída do processamento, é possível verificar que é iniciada a operação de saque de 200 e que
a operação de depósito de 100 é iniciada somente após o término da operação de saque. Como o semáforo
permite a execução de apenas um programa dentro da região crítica, não ocorre a condição de corrida.
ANEXO I
Programa sem semáforo, sujeito à condição de corrida.
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <sched.h>
6 #include <sys/wait.h>
7
8 #define TAMANHO_PILHA 65536
9
10 typedef struct {
11 double saldo;
12 } Registro;
13
Sistemas Operacionais
Marcio Quirino - 58
14 Registro registro;
15
16 Registro le_registro(int conta){
17 return registro;
18 }
19
20 void grava_registro(Registro reg, int conta){
21 registro = reg;
22 }
23
24 void atualiza_saldo(double valor, int conta) {
25 Registro registro;
26 printf("Iniciando operação [%.2f]\n", valor);
27 registro = le_registro(conta);
28 usleep(1000);
29 registro.saldo = registro.saldo + valor;
30 grava_registro(registro, conta);
31 printf("Terminada operação [%.2f]\n", valor);
32 }
33
34 int funcaoDeposito(void *arg) {
35 // Faz deposito de 100,00
36 atualiza_saldo(100, 0);
37 }
38
39 int funcaoSaque(void *arg) {
40 // Faz saque de 200,00
41 atualiza_saldo(-200, 0);
42 }
43
44 int main() {
45 void *pilha1, *pilha2;
46 int pid1, pid2;
47
48 registro.saldo = 500; // Inicializa saldo
49 printf("Saldo antes das operações = %.2f\n", registro.saldo);
50
51 // Aloca pilha para thread de depósito
52 if ((pilha1 = malloc(TAMANHO_PILHA)) == 0) {
53 perror("Erro na alocação da pilha.");
54 exit(1);
55 }
56 // Inicia thread de depósito
57 pid1 = clone(funcaoDeposito,
58 pilha1 + TAMANHO_PILHA,
59 CLONE_VM | SIGCHLD,
60 NULL);
61
62 // Aloca pilha para thread de saque
63 if ((pilha2 = malloc(TAMANHO_PILHA)) == 0) {
64 perror("Erro na alocação da pilha.");
65 exit(1);
66 }
67 // Inicia thread de saque
68 pid2 = clone(funcaoSaque,
69 pilha2 + TAMANHO_PILHA,
70 CLONE_VM | SIGCHLD,
71 NULL);
72
73 //Aguarda final da operações
74 waitpid(pid1, 0, 0);
75 waitpid(pid2, 0, 0);
76
77 printf("Saldo depois das operações = %.2f\n", registro.saldo);
78 }
ANEXO II
Programa com semáforo, solucionando a condição de corrida.
Sistemas Operacionais
Marcio Quirino - 59
1 /*********************************\
2 * *
3 * COMPILAR COM PARÂMETRO -pthread *
4 * *
5 \*********************************/
6
7 #define _GNU_SOURCE
8 #include <stdio.h>
9#include <stdlib.h>
10 #include <unistd.h>
11 #include <sched.h>
12 #include <sys/wait.h>
13 #include <semaphore.h>
14
15 #define TAMANHO_PILHA 65536
16
17 sem_t mutex; // Cria semáforo mutex
18
19 void up(sem_t *sem) {
20 sem_wait(sem);
21 }
22
23 void down(sem_t *sem) {
24 sem_post(sem);
25 }
26
27 typedef struct {
28 double saldo;
29 } Registro;
30
31 Registro registro;
32
33 Registro le_registro(int conta){
34 return registro;
35 }
36
37 void grava_registro(Registro reg, int conta){
38 registro = reg;
39 }
40
41 void atualiza_saldo(double valor, int conta) {
42 Registro registro;
43 up(&mutex);
44 printf("Iniciando operação [%.2f]\n", valor);
45 registro = le_registro(conta);
46 usleep(1000);
47 registro.saldo = registro.saldo + valor;
48 grava_registro(registro, conta);
49 printf("Terminada operação [%.2f]\n", valor);
50 down(&mutex);
51 }
52
53 int funcaoDeposito(void *arg) {
54 // Faz deposito de 100,00
55 atualiza_saldo(100, 0);
56 }
57
58 int funcaoSaque(void *arg) {
59 // Faz saque de 200,00
60 atualiza_saldo(-200, 0);
61 }
62
63 int main() {
64 void *pilha1, *pilha2;
65 int pid1, pid2;
66
67 // Inicializa mutex om valor 1 (somente um thread na região crítica)
68 sem_init(&mutex, 1, 1);
69
Sistemas Operacionais
Marcio Quirino - 60
70 registro.saldo = 500; // Inicializa saldo
71 printf("Saldo antes das operações = %.2f\n", registro.saldo);
72
73 // Aloca pilha para thread de depósito
74 if ((pilha1 = malloc(TAMANHO_PILHA)) == 0) {
75 perror("Erro na alocação da pilha.");
76 exit(1);
77 }
78 // Inicia thread de depósito
79 pid1 = clone(funcaoDeposito,
80 pilha1 + TAMANHO_PILHA,
81 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD,
82 NULL);
83
84 // Aloca pilha para thread de saque
85 if ((pilha2 = malloc(TAMANHO_PILHA)) == 0) {
86 perror("Erro na alocação da pilha.");
87 exit(1);
88 }
89 // Inicia thread de saque
90 pid2 = clone(funcaoSaque,
91 pilha2 + TAMANHO_PILHA,
92 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD,
93 NULL);
94
95 //Aguarda final da operações
96 waitpid(pid1, 0, 0);
97 waitpid(pid2, 0, 0);
98
99 printf("Saldo depois das operações = %.2f\n", registro.saldo);
100 }
Monitores
O uso de semáforos exige do programador muito cuidado, pois qualquer engano pode levar a
problemas de sincronização imprevisíveis e difíceis de reproduzir. Monitores são mecanismos de
sincronização de alto nível que tentam tornar mais fáceis o desenvolvimento e a correção de programas
concorrentes.
Um monitor é uma coleção de variáveis, procedimentos e estruturas de dados que são agrupados
em um pacote. Em determinado instante, somente um processo pode estar ativo em um monitor. Toda vez
que algum processo chama um procedimento do monitor, ele verifica se já existe outro processo executando
qualquer procedimento do monitor. Caso exista, o processo ficará aguardando a sua vez, até que tenha
permissão para executar.
Sistemas Operacionais
Marcio Quirino - 61
As variáveis globais do monitor são visíveis apenas a ele e a seus procedimentos. O bloco de
comandos do monitor é responsável por inicializar essas variáveis, sendo executado apenas uma vez na
ativação do programa onde está declarado o monitor.
Cabe ao compilador implementar as exclusões mútuas em entradas de monitor. A cláusula
synchronized da linguagem Java é um exemplo de implementação de monitores. O programador transforma
regiões críticas em procedimentos de monitor, colocando todas as regiões críticas em forma de
procedimentos no monitor. Assim, o desenvolvimento de programas concorrentes fica mais fácil.
Sincronização no Linux
O Linux oferece suporte à utilização de semáforos para sincronização de tarefas por meio da
chamada de sistema sem_wait(), que realiza a operação up(), e da chamada de sistema sem_post(), que
realiza a operação down().
Quando sem_wait() é invocada e o valor do semáforo é zero, a tarefa que faz a invocação é
bloqueada até que o semáforo seja liberado por meio da função sem_post(). Porém, em algumas situações,
a tarefa pode querer apenas verificar se é possível prosseguir, mas não é interessante bloquear, caso não
possa prosseguir.
Atenção
Para esses casos, pode ser utilizada a chamada de sistema sem_trywait(), que tem funcionamento parecido
com a sem_wait(), exceto pelo fato de que, se o decremento não puder ser executado imediatamente, a chamada de
sistema retorna um erro sem bloqueio.
Além da utilização de semáforos para sincronização de processos, o Linux permite a comunicação
entre processos por meio de troca de mensagens e de sinais.
A troca de mensagens pode ser implementada pelo mecanismo de pipe. O pipe ( | ) é um mecanismo
especial de redirecionamento utilizado para conectar a saída padrão de um processo à entrada padrão de
outro processo. Por exemplo, os comandos a seguir juntam todos os arquivos com extensão “.txt”,
ordenando suas linhas e retirando as duplicadas.
cat *.txt | sort | uniq
Entenda o significado de cada um deles:
1. O comando “cat *.txt” joga na saída padrão (tela) o conteúdo de todos os arquivos presentes
no diretório e que possuem extensão “.txt”.
2. O comando “sort” recebe linhas por sua entrada padrão (inicialmente, o teclado), ordena as
linhas e envia as linhas devidamente ordenadas para a saída padrão.
3. O comando “uniq” recebe linhas por sua entrada padrão e elimina as linhas duplicadas,
enviando para a saída padrão somente as linhas que não estão em duplicidade.
Quando é utilizada a barra vertical ( | ) entre os comandos, é introduzido um pipe entre as tarefas, ou
seja, as tarefas são colocadas em execução concorrente, e a saída padrão da tarefa à esquerda do pipe é
conectada à entrada padrão da tarefa à direita do pipe. Assim, no exemplo, a saída do comando “cat *.txt”
é enviada para a entrada do comando “sort”, que faz a ordenação, e sua saída é enviada à entrada do
comando “uniq”, que elimina as linhas repetidas e, finalmente, joga o resultado de seu processamento para
a saída padrão (o monitor, por padrão).
Outra forma de comunicação entre processos no Linux ocorre por meio de envio de sinais, que são
enviados na ocorrência de eventos, como a chegada de uma informação pela rede, o fim de um
temporizador, o término da execução de um processo filho etc. São eventos que chegam ao processo e
Sistemas Operacionais
Marcio Quirino - 62
precisam ser tratados de alguma forma. Para isso, é necessário definir uma rotina para o tratamento do
evento.
São exemplos de sinais do Linux:
Sinal Ação padrão Comentário
SIGHUP Terminar Gerado pelo fim do terminal controlador.
SIGTERM Terminar Informa que deve parar a execução.
SIGINT Terminar Recebeu uma interrupção + pelo terminal controlador.
SIGKILL Terminal Força a finalização do processo.
SIGTSTP Suspender Processo deve ser suspenso (+).
SIGSTOP Suspender Processo deve ser suspenso. Semelhante ao SIGTSTP, mas não pode ser sobrescrito.
SIGCONT Retornar à execução se estiver suspenso.
SIGCHLD Ignorar Informa ao processo pai que um processo filho terminou ou foi suspenso.
SIGALRM Terminar Fim de temporizador.
SIGURG Ignorar Condição urgente no socket. Normalmente, aviso de chegada de pacote de rede.
SIGUSR1 Terminar Sinal definido pelo usuário.
SIGUSR2 Terminar Sinal definido pelo usuário.
Quando um sinal chega ao processo, ele pode:
✓ Receber o tratamento padrão definido pelo kernel.
✓ Ser capturado, sendo, então, tratado por uma função definida pelo usuário.
✓ Ser ignorado.
Para os sinais SIGKILL e SIGSTOP, sempre é executado o tratamento padrão. Eles não podem ser
capturados nem ignorados.
Sinais podem ser gerados por:
✓ Exceções de hardware.
✓ Condições de software.
✓ Pelo shell com o comando kill.✓ Por outro processo, utilizando a chamada de sistema kill().
✓ Por uma combinação de teclas, como, por exemplo, < Ctrl> + < C>.
✓ Controle de processos.
As principais chamadas de sistema relativas ao tratamento de sinais são:
Chamada de sistema Descrição
signal() Instala rotina para tratamento do sinal.
sigaction() Define a ação a ser tomada nos sinais.
sigreturn() Retorna de um sinal.
sigpending() Obtém o conjunto de sinais bloqueados.
kill () Envia um sinal para um processo.
alarm() Ajusta o alarme do relógio para envio de um sinal.
pause() Suspende o chamador até o próximo sinal.
Sistemas Operacionais
Marcio Quirino - 63
Tratamento de sinais em Linux
No final deste documento, você encontrará um pequeno programa que exemplifica a utilização da
chamada de sistema signal(), que faz a instalação de uma rotina para capturar sinais e modificar o
comportamento padrão destes sinais.
A ação padrão dos sinais SIGINT e SIGTERM é finalizar a execução do processo. Neste programa,
vamos capturar esses sinais e executar nossa própria ação, evitando que o processo seja terminado quando
receber os sinais A linha 7 define uma função de nome captura() que será utilizada como função para
tratamento dos sinais. Ela verifica o tipo de sinal recebido e coloca essa informação na saída padrão. Antes
de terminar, ela mostra a mensagem “Pretendo continuar executando!!!” e o processo continua executando
normalmente.
A linha 18 faz a instalação da função rotina para o tratamento dos sinais SIGINT e SIGTERM por
intermédio da chamada de sistema signal(). A partir deste ponto, sempre que chegar qualquer um destes
sinais, a execução do processo será desviada para a função captura().
Ao final, o processo entra em um loop infinito no qual, a cada segundo, ele emite uma mensagem
afirmando que está executando normalmente.
Ao ser colocado em execução, o processo começa a exibir uma série de mensagens informando que
está vivo.
Para testar o sinal SIGINT, utilizaremos o próprio terminal no qual o processo está executando.
Ao pressionar simultaneamente as teclas <Ctrl>+<C>, o sinal SIGINT é enviado ao processo, que
faz com que ele seja encerrado. Como nosso programa faz a captura deste sinal, no lugar de ser encerrado,
a execução do processo será desviada para a rotina captura, que verifica o tipo do sinal, mostra essa
informação e informa que continuará executando.
O mesmo teste pode ser realizado para verificar a captura do sinal SIGTERM, mas, para enviar esse
sinal, utilizaremos o comando kill. Esse comando é utilizado para enviar sinais a processos, informando
como parâmetro o sinal que deve ser enviado. Se não for informado o tipo de sinal, por padrão é enviado o
sinal SIGTERM.
Primeiramente, precisamos descobrir o PID do processo que queremos terminar. Para isso, é
necessário abrir outro terminal e executar o comando:
ps -a
Verificamos o número do processo (PID) e enviamos o sinal para ele. Se o PID for, por exemplo,
8053, enviamos o sinal SIGTERM por intermédio do comando:
kill 8053
De forma semelhante ao explicado para o SIGINT, o sinal SIGTERM será capturado e uma
mensagem será exibida na tela.
Não importa o tipo de sinal enviado (SIGINT ou SIGTERM), o processo mostrará uma mensagem
informando o sinal recebido e continuará executando e informando a cada segundo que está vivo.
Uma forma de terminar a execução do processo é por intermédio do sinal SIGKILL, que não pode
ser capturado. Para enviar o sinal SIGKILL, podemos utilizar o comando “kill -s SIGKILL <PID>” ou o
comando “kill -9 <PID>”, onde <PID> será o PID do processo a ser encerrado. Para o exemplo anterior,
podemos encerrar o processo por intermédio do comando:
Kill -9 8053
Sistemas Operacionais
Marcio Quirino - 64
Exemplo para tratamento de sinais
1 #include <signal.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5
6 // Rotina para tratamento do sinal capturado
7 static void captura(int sinal) {
8 if (sinal == SIGINT)
9 printf("Recebido o sinal SIGINT.\n");
10 if (sinal == SIGTERM)
11 printf("Recebido o sinal SIGTERM.\n");
12 printf("Pretendo continuar executando!!!\n");
13 }
14
15 int main(){
16 int i;
17 // Captura os sinais SIGINT e SIGTERM
18 if ((signal(SIGINT, captura) == SIG_ERR) || (signal(SIGTERM, captura) == SIG_ERR)) {
19 printf("Erro ao instalar o tratador do sinais.\n");
20 exit(1);
21 }
22 i = 1;
23 while (1) { // Loop infinito
24 printf("Estou vivo [%d].\n", i++);
25 usleep(1000000);
26 }
27 }
4. Formas de escalonamento
Comparar as diferentes formas de escalonamento
Escalonamento
A multiprogramação tem como objetivo permitir que, a todo instante, haja algum processo para
maximizar a utilização da UCP.
O conceito que possibilitou a implementação de sistemas multiprogramáveis foi a possibilidade de a
UCP ser compartilhada entre diversos processos. Portanto, deve existir um critério para determinar a ordem
da escolha dos processos para execução dentre os vários que concorrem pela UCP.
O procedimento de seleção é conhecido como escalonamento (scheduling). A parte do sistema
operacional responsável pelo escalonamento é o escalonador (scheduler), às vezes chamado de agendador.
Sempre que a UCP se torna ociosa, o escalonador seleciona um processo dentre aqueles que estão na
memória prontos para serem executados (na fila de processos no estado pronto) e aloca a UCP para que
ele possa ser executado.
A principal função de um algoritmo de escalonamento é decidir qual dos processos prontos deve ser alocado
à UCP.
O algoritmo de escalonamento não é o único responsável pelo tempo de execução de um processo.
Ele afeta somente o tempo de espera na fila de processos prontos, e pode ser classificado como preemptivo
ou não preemptivo. Quando o sistema pode interromper um processo durante sua execução para colocá-lo
no estado pronto e colocar outro processo no estado executando, tem-se um sistema preemptivo. Caso
contrário, há um sistema não preemptivo.
Sistemas Operacionais
Marcio Quirino - 65
Preemptivo
O escalonamento preemptivo permite que o sistema dê atenção imediata a processos mais prioritários, além
de proporcionar melhor tempo de resposta em sistemas de tempo compartilhado. Outro benefício decorrente é o
compartilhamento do processador de maneira mais uniforme.
Realizar o escalonamento preemptivo exige que uma interrupção de relógio ocorra ao fim do intervalo
para devolver o controle da UCP ao escalonador.
A troca de um processo por outro na UCP (mudança de contexto) causada pela preempção gera um
overhead ao sistema. Para não se tornar crítico, o sistema deve estabelecer corretamente os critérios de
preempção.
Atenção
Sistemas que usam escalonamento preemptivo têm o problema da condição de corrida, o que não ocorre com
sistemas que usam escalonamento não preemptivo. No escalonamento não preemptivo, quando um processo ganha
o direito de utilizar a UCP, nenhum outro processo pode tirar dele esse recurso.
Clique nas barras para ver as informações.
Escalonamento First In First Out (FIFO)
Algoritmo de escalonamento também conhecido como primeiro a chegar, primeiro a ser servido (first
come, first served).
A grande força deste algoritmo é que ele é fácil de aprender e de programar.
Nesse escalonamento, o processo que chegar primeiro é o primeiro a ser selecionado para execução.
É preciso apenas uma fila, em que os processos que passam para o estado pronto entram no seu final.
Quando um processo ganha a UCP, ele a utiliza sem ser interrompido, caracterizando-o como um algoritmo
não preemptivo.
O problema do escalonamento FIFO é a impossibilidade de se prever quando um processo terá sua
execução iniciada. Outro problema é a possibilidade de processos CPU-bound de menor importância
prejudicarem processos I/O-bound mais prioritários.
Este algoritmofoi inicialmente implementado em sistemas batch.
Sistema batch
Tipo de processamento de dados em lote que não depende da interação com o usuário.
Escalonamento Shortest Job First (SJF)
O algoritmo SJF (tarefa mais curta primeiro) associa a cada processo seu tempo de execução.
Quando a UCP está livre, o processo no estado pronto que precisar de menos tempo para terminar é
selecionado para execução.
O escalonamento SJF beneficia processos que necessitam de pouco processamento e reduzem o
tempo médio de espera em relação ao FIFO. O problema é determinar quanto tempo de UCP cada processo
necessita para terminar seu processamento. Em ambientes de produção, é possível estimar o tempo de
execução, mas, em ambientes de desenvolvimento, é muito difícil.
Imagine quatro tarefas A, B, C e D com tempos de execução de 14, 8, 6 e 4 minutos, respectivamente.
Ao executá-las nessa ordem, o tempo de retorno para A é 14 minutos, para B, 22 minutos, para C, 28 minutos
e, para D, 32 minutos, resultando em uma média de 24 minutos. Agora, considere executar essas quatro
tarefas usando o algoritmo da tarefa mais curta primeiro. Os tempos de retorno são agora 4, 10, 18 e 32
minutos, o que resulta em uma média de 16 minutos.
Sistemas Operacionais
Marcio Quirino - 66
É um algoritmo de escalonamento não preemptivo e, assim como o FIFO, também foi utilizado nos
primeiros sistemas operacionais com processamento batch.
Escalonamento Shortest Remaining Time Next (SRTN)
O SRTN (tempo restante mais curto em seguida) é uma versão preemptiva do SJF. Nele, o
escalonador escolhe o processo cujo tempo de execução restante é o mais curto. Para o seu funcionamento,
o tempo de execução precisa ser conhecido antecipadamente.
Quando uma nova tarefa chega, seu tempo total é comparado ao tempo restante do processo atual.
Se a nova tarefa precisar de menos tempo para terminar do que o processo atual, ela é suspensa, e a nova
tarefa é iniciada. Esse esquema permite que novas tarefas curtas tenham um bom desempenho.
Escalonamento Cooperativo
O SJF e o FIFO não são algoritmos de escalonamento aplicáveis a sistemas de tempo compartilhado,
onde um tempo de resposta razoável deve ser garantido a usuários interativos.
No escalonamento cooperativo, quando um processo já está em execução por determinado tempo,
ele voluntariamente libera a UCP, retornando para a fila de processos prontos.
Sua principal característica está no fato de a liberação da UCP ser uma tarefa realizada
exclusivamente pelo processo em execução, que a libera para um outro processo. Não existe nenhuma
intervenção do sistema operacional na execução do processo. Isto pode ocasionar sérios problemas na
medida em que um programa pode não liberar a UCP ou um programa mal escrito pode entrar em loop,
monopolizando a UCP.
É um algoritmo de escalonamento não preemptivo.
Escalonamento Circular (Round Robin)
Esse algoritmo é bem semelhante ao FIFO. Entretanto, quando um processo passa para o estado
executando, existe um tempo limite (conhecido como time-slice ou quantum) para utilização da UCP de
forma contínua. Quando esse tempo expira, o processo volta ao estado pronto, dando a vez a outro
processo.
A fila de processos no estado pronto é tratada como uma fila circular. O escalonamento é realizado
alocando a UCP para cada processo da fila no intervalo de tempo determinado pelo quantum.
Se o quantum for muito pequeno, gasta-se muito tempo de UCP com trabalho administrativo. Se o
quantum for muito grande, a interatividade fica prejudicada, já que um processo que sai de execução pode
demorar muito a voltar. Em geral, o quantum varia de 10 a 100ms.
Através do escalonamento circular, nenhum processo poderá monopolizar a UCP, caracterizando-o
como um algoritmo de escalonamento preemptivo.
Um problema do escalonamento circular é que ele não oferece qualquer tratamento diferenciado a
processos I/O-bound. Assim, processos CPU-bound terão a tendência de monopolizar a utilização da UCP,
enquanto processos I/O-bound permanecem à espera.
Sistemas Operacionais
Marcio Quirino - 67
Escalonamento por Prioridade
O escalonamento circular consegue melhorar a distribuição do tempo de UCP em relação aos
escalonamentos não preemptivos, porém ainda não consegue implementar um compartilhamento equitativo
entre os diferentes tipos de processos.
Para solucionar esse problema, os processos I/O-bound devem levar alguma vantagem no
escalonamento, a fim de compensar o tempo excessivo gasto no estado bloqueado. Como alguns processos
devem ser tratados de maneira diferente dos outros, é preciso associar a cada um deles uma prioridade de
execução. Assim, processos de maior prioridade são escalonados preferencialmente.
A preempção por prioridade é implementada mediante um relógio que interrompe o processador
periodicamente, para que a rotina de escalonamento reavalie prioridades e, possivelmente, escalone outro
processo, caracterizando-o como um algoritmo de escalonamento preemptivo.
Todos os sistemas de tempo compartilhado implementam algum esquema de prioridade, que é uma
característica do contexto de software de um processo, podendo ser estática ou dinâmica.
Tem-se a prioridade estática quando a prioridade não é modificada durante a existência do processo.
Apesar da simplicidade de implementação, a prioridade estática pode ocasionar tempos de resposta
elevados.
Na prioridade dinâmica, a prioridade do processo pode ser ajustada de acordo com o tipo de
processamento realizado pelo processo ou pela carga do sistema. Quando o processo sai do estado
bloqueado, recebe um acréscimo à sua prioridade. Dessa forma, os processos I/O-bound terão mais chance
de ser escalonados e de compensar o tempo que passam no estado bloqueado.
Para evitar que processos com maior prioridade sejam executados indefinidamente, a prioridade é
diminuída com o passar do tempo.
Outra forma de obter prioridade dinâmica é fazer com que o quantum do processo seja inversamente
proporcional à fração do último quantum utilizado.
Embora os sistemas de prioridade dinâmica sejam mais complexos e gerem um overhead maior, o
tempo de resposta oferecido compensa.
Escalonamento por Múltiplas Filas
Como os processos de um sistema possuem diferentes características de processamento, é difícil
que um único mecanismo de escalonamento seja adequado a todos. Uma boa política seria classificar os
processos em função do tipo de processamento realizado e aplicar a cada grupo mecanismos de
escalonamentos distintos.
O escalonamento por múltiplas filas implementa diversas filas de processo no estado pronto, onde
cada processo é associado exclusivamente a uma delas. Cada fila possui um mecanismo próprio de
escalonamento em função das características do processo. Nesse esquema, os processos devem ser
classificados, previamente, em função do tipo de processamento para poderem ser encaminhados à
determinada fila.
Cada fila possui uma prioridade associada, que estabelece quais são prioritárias em relação às
outras. O sistema só pode escalonar processos de uma fila se todas as outras filas de prioridade maior
estiverem vazias.
Para exemplificar esse escalonamento, considere que os processos, em função de suas
características, sejam divididos em três grupos: sistema, interativo e batch. Os processos do sistema devem
ser colocados em uma fila de prioridade superior aos outros processos, implementando um algoritmo de
escalonamento baseado em prioridades. Os processos de usuários interativos devem estar em uma fila de
prioridade intermediária, implementando, por exemplo, o escalonamento circular. O mesmo mecanismo de
Sistemas Operacionais
Marcio Quirino - 68
escalonamento pode ser utilizado na fila de processos batch, com a diferença de que esta fila deverá possuir
uma prioridade mais baixa.
Escalonamento em Sistemas de Tempo Real
Um sistema de tempo real é aquele em queo tempo tem um papel essencial. Tipicamente, um ou
mais dispositivos físicos externos ao computador geram estímulos. O computador tem de reagir em
conformidade dentro de um montante de tempo fixo. Exemplos de sistemas de tempo real são equipamentos
que estão monitorando pacientes em uma UTI, o piloto automático de um avião e o controle de robôs em
uma fábrica automatizada. Em todos esses casos, ter a resposta certa tarde demais é, muitas vezes, tão
ruim quanto não tê-la.
Sistemas em tempo real geralmente são categorizados como tempo real crítico, significando que há
prazos absolutos que devem ser cumpridos, e tempo real não crítico, significando que descumprir um prazo
ocasional é indesejável, mas, mesmo assim, tolerável. Em ambos os casos, o comportamento em tempo
real é conseguido com a divisão do programa em uma série de processos, cada um dos quais é previsível
e conhecido antecipadamente. Esses processos geralmente têm vida curta e podem ser concluídos em curto
espaço de tempo. Quando um evento externo é detectado, cabe ao escalonador programar os processos
de maneira que todos os prazos sejam atendidos.
Os eventos a que um sistema de tempo real talvez tenha de responder podem ser categorizados
ainda como periódicos (significando que eles ocorrem em intervalos regulares) ou aperiódicos (significando
que eles ocorrem de maneira imprevisível). Dependendo de quanto tempo cada evento exige para o
processamento, tratar de todos talvez não seja possível. Se há m eventos periódicos e o evento i ocorre
com o período Pi e exige Ci segundos de tempo da UCP para lidar com cada evento, a carga só pode ser
tratada se:
Diz-se de um sistema de tempo real que atende a esse critério que ele é escalonável, o que significa
que ele realmente pode ser implementado. Um processo que não atende a esse critério não pode ser
escalonado, pois o montante total de tempo de UCP que os processos querem coletivamente é maior do
que a UCP pode proporcionar.
Algoritmos de escalonamento de tempo real podem ser estáticos ou dinâmicos. Algoritmos estáticos
tomam suas decisões de escalonamento antes de o sistema começar a ser executado. Algoritmos dinâmicos
tomam suas decisões no tempo de execução, após ela ter começado. O escalonamento estático funciona
apenas quando há uma informação perfeita disponível antecipadamente sobre o trabalho a ser feito e sobre
os prazos que precisam ser cumpridos. Algoritmos de escalonamento dinâmico não têm essas restrições.
Escalonamento de Threads
Quando vários processos têm, cada um, múltiplos threads, há dois níveis de paralelismo presentes:
processos e threads. O escalonamento nesses sistemas vai diferir, ainda, se são threads de usuário ou
threads de núcleo.
Sendo threads de usuário, o núcleo não tem ciência da existência dos threads. Assim, ele opera
como sempre fez, escolhendo um processo A e dando a A o controle de seu quantum. O escalonador de
thread dentro de A decide qual thread executar (A1, por exemplo). Dado que não há interrupções para
multiprogramar threads, esse thread pode continuar a ser executado por quanto tempo quiser. Se ele utilizar
todo o quantum do processo, o núcleo selecionará outro processo para executar.
Quando o processo A executar novamente, o thread A1 retomará a execução. Ele continuará a
consumir todo o tempo de A até que termine. No entanto, seu comportamento não afetará outros processos.
Sistemas Operacionais
Marcio Quirino - 69
Eles receberão o que quer que o escalonador considere sua fração apropriada, não importa o que estiver
acontecendo dentro do processo A.
Agora, considere o caso em que os threads de A tenham relativamente pouco trabalho para fazer,
por exemplo, 5ms de trabalho dentro de um quantum de 50ms. Em consequência, cada um é executado por
um tempo. Então, cede a UCP de volta ao escalonador de threads. Isso pode levar à sequência A1, A2, A3,
A1, A2, A3, A1, A2, A3, A1, antes que o núcleo chaveie para o processo B.
O algoritmo de escalonamento usado pelo sistema de tempo de execução pode ser qualquer um dos
descritos anteriormente. A única restrição é a ausência de um relógio para interromper um thread que esteja
sendo executado há tempo demais. Como os threads cooperam, isso normalmente não é um problema.
Vejamos agora a situação com threads de núcleo. Aqui, o núcleo escolhe um thread em particular
para executar. Ele não precisa levar em conta a qual processo o thread pertence, mas pode fazer isso. O
thread recebe um quantum e é suspenso compulsoriamente se o exceder. Com um quantum de 50ms e
threads que são bloqueados após 5ms, a ordem do thread por algum período de 30ms pode ser A1, B1, A2,
B2, A3, B3, algo que não é possível com esses parâmetros e threads de usuário.
Uma diferença importante entre threads de usuário e de núcleo é o desempenho. Realizar um
chaveamento de thread com threads de usuário exige um punhado de instruções de máquina. Com threads
de núcleo, é necessário um chaveamento de contexto completo, mudar o mapa de memória e invalidar o
cache, o que representa uma demora bem maior. Por outro lado, com threads de núcleo, ter um bloqueio de
thread na E/S não suspende todo o processo, como ocorre com threads de usuário.
Como o núcleo sabe que chavear de um thread no processo A para um thread no processo B é mais
caro do que executar um segundo thread no processo A, ele pode levar essa informação em conta quando
toma uma decisão.
Outro fator importante é que os threads de usuário podem empregar um escalonador de thread
específico de uma aplicação.
Escalonamento no Linux
O Linux suporta a multitarefa preemptiva, em que o escalonador decide que processo deve ser
executado e quando executá-lo. Tomar essas decisões de modo que mantenha o equilíbrio entre justiça e
desempenho para muitas cargas de trabalho diferentes é um dos desafios mais complicados dos sistemas
operacionais modernos.
O Linux é baseado em tarefas do núcleo. São definidas três classes de tarefas:
1. FIFO em tempo real
2. Escalonamento circular em tempo real
3. Tempo compartilhado
Entre essas três classes, é utilizado um escalonamento por múltiplas filas, em que a classe FIFO em
tempo real é a fila de maior prioridade e a classe tempo compartilhado é a classe de menor prioridade.
As tarefas FIFO em tempo real são escalonadas exclusivamente por prioridade, sem preempção.
Sempre que houver uma tarefa FIFO em tempo real, ela utilizará o processador sem ser interrompida, a não
ser que outra tarefa FIFO em tempo real de maior prioridade entre no estado pronto.
O escalonamento circular em tempo real funciona com algoritmo de escalonamento circular (Round
Robin), com um quantum associado a cada tarefa. Sempre que uma tarefa excede seu quantum, ocorre
uma preempção, e a tarefa é colocada no final da fila de processos prontos.
Sistemas Operacionais
Marcio Quirino - 70
Atenção
Apesar do nome “tempo real”, nenhuma dessas classes é realmente de tempo real, pois o sistema não tem
como garantir os limites de tempo necessários ao funcionamento de um sistema de tempo real.
As tarefas de tempo real recebem prioridade de 0 a 99, sendo 0 o nível de prioridade mais alto e 99
o nível de prioridade mais baixo.
As tarefas de tempo compartilhado não competem com as tarefas de tempo real. Elas são
escalonadas somente se não houver nenhuma tarefa de tempo real no estado pronto. As tarefas de tempo
compartilhado recebem prioridade de 100 a 139, sendo 100 a prioridade mais alta e 139 a prioridade mais
baixa desta fila. Dessa forma, o Linux possui um total de 140 níveis de prioridade.
Existe um utilitário de linha de comando chamado nice, que pode ser utilizado para alterar a
prioridade de uma tarefa. O nice recebe como parâmetro um valor (de -20 a +19), que é somado à prioridade
do thread. Apenas a conta root (administrator do Linux) pode fornecer um valor negativo como parâmetro
do nice. Issosignifica que um usuário comum pode solicitar apenas diminuição na prioridade de suas tarefas,
nunca aumento de prioridade.
Considerações finais
Iniciamos nosso estudo aprendendo sobre o modelo de processo, fundamental para implementação
de sistemas de tempo compartilhado, em que o usuário do sistema pode executar, concorrentemente, vários
programas, tendo a ilusão de que estão todos executando em paralelo.
Vimos ainda como a implementação de subprocessos e threads podem ser utilizados para o
desenvolvimento de aplicações que façam uso da capacidade de multiprocessamento dos sistemas atuais.
Tal técnica é de suma importância para o desenvolvimento de sistemas de alto desempenho.
Na parte de comunicação e sincronização entre processos, você pode perceber que, apesar do
desempenho que pode ser obtido com a programação concorrente, cuidados devem ser tomados,
principalmente no tocante à atualização de dados compartilhados. Para isso, podemos utilizar técnicas
oferecidas pelo sistema operacional, como semáforos, que definem e controlam o acesso a regiões críticas,
evitando as condições de corrida.
Por fim, estudamos os principais algoritmos de escalonamento que podem ser implementados pelos
sistemas operacionais, vendo as principais caraterísticas de cada um deles. Como exemplo de algoritmos
de escalonamento, vimos também como o Linux faz para selecionar o próximo processo a ser colocado em
execução.
Concluímos que o conhecimento acerca da gerência do processador de um sistema operacional e
de como se comportam processos, subprocessos e threads eleva o nível de conhecimento de um
desenvolvedor/programador, permitindo que ele se torne apto ao desenvolvimento de sistemas melhores e
de maior desempenho.
Referências
DEITEL, H. M. Sistemas Operacionais. 3. ed. São Paulo: Pearson Prentice Hall, 2005.
MACHADO, F. B.; MAIA, L. P. Arquitetura de Sistemas Operacionais. 5. ed. Rio de Janeiro: LTC,
2013.
NEMETH, E. et al. Manual completo do Linux: Guia do Administrador. 2. ed. São Paulo: Pearson
Prentice Hall, 2007
UBUNTU. Official Ubuntu Documentation. Consultado em meio eletrônico em: 2 ago. 2020.
SILBERSCHATZ, A. Fundamentos de Sistemas Operacionais. 9. ed. Rio de Janeiro: LTC, 2013.
Sistemas Operacionais
Marcio Quirino - 71
SILBERSCHATZ, A.; GALVIN, P. B. Sistemas Operacionais – Conceitos. 5. ed. São Paulo: Prentice
Hall, 2000.
SILVA, G. M. Guia Foca GNU/Linux. Consultado em meio eletrônico em: 2 ago. 2020.
SOARES, L. F. G. Redes de Computadores – Das LANs, MANs e WANS às Redes ATM. 2. ed. Rio
de Janeiro: Campus, 1995.
TANENBAUM, A. S.; BOS, H. Sistemas Operacionais Modernos. 4. ed. São Paulo: Pearson
Educacional do Brasil, 2016.
TANENBAUM, A. S.; WOODHULL, A. S. Sistemas Operacionais – Projeto e Implementação. 3. ed.
Porto Alegre: Bookman, 2008.
TANENBAUM, A. S. Redes de Computadores. 5. ed. São Paulo: Pearson, 2011.
TANENBAUM, A. S. Structured Computer Organization. 3. ed. Londres: Prentice Hall International,
1990.
Explore+
Um contratempo que pode ocorrer com a criação de problemas concorrentes é o impasse entre
processos. Aprenda sobre este assunto lendo o capítulo 6 do livro Sistemas operacionais modernos, de
Andrew Stuart Tanenbaum e Herbert Bos.
Sistemas Operacionais
Marcio Quirino - 72
Memória
Descrição
Organização de funcionamento da memória de um computador: gerência pelo sistema operacional
(SO), utilização da memória virtual e como ela é implementada no Linux.
Propósito
Apresentar as técnicas de gerência de memória empregadas pelos sistemas operacionais.
Introdução
Segundo von Neumann, para que um programa possa ser executado, suas instruções e seus dados
devem estar alocados na memória principal, a fim de que possam ser referenciados diretamente. Disso
decorre o fato de os processos ativos terem de estar alocados na memória para que possam executá-los.
A necessidade de gerenciar os espaços disponíveis na memória, bem como o local onde estão as
instruções e os dados de um processo, são, portanto, fundamentais para a sua execução.
Mesmo nos antigos sistemas monotarefa isso já era necessário, sendo ainda mais importante nos
modernos sistemas multitarefas.
Além disso, o alto custo da memória e a sua exiguidade diante das necessidades dos programas
tornaram ainda mais fundamental o gerenciamento eficiente desse recurso pelos sistemas operacionais
(SO).
Paginação, segmentação e memória virtual são técnicas comumente usadas nos SO modernos para
aumentar a eficiência do gerenciamento. Como fazer esse gerenciamento e como funcionam as diversas
técnicas são o objeto deste tema.
1. Como os processos enxergam a memória e como ela é
gerenciada
Gerência de memória
A memória do computador é um recurso extremamente importante e que exige um gerenciamento
apurado. Ela é composta por vários elementos, tais como: registradores e cache (armazenamento interno);
memória principal RAM (armazenamento primário) e armazenamento secundário; discos de estado sólido,
magnéticos e óticos; e fitas magnéticas.
Sistemas Operacionais
Marcio Quirino - 73
Figura 1: Elementos da memória. Fonte: SILBERSCHATZ, 2015.
Um sistema de computação utiliza vários níveis de armazenamento, cada um com suas próprias
características de custo, velocidade de acesso e capacidade. Como se pode notar na Figura 1, quanto mais
rápida a memória (ou seja, mais próximo ao topo da figura), mais cara ela será e menor quantidade existirá
no computador.
Na Figura 1 também podemos verificar os três grandes tipos de armazenamento:
Volátil
Guarda as informações temporariamente, e requer corrente elétrica para reter dados. Quando a energia é
desligada, todos os dados são apagados.
A. Armazenamento Interno
É constituído pela memória interna do processador e engloba os registradores e o cache. É a
memória de trabalho efetiva do processador. Trata-se de uma memória volátil.
B. Armazenamento Primário
É constituído pela memória principal do computador, normalmente referenciada como memória
RAM. Armazena o código dos programas e seus dados. Pode ser acessada diretamente pelo
processador, que solicita a transferência de seu conteúdo para os registradores da CPU durante
o processamento. É uma memória volátil.
C. Armazenamento Secundário
Também chamado de Armazenamento de Massa, é a memória que armazena os dados para uso
posterior, já que é não volátil. Para que seu conteúdo possa ser manipulado, é necessário que
seja transferido antes para a memória primária por meio de uma operação de leitura.
O sistema operacional é o responsável por realizar a gerência da memória. Entre suas funções estão:
• Alocar e desalocar os processos na memória.
• Controlar os espaços disponíveis.
• Impedir que um processo acesse o conteúdo da memória de outro processo.
Sistemas Operacionais
Marcio Quirino - 74
• Transferir dados entre o armazenamento primário e secundário e vice-versa.
Como os processos enxergam a memória
Como já sabemos, a memória de um computador é composta por um conjunto de circuitos eletrônicos
que são capazes de armazenar bits. Podemos considerar então que, do ponto vista do hardware, ela é um
conjunto de bytes, que podem, por meio de um circuito eletrônico, ser acessados individualmente. Essa
memória é denominada memória física, onde efetivamente os dados serão armazenados, e corresponde a
um espaço de endereçamento físico, normalmente representado em hexadecimal.
Do ponto de vista processual, entretanto, ela é vista como um conjunto de endereços lógicos que
podem ser acessados diretamente por comandos da linguagem de máquina do processador. Essa memória
lógica corresponde ao espaço de endereçamento do processo e, normalmente, nos sistemas atuais, é
muitas vezes maior que a memória física disponível na máquina, gerando a chamada memória virtual.
Dessa forma, podemos notar que os processos não enxergam a memóriafísica, mas apenas a
memória lógica. Portanto, todas as referências a endereço para o processo correspondem ao seu espaço
de endereçamento lógico, que lhe foi atribuído durante o processo de sua carga e que é independente do
espaço de endereçamento de qualquer outro processo.
O modelo de memória de um processo pode ser visto na Figura 2, onde temos:
Text Área da memória que armazena o código do programa.
Data Área de memória onde residem as variáveis do programa.
Heap Área de trabalho do programa onde são alocadas variáveis temporárias e variáveis dinâmicas.
Pilha Área onde ficam os registros de ativação de procedimentos, variáveis locais etc.
Figura 2: Espaço de endereçamento de um processo. Fonte: EnsineMe.
Dentro do espaço de endereçamento do programa, entre o Heap e a Pilha, temos uma área livre que
permite que eles cresçam um em direção ao outro.
Como o processo enxerga apenas os seus endereços lógicos e o circuito eletrônico enxerga somente
os endereços físicos, é necessário, então, que se faça a tradução do endereço lógico para o físico, pois,
dessa forma, um programa que referencia os seus endereços lógicos pode apontar para os respectivos
endereços físicos.
Sistemas Operacionais
Marcio Quirino - 75
O componente responsável por este mapeamento é o MMU (Memory Management Unit) que consiste
em um conjunto de chips que realiza seu trabalho a partir das seguintes regras:
A. Regra 1
Se o endereço lógico é maior que o limite inferior e menor que o limite superior, o endereço é
valido. Nesse caso, o endereço lógico é igual ao endereço físico.
B. Regra 2
Se o endereço lógico é menor que o limite superior, adiciona o endereço base ao endereço lógico
e realiza o acesso no endereço resultante. Nesse caso, o endereço lógico é diferente do endereço
físico.
C. Regra 3
Se o endereço lógico for maior que o limite superior ou menor que o limite inferior, o endereço é
inválido e é gerada uma exceção.
Os conceitos de limite superior e inferior serão tratados a seguir.
Para um processo, a memória é exclusiva para as suas demandas, no entanto, a memória é sempre
dividida entre outros processos. Mesmo quando o sistema é monotarefa, a memória terá uma área dedicada
para o sistema operacional (Figura 3a). Enquanto nos sistemas multiprogramados, a utilização e
concorrência da memória ocorre inclusive entre outros programas (Figura 3b).
Figura 3: Processos compartilhando memória. Fonte: EnsineMe.
Se não tivermos cuidado, um processo poderá “invadir” a área de outro, lendo e sobrescrevendo
dados, o que acabaria por gerar erros. Como impedir que isso aconteça?
Proteção de memória
Para realizar a proteção de forma eficiente, temos que nos certificar que cada processo tenha um
espaço de memória separado. Isso é fundamental para que possamos carregar vários processos na
memória ao mesmo tempo, permitindo a execução concorrente deles, além de protegê-los uns dos outros.
Sistemas Operacionais
Marcio Quirino - 76
A ideia básica da separação é que seja determinado um intervalo de endereços legais que possam
ser acessados pelo processo, de forma a assegurar que esse acesso seja realizado exclusivamente por ele.
É possível fornecer essa proteção usando dois registradores, geralmente um registrador base e um
registrador limite, como ilustrado na Figura 4.
Figura 4: Registradores base e limite. Fonte: SILBERSCHATZ, 2015.
Quando um processo é alocado na memória, ele recebe um endereço inicial de carregamento
(endereço base) e outro que corresponde ao tamanho total da área alocada ao processo (endereço limite).
Ambos são armazenados, respectivamente, nos registradores base e limite.
O registrador base estabelece o limite inferior da área do processo (300040), e a soma do limite com
o endereço base define o limite superior da área do processo (420940), conforme o exemplo da Figura 4.
A proteção do espaço da memória é assegurada pela MMU, que, ao receber um endereço da CPU
de um programa rodando em modo usuário, verifica se ele se encontra no intervalo legal do processo. Se
estiver, permite o acesso; caso contrário, gera um erro fatal, conforme ilustrado na Figura 5.
Figura 5: Proteção de memória com base e limite. Fonte: SILBERSCHATZ, 2015.
Sistemas Operacionais
Marcio Quirino - 77
Esse esquema impede que um programa rodando em modo usuário, de forma deliberada ou
acidental, modifique o código ou as estruturas de dados do sistema operacional ou de outros processos de
usuários.
O carregamento dos registradores base e limite exige a execução de uma instrução privilegiada pelo
Sistema Operacional (SO), concedida pelo SO estar em modo Kernel. Sendo assim, não é possível modificá-
lo sem este acesso privilegiado.
Por sua vez, o sistema operacional, em modo kernel, pode acessar qualquer endereço de memória,
sendo seu ou de qualquer outro processo de usuário.
Assim, permite a carga de programas do usuário, a descarga destes em caso de erros, o acesso e a
alteração de argumentos de chamadas de sistema, a execução de operações de entrada e saída a partir da
memória principal e vários outros serviços. Em um sistema multiprocessado, por exemplo, é possível
executar mudanças de contexto, transferindo o estado de um processo dos registradores para a memória
principal, antes de carregar o contexto do próximo processo da memória principal para os registradores.
Atenção
O esquema de proteção aqui descrito é básico e, dependendo da política de alocação adotada, poderá sofrer
modificações conforme veremos no próximo módulo.
De acordo com o que foi visto, quando um processo é alocado na memória, ele recebe um endereço
inicial para sua área de memória que é carregado no registrador base. Entretanto, o processo pode ser
carregado inicialmente em uma posição e, depois de um tempo, pode ser desalocado, apenas para, em
seguida, ser alocado novamente em outro endereço de memória. Para resolver essa situação temos o que
chamamos de relocação.
Pode parecer estranho o fato de um processo ser alocado, desalocado e depois ser realocado em
outro ponto da memória. Contudo, dependendo da política de alocação, isso é possível, conforme veremos
no próximo módulo.
Relocação
Um processo, quando alocado na memória principal, faz referências às suas posições, ou seja, aos
endereços físicos dessa memória. Por essa razão, quando um módulo foi link-editado e seus endereços já
estão associados às posições físicas do espaço de memória onde ele será alocado, diz-se que esse módulo
está em Imagem de Memória, ou que ele está com Endereçamento Absoluto.
Para que um processo possa ser alocado em qualquer posição da memória, ele não pode estar com
endereçamento absoluto e, nesse caso, um mapeamento de endereços deverá ser feito entre os endereços
dos objetos referenciados pelo processo (lógicos) e os endereços absolutos (físicos) no espaço ocupado
por eles na memória principal.
Chamamos de Espaço de Endereçamento o conjunto de endereços, sejam eles de dados ou de
instruções, referenciados por um processo, podendo este ser:
A. Espaço Lógico de Endereçamento
O conjunto de objetos ou endereços lógicos referenciados.
B. Espaço Físico de Endereçamento
O conjunto de endereços físicos correspondentes.
A relocação de memória é, portanto, a função que mapeia os endereços lógicos em endereços
físicos. Ela pode ser realizada pelo link-editor durante a resolução das referências em aberto, na geração
do módulo de carga. Os endereços são resolvidos em relação a uma base inicial e o processo só poderá
ser alocado a partir dessa base, ou seja, sempre rodará no mesmo lugar da memória.
Sistemas Operacionais
Marcio Quirino - 78
Alguns sistemas deixam essa tarefa de relocação para o carregador (loader) ou ligador-carregador
(link-loader), que faz a resolução das referências externas e a relocação de endereços no instante de
carregar o processo na memóriapara sua execução.
No primeiro caso (link-editor), a carga em imagem de memória sempre roda no mesmo lugar da
memória, enquanto no segundo caso ela pode rodar em qualquer lugar.
Quando esse mapeamento de endereços é feito antes do carregamento do módulo, o processo é
denominado Relocação Estática.
Para que a tradução dinâmica de endereços possa ser efetuada, é preciso que toda referência seja
lógica, isto é, nenhum endereço no programa poderá estar representando uma posição física, pois será
mapeado pelo hardware. Esse outro processo é chamado de Relocação Dinâmica.
Para a relocação, o nosso registrador base será agora chamado de registrador de relocação e ele
receberá o endereço inicial da área de memória do processo.
Observe a Figura 6:
Figura 6: Relocação dinâmica. Fonte: SILBERSCHATZ, 2015.
É possível notar que o valor do registrador de relocação é 14000, portanto, ao receber o endereço
lógico 346, ele soma 14000 e acessa o endereço 14346.
Atenção
Como veremos adiante, existem técnicas de gerência de memória que retiram o processo que está bloqueado
da memória e, quando ele for executado novamente, recarregam-no em outro endereço. Isso só é possível devido à
relocação.
E como fica a proteção de memória nesse caso? Como a base agora é o registrador de relocação,
isso significa que não existirá endereço menor que este, portanto, a proteção deve ser realizada apenas
para verificar se o endereço lógico não é maior que o valor do registrador limite, pois, assim, o programa
estaria ultrapassando o limite superior de sua memória, conforme pode ser visto na Figura 7:
Sistemas Operacionais
Marcio Quirino - 79
Figura 7: Proteção de memória com relocação dinâmica. Fonte: SILBERSCHATZ, 2015.
2. Políticas de alocação de memória
Políticas de alocação
As políticas de alocação de memória compreendem dois tipos básicos:
1. Manter os processos na memória principal durante toda a sua execução.
2. Mover os processos entre a memória principal e a secundária (tipicamente disco), utilizando
técnicas de swapping (Permuta)ou de paginação.
Iniciaremos agora a compreensão dessas políticas de alocação de memória.
Gerenciamento de memória sem permuta
Alocação contígua
Os primeiros sistemas computacionais eram monoprogramados, ou seja, somente um processo de
usuário por vez estava na memória. Para melhorar essa situação, foi desenvolvido um esquema muito
simples, no qual a memória principal era compartilhada entre o sistema operacional e o processo de usuário,
conforme pode ser visto na Figura 8.
Figura 8: Alocação contígua. Fonte: EnsineMe.
Essa técnica apresenta como desvantagem a limitação do tamanho máximo do programa ao
tamanho da memória disponível. Para superar essa limitação, foi desenvolvida a primeira técnica de
permuta, a overlay.
Overlay
A técnica de overlay utiliza o conceito de sobreposição, ou seja, a mesma região da memória será
ocupada por módulos diferentes do processo.
Sistemas Operacionais
Marcio Quirino - 80
Figura 9: Módulo principal e secundários. Fonte: EnsineMe.
Quando um programa excedia a memória disponível, o programador dividia o código em um módulo
principal e vários módulos secundários (Figura 9). Para que a técnica pudesse funcionar, era necessário que
os módulos secundários fossem independentes entre si e qualquer dependência seria apenas em relação
ao módulo principal.
Figura 10: Carga do módulo principal. Fonte: EnsineMe.
Nesse modelo, o primeiro passo era carregar apenas o módulo principal na memória, permanecendo
os módulos secundários no disco (Figura 10).
Figura 11: Carga do módulo 1. Fonte: EnsineMe.
O módulo 1 é um módulo secundário, tal como o 2. Estes podem ser requisitados pelo módulo
principal, e o SO os carregará em memória assim que forem demandados.
Sistemas Operacionais
Marcio Quirino - 81
Figura 12: Carga do módulo 2. Fonte: EnsineMe.
Após o término da execução do modulo 1, o controle retorna ao módulo principal, que volta a
executar. Ao necessitar de um recurso do módulo 2, ele solicita sua carga na área de overlay (Figura 12) e
é colocado em execução.
Nesse tipo de estrutura, um módulo fica sempre residente (módulo principal) e os demais são
organizados em uma árvore hierárquica de módulos, mutuamente exclusivos, em relação a sua execução,
e colocados lado a lado num mesmo nível da árvore, de modo que o mesmo espaço possa ser alocado para
mais de um módulo, os quais permanecem no disco e serão executados um de cada vez por meio de
comandos de chamada (call).
Atenção
Esse foi o primeiro uso de permuta, mas cabe ao programador escrever o código de forma correta e determinar
o endereçamento de carregamento dos módulos de overlay.
Alocação particionada fixa
Com o advento da multitarefa, apareceu também a necessidade de se manter mais de um processo
de usuário na memória ao mesmo tempo. Surgiu então a ideia de dividir a memória em partições fixas,
podendo ser de tamanhos diferentes e, em cada uma, seria alocado um processo de usuário que ali
coubesse.
Essas partições eram fixas, estabelecidas na configuração do sistema, e o processo nela permanecia
até o seu término, quando então ela era liberada para o uso de outro processo que estivesse na fila
esperando alocação.
Duas estratégias podem ser adotadas para alocar o processo e estão ilustradas na Figura 13:
Figura 13: Filas de processo e partições fixas. Fonte: TANENBAUM, 2008.
Sistemas Operacionais
Marcio Quirino - 82
Uma fila por partição: os processos são divididos em várias filas de acordo com o seu tamanho e são
alocados quando a partição atendida pela fila está disponível (Figura 13a).
b
Uma única fila de entrada: todos os processos ficam na mesma fila e vão sendo alocados na menor
partição livre que possa acomodá-los (Figura 13b).
Este método de gerência de memória baseado em partições fixas gera, de uma forma ou de outra,
um grande desperdício de memória. Isso acontece porque quando um processo é carregado em uma
partição sem ocupar todo o seu espaço, o espaço restante não poderá ser utilizado por nenhum outro
processo.
Para evitar esse desperdício foi desenvolvido um esquema de gerenciamento e alocação de memória
dinamicamente, dependendo da necessidade do processo. Esse esquema é conhecido como alocação com
partições variáveis.
Alocação com partições variáveis
Nesta técnica, em vez de particionar a memória em blocos de tamanhos predeterminados...
Clique nas setas para ver o conteúdo.
Figura 14: Área de memória livre. Fonte: EnsineMe.
A memória de usuário é inicialmente considerada uma única partição livre.
Figura 15: Alocação do processo A. Fonte: EnsineMe.
Sistemas Operacionais
Marcio Quirino - 83
Ao iniciar o processo A, ele é carregado na memória e o seu tamanho define o tamanho da sua
partição.
Figura 16: Alocação dos processos B e C. Fonte: EnsineMe.
Os processos B e C, de forma análoga, são carregados no espaço livre ainda disponível, de acordo
com o ilustrado na Figura 16.
Figura 17: Falta de espaço em memória. Fonte: EnsineMe.
Ao chegar ao processo D, não existe espaço livre contínuo suficiente para que seja carregado. Ele
deve, então, esperar que um espaço contíguo de tamanho suficiente esteja liberado, assim que o processo
A termina, logo, o processo D pode ser alocado.
Figura 18: Fragmentação em memória. Fonte: EnsineMe.
Após alocar e desalocar vários processos, a memória pode se fragmentar, por exemplo, no espaço
entre os processos D e B.
Sistemas Operacionais
Marcio Quirino - 84
Para realizar a escolha da partição para a próxima tarefa em fila, podem ser utilizados diversos tipos
de políticas. Para exemplificar, considere uma situação em que um processo C de 70k precisa ser alocado
em uma das partições a seguir (figura 19). Assim, serão descritas as formas que podemser utilizadas para
realizar a alocação de memória.
Figura 19: Ocupação de memória.
Política Best-Fit
Procura alocar o processo na partição disponível de tamanho mais próximo ao do processo. Busca
deixar fragmentos livres menores, mas exige que se percorra todas as partições para descobrir a menor em
que o processo caiba. No nosso exemplo, será alocado na partição de 80k, gerando um fragmento de 10k,
conforme visto na Figura 20.
Figura 20: Política Best-Fit.
Política First-Fit
Procura alocar o processo na primeira partição onde ele couber. Probabilisticamente, agrupa os
processos pequenos, separando-os dos grandes, e tem a vantagem de ser mais rápido, pois só percorre as
partições até encontrar uma em que o processo caiba. No caso do exemplo ilustrado na Figura 21, gera uma
área livre de 80k.
Sistemas Operacionais
Marcio Quirino - 85
Figura 21: Política First-Fit.
Política Worst-Fit
Procura alocar o processo na maior partição disponível onde ele couber. Intuitivamente, sempre
caberá mais um processo pequeno no espaço resultante. Tem como desvantagem ter que percorrer todas
as partições até encontrar a maior. No caso do exemplo, gera uma área livre de 130k, como visto na Figura
22:
Figura 22: Política Worst-Fit.
Fragmentação externa e interna
A diferença principal entre as partições fixas e as variáveis é que nas variáveis o número, tamanho
e posição das partições variam ao longo do tempo, conforme é possível observar na Figura 23:
Sistemas Operacionais
Marcio Quirino - 86
Figura 23: Fragmentação de memória nas partições variáveis. Fonte: TANENBAUM, 2008.
A longo do tempo, esse processo pode acarretar fragmentação externa, ou seja, entre as partições
podem ser gerados pequenos pedaços livres que, no seu todo, poderiam atender à necessidade de memória
de um processo, mas, como não são contíguos, não permitem a alocação.
Outro tipo de fragmentação é a interna, que você pode compreender a seguir:
Exemplo
Considere uma área livre de 16.484 bytes e que o próximo processo solicite 16.480 bytes. Se for alocado
exatamente o bloco solicitado, ficaremos com uma fragmentação externa de 4 bytes. O esforço para fazer o controle
de uma lacuna tão pequena não compensa. Dessa forma, a solução geral para esse tipo de problema é alocar todo o
bloco para o processo, que resultará numa área não utilizada de 4 bytes dentro dele, correspondendo a uma
fragmentação interna.
Uma possível solução para a fragmentação externa seria realizar a compactação da memória, que
compreende mover todos os blocos ocupados para uma das extremidades do espaço de endereçamento
físico, resultando na geração de um grande bloco livre.
Apesar de parecer uma solução tentadora, ela não é normalmente utilizada porque o seu custo de
processamento é alto.
Outra solução possível seria permitir que o espaço de endereçamento do processo não seja contíguo,
possibilitando, dessa forma, que ele receba blocos de memória disponíveis em qualquer parte do espaço de
endereçamento. Essa ideia, com o swap, é a base do funcionamento da paginação e da segmentação que
serão estudados mais adiante.
Gerenciamento de memória com permuta
Swap de memória
Um processo deve estar na memória para ser executado. Entretanto, nada impede que, enquanto
ele está bloqueado ou em espera, ele seja retirado da memória principal e transferido para a memória
secundária e novamente carregado, quando chegar sua vez de ser novamente executado. Isto é o que
chamamos de swapping.
O uso do swapping permite aumentar o grau de multiprogramação, pois permite que a memória total
dos processos ultrapasse a memória física disponível no sistema, o que era impossível no esquema de
partições.
Observe a Figura 24, que ilustra a situação em que temos o processo E esperando por um espaço
livre contíguo onde ele caiba.
No esquema de partições ele necessitaria esperar o término da execução de um dos processos em
memória, porém, se o sistema utiliza swapping, é possível retirar um processo que não esteja em execução
e que ocupe o meio de uma partição. No caso, o processo A (swap out) o coloca no disco, atribui a nova
Sistemas Operacionais
Marcio Quirino - 87
partição disponível ao processo que estava esperando e, posteriormente, atribui outra partição (swap in) ao
processo que foi desalojado.
Figura 24: Swapping. Fonte: TANENBAUM, 2008.
Como é possível perceber, essa técnica utiliza relocação dinâmica e, portanto, os processos não
podem utilizar endereçamento absoluto.
As grandes vantagens do swap são:
• Maior compartilhamento da memória (maior throughput de tarefas).
• Menor fragmentação de memória.
• Boa técnica para ambientes com processos pequenos e poucos usuários.
A desvantagem é o tempo gasto no swap in e no swap out.
Exemplo
Suponha que um processo que tem que ser retirado da memória possua 200MB (swap out) e que um processo
a ser carregado tenha 150MB (swap in). Se considerarmos que o HD possui uma velocidade de escrita de 50MB/s e
de leitura de 100MB/s o swap out demoraria 4 segundos e o swap in 1,5 segundos. Portanto, a operação total, sem
considerar outros tempos envolvidos, demoraria 5,5 segundos, uma eternidade em termos computacionais.
Devido a essa perda de tempo, essa técnica não é utilizada em sistemas modernos. Nos sistemas
operacionais atuais, variantes otimizadas no tempo de escrita/leitura, tais como a paginação, são largamente
implementadas.
Gerenciamento de espaço livre
Para que as diversas políticas de alocação possam funcionar é necessário que o SO controle os
espaços livres na memória e onde cada processo está alocado, utilizando algumas técnicas que serão
estudadas a seguir.
Gerenciamento de memória com mapas de bits
Nesta técnica, a memória é dividida em unidades de alocação e, para cada unidade, existe um bit no
mapa de bits marcado como 0 se a unidade estiver livre, e com 1 se estiver ocupada.
Neste esquema, a definição do tamanho da unidade é extremamente importante. Quanto menor for
a unidade, maior será o mapa e vice-versa. Entretanto, também existe outra implicação: se a unidade for
grande e o tamanho do processo não for múltiplo do tamanho da unidade, no último bloco alocado haverá
fragmentação interna.
Atenção
O principal problema desse tipo de gerenciamento é que, quando for necessário trazer um processo de n
unidades para a memória, o gerenciador da memória terá que encontrar uma sequência de n bits 0 consecutivos no
mapa, o que é uma operação lenta por sua própria natureza.
Sistemas Operacionais
Marcio Quirino - 88
Observe a Figura 25, que mostra a gerência de espaço livre funcionando:
Figura 25: Memória e mapeamento de espaço livre. Fonte: TANENBAUM, 2008.
• A letra (a) mostra uma parte da memória com cinco processos e três lacunas. Os tracinhos
mostram as unidades de alocação de memória.
• A letra (b) mostra o mapa de bits correspondente.
• A letra (c) apresenta as mesmas informações como uma lista encadeada.
Gerenciamento de memória com listas encadeadas
A outra forma de gerenciamento é a criação de uma lista encadeada, na qual cada nó da lista
corresponde a um segmento de memória alocada – indicada na Figura 25(c) pelo P – ou livre – indicada por
L. Além da indicação do status, o nó armazena o endereço inicial do segmento e o seu tamanho em unidades
de alocação.
Nesse exemplo, a lista de segmentos está ordenada pelo endereço. Ordenando dessa maneira,
quando um processo termina a lista deve ser atualizada. Um processo que termina normalmente tem dois
vizinhos, exceto se for o primeiro ou o último da lista, que podem ser processos ou lacunas.
Essa situação gera quatro possíveis combinações a serem tratadas e que são exibidas na Figura 26.
Considerando que o processo X terminou, deve-se:
Figura 26: Gerência da lista encadeada. Fonte: TANENBAUM, 2008.
1. Na situação (a), atualizar a lista exigesubstituir um P por um L.
2. Nas situações (b) e (c), duas entradas são aglutinadas em uma e a lista se torna uma única
entrada mais curta.
3. Na situação (d), três entradas são aglutinadas e dois itens são removidos da lista.
Sistemas Operacionais
Marcio Quirino - 89
3. Funcionamento da memória virtual
Memória virtual
Como vimos anteriormente, o espaço de endereçamento lógico de um programa tem que ser
mapeado em um espaço de endereçamento físico de memória.
De fato, essa correspondência pode ser deixada totalmente a cargo do SO, de forma que o programa
não tenha nenhuma responsabilidade em se ater a um determinado tamanho de memória física disponível.
Assim, o conjunto de endereços que um programa pode endereçar pode ser muito maior que a
memória física disponível para o processo em um determinado momento, até mesmo mais que o total de
memória fisicamente disponível na máquina.
A esse conjunto de endereços chamamos de espaço de endereçamento virtual. Ao conjunto de
endereços reais de memória denominamos espaço de endereçamento real.
Assim, como os programas podem ser maiores que a memória física, somente uma parte de cada
programa precisa estar na memória durante a execução. As partes que não são necessárias em um
determinado instante ficam em disco e somente são carregadas quando se tornarem necessárias.
Todo o processo é transparente para o usuário e também para os compiladores e link-editores, pois
estes trabalham apenas com o espaço de endereçamento virtual. Apenas o SO se incumbe de carregar ou
descarregar as partes necessárias e mapeá-las no espaço de endereçamento real durante a execução.
Como consequência desse mapeamento, o programa não precisa estar nem mesmo em regiões
contíguas de memória. Naturalmente, o nível de fracionamento do programa deve ser escolhido de forma a
não comprometer o desempenho, na medida em que a tarefa de mapeamento é feita pelo SO com recursos
de hardware.
Assim, a memória (tanto a virtual como a real) é dividida em blocos, e o SO mantém tabelas de
mapeamento para cada processo, que relacionam os blocos da memória virtual do processo com os blocos
da memória real da máquina.
A Figura 27 mostra essa ideia. Note que o espaço de endereçamento virtual é muito maior que a
memória física. Observe ainda que o processo é endereçado por partes, no caso páginas, e elas são lidas
do disco para a memória e gravadas no disco, em um processo chamado paginação, uma implementação
da ideia de swap e que estudaremos a seguir.
Figura 27: Memória virtual. Fonte: SILBERSCHATZ, 2015.
Sistemas Operacionais
Marcio Quirino - 90
Paginação
Na paginação, o espaço de endereços é dividido em blocos de igual tamanho que chamamos de
páginas. A memória principal também é dividida em blocos de mesmo tamanho (igual ao tamanho da página)
denominados molduras.
Esses blocos de memória real são compartilhados pelos processos, de forma que a qualquer
momento um determinado processo terá algumas páginas residentes na memória principal (as páginas
ativas), enquanto as restantes utilizarão a memória secundária (as páginas inativas).
O mecanismo da paginação possui duas atribuições:
• Executar a operação de mapeamento, isto é, determinar qual a página referenciada e em que
bloco de memória (se for o caso) ela se encontra.
• Transferir páginas da memória secundária para os blocos da memória principal (quando for
requerido) e guardá-las de volta na memória secundária quando elas não estiverem em uso.
Observe a Figura 28, que apresenta o esquema de paginação. Nela podemos ver que o processo foi
dividido em 4 páginas (0 a 3), a tabela de páginas que as associa com os frames e a alocação das páginas
na memória.
Figura 28: Paginação. Fonte: SILBERSCHATZ, 2015.
Na paginação, o hardware determina tanto o tamanho do quadro como o da página, sendo que o
tamanho da página é sempre uma potência de 2 variando normalmente entre 512 bytes e 2KB. O uso de
potências de 2 visou facilitar o mapeamento dos endereços virtuais para os físicos.
Controle de espaço livre
Quando um novo processo chega ao sistema para ser executado, o seu tamanho é analisado em
páginas. Como cada página do processo necessita de um quadro, se, por exemplo, ele possui 4 páginas,
pelo menos 4 quadros devem estar livres. A forma mais fácil de o SO realizar o controle das áreas livres é
mantendo uma lista dos quadros livres. Como todos os quadros possuem o mesmo tamanho e o processo
pode ser alocado de forma não contígua, a lista não necessita estar ordenada, o que facilita muito a sua
manutenção.
É exatamente esta situação que se pode observar na Figura 29(a), que mostra os quadros livres (em
tom cinza) e ocupados (em tom azul) na memória física, o novo processo de 4 páginas e a lista de quadros
livres com 5 quadros (14, 13, 18, 20 e 15).
Sistemas Operacionais
Marcio Quirino - 91
A partir desta situação, a página 0 do processo será alocada no primeiro quadro da lista, o 14, a
segunda no 13, e assim, sucessivamente, sendo preenchida a tabela de páginas do processo e gerando o
cenário que pode ser observado na Figura 29(b).
Figura 29: Paginação e controle de quadros livres. Fonte: SILBERSCHATZ, 2015.
Proteção de memória
Em um ambiente paginado, a proteção de memória é provida pelo acréscimo de bits de proteção
associados a cada quadro na tabela de páginas.
Um bit pode definir que uma página é apenas de leitura ou se permite também a gravação.
Além dos bits já descritos, normalmente um outro bit denominado válido-inválido é acrescido à tabela
para estabelecer se aquela página está no espaço de endereçamento lógico do programa (acesso legal) ou
não (acesso ilegal).
Considere a situação da Figura 30. Ela nos mostra um sistema com espaço de endereçamento de
14 bits (0 a 16383) e um processo que usa apenas os endereços de 0 a 10468. Considerando um tamanho
de página de 2KB, o processo possui 6 páginas, apesar de ter uma tabela de páginas com mais entradas
possíveis.
Na tabela de páginas, apesar de o processo possuir somente 6 delas, as entradas referentes às
páginas 6 e 7 estão com o número de quadro 0, o que faria com que uma referência à página 7 gerasse
uma exceção para o SO.
Figura 30: Proteção de memória. Fonte: SILBERSCHATZ, 2015.
Sistemas Operacionais
Marcio Quirino - 92
Paginação sob demanda
A ideia básica da paginação sob demanda é carregar na memória somente páginas que forem
referenciadas na execução.
Obviamente, em um primeiro momento, somente a página inicial do processo será carregada e à
medida que as instruções fizerem referências a outras páginas, elas serão carregadas. Essa abordagem faz
com que páginas que nunca foram referenciadas jamais sejam carregadas na memória.
E como funciona essa demanda? Como se sabe, o número de blocos alocados a um processo é
menor do que o número de páginas que ele usa. Por isso, é possível que um endereço de programa
referencie uma página ausente. Nesse caso, a entrada da tabela correspondente estará “vazia” e uma
interrupção do tipo page fault é gerada sempre que uma página inativa é requerida e causa a ação
descrita na Figura 31.
Figura 31: Paginação sob demanda. Fonte: SILBERSCHATZ, 2015.
Caso não tenha entendido o que aconteceu, segue a explicação:
Resumindo
A interrupção faz com que o mecanismo da paginação inicie a transferência da página ausente da memória
secundária para um dos blocos da memória principal e atualize a tabela de páginas. O processo muda de estado,
ficando bloqueado até que a transferência termine. A posição ocupada por uma página na memória secundária é
guardada em uma tabela separada ou na própria tabela de páginas. Se não houver nenhum bloco de memória
disponível, então é necessário desocupar um dos blocos para a página, cuja presença está sendo solicitada. A escolha
do bloco é função do algoritmo de troca, conforme será visto adiante.Working set
O conjunto das páginas mais acessadas por um determinado processo
Políticas de paginação
Para minimizar a ocorrência de page faults surgiu o conceito de working set. Assim, quando um
programa começa a executar, a possibilidade de que ele requisite páginas que não estejam na memória é
muito grande. Entretanto, à medida que mais páginas vão sendo carregadas, a ocorrência de page faults vai
diminuindo devido ao princípio da localidade dos programas. Assim, cabe ao SO definir um conjunto de
páginas que devem ficar carregadas, sabendo que se trata de um compromisso entre capacidade e
velocidade.
Quanto mais páginas carregadas no working set, menor o risco de geração de page fault, logo,
melhor é o desempenho. Em compensação, quanto mais páginas no working set, menos processos podem
estar carregados ao mesmo tempo.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 93
Definido o working set, resta definir qual deve ser a página retirada da memória quando ocorre
um page fault, pois uma página precisa ser carregada e não existe quadro livre. Esta decisão pode obedecer
a várias políticas.
Entretanto, antes de descartar qualquer página, o SO precisa saber se houve alguma alteração nela
enquanto ela esteve na memória. Se houve, então essa página precisa ser salva em disco antes de ser
descartada para que nenhum dado se perca. Neste caso, o SO mantém um bit na tabela de páginas
chamado Bit de Modificação. Caso o bit indique que houve mudança, a página é salva em disco, onde
poderá ser futuramente resgatada.
As políticas de liberação de páginas são:
A. Página aleatória
Escolhe-se simplesmente qualquer página. A desvantagem é que pode-se escolher uma página
que seja muito acessada.
B. First-in-first-out (FIFO)
Retira-se a página carregada há mais tempo. A desvantagem é que pode retirar páginas que
sejam acessadas periodicamente.
C. Least-recently-used (LRU)
Retira-se a página utilizada pela última vez há mais tempo. A desvantagem é o overhead causado
pela necessidade de, a cada acesso, atualização do momento em que a página foi acessada.
D. Not-recently-used (NRU)
A página não utilizada nos últimos k acessos é substituída. A desvantagem também é o overhead
causado pela necessidade de, a cada acesso, atualizar um contador de acessos à página.
E. Least-frequently-used (LFU)
Escolhe-se a página que tenha o menor número de acessos descritos em um contador de
acessos. A ideia é manter na memória as páginas que são muito acessadas. Entretanto, o
problema é que páginas recém-carregadas no working set terão baixo número de acessos e
serão indesejavelmente cotadas para retirada.
Sistemas reais, como o Linux, implementam normalmente alguma variação da LRU a partir de
algoritmos que buscam se aproximar desta política. Um dos mais comumente usados é o algoritmo de
segunda chance.
Algoritmo de segunda chance
Também conhecido como algoritmo do relógio, ele busca uma aproximação ao LRU. Para isso, na
tabela de páginas são acrescidos bits de referência associados a cada uma de suas entradas.
Inicialmente, todos os bits são desligados (posicionados com 0) pelo sistema operacional. Quando
um processo de usuário é executado e uma página é referenciada, o bit associado a ela é ligado (com 1)
pelo hardware. Após algum tempo, podemos determinar as páginas que têm sido usadas ou não usadas
examinando os bits de referência, embora sem saber a ordem de uso.
Comentário
O algoritmo utiliza uma política de substituição FIFO, mas com uma diferença: quando uma página é
selecionada para substituição, antes de fazer a troca verificamos seu bit de referência. Se o seu valor for zero, fazemos
a substituição; se for 1, damos à página uma segunda chance zerando o seu bit e passamos à próxima página. Esse
método assegura que se uma página for sempre referenciada, ela nunca sairá da memória.
Sistemas Operacionais
Marcio Quirino - 94
Este algoritmo é normalmente implementado como uma fila circular (Figura 32) onde um ponteiro (o
ponteiro do relógio, daí seu nome) indica a página que deve ser substituída a seguir. Quando um quadro é
necessário, o ponteiro avança até encontrar uma página com bit de referência 0. Conforme ele avança, os
bits de referência são zerados (Figura 32a).
Uma vez que uma página vítima seja encontrada, ela é substituída e a nova página é inserida na
fila circular nessa posição (Figura 32b).
Página vítima
Página vítima é a candidata a sair da memória. Ele faz a busca na fila circular: ao encontrar uma página
candidata à remoção (página vítima), ele substitui.
Observe que, na pior das hipóteses, quando todos os bits estão ligados, o ponteiro circula a fila
inteira, dando a cada página uma segunda chance. Ele zera todos os bits de referência antes de selecionar
a próxima página para substituição.
A substituição da segunda chance degenera para uma substituição FIFO se todos os bits estiverem
ligados.
Figura 32: Algoritmo de segunda chance. Fonte: SILBERSCHATZ, 2015.
Um aperfeiçoamento que pode ser realizado neste algoritmo é levar em conta também o valor de bit
de modificação. Dessa forma, a página pode estar em uma destas quatro categorias, da mais baixa para a
mais alta:
A. (0, 0) nem recentemente utilizada, nem modificada
Melhor página para a substituição.
B. (0, 1) não recentemente utilizada, mas modificada
Não é uma opção tão boa porque a página terá que ser gravada em disco antes da substituição.
C. (1, 0) recentemente utilizada, mas não modificada
Provavelmente será usada outra vez em breve.
D. (1, 1) recentemente utilizada e modificada
Provavelmente será usada de novo em breve e terá que ser gravada em disco antes de poder
ser substituída.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 95
Nesta implementação, continuamos utilizando uma fila circular, mas em vez de verificar o bit de
referência, determinamos a categoria da página e substituímos a primeira página da categoria mais baixa.
A principal diferença entre esse algoritmo e o algoritmo do relógio mais simples é que aqui damos
preferência à substituição de páginas que não foram modificadas, para reduzir o número de operações de
gravação requeridas, já que páginas que não foram modificadas ao serem substituídas não necessitam de
gravação no disco.
Segmentação
Devido ao grande número de page faults da paginação, a segmentação surgiu como uma alternativa
de gerência de memória, na qual o programa não mais é dividido em blocos de comprimentos fixos (as
páginas), mas, sim, em segmentos de comprimentos variados com um sentido lógico. Ou seja, a paginação
procedia uma divisão física do programa e, às vezes, isso levava a um número muito grande de page faults
por não observar suas características lógicas.
A segmentação busca aproveitar exatamente essas características, agrupando num segmento partes
do programa que se referenciam mutuamente e que, quando trazidas à memória, estarão todas juntas
evitando o que nesse caso é chamado de segment faults com tanta frequência.
Nesse esquema, o espaço de endereços torna-se bidimensional: endereços de programa são
denotados pelo par (nome do segmento, endereço dentro do segmento).
Para facilitar a implementação do mecanismo de transformação de endereços, o SO troca o nome
do segmento por um número, quando o segmento é referenciado pela primeira vez.
Considerando o par (s,d) como sendo um endereço de programa generalizado, onde s = número do
segmento e d = endereço dentro do segmento, então o esquema de geração seria o apresentado na Figura
33:
Figura 33: Segmentação. Fonte: SILBERSCHATZ, 2015.
Conforme podemos ver, a transformação de endereço é realizada por intermédio de uma tabela de
segmentos (uma tabela para cada processo). A “s-ésima” entrada da tabela contém o tamanho (l) e a posição
inicial (a) da memória, onde o “s-ésimo” segmento foi carregado.
As entradasda tabela são chamadas de Descritores de Segmento. O algoritmo de mapeamento é:
1. Extrair endereço de programa (s,d).
2. Usar “s” para indexar tabela de segmentos.
3. Retirar o endereço inicial do segmento (a).
4. Se d < 0 ou d > l então “violação de memória”.
Sistemas Operacionais
Marcio Quirino - 96
5. a + d é o endereço requerido.
A busca de espaços para um segmento a ser trazido à memória nos leva novamente aos problemas
de gerência por partições variáveis. A diferença aqui é que o espaço contíguo só é necessário para o
segmento e não para todo o processo.
A diferença principal entre a segmentação e a paginação é que a primeira busca uma divisão lógica
do espaço de endereçamento do processo, enquanto a segunda busca uma divisão física desse mesmo
espaço. Ambas se engajam na implementação de uma política de memória virtual de um único nível. Páginas
têm seu tamanho determinado pelo tamanho da palavra, enquanto segmentos têm seu tamanho
determinado pelo tamanho da memória disponível.
4. Como o Linux realiza a sua gerência de memória
Gerenciamento de memória no Linux
A gerência de memória no Linux envolve dois aspectos distintos, cada um efetuado por um
componente específico:
• A alocação e liberação da memória física – páginas, grupos de páginas e pequenos blocos
de RAM.
• A manipulação da memória virtual – mapeamento da memória física para o espaço de
endereçamento de processos em execução.
Gerenciamento da memória física no Linux
Em razão de restrições específicas do hardware, o Linux divide a memória física em quatro zonas ou
regiões diferentes que, dependendo da arquitetura, correspondem a especificações diferentes.
Em uma arquitetura Intel x86 de 32 bits elas são:
A. ZONE_DMA
Para dispositivos ISA que somente podem acessar os primeiros 16MB da memória para fazer
acesso direto.
B. ZONE_DMA32
Para dispositivos que podem acessar os primeiros 4GB da memória em operações de DMA.
C. ZONE_HIGHMEM
Corresponde à memória alta (high memory) que não está mapeada para o kernel do sistema,
que, nesta arquitetura, corresponde aos primeiros 896MB do espaço de endereçamento, ficando
todo o espaço restante até 4GB disponível para uso.
D. ZONE_NORMAL
Corresponde a todo o resto da memória.
Importante: em modernos sistemas de 64 bits, como o Intel x86 de 64 bits, existe uma pequena
ZONE_DMA de 16 MB para ser utilizada por sistemas legados e todo o resto fica na ZONE_NORMAL, sem
ZONE_HIGHMEN ou ZONE_DMA32.
A Figura 34 mostra o mapeamento entre as zonas e os endereços físicos na arquitetura do Intel x86
de 32 bits.
Sistemas Operacionais
Marcio Quirino - 97
Zona Memória física
ZONE_DMA < 16 MB
ZONE_NORMAL 16 .. 896 MB
ZONE_HIGHMEM > 896 MB
Figura 34: Relacionamento entre endereços físicos e zonas no Intel x8632. Fonte: SILBERSCHATZ, 2015.
O kernel mantém uma lista das páginas livres em cada zona e, ao receber uma solicitação de
memória, faz o atendimento disponibilizando as páginas na zona apropriada.
Cada zona possui seu próprio alocador de páginas que é o responsável por alocar e desalocar as
páginas físicas.
Memória virtual no Linux
O mecanismo de memória virtual é o responsável por manter o espaço de endereçamento disponível
para cada processo. Para fazer o gerenciamento são mantidos, para cada processo, duas visões diferentes:
A. Visão lógica
Descreve as instruções que o sistema de memória virtual recebeu relacionadas com o layout do
espaço de endereçamento. Nessa visão, o espaço de endereçamento consiste em um conjunto
de regiões não sobrepostas, cada uma representando um subconjunto alinhado e contínuo de
páginas do espaço de endereçamento. As regiões de cada espaço de endereçamento são
vinculadas em uma árvore binária balanceada para permitir a busca rápida da região
correspondente a qualquer endereço virtual.
B. Visão física
Corresponde a entradas nas tabelas de páginas de hardware do processo, que identificam a
locação corrente exata de cada página de memória virtual, esteja ela em disco ou na memória
física.
Regiões de memória virtual
O Linux trabalha com vários tipos de regiões de memória virtual.
Uma região de memória virtual tem normalmente um arquivo associado a ela e funciona como uma
porta de entrada para uma seção do arquivo de paginação.
Sempre que o processo faz referência a uma página da região, a entrada correspondente na tabela
de páginas é preenchida com o endereço de uma página do cache de páginas do kernel com o valor do
deslocamento apropriado no arquivo.
Outro aspecto importante nas regiões é como elas tratam as gravações. Uma região pode ser
mapeada de forma compartilhada ou privada para o espaço de endereçamento de um processo.
Você sabia
Se um processo gravar em uma região mapeada privadamente, então, o paginador detectará que uma cópia
após a gravação é necessária para manter as alterações locais ao processo. Por outro lado, gravações em uma região
compartilhada resultam na atualização do objeto mapeado para essa região, de modo que a alteração seja
imediatamente visível por qualquer outro processo que esteja mapeando esse objeto.
Tempo de vida de um espaço de endereçamento virtual
Duas situações geram a criação de um novo espaço de endereçamento virtual:
A. Quando um processo executa um novo programa com a chamada de sistema exec( )
Sistemas Operacionais
Marcio Quirino - 98
Neste caso, o processo recebe um novo espaço de endereçamento virtual totalmente vazio. A
rotina de carga do programa faz o mapeamento do espaço de endereçamento com regiões de
memória virtual.
B. Quando um novo processo é criado pela chamada de sistema fork( )
Neste caso, é realizada uma cópia completa do espaço de endereçamento virtual do processo
pai para o conjunto das tabelas de páginas criadas para o processo filho, copiadas diretamente
nas tabelas do filho. Portanto, após a ramificação, o pai e o filho compartilham as mesmas
páginas físicas de memória em seus espaços de endereçamento.
Permuta e paginação
Como se sabe, uma atividade extremamente importante na memória virtual é a execução
do pagein e pageout.
Como acontece nos demais sistemas de paginação, o Linux trabalha em dois passos:
• Decide qual página deve ser substituída na memória e se é necessário gravá-la em disco.
• Carrega a nova página no quadro liberado pela página que foi substituída.
No Linux, a substituição de páginas utiliza uma política similar ao algoritmo do relógio, modificada
para trabalhar com múltiplos ciclos.
Cada página possui uma “idade” que é ajustada a cada ciclo do relógio. A idade indica há quanto
tempo a página foi alocada e/ou o nível de atividade que a página experimentou recentemente. Dessa forma,
páginas acessadas com frequência recebem um valor de idade mais alto e a idade de páginas acessadas
raramente cairá em direção a zero a cada ciclo.
Essa quantificação da idade permite que o paginador selecione as páginas a serem extraídas com
base na política da menos frequentemente utilizada (LFU).
Você sabia
No tocante à Memória virtual do Kernel, o Linux reserva para seu próprio uso interno uma região constante do
espaço de endereçamento virtual de cada processo. As entradas da tabela de páginas que são mapeadas para essas
páginas do kernel são marcadas como protegidas, para que as páginas não sejam visíveis ou modificáveis quando o
processador estiver sendo executado em modalidade de usuário.
Execução e carga de programas de usuário
A execução de programas de usuário pelo kernel do Linux é disparada pela chamada de sistema
exec( ), que instrui o kernel a executar um novo programa dentro do processo em curso, substituindo
completamente o contexto de execução corrente pelo contexto inicial do novo programa.
Para fazer a carga, é invocado o carregador que pode inicialmente fazer ou não a alocação na
memória física do processo, mas sempre realiza o mapeamento do programapara a memória virtual.
Mapeamento de programas para a memória
No Linux, o carregador binário não carrega um arquivo desse mesmo sistema na memória física. Em
vez disso, as páginas do arquivo binário são mapeadas para regiões de memória virtual. É utilizada a técnica
da Paginação sob Demanda, de forma que somente quando a página for necessária é que ele será
efetivamente alocado na memória física.
É responsabilidade do carregador binário do kernel estabelecer o mapeamento inicial da memória.
Um arquivo binário no formato ELF consiste em um cabeçalho seguido por várias seções de páginas
alinhadas. O carregador ELF funciona lendo o cabeçalho e mapeando as seções do arquivo para regiões
separadas da memória virtual.
Sistemas Operacionais
Marcio Quirino - 99
A Figura 35 mostra o layout típico de regiões de memória estabelecidas pelo carregador ELF. Em
uma região reservada em uma extremidade do espaço de endereçamento reside o kernel, em sua própria
região privilegiada de memória virtual inacessível a programas normais de modalidade de usuário.
O resto da memória virtual fica disponível para aplicações que podem usar as funções de
mapeamento da memória do kernel para criar regiões que mapeiem uma parte de um arquivo ou que estejam
disponíveis para dados de aplicações.
Figura 35: Layout de memória de um arquivo ELF. Fonte: SILBERSCHATZ, 2015.
A função do carregador é estabelecer o mapeamento inicial da memória para permitir que a execução
do programa comece. As regiões que precisam ser inicializadas incluem a pilha e as regiões de texto e
dados do programa.
A pilha é criada no topo da memória virtual de modalidade de usuário; ela cresce para baixo em
direção a endereços de numeração menor e inclui cópias dos argumentos e variáveis de ambiente fornecidas
ao programa na chamada de sistema exec( ).
As outras regiões são criadas perto da extremidade inferior da memória virtual e incluem:
As seções do arquivo binário que contêm texto de programas ou dados apenas de leitura, que são
definidas como apenas leitura e protegidas contra gravação.
Dados inicializados ou não que podem sofrer gravação são alocados a seguir.
O heap, que é uma área de tamanho variável e que armazena os dados em tempo de execução.
Tem seu limite apontado por brk, o que permite sua expansão ou retração.
Uma vez que esses mapeamentos tenham sido estabelecidos, o carregador inicializa o registrador
de contagem de programas do processo com o ponto inicial registrado no cabeçalho ELF, e o processo pode
ser incluído no escalonador.
Vinculação estática e dinâmica
Uma vez que o programa foi carregado e escalonado para o processador, todos os conteúdos
necessários para sua execução já estarão no seu espaço de endereçamento virtual. Entretanto, a maior
parte dos programas utiliza códigos de bibliotecas do sistema que também devem ser carregados.
Exemplo
Uma maneira simples de lidar com isso é embutir diretamente no executável do programa o código da
biblioteca, o que é realizado pelo ligador durante a geração do executável. Dessa forma, o programa é vinculado
Sistemas Operacionais
Marcio Quirino - 100
estaticamente às suas bibliotecas e executáveis. Com este tipo de vinculação podem iniciar a execução imediatamente
assim que carregados.
Essa abordagem, entretanto, tem como principal desvantagem o fato de que cada programa gerado
deve conter cópias exatas das mesmas funções comuns das bibliotecas do sistema. É muito mais eficiente,
tanto em termos de memória física como também de uso de espaço em disco, carregar as bibliotecas do
sistema na memória apenas uma vez. Isso pode ser feito utilizando os recursos de vinculação dinâmica
existentes no Linux.
Cada programa vinculado dinamicamente contém uma pequena função vinculada estaticamente que
é chamada quando o programa é iniciado. Essa função estática apenas mapeia a biblioteca de vinculação
para a memória e executa o código contido na função.
A biblioteca de vinculação determina as bibliotecas dinâmicas requeridas pelo programa e os nomes
das variáveis e funções necessárias destas por meio da leitura das informações contidas em seções do
binário ELF. Ela mapeia, então, as bibliotecas para o meio da memória virtual e resolve as referências para
os símbolos contidos nas mesmas. Não importa para que local exato da memória essas bibliotecas
compartilhadas sejam mapeadas, elas são compiladas em código independente de posição, que pode ser
executado em qualquer endereço da memória.
Utilitários e comandos para gerenciar a memória do sistema Linux
Para gerenciar a memória de um sistema Linux podem ser utilizados diversos comandos, inclusive
utilitários para esse propósito, conforme será apresentado na sequência. Ao final deste módulo é
disponibilizado o vídeo em que os pontos abordados são executados na prática, em uma máquina virtual.
Obtendo informações pela linha de comando
Para estes exemplos usaremos uma máquina virtual com Ubuntu instalado.
Captura de tela da execução do comando htop.
O Linux provê uma série de comandos de linha que obtêm informações do sistema de memória.
Vejamos os principais:
A. FREE
O comando free exibe informações precisas sobre o uso dos recursos de memória do sistema.
Neste exemplo, pode-se observar uma máquina virtual com aproximadamente 4GB (3996 MB)
de RAM e tendo em uso 1235MB, sobrando 2338MB disponíveis.
O comando mostra ainda que temos uma área de 448MB para swap da qual foram utilizados
8MB.
O argumento -m faz com que as informações venham em MB.
Sistemas Operacionais
Marcio Quirino - 101
B. TOP
Este comando mostra informações a respeito dos processos rodando em sua máquina, incluindo
o uso da memória.
C. VMSTAT
Este comando mostra a situação da memória virtual da máquina.
D. GETCONF PAGESIZE
O comando getconf PAGESIZE mostra o tamanho da página no sistema de memória virtual que,
neste caso, é de 4096 bytes, ou seja, 4KB.
E. SWAPON
O comando swapon mostra informações a respeito do arquivo de swap.
Utilitários para acesso à informação
Vários utilitários que podem ser chamados na linha de comando ou por meio da interface gráfica
podem ser utilizados para monitorar o uso da memória do Linux. Com exceção do monitor do sistema, todos
os outros precisam ser instalados no Ubuntu, pois não existem como padrão.
Htop
Esse aplicativo htop é uma evolução do comando top e exibe de forma interativa as informações do
sistema.
Ele é chamado na linha de comando.
Captura de tela do Software htop.
Sistemas Operacionais
Marcio Quirino - 102
Após a chamada, ele carrega sua interface, na qual, em sua parte superior, temos informações
agregadas e, na parte inferior, a lista de processos com suas informações de CPU, memória etc.
Captura de tela do Software htop.
Neste aplicativo, as cores que aparecem no resumo na parte superior possuem significado.
Captura de tela do Software htop.
Sendo assim, observa-se o seguinte:
A. CPU
• Verde: Threads rodando com prioridade normal.
• Azul: Threads rodando com baixa prioridade.
• Vermelho: Threads rodando em favor do kernel.
B. Memória
• Verde: Memória em uso pelas aplicações.
• Azul: Buffers em utilização.
• Amarelo / Laranja: Cache.
C. Swap
• Vermelha: Representa a quantidade de memória swap utilizada
Essas informações podem ser obtidas ainda apertando F1
Sistemas Operacionais
Marcio Quirino - 103
Captura de tela do Software htop.
Ksysguard
O utilitário ksysguard é outro que pode ser chamado na linha de comando e que não vem instalado
por padrão.
Captura de tela da execução do utilitário ksysguard.
Ele possui uma interface gráfica que mostra as informações do sistema.
Na aba Tabela de Processos, ele mostra todos os existentes no sistema e as informações de CPU e
memória.
Captura de tela do Software htop.
Na aba Carga do Sistema, ele mostrade forma gráfica a utilização pelos processos.
Sistemas Operacionais
Marcio Quirino - 104
Captura de tela do Software htop.
Monitor do Sistema
Este aplicativo vem instalado por padrão e pode ser acessado na aba Ferramentas.
Após a abertura, ele apresenta uma interface gráfica com 3 abas na parte superior. São elas:
Sistemas Operacionais
Marcio Quirino - 105
Local onde são exibidas as informações de cada um dos processos.
Janela que mostra, em forma de gráfic, o uso de cada recurso do sistema.
Considerações finais
Ao longo deste tema, fizemos uma viagem pelos conceitos relacionados ao gerenciamento de
memória pelos sistemas operacionais.
No primeiro módulo, vimos como os processos enxergam a memória e os princípios básicos da
gerência pelo SO, incluindo proteção e relocação. No segundo módulo, vimos as principais políticas de
alocação de memória, desde a mais simples em sistemas monotarefa até as mais complexas, com troca de
processo entre o disco e a memória.
Em seguida, estudamos a memória virtual, como ela é implementada utilizando a paginação sob
demanda e a diferença entre segmentação e paginação. Finalmente, no último módulo, estudamos um SO
real, o Linux, que realiza o gerenciamento de sua memória.
Referências
OFFICIAL UBUNTU DOCUMENTATION. Consultado em meio eletrônico em: 10 dez. 2020.
SILBERSCHATZ, A. Fundamentos de sistemas operacionais. 9. ed. Rio de Janeiro: LTC, 2015.
SILVA, G. M. Guia Foca GNU/Linux. Consultado em meio eletrônico em: 10 dez. 2020.
TANENBAUM, A. S. Organização estruturada de computadores. 5. ed. São Paulo: Pearson, 2006.
TANENBAUM, A. S.; BOS, H. Sistemas operacionais modernos. 4. ed. São Paulo: Pearson
Educacional do Brasil, 2016.
TANENBAUM, A. S.; WOODHULL, A. S. Sistemas operacionais: projeto e Implementação. 3. ed.
Porto Alegre: Bookman, 2008.
EXPLORE+
Para saber mais sobre os assuntos tratados neste tema, acesse o site:
Training, do professor Luiz Paulo Maia.
Sistemas Operacionais
Marcio Quirino - 106
Sistema de Arquivos
Descrição
Tecnologias para particionamento e formatação de sistemas de arquivos e sua implementação e
gerenciamento no sistema operacional Linux.
Propósito
Compreender a importância da implementação e o gerenciamento de sistemas de arquivos pelos
principais sistemas operacionais do mercado, objetivando determinar o melhor sistema para cada situação,
assim como realizar sua gerência de forma eficaz.
Preparação
Antes de iniciar o estudo do tema, é desejável ter acesso a um computador (ou máquina virtual) com
Linux instalado. Para os exemplos, foi utilizado o Ubuntu Desktop 20.04 LTS.
Introdução
Os sistemas operacionais são softwares essenciais para quaisquer dispositivos computacionais. Eles
permitem que haja maior facilidade de uso por parte dos usuários, disponibilizando interfaces amigáveis
assim como recursos para que as tarefas dos desenvolvedores sejam menos árduas.
Uma das tarefas que são desempenhadas pelos sistemas operacionais é fornecer um sistema que
garanta a persistência das informações tratadas, armazenando-as nos dispositivos intermediários, como os
discos rígidos magnéticos. Essas tarefas são implementadas pelos sistemas de arquivos.
Para compreender tal funcionalidade, estudaremos os conceitos de sistemas de arquivos e as
diferentes formas de implementação, com ênfase nas vantagens e desvantagens de cada uma. Para isso,
veremos as técnicas de formatação que foram desenvolvidas e conheceremos como se dá a implementação
do sistema de arquivos do Linux.
De forma a solidificar esse conhecimento, aprenderemos sobre discos e partições, e como estas
últimas são criadas em um sistema Linux. Veremos ainda como fazer a formação e a montagem dos
sistemas de arquivos.
Uma vez criado tal sistema, vamos estudar as principais ferramentas que um administrador deve
conhecer para manter o funcionamento. Por fim, conheceremos os principais editores de arquivos que
podem ser utilizados para a configuração e a manutenção de um sistema Linux.
1. Como são implementados os sistemas de arquivos
Conceitos
Quando estamos utilizando um sistema computacional, é comum que, ao encerrar nossas atividades,
guardemos o trabalho realizado para continuarmos em um momento posterior. Portanto, o armazenamento
e a recuperação de informações são atividades essenciais para aplicações.
As principais exigências para armazenamento de informações são:
• Deve ser possível armazenar uma grande quantidade de informações.
• A informação deve sobreviver à finalização do processo que a utiliza (deve ser persistente).
Sistemas Operacionais
Marcio Quirino - 107
• Múltiplos processos devem ser capazes de acessar as informações concorrentemente.
É mediante a implementação de arquivos em discos ou outras mídias que o sistema operacional
estrutura e organiza essas informações.
Você sabia
A parte responsável por essa gerência é denominada sistema de arquivos.
A manipulação de arquivos deve ocorrer de maneira uniforme, independente dos diferentes
dispositivos de armazenamento.
De uma forma simplista, bastaria pensar em um dispositivo de armazenamento como uma sequência
linear de blocos de tamanho fixo que dão suporte a duas operações:
• Ler o bloco k;
• Escrever no bloco k.
No entanto, essas são operações inconvenientes quando utilizadas em sistemas com muitas
aplicações e possivelmente múltiplos usuários.
Algumas questões que surgem são:
• Como saber em qual bloco se encontram as informações que necessitamos?
• Como impedir que um usuário leia os dados de outro usuário?
• Como saber quais blocos estão livres?
O sistema de arquivos é constituído de duas partes distintas:
A. Conjunto de arquivos
Armazena dados.
B. Estrutura de diretórios
Organiza e fornece informações sobre os arquivos do sistema.
Arquivos são unidades lógicas de informação criadas por processos. Um disco normalmente conterá
milhares ou mesmo milhões deles, cada um independente dos outros.
Processos
É um programa em execução em um sistema operacional, incluindo seu contexto de hardware (estado do
hardware em determinado momento), valores de variáveis e seu espaço de endereçamento (região de memória que o
processo pode utilizar).
Arquivos
Computadores podem armazenar dados em diferentes dispositivos de armazenamento, porém o
usuário precisa acessar os dados contidos nestes dispositivos da mesma forma, independente do seu tipo.
Resumindo
Para que um sistema possa ser usado de forma conveniente, o sistema operacional deve oferecer uma visão
lógica e uniforme do dispositivo de armazenamento.
Um arquivo é constituído de informações, podendo representar programas ou dados. Um programa
contém instruções compreendidas pela UCP (arquivo executável), enquanto um arquivo de dados pode ser
estruturado livremente (podem ser numéricos, alfabéticos, alfanuméricos ou binários).
Sistemas Operacionais
Marcio Quirino - 108
Um arquivo é identificado por intermédio de um nome, e as regras para os nomes variam de sistema
para sistema.
Saiba mais
Em alguns sistemas operacionais, o nome do arquivo é composto por duas partes separadas com um ponto.
A parte após o ponto é denominada extensão do arquivo e tem como finalidade identificar seu conteúdo.
As principais diferenças entre as regras para os nomes de arquivo são:
• Quantidade máxima de caracteres;
• Diferenciação entre caracteres maiúsculos e minúsculos;
• Uso de caracteres especiais;
• Nomes com extensão tendo significado ou não.
Em alguns sistemas, as extensões de arquivos são apenas convenções e não são impostas pelo
sistema operacional.
Exemplo
Um arquivo chamado “file.txt” pode ser algum tipo de arquivo de texto, mas aquele nome tem mais função de
lembrar o proprietário do que transmitir qualquer informação real para o computador. Por outro lado, um compilador de
Linguagem C pode exigir queos arquivos que ele tem de compilar terminem em “.c”. O sistema operacional, no entanto,
não se importa.
Ao criar um arquivo, o processo deve atribuir um nome a ele para que, quando termine sua execução,
o arquivo continue existindo e possa ser acessado por outros processos pelo mesmo nome. Ao receber um
nome, o arquivo se torna independente do processo, do usuário e até mesmo do sistema que o criou.
Estrutura de Arquivos
No momento da criação de um arquivo, é possível definir qual organização será adotada. Esta
organização pode ser uma estrutura suportada pelo sistema operacional ou definida pela própria aplicação.
Clique nas barras para ver as informações.
A. Sequência desestruturada de bytes
É a forma mais simples de organização de arquivos. Nesse tipo de organização, o sistema de
arquivos não impõe nenhuma estrutura lógica para os dados (a aplicação deve definir toda a
organização). A grande vantagem é a flexibilidade para criar diferentes estruturas de dados, mas
todo o controle de acesso ao arquivo é de inteira responsabilidade da aplicação. Qualquer
significado deve ser imposto por programas em nível de usuário. Tanto o Linux quanto o MS
Windows usam essa abordagem.
B. Sequência de registros de tamanho fixo
É uma forma estruturada para o armazenamento de arquivos. Nela o arquivo é composto por
uma série de registros com uma estrutura interna característica, e as operações de
leitura/gravação trabalham com registros inteiros. No passado, quando o cartão de 80 colunas
perfurado era utilizado, muitos sistemas operacionais de computadores de grande porte
baseavam seus sistemas de arquivos em arquivos consistindo em registros de 80 caracteres.
Esses sistemas também aceitavam arquivos com registros de 132 caracteres, destinados às
impressoras de linha. Nenhum sistema de propósito geral atual utiliza esse modelo como seu
sistema primário de arquivos.
C. Árvore de registros
Sistemas Operacionais
Marcio Quirino - 109
Uma organização que é composta por registros que não possuem necessariamente o mesmo
tamanho, e cada um contém um campo com uma chave em uma posição fixa. A árvore é
ordenada pelo campo chave de forma a permitir uma busca rápida. Esse tipo de arquivo é
bastante diferente das sequências de bytes desestruturadas, sendo utilizado em alguns
computadores de grande porte para o processamento de dados comerciais.
Métodos de Acesso
Os primeiros sistemas operacionais acessavam os registros de um arquivo na ordem em que eram
gravados, possibilitando a gravação de novos registros apenas no final. A esse tipo de acesso dá-se o nome
de acesso sequencial.
Atenção
É possível retroceder registros, mas nunca pular ou lê-los fora de ordem.
Posteriormente, surgiu um método de acesso mais eficientes, o acesso aleatório (também conhecido
como acesso direto), que permite a leitura/gravação de um registro diretamente na sua posição relativa ao
início do arquivo. O acesso aleatório somente é possível quando o arquivo é definido com registros de
tamanho fixo. Neste acesso, não existe restrição à ordem em que os registros são lidos ou gravados.
• Em sistemas operacionais antigos, o tipo de acesso ao arquivo é determinado quando o
arquivo é criado.
• Nos sistemas operacionais modernos, todos os arquivos são de acesso aleatório,
excetuando-se os casos em que o dispositivo no qual o arquivo está armazenado não permita
esse tipo de acesso.
Existem ainda outros métodos de acesso menos comuns, como o acesso indexado, que envolve a
construção de um índice para o arquivo. Esse índice contém ponteiros para os blocos e, para acessar uma
posição de arquivo, primeiro pesquisa-se o índice e depois utiliza-se o ponteiro para a posição desejada.
Tipos de Arquivos
Os sistemas operacionais costumam suportar vários tipos de arquivos, sendo os mais comuns:
A. Arquivos regulares
Arquivos que contém informações genéricas como, por exemplo, dados dos usuários.
B. Diretórios
Arquivos de sistema usados para manter a estrutura do sistema de arquivos.
C. Arquivos especiais de caractere
Sistemas Operacionais
Marcio Quirino - 110
Relacionam-se com operações de E/S e costumam modelar dispositivos seriais.
D. Arquivos especiais de bloco
Usados para modelar dispositivos de bloco, em especial discos.
Em geral, arquivos regulares são classificados como arquivo texto ou arquivo binário.
A. Arquivo texto
Um arquivo texto (ou arquivo ASCII) é constituído por linhas de texto que podem ter tamanhos
diferentes e terminam por caracteres especiais para indicar o fim da linha. São arquivos que,
quando exibidos na tela ou impressos, podem ser compreendidos pelas pessoas e, ainda,
editados com um editor de textos comum.
B. Arquivo binário
Arquivos binários não são arquivos texto. Sua listagem gera um conjunto de caracteres
incompreensíveis. Eles podem ser arquivos de usuários (com ou sem estrutura interna) ou
arquivos executáveis (com estrutura conhecida pelo sistema operacional e códigos que são
executados pela UCP).
Diretórios
A estrutura de diretórios é o modo como o sistema organiza logicamente os diversos arquivos
contidos em um disco. O diretório (ou pasta) é um arquivo que contém uma estrutura de dados com entradas
associadas aos arquivos onde são armazenadas informações como localização física, nome, organização e
demais atributos.
Quando é solicitada a abertura de um arquivo, o sistema operacional pesquisa o diretório até
encontrar uma entrada com o nome do arquivo. Quando a entrada é localizada, o sistema operacional copia
os atributos e os endereços de disco e os coloca em uma estrutura na memória, a tabela de arquivos, que
contém todos os arquivos abertos. Quando o arquivo é fechado, sua entrada na tabela de arquivos é
liberada.
A implementação mais simples de uma estrutura de diretórios é chamada de nível único (um nível),
no qual existe um único diretório contendo todos os arquivos do disco. Este modelo é bastante limitado, já
que não permite que usuários criem arquivos com o mesmo nome, o que ocasionaria um conflito no acesso
a eles.
Comentário
Este tipo de implementação não é mais utilizado atualmente.
Em outra estrutura, conhecida como diretório de dois níveis, existe um diretório para os arquivos do
sistema e um diretório para cada usuário. Com esta implementação, cada usuário pode criar arquivos sem
a preocupação de conhecer os demais arquivos do disco. Para que o sistema possa localizar arquivos nesta
estrutura, existe um nível de diretório adicional denominado master file directory, indexado pelo nome do
usuário. Nele, cada entrada aponta para o diretório de cada usuário.
Atenção
Para referenciar um arquivo neste tipo de estrutura, é necessário especificar o diretório onde ele se encontra
e o seu nome, especificando assim seu caminho (path).
A organização dos arquivos em um único diretório não permite uma organização adequada. A
extensão para um modelo de múltiplos níveis permite que os arquivos sejam mais bem organizados. Este
modelo, chamado estrutura de diretórios em árvore ou sistema de diretórios hierárquico, é atualmente
adotado pela maioria dos sistemas operacionais.
Sistemas Operacionais
Marcio Quirino - 111
Na estrutura em árvore, é possível criar quantos diretórios se deseje, podendo um diretório conter
arquivos ou outros diretórios (subdiretórios). Cada arquivo, nesta estrutura, possui um caminho (path) único
que descreve todos os diretórios desde a raiz até o diretório no qual o arquivo está, mais o nome do arquivo.
Quando o sistema de arquivos é organizado como uma árvore de diretórios, os nomes de caminhos
podem ser absolutos ou relativos.
A. Caminho absoluto
Consiste no caminho desde o diretório raiz (diretório inicial do sistema de arquivos) até o arquivo.
B. Caminho relativo
É utilizado em conjunto com o conceito de diretório de trabalho (diretório atual), que é o diretório
usadoatualmente pelo processo e serve como base caso o nome do caminho não inicie com o
diretório raiz. Quando é utilizado um caminho relativo, o caminho até o arquivo é buscado a partir
do diretório de trabalho.
Cada processo possui seu próprio diretório de trabalho. Assim, se algum processo alterar seu
diretório de trabalho, os outros processos do usuário não serão afetados.
A maioria dos sistemas que suporta estrutura de diretórios em árvore tem duas entradas especiais
para cada diretório:
• “.” → Diretório atual;
• “..” → Diretório pai. Pode ser usada para subir na árvore de diretórios.
Implementação do sistema de arquivos
Os discos são o meio de armazenamento secundário mais comum no qual os arquivos são
armazenados. As transferências de dados entre a memória e o disco são realizadas em unidades chamadas
blocos, cada qual constituída por um ou mais setores.
Exemplo
Dependendo do disco, um setor pode variar de 32 a 4096 bytes, sendo mais comum setores de 512 bytes.
A maioria dos discos pode ser dividida em uma ou mais partições, com sistemas de arquivos
independentes em cada partição. O Setor 0 do disco é chamado de MBR (Master Boot Record — registro
mestre de inicialização) e é usado para inicializar o computador. No final do MBR está localizada a tabela
de partição, que armazena os endereços de início e fim de cada partição. Uma das partições da tabela é
marcada como ativa.
Quando o computador é inicializado, a BIOS lê e executa o MBR. A primeira coisa que o programa
MBR faz é localizar a partição ativa, ler seu primeiro bloco (bloco de inicialização) e executá-lo. O programa
no bloco de inicialização carrega o sistema operacional contido naquela partição. Cada partição começa
com um bloco de inicialização, mesmo que ela não contenha um sistema operacional que possa ser
inicializado.
BIOS
BIOS (Basic Input/Output System - Sistema Básico de Entrada/Saída) é um pequeno programa gravado em
uma memória não volátil usado para realizar a inicialização do hardware durante o processo de inicialização de um
computador e para fornecer serviços de tempo de execução para sistemas operacionais e programas.
Atenção
O esquema de uma partição de disco varia bastante entre sistemas de arquivos.
Sistemas Operacionais
Marcio Quirino - 112
O primeiro item em uma partição costuma ser o superbloco. Ele contém todos os parâmetros chave
a respeito do sistema de arquivos e é lido para a memória quando o computador é inicializado ou o sistema
de arquivos é tocado pela primeira vez. Informações típicas no superbloco incluem um identificador para o
tipo de sistema de arquivos, sua quantidade de blocos e outras informações administrativas.
Podem vir informações a respeito de blocos disponíveis no sistema de arquivos, na forma de um
mapa de bits ou de uma lista encadeada.
Pode ser seguido pelos i-nodes (um arranjo de estruturas de dados, um por arquivo, dizendo tudo
sobre ele). Depois pode vir o diretório-raiz, que contém o topo da árvore do sistema de arquivos. Por fim, o
restante do disco contém todos os outros diretórios e arquivos.
A criação de arquivos em disco exige que o sistema operacional tenha o controle de quais blocos no
disco estão livres. Este controle é realizado através de uma estrutura de dados que armazena informações
que possibilitam ao sistema de arquivos gerenciar o disco.
Dica
A forma mais simples de implementar uma estrutura de espaços livres é através de uma tabela denominada
mapa de bits. A cada entrada da tabela é associada um bloco do disco representado por um bit, que pode assumir
valor igual a 0 (bloco livre) ou 1 (bloco alocado).
Uma segunda maneira de realizar este controle é por meio de uma lista encadeada de todos os
blocos livres do disco. Cada bloco possui uma área reservada para armazenamento do endereço do próximo
bloco, e a partir do primeiro bloco livre pode-se ter acesso aos demais de forma encadeada. Suas principais
restrições são o espaço utilizado no bloco com informação de controle e o fato do algoritmo de busca de
espaço livre sempre ter que realizar uma pesquisa sequencial na lista.
Outra solução leva em conta que blocos contíguos são geralmente alocados ou liberados
simultaneamente, enxergando o disco como um conjunto de segmentos de blocos livres. Assim, é possível
manter uma tabela de blocos livres com o endereço do primeiro bloco de cada segmento e o número de
blocos livres contíguos que se seguem.
Alocação Contígua
A alocação contígua consiste em armazenar um arquivo em blocos contíguos de dados no disco.
Neste tipo de alocação, o sistema localiza um arquivo através do endereço do primeiro bloco e da sua
quantidade de blocos.
A alocação de espaço de disco contíguo tem duas vantagens.
A. Primeira vantagem
Ela é simples de implementar. Dado o número do primeiro bloco, o número de qualquer outro
bloco pode ser encontrado mediante uma simples adição.
Sistemas Operacionais
Marcio Quirino - 113
B. Segunda vantagem
O desempenho da leitura é excelente, pois o arquivo inteiro pode ser lido do disco em uma única
operação.
O acesso a arquivos dispostos contiguamente no disco é bastante simples tanto para o acesso
sequencial quanto para o acesso aleatório. Seu principal problema é a alocação de espaço livre para novos
arquivos, já que para um arquivo ser criado com n blocos é necessário que exista uma cadeia de n blocos
dispostos sequencialmente no disco.
O disco pode ser visto como um grande vetor no qual os elementos podem ser considerados
segmentos com tamanhos diferentes de blocos contíguos, dispostos alternadamente entre segmentos
ocupados e segmentos livres. No momento em que o sistema operacional deseja alocar espaço para
armazenar um novo arquivo, pode existir mais de um segmento livre disponível com o tamanho exigido.
Atenção
Neste caso, é necessário que alguma estratégia de alocação seja adotada para selecionar qual o segmento na
lista de blocos livres deve ser escolhido.
Seja qual for a estratégia adotada, se o bloco livre selecionado para o armazenamento do arquivo
for maior que o tamanho do arquivo, sobrará um espaço livre ao final do bloco. Com a operação em longo
prazo, surgirão vários blocos livres espalhados pelo disco, ocorrendo a chamada fragmentação do disco.
Quando o disco estiver muito fragmentado para que se possa alocar espaço para a criação do
arquivo, será necessário realizar a desfragmentação do disco, que consiste em mover os arquivos para abrir
espaço suficiente para o novo arquivo.
Dica
A desfragmentação é um processo demorado que deve ser realizado periodicamente.
Atualmente, a alocação contígua é usada em CD-ROMs. Todos os tamanhos de arquivos são
conhecidos antecipadamente e jamais mudarão durante o uso subsequente do sistema de arquivos do CD-
ROM.
Alocação por Lista Encadeada
Na alocação por lista encadeada, um arquivo é organizado como um conjunto de blocos ligados
logicamente, independente da sua localização física. Cada bloco deve possuir um ponteiro para o bloco
seguinte e assim por diante.
Atenção
A entrada de diretório precisa armazenar somente o endereço do primeiro bloco.
A fragmentação do disco não ocorre na alocação encadeada já que os blocos alocados para
determinado arquivo não precisam estar em posições contíguas. O que ocorre, neste método, é
a fragmentação de arquivos, que é a quebra do arquivo em diversos blocos espalhados pelo disco. Tal
fragmentação resulta no aumento do tempo de acesso, pois o processo de leitura/gravação provoca muitos
deslocamentos da cabeça de leitura/gravação do disco.
Dica
Para otimizar o tempo das operações de E/S é importante que o sistema de arquivos seja desfragmentado
periodicamente.
Sistemas Operacionais
Marcio Quirino - 114
A alocação por lista encadeada permite apenas o acesso sequencial aos blocos de um arquivo. Isso
constitui uma das principais desvantagens dessatécnica. Além disso, é desperdiçado espaço nos blocos
com o armazenamento de ponteiros.
Outro problema da alocação por lista encadeada é a confiabilidade. Se o valor de um ponteiro é
corrompido, se perde o encadeamento do arquivo.
Alocação por Lista Encadeada Utilizando Índice
É um esquema de alocação muito parecido com a alocação por lista encadeada, mas no lugar de
fazer o encadeamento utilizando um ponteiro no bloco, o encadeamento é mantido em uma tabela.
Embora a cadeia ainda precise ser seguida para o acesso aleatório, ela é seguida por intermédio da
tabela e não consultando bloco a bloco. Se a tabela for mantida em memória, o acesso torna-se bem mais
rápido.
Essa tabela na memória principal é chamada de FAT (File Allocation Table — tabela de alocação de
arquivos). Usando essa organização, todo o bloco do disco fica disponível para os dados do arquivo.
Da mesma maneira que no método anterior, é suficiente para a entrada de diretório manter um único
inteiro (o número do bloco inicial) e ainda assim ser capaz de localizar todos os blocos, não importando o
tamanho do arquivo.
A principal desvantagem é que a tabela pode ser muito grande para discos grandes. Esse problema
pode ser minimizado agrupando os blocos em clusters para formar unidades maiores, mas isto provoca um
maior desperdício por causa da parte não utilizada do último cluster, que tenderá a ser maior.
Clusters
Agrupamentos de blocos de um disco.
I-nodes (Alocação Indexada)
O método consiste em associar a cada arquivo uma tabela denominada i-node (nó-índice ou nó-i),
que lista os atributos e os endereços em disco dos blocos do arquivo.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 115
A grande vantagem desse esquema sobre a alocação por lista encadeada utilizando índice é que o
i-node precisa estar na memória apenas quando o arquivo correspondente estiver aberto.
Se cada i-node ocupar n bytes e k arquivos estiverem abertos simultaneamente, a memória total
ocupada pelo arranjo contendo os i-nodes para os arquivos abertos é de apenas k × n bytes.
Esse arranjo é, em geral, muito menor do que o espaço ocupado pela tabela de arquivos na alocação
por lista encadeada utilizando índice.
Os primeiros endereços de disco são armazenados no próprio i-node. Isto significa que para arquivos
pequenos toda a informação está contida no próprio i-node e pode ser transferida para a memória quando
o arquivo é aberto.
A. Bloco indireto simples
Contém endereços de blocos adicionais para o arquivo. É usado para arquivos maiores.
B. Bloco indireto duplo
Contém o endereço do bloco que possui uma lista de blocos indiretos simples relativos ao arquivo.
C. Bloco indireto triplo
Contém o endereço do bloco que possui uma lista de blocos indiretos duplos.
IMPLEMENTAÇÃO DE CACHE
O acesso a disco é lento quando comparado com o acesso à memória principal. Este é o fator básico
para as operações de E/S com discos serem um problema para o desempenho do sistema. Com o objetivo
de minimizar tal problema, a maioria dos sistemas de arquivos implementa uma técnica denominada cache.
Sistemas Operacionais
Marcio Quirino - 116
Neste esquema, o sistema operacional reserva uma área na memória principal para que se tornem
disponíveis caches utilizados em operações de acesso ao disco.
Quando uma operação de E/S é realizada, o sistema verifica se a informação desejada se encontra
na cache. Caso esteja disponível, não é necessário acesso ao disco. Caso o bloco requisitado não se
encontre na cache, a operação de E/S é realizada e a cache é atualizada. Como existe uma limitação no
tamanho da cache, o sistema deve adotar uma política para substituição de blocos.
Apesar de melhorar o desempenho do sistema, aspectos de segurança devem ser levados em
consideração. No caso de blocos de dados permanecerem por muito tempo na cache, a ocorrência de
problemas de energia pode ocasionar a perda de tarefas já realizadas e consideradas salvas em disco.
Existem duas maneiras de tratar este problema.
• No primeiro caso, o sistema operacional possui uma rotina que executa periodicamente
atualizando em disco todos os blocos modificados da cache.
• Uma segunda alternativa, conhecida como write-through, é realizar imediatamente uma
atualização no disco sempre que um bloco da cache for modificado.
Quando a memória cache não atualiza o disco imediatamente, temos a chamada cache write-back.
A técnica write-back implica em menor quantidade de operações de E/S, porém o risco de perda de dados
é maior. Isso não acontece nas caches write-through em função do seu próprio funcionamento, mas o
aumento considerável nas operações de E/S torna este método menos eficiente.
2. Conceitos de sistemas de arquivos
O sistema de arquivos do Linux
O Linux trata o conceito de arquivo de uma forma bastante ampla. Um arquivo não precisa ser um
objeto em um disco, mas pode ser qualquer objeto capaz de manipular dados. O Linux manipula todos esses
objetos ocultando detalhes específicos de cada implementação por intermédio de uma camada de software
denominada sistema de arquivos virtual (Virtual File System – VFS).
O VFS define quatro tipos de objetos principais:
A. I-Node
Representa um arquivo individual.
B. Arquivo
Representa um arquivo aberto.
C. Superbloco
Representa um sistema de arquivos inteiro.
D. Dentry
Representa uma entrada de diretório individual.
Para cada um desses objetos, o VFS define um conjunto de chamadas de sistema, e cada objeto
possui um ponteiro para uma tabela de funções que lista as funções reais que implementam as chamadas
de sistema dos objetos. O VFS pode executar uma operação sobre um dos objetos do sistema de arquivos,
chamando a função apropriada na tabela de funções sem precisar saber de antemão com que tipo de objeto
está lidando.
Sistemas Operacionais
Marcio Quirino - 117
Os objetos arquivo pertencem a um único processo, mas os objetos i-node não. Há um
objeto arquivo para cada instância de um arquivo aberto, mas um único objeto i-node.
Atenção
Mesmo quando um arquivo não está sendo mais utilizado por algum processo, seu objeto i-node ainda pode
ser armazenado em cache pelo VFS.
Arquivos de diretório são manipulados de forma diferente de outros arquivos. O Linux define as
chamadas de sistema para diretórios (criação, exclusão, renomeação de arquivo etc.) no objeto i-node, no
lugar do objeto arquivo.
O Linux mantém um único objeto superbloco para cada dispositivo de disco montado e para cada
sistema de arquivos de rede conectado. A principal função do objeto superbloco é dar acesso a i-nodes.
Um objeto dentry representa uma entrada de diretório que pode ser o nome de um diretório no nome
de caminho de um arquivo.
Exemplo
arquivo “/home/maria/teste.txt” possui as entradas de diretório “/”, “home”, “maria” e “teste.txt”, sendo cada uma
destas entradas representada por um objeto dentry diferente.
MINIX
Sistema Operacional Unix-like escrito por Andrew S. Tanenbaum e utilizado por Linus Torvalds como base para
o desenvolvimento do Linux.
A primeira versão do Linux implementava o sistema de arquivos do MINIX, que limitava os nomes de
arquivos a 14 caracteres e tinha um limite de 64 MB para o tamanho máximo que eles poderiam ter.
Desde o início, havia o interesse em desenvolver um sistema de arquivos mais satisfatório, que foi
implementado com o sistema de arquivos ext, o qual permitia arquivos com até 2 GB de dados e com nomes
de até 255 caracteres. Seu problema consistia em ser mais lento que o sistema de arquivos do MINIX.
Você sabia
A versão 2 do ext, conhecida como ext2, resolveu os problemas de desempenho do ext, permitindo nomes
longos e tornando o principal sistema de arquivos do Linux.
Devido à implementação do VFS, o Linux suporta dezenas de sistemas de arquivos diferentes.
Quando o sistema é iniciado, o conjunto de funções para oacesso ao sistema de arquivos principal deve
estar compilado no núcleo do Linux para que possa ser montado e utilizado. Durante a execução, módulos
para acesso a outros sistemas de arquivos podem ser carregados dinamicamente, permitindo, assim, a
utilização simultânea de diferentes sistemas de arquivos.
O Linux não exige que arquivos possuam extensões (caracteres após um caractere “.”), nem limita o
tipo ou a quantidade de caracteres presentes em uma extensão. Ainda, arquivos podem ter qualquer
quantidade de extensões (o que é comum). No entanto, vários programas esperam que arquivos possuam
extensões.
Está a cargo dos programas interpretar ou não o tipo do arquivo por sua extensão, visto que para o
Linux a extensão de um arquivo não possui nenhum significado.
O diretório raiz é chamado /, que além de ser utilizado para separar nomes de diretórios, é um
caractere que contém subdiretórios. Sob o diretório raiz, existe um grupo de diretórios comuns à maioria das
distribuições Linux. Alguns dos que estão localizados diretamente no diretório raiz, são:
• /bin → Arquivos executáveis;
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 118
• /boot → Arquivos de configuração do boot, kernel e outros arquivos necessários para a
inicialização do sistema;
• /dev → Contém os dispositivos do sistema;
• /etc → Arquivos de configuração, scripts de inicialização etc;
• /home → Diretórios home para usuários do sistema;
• /lib → Bibliotecas e módulos do sistema;
• /lib64 → Semelhante ao /lib, mas contém arquivos específicos do sistema 64 bits;
• /media → Onde são montados dispositivos externos;
• /mnt → Local para montagem manual de dispositivos. Normalmente utilizado para montagens
provisórias;
• /opt → Fornece um local opcional para aplicações serem instaladas. Normalmente utilizado para
instalação de aplicativos que não fazem parte do sistema;
• /proc → Diretório dinâmico especial que mantém informação sobre o estado do sistema, incluindo
os processos em execução. Contém algumas variáveis do kernel que podem, inclusive, ser
modificadas;
• /root → Diretório home do usuário root (administrador do sistema);
• /sbin → Arquivos executáveis para administração do sistema;
• /srv → Contém arquivos de alguns serviços do sistema. Por exemplo, servidor web;
• /sys → Semelhante ao /proc. Contém arquivos especiais do kernel;
• /tmp → Contém arquivos temporários;
• /usr → Aplicativos e arquivos que são, na maioria das vezes, disponíveis para acesso por todos
os usuários;
• /var → Arquivos de logs e bancos de dados.
ext2
O ext2 foi um dos sistemas de arquivos mais populares desenvolvido para o Linux. O layout de uma
partição ext2 é mostrado na figura a seguir:
Layout de uma partição ext2.
O primeiro bloco (bloco de inicialização) não é utilizado pela partição, é reservado para a inicialização
do computador. Após o primeiro bloco seguem grupos de blocos de mesma organização.
O superbloco contém informações sobre o layout do sistema de arquivos, como a quantidade de i-
nodes e a quantidade de blocos de disco. O descritor do grupo contém informações sobre a localização
dos mapas de bits, a quantidade de blocos livres e i-nodes no grupo, e a quantidade de diretórios no grupo.
Dois mapas de bits são usados para controlar os blocos livres e i-nodes livres. Cada mapa contém um bloco
de comprimento. Em seguida, estão os i-nodes, os quais são numerados de 1 até algum máximo. Cada i-
node tem 128 bytes de comprimento e descreve exatamente um arquivo.
Sistemas Operacionais
Marcio Quirino - 119
Atenção
Um i-node contém informações de contabilidade e dados suficientes para localizar todos os blocos de disco
que contêm os dados do arquivo.
Por fim, estão os blocos de dados, onde os arquivos e diretórios estão armazenados.
Os diretórios ficam dispersos pelos grupos de blocos do disco. O ext2 procura colocar arquivos de
dados no mesmo grupo de blocos que o i-node do arquivo original. Os mapas de bits são usados para tomar
decisões rápidas sobre onde alocar novos dados do sistema de arquivos. Quando novos blocos de arquivos
são alocados, o ext2 pré-aloca 8 blocos adicionais de maneira a minimizar a fragmentação de arquivos por
futuras operações de escrita. Essa pré-alocação equilibra a carga do sistema de arquivos.
Um diretório armazena nomes de arquivos, sendo ilustrado na figura a seguir, onde existem entradas
para 2 arquivos (projeto e documento.txt) e um diretório (dados).
Diretório do ext2.
Dentro de um diretório, as entradas para arquivos e diretórios estão fora de ordem. Pode acontecer
de uma entrada não ocupar blocos de disco inteiros, então é comum haver bytes não utilizados no final de
cada bloco de disco.
Cada entrada de diretório consiste em 4 campos de comprimento fixo e 1 campo de comprimento
variável.
A. Primeiro campo
O primeiro campo é o número do i-node (16 para projeto, 22 para documento.txt e 91 para dados).
B. Segundo campo
O segundo campo contém o tamanho da entrada, incluindo espaços não utilizados no seu final.
C. Terceiro campo
O terceiro campo identifica o tipo (arquivo, diretório, link etc.).
D. Quarto campo
O quarto campo é o tamanho do nome do arquivo.
E. Quinto campo
O quinto campo possui tamanho variável e armazena o nome do arquivo.
Sistemas Operacionais
Marcio Quirino - 120
Formato do i-node.
Hardlinks
Hardlinks são diferentes entradas de diretórios que apontam para o mesmo i-node (o mesmo arquivo).
Cada i-node do ext2 possui o formato ilustrado acima, em que:
• Permissões → Permissões de acesso ao arquivo;
• Contador de links → Quantidade de hardlinks que o arquivo possui;
• UID → Dono do arquivo;
• GID → Grupo do dono do arquivo.
Journaling
Para evitar perda de dados após problemas, como falta de energia, é necessário escrever blocos de
dados no disco tão logo o bloco seja criado ou alterado, levando a um baixo desempenho do sistema devido
à necessidade de movimentação das cabeças de leitura/gravação dos discos.
Saiba mais
Para minimizar tal problema, foi desenvolvido um recurso conhecido como journaling, no qual as alterações
são gravadas sequencialmente em um diário em vez de serem escritas diretamente nos blocos de disco.
Um conjunto de operações que executam uma tarefa específica é denominada transação. Quando
uma transação é escrita no diário, ela é considerada confirmada e o sistema pode prosseguir.
A seguir, as entradas do diário relacionadas com as transações são reexecutadas nas estruturas
reais do sistema de arquivos. Quando uma transação é concluída (totalmente salva no disco), ela é removida
do diário, que pode estar em uma seção separada do sistema de arquivos ou em um eixo separado do disco.
Como fica em uma seção confinada do disco, é mais eficiente por diminuir os tempos de disputa e busca do
cabeçote.
Atenção
Se o sistema cair e algumas transações permanecerem no diário, elas deverão ser concluídas quando o
sistema se recuperar.
O journaling foi implementado no Linux na terceira versão do ext, conhecida como ext3.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 121
Disco Rígido
Um disco rígido é constituído por discos sobrepostos, unidos por um eixo vertical girando a uma
velocidade constante. Cada disco é composto por trilhas concêntricas, que são divididas em setores. As
trilhas dos diferentes discos que ocupam a mesma posição vertical formam um cilindro. Para cada superfície
de um disco existe uma cabeça de leitura/gravação. O conjunto de cabeças é preso a um braço que se
movimenta entre os vários cilindros no sentido radial.
O tempo necessário para ler/gravar um bloco de dados é função de três fatores: tempo de busca,
latência e transferência.
• O tempo de busca (seek) é o tempo gasto para mover o braço até o cilindro onde o bloco se
encontra.
• O tempo de latência é o tempo de espera até que o setor desejadose posicione sob a
cabeça de leitura/gravação.
• O tempo de transferência corresponde ao tempo necessário para ler/gravar o bloco.
Partições
O particionamento de disco é uma divisão de seu espaço disponível em seções de forma que possam
ser utilizadas de maneira independente. Uma partição pode ocupar um disco inteiro, ou podem ser criadas
várias partições por disco, cada uma contendo um sistema de arquivos independente.
As informações sobre as partições existentes em um disco são gravadas em uma tabela de partição
no próprio disco. Para ser utilizada, uma partição deve ser formatada com um sistema de arquivos para que
estes possam ser gravados nela.
No Linux, os dispositivos são identificados por arquivos especiais que ficam armazenados no
diretório /dev. A identificação de discos e partições é realizada de acordo com o seguinte padrão:
Sistemas Operacionais
Marcio Quirino - 122
Algumas identificações de discos e partições em sistemas Linux:
• /dev/fd0 → Primeira unidade de disquetes;
• /dev/fd1 → Segunda unidade de disquetes;
• /dev/sda → Primeiro disco rígido na primeira controladora SATA ou SCSI;
• /dev/sda1 → Primeira partição do primeiro disco rígido SATA ou SCSI;
• /dev/sda2 → Segunda partição do primeiro disco rígido SATA ou SCSI;
• /dev/sdb → Segundo disco rígido na primeira controladora SATA ou SCSI;
• /dev/sdb3 → Terceira partição do segundo disco rígido SATA ou SCSI;
• /dev/sr0 → Primeiro CD-ROM SATA ou SCSI;
• /dev/hda → Primeiro disco rígido na primeira controladora IDE;
• /dev/hda1 → Primeira partição do primeiro disco rígido IDE.
Gerenciamento de partições em Linux
Veremos agora como fazer o gerenciamento de partições em sistemas Linux. Para efeito de exercício, será
utilizado o software de máquina virtual Oracle VirtualBox, que pode ser baixado gratuitamente do site
https://www.virtualbox.org/. Para a criação deste documento, foi utilizada a versão 6.1 do VirtualBox.
As telas e comandos mostrados neste exemplo são relativos ao Ubuntu 20.04 LTS, instalado em uma máquina
virtual no VirtualBox.
Abra um terminal e procure por todas as entradas de dispositivos no diretório /dev que possua “sd” no nome
utilizando o comando “ls -l /dev | grep sd”.
fabio@ubuntu20:~$ ls -l /dev | grep sd
brw-rw---- 1 root disk 8, 0 ago 28 16:44 sda brw-rw---- 1 root disk 8,
1 ago 28 16:44 sda1 brw-rw---- 1 root disk 8, 2 ago 28 16:44 sda2 brw-rw---- 1
root disk 8, 5 ago 28 16:44 sda5
Podemos ver pela saída do comando que existe o dispositivo /dev/sda e que ele possui 3 partições (/dev/sda1,
/dev/sda2 e /dev/sda5).
Vamos agora criar um segundo disco rígido virtual para este sistema operacional. Para isso, desligue e máquina
virtual antes de prosseguir.
Com a máquina virtual desligada, (1) selecione a máquina virtual no painel esquerdo do VirtualBox e (2) clique
no botão Configurações.
Sistemas Operacionais
Marcio Quirino - 123
Na janela de configurações da máquina virtual, (1) seleciona Armazenamento no painel esquerdo, (2) marque
o controlador de disco rígido e (3) clique no botão para adicionar um novo disco rígido.
Na tela seguinte, clique no botão Criar para criar um disco. Escolha o tipo de disco (VDI) e clique no botão
Próximo, escolha como será a alocação (Dinamicamente alocado) e clique no botão Próximo, escolha um nome
(utilizaremos o nome extra) e o tamanho do disco (utilizaremos 10 GB), e clique no botão Criar.
No seletor de discos (1) selecione o disco criado e (2) clique no botão Escolher.
Sistemas Operacionais
Marcio Quirino - 124
Deverá aparecer em sua máquina virtual o disco extra.vdi, conforme a imagem a seguir.
Confirme clicando no botão Ok e inicie sua máquina virtual.
O resultado deste processo realizado na máquina virtual é o mesmo que acrescentar um novo disco rígido de
10 GB a um computador real.
Abra um terminal e procure novamente por todas as entradas de dispositivos no diretório /dev que possua “sd”
no nome utilizando o comando “ls -l /dev | grep sd”.
fabio@ubuntu20:~$ ls -l /dev | grep sd
brw-rw---- 1 root disk 8, 0 ago 28 17:18 sda brw-rw---- 1 root disk 8, 1 ago 28 17:18 sda1 brw-rw-
--- 1 root disk 8, 2 ago 28 17:18 sda2 brw-rw---- 1 root disk 8, 5 ago 28 17:18 sda5 brw-rw---- 1 root disk
8, 16 ago 28 17:18 sdb
Podemos ver, pela saída do comando, que em comparação com a execução anterior, apareceu o dispositivo
/dev/sdb (segundo disco rígido SATA). Como ainda não foram criadas partições para este disco rígido, não existem
outras informações.
O primeiro passo para a utilização de um disco rígido recém instalado é a criação das partições para posterior
instalação do sistema operacional. Os programas mais utilizados para o particionamento de discos são:
fdisk → Programa padrão de sistema Unix para particionamento de discos. Interface em modo texto.
cfdisk → Programa para operações básicas de particionamento de disco baseado na biblioteca ncurses, que
fornece uma interface mais amigável, embora ainda em modo texto.
Sistemas Operacionais
Marcio Quirino - 125
parted → Programa em modo texto que permite operações complexas sobre partições como, por exemplo,
criação, eliminação, alteração de tamanho e movimentação.
gparted → Interface gráfica que permite operações complexas sobre partições. Funciona emitindo comandos
para o parted, que deve estar instalado no sistema.
Veremos como criar as partições utilizando primeiro o fdisk, programa padrão para sistemas Linux. Depois
veremos como obter o mesmo resultado utilizando o gparted.
Criaremos 3 partições no disco rígido recém instalado.
Nº da partição Tipo Tamanho
1 ext4 3 GB
2 NTFS 2 GB
3 ext4 1 GB
O fidsk é um programa que requer privilégios de administrador para sua execução, portanto ao ser executado
deve ser precedido pelo comando sudo para que execute com privilégios da conta root. Sua conta deve permitir o uso
do sudo.
Para ver o estado da partição /dev/sdb pode ser executado o comando “”.
fabio@ubuntu20:~$ sudo fdisk /dev/sdb -l
Disco /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 setores
Disk model: VBOX HARDDISK
Unidades: setor de 1 * 512 = 512 bytes
Tamanho de setor (lógico/físico): 512 bytes / 512 bytes Tamanho E/S (mínimo/ótimo): 512
bytes / 512 bytes
Criando partições com o fdisk
O fdisk é um utilitário em modo texto que recebe comandos do teclado e vai configurando o disco em memória.
Dessa forma, caso haja arrependimento das instruções fornecidas ao fdisk, basta abandonar a edição sem salvar,
assim, nenhuma alteração será gravada em disco.
Para iniciar a configuração do dispositivo /dev/sdb com o fdisk, deve ser executado o comando “sudo fdisk
/dev/sdb”. Aparecerá o seguinte prompt:
Comando (m para ajuda):
solicitando que seja fornecido um comando para o fdisk. Pressionando a tecla m e teclando Enter é exibida a
lista de comando do fdisk. Faça isso e veja os comandos que podem ser utilizados no utilitário.
Vamos ver a sequência de comandos para criar a primeira partição (partição ext4 com 3 GB).
1. Pressione a tecla n e depois Enter. Esse é o comando para criar uma nova partição. A seguir, é
perguntado o tipo da partição. Uma partição primária é uma partição na qual será formatado um sistema
de arquivos, enquanto uma partição secundária é uma partição na qual podem ser criadas outras
partições. São permitidas apenas 4 partições no primeiro nível, assim, se forem necessárias mais que
4 partições será preciso criar pelo menos uma partição secundária para comportar as demais.
2. Pressione a tecla p e depois Enter. Com isso, será criada uma partição primária. Será perguntado o
número da partição.
3. Pressione a tecla 1 e depois Enter. Será perguntado o número do primeiro setor. Senão tiver absoluta
certeza para escolher um número, aceite o valor padrão que o sistema alinhará a partição com o final
da partição anterior.
Sistemas Operacionais
Marcio Quirino - 126
4. Pressione Enter sem fornecer nenhum valor, aceitando a sugestão padrão para o setor inicial. Será
perguntado o número do último setor. Em vez de fornecer o número do último setor, é mais conveniente
digitar o caractere + seguido do tamanho da partição.
5. Digite +3G e tecle Enter para criar a partição com 3 GB.
Seguirá uma mensagem informando que foi criada uma partição do tipo “Linux” e de tamanho 3 GB. Para ver
a partição criada digite p e depois tecle Enter. Aparecerá uma informação como a seguinte:
Disco /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 setores
Disk model: VBOX HARDDISK
Unidades: setor de 1 * 512 = 512 bytes
Tamanho de setor (lógico/físico): 512 bytes / 512 bytes
Tamanho E/S (mínimo/ótimo): 512 bytes / 512 bytes
Tipo de rótulo do disco: dos
Identificador do disco: 0x90c78077
Dispositivo Inicializar Início Fim Setores Tamanho Id Tipo
/dev/sdb1 2048 6293503 6291456 3G 83 Linux
Faça a criação das partições 2 e 3, com 2 GB e 1 GB respectivamente. A sequência de comandos para a
criação destas partições é:
Comando Descrição
n Cria partição
p Tipo primária
2 Segunda partição
Primeiro setor (em branco para valor padrão)
+2G Tamanho (2 GB)
n Cria partição
p Tipo primária
3 Terceira partição
Primeiro setor (em branco para valor padrão)
+1G Tamanho (1 GB)
Digite p seguido de Enter para ver como ficou a configuração. Deverá surgir uma saída como:
Disco /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 setores
Disk model: VBOX HARDDISK
Unidades: setor de 1 * 512 = 512 bytes
Tamanho de setor (lógico/físico): 512 bytes / 512 bytes
Tamanho E/S (mínimo/ótimo): 512 bytes / 512 bytes
Tipo de rótulo do disco: dos
Identificador do disco: 0x90c78077
Dispositivo Inicializar Início Fim Setores Tamanho Id Tipo
/dev/sdb1 2048 6293503 6291456 3G 83 Linux
/dev/sdb2 6293504 10487807 4194304 2G 83
/dev/sdb3 10487808 12584959 2097152 1G 83 Linux
Está quase tudo conforme o planejado, exceto que a partição 2 está como do tipo Linux, mas queremos uma
partição do tipo NTFS (Windows). Para alterar o tipo de partição deve ser utilizado o comando t.
1. Pressione t e tecle Enter. Será perguntada qual partição deve ser alterada. No caso queremos alterar
a segunda partição.
2. Pressione 2 e tecle Enter para selecionar a segunda partição. Será perguntado o código hexadecimal
para a partição. Se não souber pressione L ver a relação.
3. Pressione L e tecle Enter para ver a relação de códigos de partições. Pela relação, vemos que o NTFS
possui código 7.
4. Pressione 7 e tecle Enter. Surgirá uma mensagem informando que o tipo da partição foi alterado.
Digite p seguido de Enter para ver como ficou a configuração final. Deverá surgir uma saída como:
Linux
Sistemas Operacionais
Marcio Quirino - 127
Disco /dev/sdb: 10 GiB, 10737418240 bytes, 20971520 setores
Disk model: VBOX HARDDISK
Unidades: setor de 1 * 512 = 512 bytes
Tamanho de setor (lógico/físico): 512 bytes / 512 bytes
Tamanho E/S (mínimo/ótimo): 512 bytes / 512 bytes
Tipo de rótulo do disco: dos Identificador do disco: 0x90c78077
Dispositivo Inicializar Início Fim Setores Tamanho Id Tipo
/dev/sdb1 2048 6293503 6291456 3G 83 Linux
/dev/sdb2 6293504 10487807 4194304 2G 7 HPFS/NTFS/exFAT
/dev/sdb3 10487808 12584959 2097152 1G 83 Linux
Para salvar o trabalho digite w e tecle Enter. As informações serão gravadas em disco. Para sair sem alterar
nada, basta digitar q e teclar Enter.
Procure novamente por todas as entradas de dispositivos no diretório /dev que possua “sd” no nome utilizando
o comando “ls -l /dev | grep sd”. Você verá o dispositivo /dev/sdb com todas as partições criadas.
fabio@ubuntu20:~$ ls -l /dev | grep sd
brw-rw---- 1 root disk 8, 0 ago 28 17:18 sda brw-rw---- 1 root disk 8,
1 ago 28 17:18 sda1 brw-rw---- 1 root disk 8, 2 ago 28 17:18 sda2 brw-rw---- 1
root disk 8, 5 ago 28 17:18 sda5 brw-rw---- 1 root disk 8, 16 ago 28
18:24 sdb brw-rw---- 1 root disk 8, 17 ago 28 18:24 sdb1 brw-rw---- 1 root disk
8, 18 ago 28 18:24 sdb2 brw-rw---- 1 root disk 8, 19 ago 28 18:24 sdb3
Criando partições com o gparted
O gparted é um utilitário de interface gráfica que permite uma série de operações sobre partições, incluindo
aumento, diminuição e movimentação de partições sem perda dos dados.
Antes de utilizá-lo, é necessário verificar se ele está instalado em seu sistema. Caso não esteja, é necessário
verificar como é sua instalação na distribuição que você está utilizando.
Para instalar o gparted em distribuições derivadas do Debian, como é o caso do Ubuntu, basta executar os
comandos:
sudo apt-get update sudo apt-get install gparted
O primeiro comando faz a sincronização da base de programas instalados com o servidor de atualizações,
enquanto o segundo faz o download e a instalação da versão mais recente do gparted (e de tudo mais que o utilitário
precisar).
Para a realização deste exercício, garanta que o disco /dev/sdb esteja sem nenhuma partição configurada.
Você pode fazer isso executando “sudo fdisk /dev/sdb” e utilizando o comando d do fdisk para eliminar cada uma das
partições. Não esquecer de sair com o comando w.
Para executar o gparted, basta procurá-lo na lista de utilitário da interface gráfica ou chamá-lo pela linha de
comando. Como trata-se de um utilitário que precisa executar com privilégios de administrador, abra um terminal e
coloque o gparted em execução com o comando “sudo gparted”. Deverá surgir a seguinte tela (ou semelhante):
Esta tela mostra o primeiro disco do sistema (/dev/sda). Para criar as partições no disco /dev/sdb, selecione
/dev/sdb no canto superior direito.
Sistemas Operacionais
Marcio Quirino - 128
Para criar uma nova partição, clique com o botão direito sobre a área não alocada e selecione Novo. Para criar
a primeira partição de acordo com o solicitado (ext4 com 3 GB) preencha os dados conforme a figura abaixo:
Clique em Adicionar.
Para criar as partições seguintes, NTFS com 2 GB e ext4 com 1 GB, preencha as seguintes informações (uma
vez para cada partição):
Sistemas Operacionais
Marcio Quirino - 129
Após a configuração das 3 partições, o gparted deverá estar com uma aparência semelhante à da seguinte
imagem:
A parte inferior da janela informa que existem 3 operações pendentes. Clique no menu “Editar Aplicar todas as
operações” para gravar as partições no disco. Ao final da operação a tela estará como mostrado na figura abaixo.
Formatação
O processo de criação de partições em um disco reserva espaço para o sistema de arquivos,
mas ainda não faz com que o dispositivo possa ser utilizado. A fim de que seja possível fazer
uso das partições criadas, é necessário criar uma estrutura para o sistema de arquivos. É um
processo conhecido como formatação.
Formatar uma partição Linux significa criar os grupos de blocos, cada um deles contendo seu
superbloco, descritor do grupo, mapas de bits, i-nodes e reservar espaço para os blocos de
dados.
Formatação de partições em Linux
Para execução deste exemplo, utilizaremos as partições criadas no exemplo anterior:
Partição Tipo Tamanho
/dev/sdb1 ext4 3 GB
/dev/sdb2 NTFS 2 GB
/dev/sdb3 ext4 1 GB
As partições estão criadas em um disco (/dev/sdb) de 10 GB.
Sistemas Operacionais
Marcio Quirino - 130
O programa do Linux para formatarpartições é o mkfs. Deve ser passado como parâmetro a partição que será
formatada. As opções dependem do tipo da partição.
fabio@ubuntu20:~$ sudo mkfs.ext4 /dev/sdb1 mke2fs 1.45.5 (07-Jan-2020)
/dev/sdb1 contém um sistema de ficheiros ext4 criado em Fri Aug 28 19:02:54 2020
Proceder mesmo assim? (y,N) y
A criar sistema de ficheiros com 786432 4k blocos e 196608 inodes UUID do sistema de
ficheiros: ec830215-63cd-42c6-a0fe-53e59511b2e6 Cópias de segurança de superblocos gravadas
em blocos:
32768, 98304, 163840, 229376, 294912
A alocar tabelas de grupo: pronto
Gravando tabelas inode: pronto
A criar diário (16384 blocos): concluído
Escrevendo superblocos e informações de contabilidade de sistema de arquivos: 0concluído
Mas para os tipos de partição mais usuais existem comandos específicos, que são extensões do mkfs e
permitem a formatação passando como parâmetros apenas a partição a ser formatada, sem necessidade de opções.
Alguns destes comandos são: mkfs.ext2, mkfs.ext3, mkfs.ext4, mkfs.fat, mkfs.vfat, mkfs.msdos, mkfs.ntfs, mkfs.minix,
mkfs.bfs e mkfs.cramfs.
Para formatar, por exemplo, a partição /dev/sdb1 com o sistema de arquivos ext4, deve-se utilizar o comando
“mkfs.ext4 /dev/sdb1”.
Portanto, para formatar as partições especificadas na tabela acima, devem ser executados os comandos:
sudo mkfs.ext4 /dev/sdb1
sudo mkfs.ntfs /dev/sdb2
sudo mkfs.ext4 /dev/sdb3
O resultado do processamento será:
fabio@ubuntu20:~$ sudo mkfs.ntfs /dev/sdb2
Cluster size has been automatically set to 4096 bytes.
Initializing device with zeroes: 100% - Done.
Creating NTFS volume structures.
mkntfs completed successfully. Have a nice day.
fabio@ubuntu20:~$
fabio@ubuntu20:~$ sudo mkfs.ext4 /dev/sdb3
mke2fs 1.45.5 (07-Jan-2020)
/dev/sdb3 contém um sistema de ficheiros ext4 criado em Fri Aug 28 19:02:59 2020
Proceder mesmo assim? (y,N) y
A criar sistema de ficheiros com 262144 4k blocos e 65536 inodes UUID do sistema de ficheiros:
fe19c1f6-5f27-43e4-b167-6d2136998823 Cópias de segurança de superblocos gravadas em blocos:
32768, 98304, 163840, 229376
A alocar tabelas de grupo: pronto
Gravando tabelas inode: pronto
A criar diário (8192 blocos): concluído
Escrevendo superblocos e informações de contabilidade de sistema de arquivos: 0/concluído
Ao final deste processo, as partições estarão formatas e prontas para o recebimento de arquivos.
Montagem do sistema de arquivos
Muitos sistemas possuem dois ou mais discos ou partições. Além disso, muitos sistemas atuais
permitem a utilização de diversos dispositivos de armazenamento simultaneamente, como discos ópticos e
unidades USB, devendo se pensar em uma forma de acessar os diferentes sistemas de arquivos.
Uma solução está em manter cada sistema separado e encontrar uma forma de referenciá-los,
conforme a maneira adotada pela Microsoft para o sistema operacional Windows. Nela, os dispositivos são
identificados por uma letra seguido pelo caractere dois pontos.
Sistemas Operacionais
Marcio Quirino - 131
Exemplo
Uma partição de um disco rígido pode ser identificada por C:, uma unidade óptica por D:, um dispositivo USB
por G:, e assim por diante. Cada unidade possui seu próprio diretório raiz, arquivos e subdiretórios. Nessa solução, o
usuário precisa especificar tanto o dispositivo quanto o arquivo.
Outro meio, adotado pelo Linux, é fazer com que o sistema de arquivos de um dispositivo/partição
seja montado sobre a árvore de diretórios do sistema. Dessa forma, uma unidade ótica poderia ser montada,
por exemplo, no diretório /mnt, enquanto um dispositivo USB poderia ser montado no diretório /media/usb1.
A figura abaixo mostra a estrutura do sistema de arquivos descrito (a) no Microsoft Windows e (b) no
Linux.
Exemplo de montagem de dispositivos (a) no MS Windows e (b) no Linux.
Montagem de partições em Linux
Para execução deste exemplo, utilizaremos as partições criadas e formatadas nos exemplos anteriores:
Partição Tipo Tamanho
/dev/sdb1 ext4 3 GB
/dev/sdb2 NTFS 2 GB
/dev/sdb3 ext4 1 GB
As partições estão criadas em um disco (/dev/sdb) de 10 GB.
Sistemas Operacionais
Marcio Quirino - 132
No Microsoft Windows®, cada letra de unidade (C:, D:, E:) identifica um sistema de arquivos em uma partição
de disco ou em um dispositivo, enquanto no Linux as partições são acessadas em diretórios, conhecidos como pontos
de montagem, e que fazem parte da estrutura do sistema de arquivos raiz.
Cada partição pode ser montada em qualquer diretório.
No caso de um sistema de arquivos cheio, pode-se copiar o conteúdo de um diretório para outro sistema de
arquivos, apagar o conteúdo do diretório original e montar partição onde foram copiados os arquivos naquele local.
A adição de novas partições ou substituição de discos rígidos não afeta a ordem de identificação dos discos e
pontos de montagem.
Montagem de sistema de arquivos
A montagem de um sistema de arquivos é feita por intermédio do comando mount, cuja sintaxe é:
mount [dispositivo] [ponto de montagem] [opções]
Sendo:
• dispositivo → Identificação do dispositivo/partição que será montado.
• ponto de montagem → Diretório de onde o dispositivo/partição será montado.
Algumas opções:
• -t [tipo] → Tipo do sistema de arquivos utilizado (ext2, ext3, ext4, vfat, iso9660, etc.).
• -r → Monta a partição como somente leitura.
• -w → Monta a partição como leitura/gravação. É o padrão.
Desmontagem de sistema de arquivos
Para desmontar um sistema de arquivos utiliza-se o comando umount, cuja sintaxe é:
umount [dispositivo ou ponto de montagem]
Montando e desmontando sistemas de arquivos
Para a montagem dos sistemas de arquivos criados anteriormente, utilizaremos os seguintes pontos de
montagem:
Partição Ponto de montagem
/dev/sdb1 /extra/b1
/dev/sdb2 /extra/b2
/dev/sdb3 /extra/b3
O primeiro passo é montar a estrutura de diretórios que será utilizada para a montagem dos sistemas de
arquivos. Os comandos utilizados para a criação dos diretórios, que serão estuados adiante, são:
sudo mkdir -p /extra/b1
sudo mkdir /extra/b2
sudo mkdir /extra/b3
Uma vez criados os pontos de montagem, basta utilizar o comando mount para montar as partições:
sudo mount -t ext4 /dev/sdb1 /extra/b1
sudo mount -t ntfs /dev/sdb2 /extra/b2
sudo mount -t ext4 /dev/sdb3 /extra/b3
Neste momento, temos os sistemas de arquivos formatados e montados, prontos para utilização.
Uma forma de verificar os sistemas de arquivos montados e prontos para uso é por intermédio do comando “df
-h”. Na listagem a seguir podemos ver o resultado do comando. Para facilitar a leitura, foram omitidos alguns sistemas
de arquivos montados por padrão.
Sistemas Operacionais
Marcio Quirino - 133
fabio@ubuntu20:~$ df -h
Sist. Arq. Tam. Usado Disp. Uso% Montado em udev 967M 0 967M 0%
/dev tmpfs 199M 1,4M 198M 1%
/run
/dev/sda5 15G 6,1G 7,4G 46% /
/dev/sda1 511M 4,0K 511M 1% /boot/efi
/dev/sdb1 2,9G 9,0M 2,8G 1% /extra/b1
/dev/sdb2 2,0G 11M 2,0G 1% /extra/b2
/dev/sdb3 976M 2,6M 907M 1% /extra/b3
Para desmontar os sistemas de arquivos montados, utiliza-se o comando umount.
sudo umount /dev/sdb1
sudo umount /dev/sdb2
sudo umount /dev/sdb3
Podemos verificar que os sistemas de arquivos foram desmontados executando novamente o comando “df -h”.
Montagem automática de sistemas de arquivos
O arquivo /etc/fstab permite que as partições do sistema sejam montadas facilmente, especificando somente
o dispositivo ou o ponto de montagem. É possível também configurar para que os sistemas de arquivos sejam montados
automaticamente duranteo boot. Este arquivo contém parâmetros sobre as partições que são lidos pelo comando
mount.
Cada linha deste arquivo contém a partição a ser montada, o ponto de montagem, o sistema de arquivos
utilizado pela partição e outras opções. O arquivo possui a seguinte estrutura:
Sistema_de_arquivos Ponto_de_Montagem Tipo Opções dump ordem
/dev/sda1 / ext4 defaults 0 1
/dev/sda2 /home ext4 defaults 0 2
/dev/sda3 /extra vfat defaults,noauto,rw 0 0
/dev/sda4 none swap sw 0 0
Em que:
• Sistema de Arquivos → Partição a ser montada.
• Ponto de montagem → Diretório onde a partição será montada.
• Tipo → Tipo de sistema de arquivos utilizado na partição que será montada.
• Opções → Especifica as opções usadas com o sistema de arquivos. Algumas opções:
o defaults → Utiliza valores padrões de montagem.
o noauto → Não monta os sistemas de arquivos durante a inicialização. o ro → Monta como
somente leitura. o user → Permite que usuários montem o sistema de arquivos.
o sync → Faz com que os dados sejam gravados imediatamente na unidade.
• dump → Especifica se será feito backup com utilitário dump, se estiver instalado. Podem ser colocados
os valores 0 (desativa backup) ou 1 (ativa backup).
• ordem → Define a ordem que os sistemas de arquivos serão verificados na inicialização do sistema.
Se usar 0, o sistema de arquivos não é verificado. O primeiro sistema de arquivos que deverá ser
verificado é o raiz.
Para o exemplo acima, após configurar o arquivo /etc/fstab, basta digitar o comando “mount /dev/sda3” ou
“mount /extra” para que a partição “/dev/sda3” seja montada no ponto de montagem “/extra”. Não é necessário
especificar o sistema de arquivos da partição, pois o mount verificará no arquivo /etc/fstab.
Partição de swap (memória virtual)
A partição de swap é utilizada para oferecer o suporte à memória virtual, em adição à memória RAM do
sistema.
Sistemas Operacionais
Marcio Quirino - 134
Quando um processo começa a encher a memória RAM, o sistema operacional move automaticamente os
dados que não estão sendo usados para a partição de swap e libera parte da memória RAM para continuar carregando
os dados necessários. Quando os dados movidos para a partição de swap são solicitados, o sistema operacional os
move da partição de swap de volta para a memória RAM.
Criando o swap em uma partição
No disco criado anteriormente (/dev/sdb), vamos utilizar o espaço restante para criar a partição /dev/sdb4,
ocupando o restante do disco. Vamos ainda definir o tipo da partição como swap para que possa ser utilizada em
adição à memória RAM do sistema.
Para criar a partição, utilize o fdisk executando-o em um terminal. Para isso, digite no shell o comando “sudo
fdisk /dev/sdb”. Forneça a seguinte sequência de comandos:
Comando Descrição
n Cria partição
p Tipo primária
Primeiro setor (em branco para valor padrão)
Último setor (em branco para utilizar todo o restante do disco)
t Alterar o tipo da partição
4 Selecionar a partição 4 para alteração do tipo
L Verificar o código hexadecimal para partição de swap
82 Identifica a partição como swap
w Sai gravando as alterações
Como ainda restavam 4 GB de espaço livre no disco, foi criada uma partição de 4GB do tipo Linux swap.
O programa usado para formatar uma partição de swap é o mkswap. Para criar uma partição de swap em
“/dev/sdb4”, por exemplo, deve-se executar o comando “sudo mkswap /dev/sdb4”.
A opção “-c” pode ser usada com o mkswap para verificar se existem clusters danificados na partição.
Com a partição de swap formatada, o comando “sudo swapon /dev/sdb4” deve ser utilizado para ativar a
partição de swap.
O swapon ativa a partição de swap até a próxima reinicialização do sistema. Para ativar esta partição de swap
para todas as seções, é necessário acrescentar no arquivo /etc/fstab uma linha como a abaixo:
/dev/sdb4 none swap sw 0 0
Se utilizar mais do que uma partição de swap, pode ser útil o uso da opção “-p NUM”, que especifica a
prioridade em que a partição de swap será usada. Partições com número maior serão usadas primeiro. Devem ser
utilizados números maiores para partições mais rápidas e números menores para partições mais lentas.
Caso precise desativar a partição de swap, use o comando: “swapoff <partição>”. Para o nosso exemplo, o
comando seria “sudo swapoff /dev/sdb4”.
Criando o swap em um arquivo
Também é possível criar um arquivo para ser utilizado como apoio à memória virtual.
Utilize o comando dd para criar um arquivo com o tamanho desejado. Os parâmetros do dd são:
• if → Arquivo fonte de onde os dados serão copiados.
• of → Arquivo destino para onde os dados serão copiados.
• bs → Quantidade de dados a ser copiado por bloco.
• count → Quantidade de blocos a ser copiado.
Para criar o arquivo /troca com 512 MB contendo valores o (zero), executar o comando:
Sistemas Operacionais
Marcio Quirino - 135
sudo dd if=/dev/zero of=/troca bs=1024 count=524288
Após, executar o comando “sudo mkswap /troca” para formatar o arquivo. Feito isso, o sistema de arquivos
swap estará criado e pronto para ser utilizado.
Ativar o arquivo de swap com o comando “sudo swapon /troca”.
Para conferir a qualquer momento se o tamanho da memória virtual foi modificado pode-se utilizar o comando
“free”.
Podem ser usadas partições de swap e arquivos de swap ao mesmo tempo.
O swapon ativa o arquivo de swap até a próxima reinicialização do sistema. Para ativar este arquivo de swap
para todas as seções, é necessário acrescentar no arquivo /etc/fstab uma linha como a abaixo:
/troca none swap sw 0 0
Outros comandos para gerenciamento de partições
Além dos comandos vistos nos exemplos para criação e gerenciamento de partições, o Linux possui
outros utilitários que podem ser úteis para a administração do sistema. Vejamos alguns destes comandos.
A. fsck
Utilitário utilizado para verificar e, se necessário, corrigir um sistema de arquivos. Se não for
informado o tipo do sistema de arquivos, o fsck procurará por essa informação no
arquivo /etc/fstab.
/etc/fstab
Arquivo que contém informações sobre os sistemas de arquivos a serem utilizados no sistema, como a
partição em que se encontra, o tipo do sistema de arquivos, o ponto de montagem etc.
Algumas opções:
• -t → Especifica o tipo do sistema de arquivos;
• -C → Mostra uma barra de progresso da verificação. Não está disponível para todos os
sistemas de arquivos;
• -V → Gera saída detalhada das operações realizadas.
Exemplo:
$ sudo fsck -t ext4 /dev/sdb1
fsck de util-linux 2.34
e2fsck 1.45.5 (07-Jan-2020)
/dev/sdb1: limpo, 11/196608 ficheiros, 31036/786432 blocos
B. df
Utilitário para exibição do espaço livre/ocupado por cada sistema de arquivos montados. Informa
a partição onde o sistema de arquivos reside, o tamanho do sistema de arquivos, os espaços
utilizado e disponível, o percentual de uso e onde se localiza o ponto de montagem do sistema
de arquivos.
Algumas opções:
• -h → Mostra o espaço livre/ocupado em MB, KB, GB em vez de blocos;
• -k → Lista em Kbytes;
• -l → Somente lista sistema de arquivos locais;
• -m → Lista em Mbytes.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 136
Exemplo:
$ df -h
Sist. Arq. Tam. Usado Disp. Uso% Montado em
Udev 967M 0 967M 0% /dev
tmpfs 199M 1,3M 198M 1% /run
/dev/sda5 15G 6,7G 6,9G 50% /
Compartilhada 446G 430G 17G 97% /media/sf_Compartilhada
C. lsblk
Utilitário que lista informações sobre os dispositivos de bloco do sistema, excetuando-se os
discos de RAM. As informações incluem o tamanho total da partição/blocoe o ponto de
montagem, se o sistema de arquivos estiver montado. Este comando não informa o espaço em
disco utilizado/livre nos sistemas de arquivos.
Algumas opções:
• -a → Lista todos os dispositivos, incluindo discos de RAM;
• -b → Informa o tamanho em bytes;
• -f → Exibe informações sobre os sistemas e arquivos.
Exemplo:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
das 8:0 0 15G 0 disk
├─sda1 8:1 0 512M 0 part /boot/efi
├─sda2 8:2 0 1K 0 part
└─sda5 8:5 0 14,5G 0 part /
sdb 8:16 0 10G 0 disk
├─sdb1 8:17 0 3G 0 part
├─sdb2 8:18 0 2G 0 part /extra/b2
├─sdb3 8:19 0 1G 0 part
└─sdb4 8:20 0 4G 0 part
sr0 11:0 1 1024M 0 rom
3. Ferramentas de gerenciamentos de arquivos do Linux
Conceitos
O Linux é um sistema operacional, ou melhor, o núcleo do sistema. É o responsável pelo
gerenciamento dos dispositivos e recursos do sistema, oferecendo para os softwares que executam acima
dele chamadas de sistema que transformam o verdadeiro hardware em uma máquina mais fácil de ser
utilizada.
O Linux é a base, mas ainda faltam softwares que forneçam a funcionalidade que os usuários
desejam de um sistema. A fim de oferecer esta funcionalidade, organizações e pessoas empacotam um
conjunto de softwares que, com o núcleo do sistema (o Linux), formam um sistema completo que pode ser
instalado e utilizado pelas pessoas.
Saiba mais
Esse conjunto de softwares é conhecido como distribuição.
Cada distribuição é livre para escolher os pacotes de software que serão instalados, e isso se torna
mais evidente com relação ao ambiente gráfico. Existem várias interfaces gráficas que podem ser utilizadas,
cada uma configurada com diferentes softwares.
Sistemas Operacionais
Marcio Quirino - 137
Exemplo
Algumas das interfaces gráficas mais populares para o Linux são: GNOME, KDE, Cinnamon, MATE e XFCE.
O Linux também possui uma série de comandos em modo texto que permitem realizar praticamente
qualquer tarefa no sistema, e, ao contrário das interfaces gráficas, são utilitários padrão (ou quase isso)
disponíveis para utilização em qualquer distribuição. Por essa razão, estudaremos os comandos em modo
texto, que permitirão que você possa utilizar qualquer sistema Linux.
Durante a inicialização, o Linux monta um sistema de arquivos conhecido como sistema de arquivos
raiz. Ele tem início em um diretório representado pela barra de divisão “/”, que recebe o nome de root (raiz).
Atenção
Não confundir o diretório root com o usuário root (administrador do sistema).
Os demais sistemas de arquivos que forem necessários para a execução do sistema serão montados
em pontos de montagem da hierarquia de diretórios.
Saiba mais
Para alguns sistemas operacionais, como o Microsoft Windows, um arquivo torna-se oculto quando é ativada
uma de suas propriedades. Ou seja, ser oculto é uma propriedade.
O Linux trata arquivos ocultos de uma forma diferente. Um arquivo oculto nesse sistema operacional
é aquele que inicia com o caractere ponto “.”. Esse tipo de arquivo não aparece em listagens do conteúdo
dos diretórios, a não ser que seja explicitamente solicitado.
Comandos para manipulação de diretórios
A. ls
Lista o conteúdo do diretório corrente.
Sintaxe: ls [opções] [caminho]
Se caminho for informado, mostra o conteúdo do diretório caminho, senão mostra o conteúdo do
diretório corrente.
Algumas opções:
• -a → Lista todos os arquivos de um diretório, incluindo arquivos ocultos;
• -h → Mostra o tamanho dos arquivos em Kbytes, Mbytes, Gbytes;
• -i → Mostra o número do i-node de cada arquivo.
• -l → Mostra mais informações, como lista as permissões, data de modificação, donos,
grupos etc.
• -R → Lista diretórios e subdiretórios recursivamente.
• -r → Inverte a ordem de classificação.
• -c → Classifica pela data de alteração.
• --help → Mostra uma tela de ajuda.
Exemplo:
fabio@ubuntu20:~$ ls
‘Área de Trabalho’ Downloads Modelos Público teste Vídeos Documentos Imagens Música snap
teste.txt
fabio@ubuntu20:~$ ls -a
. .gnupg .ssh
.. Imagens .sudo_as_admin_successful
‘Área de Trabalho’ .joe_state teste
.bash_history .lesshst teste.txt
Sistemas Operacionais
Marcio Quirino - 138
.bash_logout .local .vboxclient-clipboard.pid
.bashrc Modelos .vboxclient-display-svga-x11.pid
.cache Música .vboxclient-draganddrop.pid
.config .profile .vboxclient-seamless.pid
Documentos Público Vídeos
Downloads snap
fabio@ubuntu20:~$ ls -lh
total 20M
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 'Área de Trabalho'
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Documentos
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Downloads
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Imagens
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Modelos
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Música
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Público
-rwxr-xr-x 1 fabio fabio 20M ago 29 14:30 snap
drwxrwxr-x 2 fabio fabio 4,0K ago 14 10:14 teste
-rw-rw-r-- 1 fabio fabio 5,0K ago 13 17:21 teste.txt
drwxr-xr-x 2 fabio fabio 4,0K ago 9 20:18 Vídeos
B. pwd
Mostra o caminho do diretório de trabalho.
Exemplo:
$ pwd
/home/fabio
No exemplo, o comando pwd mostrou que o diretório de trabalho é o /home/fabio, ou seja, o
diretório padrão de trabalho do usuário fabio (diretório home de fabio).
C. cd
Altera o diretório atual de trabalho.
Sintaxe: cd [diretório]
Altera o diretório de trabalho para o passado como parâmetro. Se o diretório de destino não for
especificado, vai para o diretório home do usuário.
Exemplo:
$ pwd
/var/spool
$ cd
$ pwd
/home/fabio
$ cd /etc
$ pwd
/etc
No caso acima, o diretório de trabalho era /var/spool. Ao executar o comando “cd” o diretório de
trabalho mudou para /home/fabio. Ao executar o comando “cd /etc” o diretório de trabalho mudou
para /etc.
Algumas utilizações do comando cd:
• cd / → Vai para o diretório raiz (root);
• cd .. → Sobe um nível na árvore de diretórios;
• cd - → Retorna para o diretório que estava antes de entrar no diretório atual.
D. mkdir
Cria um ou mais diretórios.
Sistemas Operacionais
Marcio Quirino - 139
Sintaxe: mkdir [caminho/diretório]
Na qual caminho é o local em que o diretório será criado e diretório é o nome do diretório a ser
criado.
Algumas opções:
• -p → Caso os diretórios dos níveis acima não existam, serão criados.
Exemplo:
$ mkdir a/b/c
mkdir: não foi possível criar o diretório “a/b/c”: Arquivo ou diretório não encontrado
$ mkdir -p a/b/c
$ mkdir a/b/c/d
No exemplo, o primeiro comando (mkdir a/b/c) falhou porque tentou criar, a partir do diretório
corrente, o diretório “c” dentro do caminho “a/b”, mas o caminho “a/b” não existe. O segundo
comando (mkdir -p a/b/c) funcionou por causa do parâmetro -p, que forçou a criação dos diretórios
“a” e “a/b” caso não existissem. O terceiro comando criou o diretório “d” dentro do caminho “a/b/c”.
E. rmdir
Remove um diretório vazio.
Sintaxe: rmdir [caminho/diretório]
Exemplo:
$ rmdir a/b/c/d
$ rmdir a
rmdir: falhou em remover 'a': Diretório não vazio
Neste caso, o primeiro comando funcionou e removeu o diretório “d” que estava no caminho
“a/b/c”. O segundo comando não funcionou porque tentou remover o diretório “a”, que não está
vazio. Para a remoção de diretórios que contenham arquivos e/ou subdiretórios, consulte o
comando rm.
Comandos para manipulação de arquivos
Clique nas barras para ver as informações.
A. rm
O comando rm é utilizado para apagar arquivos. Pode ser utilizado também para apagar
diretórios e subdiretórios recursivamente.
Sintaxe: rm [opções] arquivo/diretório
Algumas opções:
• -i → Pergunta antes de remover;• -v → Mostra os nomes dos arquivos/diretórios conforme são removidos;
• -r → Remove arquivos e diretórios recursivamente;
• -f → Remove sem perguntar.
Utilizando a opção “-r” é possível apagar diretórios recursivamente. Desta forma, sempre que for
necessário apagar um diretório que contenha arquivos, o comando “rm -r” deve ser utilizado no
lugar do comando “rmdir”.
Exemplo:
$ rm -rf a
Sistemas Operacionais
Marcio Quirino - 140
Neste caso, o arquivo a é removido, pois sendo a um diretório, ele será apagado, assim como
todos os seus arquivos e subdiretórios, sem que seja solicitada a confirmação.
B. cp
Copia arquivos e diretórios. É necessário especificar tanto a origem quanto o destino do
arquivo/diretório a ser copiado.
Sintaxe: cp [opções] origem destino
Na qual origem indica o arquivo ou diretório a ser copiado e destino indica o local para onde deve
ser copiado.
Algumas opções:
• -i → Pergunta antes de substituir um arquivo existente;
• -f → Substitui arquivos existentes sem perguntar;
• -r → Copia arquivos dos diretórios e subdiretórios da origem para o destino;
• -R → Copia arquivos e diretórios recursivamente, assim como os arquivos especiais FIFO
e dispositivos;
• -v → Mostra os nomes dos arquivos que estão sendo copiados;
• -p → Preserva atributos do arquivo;
• -u → Copia somente se o arquivo de origem é mais novo que o arquivo de destino ou
quando o arquivo de destino não existe.
Exemplo:
$ cp /etc/fstab .
$ cp teste.txt outro.txt
O primeiro exemplo copia o arquivo fstab que está no diretório /etc para o diretório de trabalho
(“.”). O segundo exemplo copia o arquivo teste.txt para outro.txt no mesmo diretório.
C. mv
Move ou renomeia arquivos e diretórios.
Sintaxe: mv [opções] origem destino
Em que origem indica o arquivo ou diretório a ser movido e destino indica o local para onde deve
ser movido.
Algumas opções:
• -f → Substitui o destino sem perguntar;
• -i → Pergunta antes de substituir;
• -v → Mostra os nomes dos arquivos que estão sendo movidos;
• -u → Move somente se o arquivo origem for mais novo que o arquivo destino, ou se o
arquivo não existir no destino.
Exemplo:
$ mv teste.txt outro.txt
$ mv outro.txt a/b/
No exemplo, o primeiro comando renomeia o arquivo teste.txt para outro.txt. O segundo comando
move o arquivo outro.txt para o diretório b que está dentro do diretório a.
D. cat
Mostra o conteúdo de um arquivo.
Sistemas Operacionais
Marcio Quirino - 141
Sintaxe: cat [opções] arquivo
Algumas opções:
• -n → Mostra o número das linhas enquanto o conteúdo do arquivo é exibido;
• -s → Não mostra mais que uma linha em branco entre um parágrafo e outro.
Exemplo:
$ cat /etc/host.conf
# The "order" line is only used by old versions of the C library.
order hosts,bind
multi on
E. find
Procura por arquivos/diretórios no disco, podendo ser por intermédio de diversas opções, como
nome, data de modificação, tamanho etc.
Sintaxe: find [diretório] [opções/expressão]
Em que diretório é o local onde se inicia a busca, percorrendo todos os seus subdiretórios.
Algumas opções/expressão:
• -name [expressão] → Procura pelo nome [expressão];
• -amin [num] → Procura por arquivos que foram acessados [num] minutos atrás. Caso for
antecedido por “-” (menos), procura por arquivos que foram acessados entre [num]
minutos atrás até agora;
• -atime [num] → Procura por arquivos que foram acessados [num] dias atrás. Caso for
antecedido por “-” (menos), procura por arquivos que foram acessados entre [num] dias
atrás e a data atual;
• -gid [num] → Procura por arquivos que possuam a identificação numérica do grupo igual
a [num];
• -group [nome] → Procura por arquivos que possuam a identificação de nome do grupo
igual a [nome];
• -uid [num] → Procura por arquivos que possuam a identificação numérica do usuário igual
a [num];
• -user [nome] → Procura por arquivos que possuam a identificação de nome do usuário
igual a [nome];
• -mmin [num] → Procura por arquivos que tiveram seu conteúdo modificado há [num]
minutos. Caso for antecedido por “-” (menos), procura por arquivos que tiveram seu
conteúdo modificado entre [num] minutos atrás até agora;
• -mtime [num] → Procura por arquivos que tiveram seu conteúdo modificado há [num] dias.
Caso for antecedido por “-” (menos), procura por arquivos que tiveram seu conteúdo
modificado entre [num] dias atrás até agora;
• -perm [modo] → Procura por arquivos que possuam os modos de permissão [modo];
• -size [num] → Procura por arquivos que tiverem o tamanho [num]. [num] pode ser
antecedido de “+” ou “-” para especificar um arquivo maior ou menor que [num].
Exemplo:
$ find /home -name outro.txt
/home/fabio/a/b/outro.txt
Neste caso, o comando procura por um arquivo ou diretório de nome outro.txt, fazendo a busca
a partir do diretório /home. O retorno do comando informa que o arquivo se encontra no
diretório /home/fabio/a/b.
Sistemas Operacionais
Marcio Quirino - 142
Links simbólicos e hardlinks
Links simbólicos são entradas de diretório que, ao invés de indicar um local no disco onde se
encontram dados de um arquivo, apontam para outras entradas de diretórios. Funcionam como atalho para
um arquivo ou diretório.
Para efeito de utilização, não faz diferença acessar um arquivo diretamente ou por intermédio de um
link simbólico. O processo é totalmente transparente para o usuário.
Tais entradas de diretório são bastante úteis para compatibilizar sistemas que procuram por arquivos
em diferentes locais. No lugar de manter cópias do mesmo arquivo, cria-se links simbólicos nos diferentes
locais onde os arquivos podem ser procurados, economizando espaço em disco e evitando inconsistências
que poderiam ser provocadas por arquivos que deveriam ser iguais mais possuem conteúdo diferente.
No Linux, um arquivo é acessado por meio de seu i-node. Uma entrada de diretório de arquivo deve
apontar para o i-node do arquivo de forma a poder acessar o seu conteúdo. O sistema de arquivos do Linux
permite que diferentes entradas de diretórios apontem para o mesmo i-node, fazendo com que essas
entradas apontem, na prática, para o mesmo arquivo.
Dica
A maneira de fazer com que diferentes entradas apontem para o mesmo arquivo se dá através de
um hardlink (denominado link duro por alguns autores).
Para criar links simbólicos e hardlinks, utiliza-se o comando ln, cuja sintaxe é:
ln [opções] alvo [nome_do_link/diretório]
• alvo é o arquivo/diretório que será referenciado pelo link;
• nome_do_link/diretório é o nome do link que será criado ou o diretório onde será criado o link
com o mesmo nome do alvo.
Algumas opções:
• -s → Cria um link simbólico;
• -v → Mostra o nome de cada arquivo antes de fazer o link;
• -d → Cria hardlink para diretórios.
Suponha que em um diretório exista um arquivo de nome “documento.txt”. O comando abaixo cria
no mesmo diretório um link simbólico chamado “simbolico.txt” que aponta para “documento.txt”.
$ ln -s documento.txt simbolico.txt
$ ls -lh
total 4,0K
-rw-r--r-- 1 fabio fabio 2,8K ago 29 21:05 documento.txt
lrwxrwxrwx 1 fabio fabio 13 ago 29 21:21 simbolico.txt -> documento.txt
Observe pela saída do comando “ls -lh” que “simbolico.txt” é um link simbólico para “documento.txt”.
Para criar um hardlink de nome “hard.txt” para o arquivo “documento.txt”, é utilizado também o
comando ln, mas sem a opção -s.
$ ln documento.txt hard.txt
$ ls -lhi
total 8,0K
404662 -rw-r--r-- 2 fabio fabio 2,8K ago 29 21:05 documento.txt
404662 -rw-r--r-- 2 fabio fabio 2,8K ago 29 21:05 hard.txt
396432 lrwxrwxrwx 1 fabio fabio 13 ago 29 21:21 simbolico.txt -> documento.txt
Sistemas Operacionais
Marcio Quirino - 143
A opção -i do comando ls (para listar o conteúdo do diretório) informa o número do i-node do arquivo.
Observe no exemplo acima que ambos “documento.txt” e “hard.txt”possuem o mesmo número de i-node
(404662), ou seja, ambos apontam para o mesmo arquivo.
Qualquer alteração feita no arquivo sendo acessado por qualquer um dos 3 nomes (“documento.txt”,
“simbolico.txt” ou “hard.txt”) acarretará alteração para todos, já que apontam para o mesmo arquivo.
Observe também, no último exemplo, para no número 2 antes do nome do dono do arquivo (fabio).
Isso indica que existem duas entradas de diretório apontando para o mesmo i-node (404662). O link
simbólico não é computado.
Resumindo
Isso significa que o sistema de arquivos mantém uma contagem de quantas entradas de diretório estão
apontando para o mesmo i-node (o mesmo arquivo).
Assim, se alguma das entradas de diretório for eliminada por rm ou por rmdir, o arquivo/diretório será
efetivamente eliminado somente se existir uma única entrada de diretório apontada para ele. Se houver mais
de um hardlink para o arquivo, somente a entrada de diretório é apagada.
4. Funcionamento dos principais editores de arquivos do
Linux
ASCII
ASCII (American Standard Code for Information Interchange - Código Padrão Americano para o Intercâmbio
de Informação) é um padrão de codificação de caracteres criado para padronizar a forma como os computadores
representam letras, números, acentos, sinais diversos e alguns códigos de controle.
Editor de arquivos x processador de textos
Arquivos de texto puro e documentos criados por processadores de texto como o Microsoft Word ou
o LibreOffice Writer possuem diferenças significativas.
Um arquivo de texto puro contém caracteres ASCII, cujo conteúdo pode ser entendido visualizando
diretamente o conteúdo do arquivo, sem necessidade de conversões ou interpretações.
Saiba mais
Alguns arquivos deste tipo, como o HTML, possuem marcações de controle, mas todos compostos por
caracteres ASCII.
Já documentos criados por um processador de texto possui conteúdo com formato específico,
possuindo vários caracteres de controle e, comumente, informações binárias. Neste tipo de arquivo, é
possível seleção de diferentes fontes para texto, com diferentes tamanhos e formatações (negrito, itálico
etc.), além da incorporação de tabelas, imagens e links, entre outros.
Um processador de textos muito utilizado por usuários do Linux é o Writer, ferramenta integrante do
pacote LibreOffice, um software livre de escritório com software para processamento de texto, planilha
eletrônica, apresentação, desenho, banco de dados, edição de equações matemáticas etc.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 144
Arquivos de texto puro são comumente utilizados para configuração de sistemas em um ambiente
Linux, e um administrador de sistemas precisa saber como editar tais arquivos. Neste módulo,
conheceremos alguns dos principais editores de texto puro disponíveis para utilização em sistemas Linux.
BSD
Berkeley Software Distribution (BSD) é um sistema operacional Unix desenvolvido na Universidade da
Califórnia em Berkeley.
vim
Vim é uma abreviação de Vi Improved (Vi Melhorado), sendo uma melhoria do editor de textos vi,
criado em 1976 para o BSD. O vi destacou-se por ser um editor pequeno e leve, podendo ser colocado junto
de mídias com pouco espaço de armazenamento para ser executado durante operações de manutenção de
emergência, ou em sistemas com pouco recurso computacional disponível. Por essas características, é um
editor de textos disponível em toda distribuição Linux, que pode ser utilizado tanto em emergências quanto
no dia a dia para alterar arquivos de configuração do sistema.
O vim é um editor modal, ou seja, possui diferentes modo de operação (comando e edição) e as
teclas possuem diferentes funções em cada modo. Ele é executado a partir de um terminal, sendo colocado
em execução digitando o comando “vim nome_do_arquivo” ou “vi nome_do_arquivo”, no
qual nome_do_arquivo é o nome do arquivo que será executado. A opção por utilizar vi ou vim depende da
distribuição.
Exemplo
No Ubuntu 20.04 LTS, utilizado em nossos exemplos, deve ser aplicada a forma “vi nome_do_arquivo”.
Sempre que o editor é aberto, ele inicia no modo de comando. Nesse modo, quando se digita algo
no teclado, são enviados comandos para que o editor execute determinadas ações sobre o texto que está
sendo editado. Quando estiver no modo de edição, todo o texto digitado será inserido na posição onde se
encontra o cursor.
Dica
Para sair do modo de edição e voltar ao modo de comando, pressione a tecla <Esc>.
Principais comandos do vim
Para que você possa utilizar o vim, serão apresentados alguns comandos básicos para utilização
deste poderoso editor de textos.
Clique nas barras para ver as informações.
A. Entrar em modo de edição
• i → Insere na posição atual.
• I → Insere no começo da linha.
• a → Acrescenta na posição atual.
• A → Acrescenta ao final da linha.
• o → Insere na linha abaixo, criando uma nova linha.
• O → Insere na linha acima, criando uma nova linha.
• cc → Deleta a linha e entra no modo de inserção.
• C → Deleta linha a frente do cursor e entra em modo de inserção.
• s → Deleta caractere e entra no modo de inserção.
• S → Deleta linha e entra no modo de inserção.
B. Sair do modo de edição
• Pressionar a tecla <Esc>.
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 145
C. Salvar e sair
• :w → Salva as alterações.
• :w arquivo → Salva as alterações no arquivo especificado. Funciona como “save as”.
Continuará editando (e salvando) o arquivo com o nome anterior.
• :sav arquivo → Salva as alterações no arquivo especificado. Funciona como “save as”.
Continuará editando (e salvando) o arquivo com o novo nome.
• :q → Sai.
• :q! → Força a saída sem salvar as alterações.
• :wq → Salva as alterações e sai.
D. Movimentação
A forma mais comum para movimentar o cursor ainda é por meio das setas do teclado, mas
podem ser utilizadas também as teclas alfabéticas.
• j → Baixo.
• k → Cima.
• l → Direita.
• h → Esquerda.
• 0 → Volta ao começo da linha (tecla zero).
• ^ → Volta ao começo da linha (duas vezes já que se trata de um acento).
• $ → Vai até o final da linha.
• w → Avança até a próxima palavra.
• e → Avança até o fim da palavra atual.
• b → Retorna ao início da palavra.
• f[caractere] → Pressione f seguido de algum caractere para posicionar o cursor na
próxima ocorrência desse caractere.
• t[caractere] → A mesma coisa para o f, mas posiciona um caractere antes do caractere
pressionado.
• gg → Retorna à primeira linha.
• G → Vai até a última linha.
• :[número da linha] → Vai até a linha especificada.
• ´´ → Volta até onde você estava antes de pular de posição.
E. Apagando
Em modo de edição, utilize as teclas <Backspace> e <Delete> para as correções normais.
Em modo de comando podem ser realizadas exclusões mais elaboradas.
• x → Apaga o caractere sob o cursor.
• X → Apaga o caractere antes do cursor.
• D → Apaga da posição atual até o fim da linha.
• J → Junta duas linhas. 5J juntará cinco linhas contíguas.
• dd → Apaga toda a linha.
• dj → Apaga 2 linhas abaixo.
• dk → Apaga 2 linhas acima.
• dw → Apaga até o fim da palavra.
• dt" → Apaga da posição atual até o fechamento das aspas.
• 5db → Apaga cinco palavras para trás.
• d[algum comando de posicionamento] - Combina o comando com qualquer outro
comando de posicionamento, veja os exemplos abaixo:
Sistemas Operacionais
Marcio Quirino - 146
F. Desfazendo alterações
• u → Desfazer.
• ^r → Refazer.
G. Copiando
• yy → Copia toda a linha.
• Y → Copia toda a linha.
• yw → Copia até o fim da palavra.
• y2j → Copia mais duas linhas abaixo.
• "+y → Copia para a área de transferência.
H. Colando
• p → Cola a partir da posição atual.
• P → Cola na posição atual.
• [p → Colar antes.
• ]p → Colar depois.
• "+gp → Colar da área de transferência.
I. Repetição de comandos
Para repetir um comando,digite antes um número representando a quantidade de vezes que
deseja repeti-lo. Exemplo:
• 3w - Avança três palavras
• 10k - Sobe dez linhas
• 2t" - Coloca o cursor antes da segunda aspas
• 3i[escreva e pressione a tecla <Esc>] → O que for digitado será inserido 3 vezes.
J. Buscar
• /txt → Busca pelo termo txt. Para ignorar a diferença entra maiúsculas e minúsculas, basta
incluir \c no termo da busca: /\ctxt - Realiza uma busca do termo digitado sem diferenciar
se está em maiúsculo ou minúsculo. /\Ctxt (C maiúsculo) força a diferenciação de
maiúsculas e minúsculas.
• n → Localiza a próxima ocorrência.
• N → Localiza a ocorrência na direção contrária.
• * → Localiza palavra sob o cursor.
• :set hlsearch → Destaca todos os termos encontrados (highlight).
• :set nohlsearch → Desabilita a funcionalidade.
• :set ignorecase → Configura todas as buscas para não diferenciar maiúsculas e
minúsculas.
É possível utilizar expressões regulares como padrão de busca!
Montagem de partições em Linux
Para execução deste exemplo, utilizaremos as partições criadas e formatadas nos exemplos anteriores:
Partição Tipo Tamanho
/dev/sdb1 ext4 3 GB
/dev/sdb2 NTFS 2 GB
/dev/sdb3 ext4 1 GB
As partições estão criadas em um disco (/dev/sdb) de 10 GB.
No Microsoft Windows®, cada letra de unidade (C:, D:, E:) identifica um sistema de arquivos em uma partição
de disco ou em um dispositivo, enquanto no Linux as partições são acessadas em diretórios, conhecidos como pontos
de montagem, e que fazem parte da estrutura do sistema de arquivos raiz.
Sistemas Operacionais
Marcio Quirino - 147
Cada partição pode ser montada em qualquer diretório.
No caso de um sistema de arquivos cheio, pode-se copiar o conteúdo de um diretório para outro sistema de
arquivos, apagar o conteúdo do diretório original e montar partição onde foram copiados os arquivos naquele local.
A adição de novas partições ou substituição de discos rígidos não afeta a ordem de identificação dos discos e
pontos de montagem.
Montagem de sistema de arquivos
A montagem de um sistema de arquivos é feita por intermédio do comando mount, cuja sintaxe é:
mount [dispositivo] [ponto de montagem] [opções]
Sendo:
• dispositivo → Identificação do dispositivo/partição que será montado.
• ponto de montagem → Diretório de onde o dispositivo/partição será montado.
Algumas opções:
• -t [tipo] → Tipo do sistema de arquivos utilizado (ext2, ext3, ext4, vfat, iso9660, etc.).
• -r → Monta a partição como somente leitura.
• -w → Monta a partição como leitura/gravação. É o padrão.
Desmontagem de sistema de arquivos
Para desmontar um sistema de arquivos utiliza-se o comando umount, cuja sintaxe é:
umount [dispositivo ou ponto de montagem]
Montando e desmontando sistemas de arquivos
Para a montagem dos sistemas de arquivos criados anteriormente, utilizaremos os seguintes pontos de
montagem:
Partição Ponto de montagem
/dev/sdb1 /extra/b1
/dev/sdb2 /extra/b2
/dev/sdb3 /extra/b3
O primeiro passo é montar a estrutura de diretórios que será utilizada para a montagem dos sistemas de
arquivos. Os comandos utilizados para a criação dos diretórios, que serão estuados adiante, são:
sudo mkdir -p /extra/b1
sudo mkdir /extra/b2
sudo mkdir /extra/b3
Uma vez criados os pontos de montagem, basta utilizar o comando mount para montar as partições:
sudo mount -t ext4 /dev/sdb1 /extra/b1
sudo mount -t ntfs /dev/sdb2 /extra/b2
sudo mount -t ext4 /dev/sdb3 /extra/b3
Neste momento, temos os sistemas de arquivos formatados e montados, prontos para utilização.
Uma forma de verificar os sistemas de arquivos montados e prontos para uso é por intermédio do comando “df
-h”. Na listagem a seguir podemos ver o resultado do comando. Para facilitar a leitura, foram omitidos alguns sistemas
de arquivos montados por padrão.
fabio@ubuntu20:~$ df -h
Sist. Arq. Tam. Usado Disp. Uso% Montado em
Udev 967M 0 967M 0%
/dev tmpfs 199M 1,4M 198M 1% /run
/dev/sda5 15G 6,1G 7,4G 46% /
/dev/sda1 511M 4,0K 511M 1% /boot/efi
/dev/sdb1 2,9G 9,0M 2,8G 1% /extra/b1
/dev/sdb2 2,0G 11M 2,0G 1% /extra/b2
/dev/sdb3 976M 2,6M 907M 1% /extra/b3
Sistemas Operacionais
Marcio Quirino - 148
Para desmontar os sistemas de arquivos montados, utiliza-se o comando umount.
sudo umount /dev/sdb1
sudo umount /dev/sdb2
sudo umount /dev/sdb3
Podemos verificar que os sistemas de arquivos foram desmontados executando novamente o comando “df -
h”.
Montagem automática de sistemas de arquivos
O arquivo /etc/fstab permite que as partições do sistema sejam montadas facilmente, especificando somente
o dispositivo ou o ponto de montagem. É possível também configurar para que os sistemas de arquivos sejam montados
automaticamente durante o boot. Este arquivo contém parâmetros sobre as partições que são lidos pelo comando
mount.
Cada linha deste arquivo contém a partição a ser montada, o ponto de montagem, o sistema de arquivos
utilizado pela partição e outras opções. O arquivo possui a seguinte estrutura:
Sistema_de_arquivos Ponto_de_Montagem Tipo Opções dump ordem
/dev/sda1 / ext4 defaults 0 1
/dev/sda2 /home ext4 defaults 0 2
/dev/sda3 /extra vfat defaults,noauto,rw 0 0
/dev/sda4 none swap sw 0 0
Em que:
• Sistema de Arquivos → Partição a ser montada.
• Ponto de montagem → Diretório onde a partição será montada.
• Tipo → Tipo de sistema de arquivos utilizado na partição que será montada.
• Opções → Especifica as opções usadas com o sistema de arquivos. Algumas opções:
o defaults → Utiliza valores padrões de montagem.
o noauto → Não monta os sistemas de arquivos durante a inicialização. o ro → Monta como
somente leitura. o user → Permite que usuários montem o sistema de arquivos.
o sync → Faz com que os dados sejam gravados imediatamente na unidade.
• dump → Especifica se será feito backup com utilitário dump, se estiver instalado. Podem ser colocados
os valores 0 (desativa backup) ou 1 (ativa backup).
• ordem → Define a ordem que os sistemas de arquivos serão verificados na inicialização do sistema.
Se usar 0, o sistema de arquivos não é verificado. O primeiro sistema de arquivos que deverá ser
verificado é o raiz.
Para o exemplo acima, após configurar o arquivo /etc/fstab, basta digitar o comando “mount /dev/sda3” ou
“mount /extra” para que a partição “/dev/sda3” seja montada no ponto de montagem “/extra”. Não é necessário
especificar o sistema de arquivos da partição, pois o mount verificará no arquivo /etc/fstab.
Partição de swap (memória virtual)
A partição de swap é utilizada para oferecer o suporte à memória virtual, em adição à memória RAM do
sistema.
Quando um processo começa a encher a memória RAM, o sistema operacional move automaticamente os
dados que não estão sendo usados para a partição de swap e libera parte da memória RAM para continuar carregando
os dados necessários. Quando os dados movidos para a partição de swap são solicitados, o sistema operacional os
move da partição de swap de volta para a memória RAM.
Criando o swap em uma partição
No disco criado anteriormente (/dev/sdb), vamos utilizar o espaço restante para criar a partição /dev/sdb4,
ocupando o restante do disco. Vamos ainda definir o tipo da partição como swap para que possa ser utilizada em
adição à memória RAM do sistema.
Para criar a partição, utilize o fdisk executando-o em um terminal. Para isso, digite no shell o comando “sudo
fdisk /dev/sdb”. Forneça a seguinte sequência de comandos:
Sistemas Operacionais
Marcio Quirino - 149
Comando Descrição
n Cria partição
p Tipo primária
Primeiro setor (embranco para valor padrão)
Último setor (em branco para utilizar todo o restante do disco)
t Alterar o tipo da partição
4 Selecionar a partição 4 para alteração do tipo
L Verificar o código hexadecimal para partição de swap
82 Identifica a partição como swap
w Sai gravando as alterações
Como ainda restavam 4 GB de espaço livre no disco, foi criada uma partição de 4GB do tipo Linux swap.
O programa usado para formatar uma partição de swap é o mkswap. Para criar uma partição de swap em
“/dev/sdb4”, por exemplo, deve-se executar o comando “sudo mkswap /dev/sdb4”.
A opção “-c” pode ser usada com o mkswap para verificar se existem clusters danificados na partição.
Com a partição de swap formatada, o comando “sudo swapon /dev/sdb4” deve ser utilizado para ativar a
partição de swap.
O swapon ativa a partição de swap até a próxima reinicialização do sistema. Para ativar esta partição de swap
para todas as seções, é necessário acrescentar no arquivo /etc/fstab uma linha como a abaixo:
/dev/sdb4 none swap sw 0 0
Se utilizar mais do que uma partição de swap, pode ser útil o uso da opção “-p NUM”, que especifica a
prioridade em que a partição de swap será usada. Partições com número maior serão usadas primeiro. Devem ser
utilizados números maiores para partições mais rápidas e números menores para partições mais lentas.
Caso precise desativar a partição de swap, use o comando: “swapoff <partição>”. Para o nosso exemplo, o
comando seria “sudo swapoff /dev/sdb4”.
Criando o swap em um arquivo
Também é possível criar um arquivo para ser utilizado como apoio à memória virtual.
Utilize o comando dd para criar um arquivo com o tamanho desejado. Os parâmetros do dd são:
• if → Arquivo fonte de onde os dados serão copiados.
• of → Arquivo destino para onde os dados serão copiados.
• bs → Quantidade de dados a ser copiado por bloco.
• count → Quantidade de blocos a ser copiado.
Para criar o arquivo /troca com 512 MB contendo valores o (zero), executar o comando:
sudo dd if=/dev/zero of=/troca bs=1024 count=524288
Após, executar o comando “sudo mkswap /troca” para formatar o arquivo. Feito isso, o sistema de arquivos
swap estará criado e pronto para ser utilizado.
Ativar o arquivo de swap com o comando “sudo swapon /troca”.
Para conferir a qualquer momento se o tamanho da memória virtual foi modificado pode-se utilizar o comando
“free”.
Podem ser usadas partições de swap e arquivos de swap ao mesmo tempo.
O swapon ativa o arquivo de swap até a próxima reinicialização do sistema. Para ativar este arquivo de swap
para todas as seções, é necessário acrescentar no arquivo /etc/fstab uma linha como a abaixo:
/troca none swap sw 0 0
Sistemas Operacionais
Marcio Quirino - 150
nano
Assim como vim, o nano é também um editor de texto para linha de comando que está presente na
grande maioria das distribuições Linux. Mas ao contrário do vim, é um editor que não possui diferentes
modos de operação, sendo bem mais intuitiva sua utilização.
O nano é executado a partir de um terminal digitando o comando “nano nome_do_arquivo”, no
qual nome_do_arquivo é o nome do arquivo que será executado. O programa já inicia sua execução com o
arquivo aberto para execução.
Editor de texto nano executando em um terminal do Linux.
Os principais comandos do nano ficam disponíveis de forma visual na parte inferior da tela. São eles:
• <CTRL>+G → Obter ajuda;
• <CTRL>+O → Salvar o arquivo;
• <CTRL>+X → Sair;
• <CTRL>+R → Abrir arquivo;
• <CTRL>+W → Procurar no texto;
• <CTRL>+\ → Pesquisar e substituir;
• <CTRL>+K → Copia a linha para a memória e a apaga;
• <CTRL>+U → Cola o que estiver na memória;
• <CTRL>+J → Quebra a linha em várias linhas de forma a caberem na janela;
• <CTRL>+C → Mostra uma linha indicando a posição em que se encontra no texto;
• <CTRL>+L → Ir para a linha.
Dica
Consulte a lista de todos os comandos disponíveis abrindo o editor de textos nano e teclando <CTRL>+G.
gedit
É bom que o administrador de sistemas Linux tenha conhecimentos de utilização de editores de texto
de linhas de comando, pois em uma emergência, em que não é possível utilizar o sistema pela interface
Sistemas Operacionais
Marcio Quirino - 151
gráfica, ele poderá editar os arquivos de configuração do sistema sem maiores dificuldades. Porém, quando
existe uma interface gráfica disponível para utilização, pode ser mais confortável a utilização de um editor
de textos em modo gráfico.
Como as distribuições Linux são livres para escolherem as interfaces gráficas que comporão o
sistema, e cada uma possui seu próprio editor de textos puro, o editor utilizado na interface gráfica dependerá
da interface utilizada.
O Ubuntu 20.04 LTS, sistema operacional Linux aplicado em nossos exemplos, utiliza o GNOME
como interface gráfica. O editor de textos puro do GNOME é conhecido como gedit e pode ser colocado em
execução de diferentes formas.
A primeira delas é navegando pelos arquivos utilizando o gerenciador de arquivos do GNOME e
clicando duas vezes sobre o nome do arquivo. Se for um arquivo cujo formato esteja configurado para ser
aberto no gedit, ele será colocado em execução.
A segunda forma é abrindo a tela de aplicativos do GNOME e selecionando o gedit. Normalmente
aparece com o nome “Editor de texto”.
Uma terceira forma de abrir o gedit é por intermédio do shell. Abra um terminal, entre no diretório
onde se encontra o arquivo que deseja editar e entre com o comando “gedit <nome_do_arquivo>” para que
o arquivo seja aberto no gedit. Ou simplesmente digite gedit para iniciar o gedit.
Na imagem a seguir podemos ver a tela do gedit com um arquivo em edição:
Tela do gedit (editor padrão do GNOME).
No canto superior esquerdo da tela (1) existe um botão “Abrir”. Ele pode ser utilizado para abrir um
novo documento no gedit.
Atenção
Ao abrir um novo documento, o anterior também permanece em edição, sendo que a tela do gedit é dividida
verticalmente para permitir a visualização de ambos.
Sistemas Operacionais
Marcio Quirino - 152
À direita do botão, existe um triângulo invertido (2) que pode ser utilizado para buscar rapidamente
os últimos arquivos que foram abertos no editor. Ainda próximo ao triângulo, existe um botão (3) utilizado
quando se deseja criar um arquivo.
No centro (4) aparece o nome do arquivo e edição, e o nome do diretório onde o arquivo se encontra.
O botão “Salvar” (5) é utilizado para salvar o arquivo em edição a qualquer momento.
À direita do botão “Salvar” (6) fica o botão para acesso ao menu do editor, onde existem várias
opções e ferramentas, entre elas opções para localizar e imprimir. Abaixo na janela, na barra de status, é
possível selecionar o tipo do arquivo em edição (7).
Dica
Normalmente, o gedit seleciona automaticamente o tipo do arquivo e utiliza-o para auxiliar o usuário na edição
do arquivo.
No exemplo, está sendo editado um arquivo em Linguagem C, então o gedit auxilia fazendo
automaticamente as tabulações e colorindo as palavras reservadas por tipo. É possível também escolher a
largura da tabulação (8) adicionada quando é pressionada a tecla “Tab”. À direita, é informada a posição do
cursor (linha, coluna) e por fim é mostrado (10) se a entrada de dados está em modo de inserção ou
de sobreposição.
Considerações finais
Acabamos de estudar o funcionamento dos sistemas de arquivos utilizados pelos sistemas
operacionais modernos. Para isso, iniciamos nossa jornada entendo os conceitos de arquivos e diretórios,
e como são as técnicas de implementação destes objetos pelos diferentes sistemas de arquivos. Além disso,
vimos como um sistema de cache é importante para aumentar o desempenho de acesso.
Após o estudo conceitual, observamos como administrar um sistema de arquivos no sistema
operacionalLinux. Para tanto, compreendemos como é o funcionamento de um disco rígido e como
particioná-lo. Após particionar e definir o tipo do sistema de arquivos, aprendemos a formatá-lo e fazer sua
montagem sobre o sistema de arquivos raiz do sistema operacional.
No entanto, não basta criar e montar o sistema de arquivos, é preciso também saber como gerenciá-
lo. Por conta disso, aprendemos diversos comando para gerenciamento de arquivos e diretórios no Linux,
assim como criar links simbólicos e hardlinks.
Por fim, conhecemos sobre os principais editores de texto puro utilizados pelo Linux. Uma percepção
importante para que os administradores de sistemas Linux possam recuperar o sistema na eventualidade
de uma falha grave.
Referências
ARCHLINUX. Partitioning. Consultado em meio eletrônico em 22 ago. 2020.
DEITEL, H.M. Sistemas Operacionais. 3 ed. São Paulo: Pearson Prentice Hall, 2005.
MACHADO, F. B. & MAIA, L. P. Arquitetura de Sistemas Operacionais. 5 ed. Rio de Janeiro: LTC,
2013.
NEMETH, E. et al. Manual Completo do Linux: Guia do Administrador. 2 ed. São Paulo: Pearson
Prentice Hall, 2007.
UBUNTU. Official Ubuntu Documentation. Consultado em meio eletrônico em 2 ago. 2020.
SILBERSCHATZ, A. Fundamentos de Sistemas Operacionais. 9 ed. Rio de Janeiro: LTC, 2013.
Sistemas Operacionais
Marcio Quirino - 153
SILBERSCHATZ, A. & GALVIN, P. B. Sistemas Operacionais – Conceitos. 5 ed. São Paulo: Prentice
Hall, 2000.
SILVA, G. M. Guia Foca GNU/Linux. In: Guia Foca.
SOARES, L. F. G. Redes de Computadores – Das LANs, MANs e WANS às Redes ATM. 2 ed. Rio
de Janeiro: Campus, 1995.
TANENBAUM, A. S. & BOS, H. Sistemas Operacionais Modernos. 4 ed. São Paulo: Pearson
Educacional do Brasil, 2016.
TANENBAUM, A. S. & WOODHULL, A. S. Sistemas Operacionais – Projeto e Implementação. 3 ed.
Porto Alegre: Bookman, 2008.
TANENBAUM, A. S. Redes de Computadores. 5 ed. São Paulo: Pearson, 2011.
TANENBAUM, A. S. Structured Computer Organization. 3 ed. Prentice Hall International, 1990.
Página consultada: NBCGIB
Explore+
Para saber mais sobre os assuntos tratados neste tema, pesquise:
LVM (Logical Volume Manager — gerenciador de volume lógico) disponível em Archlinux;
Tópicos: Listas de controle de acesso e Adicionando novos usuários do livro NEMETH, E. et al.
Manual Completo do Linux: Guia do Administrador. 2 ed. São Paulo: Pearson Prentice Hall, 2007.
Acesse os sites:
Distrowatch. Sobre distribuições Linux;
Libreoffice. Para mais detalhes sobre essa ferramenta.
Sistemas Operacionais
Marcio Quirino - 154
Automatizando Tarefas no Linux
Descrição
Ferramentas para automatização de tarefas em um sistema operacional Linux, empregando a
ferramenta CRON e programação SHELL SCRIPT.
Propósito
Conhecer ferramentas de automatização do Linux essenciais para tarefas do dia a dia em servidores
e estações de trabalho permitirá que tarefas rotineiras de administração e gerência de sistemas Linux sejam
realizadas de forma mais eficiente e eficaz.
Preparação
Para melhor aproveitamento do curso, recomendamos dispor de uma máquina Linux para treinar
com os exemplos apresentados. Caso não disponha de um computador com o Linux instalado, há duas
formas simples de obtê-lo, sem precisar reinstalar seu computador:
Mais simples e rápido: No Windows 10, instale o “Linux Subsystem” (da própria Microsoft). Em
seguida, na loja Microsoft Store, instale uma distribuição Linux. Sugestão: Ubuntu Linux.
Mais avançado: Instale uma máquina virtual utilizando um Hypervisor como o Virtualbox ou o
Microsoft Hyper-V. Em seguida, baixe o CD de instalação de uma distribuição, à sua escolha, e instale-o na
máquina virtual.
Introdução
Imagine que você é o administrador de um servidor Linux, responsável por um importante serviço da
empresa e, ao final de cada dia, um relatório sobre esse serviço deve ser enviado para os gestores. Para
emitir esse relatório diário, são necessários alguns comandos e a contabilização de resultados. Em seguida,
os números obtidos precisam ser digitados e enviados por e-mail.
A situação acima é muito comum e faz parte do dia a dia de muitos administradores, com tarefas
rotineiras e repetitivas. Mas o cenário também descreve uma condição de baixa eficiência do trabalho, já
que exige um esforço manual para realizar uma tarefa recorrente. Pense em todo o tempo acumulado, ao
longo de meses ou anos, para a realização de uma única e específica tarefa, repetidamente. Por fim, a
pessoa responsável por essa tarefa deverá estar disponível nos dias e horários determinados, e sua
ausência precisará ser suprida por outra pessoa, ou a tarefa não será realizada.
Para cenários assim, existem ferramentas capazes de automatizar tarefas em sistemas operacionais,
como o Linux. Neste curso, aprenderemos como programar SCRIPTS para realizar tarefas concatenando
sequências de comandos. Aprenderemos, também, a utilizar o serviço CRON para agendar a execução
automática de processos.
1. Agendamentos, por meio da ferramenta CRON
Conceitos
A necessidade de automatizar a execução de processos não é nova, e ainda nos anos 1970, nas
primeiras versões do sistema operacional UNIX, foi criada uma ferramenta específica para essa função, que
recebeu o nome de CRON.
De simples configuração, o CRON permite determinar dias e horários em que comandos serão
executados, de forma automática e sem a intervenção de usuários.
Sistemas Operacionais
Marcio Quirino - 155
Ferramenta multiusuário
É importante entender que o CRON é uma ferramenta multiusuário. Na prática, isso significa que
cada usuário no sistema operacional poderá ter sua configuração própria e independente dos demais
usuários.
No CRON, os processos são sempre executados pelo usuário a quem pertence a configuração.
Por exemplo, considere duas configurações de CRON, aqui representadas com palavras:
Exemplo
– Usuário ‘root’
Executar todos os dias às 23h o comando: /usr/limpeza
- Usuário ‘bob’
Executar todos os domingos às 9h o comando: /home/bob/relatorio_semanal
O CRON se encarregará de executar, todos os dias, às 23h o comando ‘/usr/limpeza’.
Esse comando será executado pelo usuário ‘root’ no sistema operacional.
Da mesma forma, todos os domingos, às 9h, o comando ‘/home/bob/relatorio_semanal’ será executado pelo
usuário ‘bob’, automaticamente, através do CRON.
Atenção
Lembre-se: Como o CRON é um serviço multiusuário, sempre considere qual usuário executará os comandos
configurados. Esteja atento para as permissões que serão necessárias para cada comando ser executado com
sucesso.
Como configurar o cron de usuário
crontab
O ‘TAB’ se refere a tabelas.
A configuração que cada usuário possui no CRON é chamada de CRONTAB. Para manipular uma
CRONTAB, usaremos o comando com esse mesmo nome.
Para editar o CRONTAB, é usado o comando:
$ crontab -e
Observação:
Ao usar o comando ‘crontab’ pela primeira vez, poderá ser apresentada uma opção para escolha do editor de
texto.
Assim como em quase tudo no Linux, o CRONTAB também é um arquivo texto.
Dica
Escolha o editor com que se sinta mais confortável. Se não tiver certeza, prefira um editor mais simples como
o ‘nano’.
$ crontab -e
no crontab for bob - using an empty one
Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed
javascript:void(0)
Sistemas Operacionais
Marcio Quirino - 156
Quando terminar de editar o CRONTAB, você deverá salvá-lo. Se for o editor ‘nano’, use a
combinação de teclas Crtl-X.
Ao abrir o editor, o CRONTAB poderá estar preenchido com diversas informações e instruções que
as distribuições Linux incluem para auxiliar os usuários.
Regras e formatos do crontab
Todas as linhas sem conteúdo são ignoradas pelo CRON.
• Todas as linhas começadas com ‘#’ são tratadas como comentários e, portanto, ignoradas
peloCRON. Em geral, os editores mostrarão essas linhas com cores destacadas para facilitar
a visualização.
• Cada tarefa será configurada em uma (e somente uma) linha, que deverá conter os campos
requeridos pelo CRON.
• Cada linha possui 6 ou mais campos de configuração, separados por espaços.
• Os cinco primeiros campos de cada linha representarão as configurações de tempo, ou seja,
quando cada tarefa deverá ser executada.
• O sexto campo e os demais representam o comando que será executado pelo CRON, no dia
e horário estabelecido.
A. Minuto
Número de 0 a 59, representando o minuto em que a tarefa será executada.
B. Hora
Número de 0 a 23, representando a hora em que a tarefa será executada.
C. Dia do mês
Número de 1 a 31, representando o dia do mês.
D. Mês
Número de 1 a 12 (1 – janeiro, 2 – fevereiro etc). Também são admitidas abreviações, em inglês,
‘jan’, ‘feb’, ‘mar’...
E. Dia da semana
0 → domingo
1 → segunda-feira
2 → terça-feira...
O dia da semana costuma causar confusão entre nativos da língua portuguesa, pois nesse idioma
alguns dias da semana são numerais (segunda, terça, quarta...). Esteja atento para isso.
O CRON também admite o uso da máscara ‘*’ para representar ‘qualquer valor’.
Vejamos alguns exemplos:
Sistemas Operacionais
Marcio Quirino - 157
O CRON executará ‘/bin/comando’ todos os dias, sempre às 17h30.
30 17 10 * * /bin/comando
O CRON executará ‘/bin/comando’ no dia 10 de todos os meses, às 17h30.
30 17 * 12 * /bin/comando
O CRON executará ‘/bin/comando’ todos os dias do mês de dezembro, às 17h30.
30 17 * * 5 /bin/comando
O CRON executará ‘/bin/comando’ nas sextas-feiras, às 17h30.
30 17 10 3 * /bin/comando
O CRON executará ‘/bin/comando’ no dia 10 de março, às 17h30.
30 17 * 3 1 /bin/comando
O CRON executará ‘/bin/comando’ todas as segundas-feiras do mês de março, às 17h30.
O CRON também permite incluir mais de um valor por campo e criar agendamentos sofisticados.
• Valores separados por vírgula: A execução será disparada quando qualquer um dos valores
for atendido.
• Valores separados por traço: Indica intervalo, e o comando será executado quando qualquer
um dos valores no intervalo for atendido.
0,15,30,45 * * * * /bin/comando
Nesse exemplo, o comando será executado nos minutos 0, 15, 30 e 45, todas as horas do dia,
todos os dias.
0,15,30,45 0,12 * * * /bin/comando
Ao incluir as horas 0 e 12, o comando será executado todos os dias em 8 horários:
• 0h, 0h15, 0h30, 0h45, 12h, 12h15, 12h30 e 12h45.
0,15,30,45 0,12 * * 1-5 /bin/comando
O comando será executado nos horários do exemplo acima, porém somente de segunda a sexta-
feira (1-5).
0,30 8-17 * * 1-5 /bin/comando
O comando será executado nos minutos 0 e 30, das horas 8 a 17 e somente de segunda a sexta-
feira. Ex: 8h, 8h30, 9h, 9h30 .... 16h30, 17h e 17h30.
Também é possível definir o período entre intervalos usando o caractere ‘/’. Por exemplo:
0 */2 * * * /bin/comando
O comando será executado no minuto 0 a cada duas horas (/2): 0h, 2h, 4h, 6h ... 22h.
0/10 * * * * /bin/comando
O comando será executado a cada 10 minutos: 0h, 0h10, 0h20....
Sistemas Operacionais
Marcio Quirino - 158
Exemplo de uma tarefa automática de backup
Como vimos, os cinco primeiros campos de cada linha correspondem ao agendamento da execução.
A partir do 6º campo você deve incluir o comando que será executado, com seus parâmetros, exatamente
como faria em um terminal.
O comando ‘tar -cfz /tmp/backup.tar.gz /home/bob’ cria um arquivo TAR comprimido, com todo o
conteúdo do diretório /home/bob. O nome desse arquivo será backup.tar.gz e estará dento do diretório /tmp.
É um tipo de comando muito usado para fazer o backup de diretórios de usuários ou de aplicações em geral.
Se quisermos automatizar essa tarefa para que seja executada todos os dias às 20h, devemos incluir
a linha abaixo no CRONTAB:
0 20 * * * tar -cfz /tmp/backup.tar.gz /home/bob
Lembre-se: o CRON é um serviço multiusuário. O usuário que for executar o comando ‘tar’ deverá
ter, ao mesmo tempo, permissão para ler o conteúdo do diretório de origem (/home/bob) e permissão para
escrever no diretório de destino (/tmp).
Atenção
Importante: Não use o comando ‘sudo’ dentro do CRONTAB. O comando ‘sudo’ é interativo e requer a digitação
de senha, o que não será possível enquanto estiver sendo executado pelo CRON. Em vez de usar o ‘sudo’, configure
o comando diretamente no CRONTAB do usuário ‘root’.
A configuração de cron para o sistema
Além das configurações individuais por usuário, o CRON também pode ter uma configuração global,
para o todo o sistema. Em geral, esse tipo de configuração é usado para rotinas de serviços e processos do
sistema.
Exemplo
Alguns exemplos:
• Rotação periódica de arquivos de logs, apagando arquivos antigos.
• Restart automático de processos que necessitem desse tipo de operação.
• Verificação de atualizações disponíveis e notificação.
• Verificação de dispositivos de hardware e notificação dos problemas encontrados.
O arquivo usado é o /etc/crontab. Como ele define uma configuração para todo o sistema, seu
formato exige um campo a mais em cada linha, que é a indicação do usuário que executará cada comando.
30 10 * * * root /bin/comando
No exemplo acima, a indicação de que o CRON deverá executar o comando como o usuário ‘root’.
As principais distribuições Linux trazem a configuração global com uma organização prática, já que
rotinas poderão ser incluídas e excluídas à medida que serviços são instalados e desinstalados no servidor.
Para que o arquivo /etc/crontab não precise ser alterado, é costume criar diretórios para as rotinas
diárias, semanais e mensais. Em cada diretório serão criados programas (scripts) para cada uma das tarefas
definidas.
E no arquivo principal, /etc/crontab, é configurada a execução de todos os SCRIPTS em cada um
dos diretórios, nos horários agendados.
Esses diretórios costumam ter os nomes como abaixo:
• /etc/cron.hourly → Rotinas de hora em hora.
• /etc/cron.daily → Rotinas diárias.
Sistemas Operacionais
Marcio Quirino - 159
• /etc/cron.weekly → Rotinas semanais.
• /etc/cron.monthly → Rotinas mensais.
Para exercitar: Procure esses diretórios na sua máquina Linux e veja os seus conteúdos.
2. SCRIPTS para a automatização de tarefas no terminal
Linux
Conceitos
Uma das características do terminal (SHELL) de comandos dos sistemas baseados no Unix, incluindo
o Linux, é a possibilidade de desenvolvimento de lógicas avançadas por meio de SCRIPTS.
Os SCRIPTS são arquivos-texto com sequências de comandos a serem executados pelo SHELL.
Comentário
Por serem lidos e interpretados diretamente pelo SHELL, não há necessidade de um processo de compilação
como em diversas linguagens de programação. Após escritos, os arquivos contendo os SCRIPTS podem ser
executados imediatamente.
O SHELL do Linux permite a criação de variáveis de ambiente e possui comandos para comparação
e operação de valores, além de estruturas de repetição. Combinando esses recursos, podemos automatizar
tarefas complexas.
Os SCRIPTS mais simples são aqueles que simplesmente executam diversos comandos em
sequência, economizando nosso tempo. Os mais complexos podem analisar os resultados dos comandos e
tomar decisões para os comandos seguintes, executando a tarefa com segurança, integridade e eficiência.
O interpretador de shell
Existem diversos interpretadores disponíveis para o Linux, e as sintaxes para SCRIPTS variam entre
eles. Atualmente, as principais distribuições adotam o interpretador BASH como o padrão, portanto vamos
utilizá-lo como referência nesse curso.
A palavra mágica que identifica um script
O Linux nos permite executar um SCRIPT da mesma forma que executamos um comando ou
programa qualquer, mas isso tem uma implicação: Precisamos informar que o conteúdo daquele arquivo é
um SCRIPT e não um arquivo qualquer.Para isso, todo SCRIPT deve ser iniciado com uma palavra mágica, composta por dois caracteres
‘#!’ seguidos do caminho para o interpretador de SHELL.
#!/bin/bash
Neste exemplo estamos declarando que o arquivo é um SCRIPT e deverá ser interpretado pelo
BASH, sendo /bin/bash o caminho completo para esse programa interpretador.
Um script muito simples
Vamos fazer um SCRIPT muito simples, contendo apenas um comando. O nome desse SCRIPT será
‘script1’ e criaremos em nosso diretório HOME.
Para começar, abrimos um editor de texto comum, como o ‘vi’ ou ‘nano’.
$ nano script1
Dentro do arquivo vamos incluir apenas duas linhas:
Sistemas Operacionais
Marcio Quirino - 160
1. A palavra mágica.
2. O comando ‘date’, que apenas retornará a data e hora atual.
#!/bin/bash
date
Já percebemos que esse exemplo é um SCRIPT muito simples, sem grande utilidade, já que terá o
mesmo efeito de simplesmente digitarmos o comando ‘date’. Mas o objetivo agora é rodar o nosso primeiro
SCRIPT.
Salve o arquivo e saia do editor. Se estiver usando o ‘nano’, use Ctrl-X, confirme que deseja salvar
o arquivo e escolha o nome (‘script1’).
Se agora usarmos o comando ‘ls’ para ver o conteúdo do diretório, encontraremos o nosso ‘script1’
entre os arquivos. Porém, se tentarmos digitar ‘script1’ no PROMPT, receberemos um erro do tipo “comando
não encontrado”.
Há duas questões que ainda precisam ser tratadas:
1. A primeira delas é que criamos um arquivo texto, mas em momento algum autorizamos que
ele seja executado, e essa é uma característica de segurança importante do Linux.
Não basta criar um arquivo texto para que ele se comporte como um programa, é necessário
dar permissão de execução para ele.
Para dar essa permissão, usaremos o comando ‘chmod’.
$ chmod u+x script1
Nesse exemplo, concederemos a permissão de execução para o proprietário (OWNER) do
arquivo. Caso deseje-se que qualquer usuário possa executá-lo, podemos usar o parâmetro
‘a+x’.
Ao usar o comando ‘ls -l’, veremos que a permissão foi concedida:
-rwxr--r-- 1 bob bob 17 Sep 14 14:50 script1
Se tentarmos executar o comando agora, ainda receberemos um erro, o que nos leva à segunda
questão, outro mecanismo de proteção do Linux: Apenas programas em alguns diretórios do sistema podem
ser executados sem a necessidade de indicar o seu caminho.
2. Como o SCRIPT foi criado no HOME e esse não é um dos diretórios permitidos, precisaremos
indicar o caminho, que pode ser utilizando o caminho absoluto ou relativo.
Para executar o ‘script1’ que está no diretório atual, vamos empregar o caminho relativo, com
o comando abaixo:
./script1
Onde ./ é um caminho que significa ‘o diretório atual’.
Ao executar o SCRIPT teremos no terminal a saída do comando ‘date’, como esperávamos.
$ ./script1
Mon Sep 14 16:29:45 -03 2020
Linhas em branco e comentários
Você sabia
As linhas sem conteúdo e as iniciadas com ‘#’ são ignoradas pelo interpretador.
Sistemas Operacionais
Marcio Quirino - 161
As linhas iniciadas com ‘#’ podem ser usadas para comentários, observações e instruções sobre o SCRIPT.
Não confunda com a palavra mágica na primeira linha: Ela começa com ‘#’ e é importante.
Incluindo textos na resposta do script
Nosso primeiro objetivo foi atingido: Criar um SCRIPT simples e executá-lo. O próximo passo é
evoluir esse SCRIPT e mudar seu comportamento.
Nesse exemplo, vamos mudar a saída para indicar somente a hora e com um texto mais amigável.
Desejamos que a saída seja assim quando o SCRIPT for executado:
Hora certa 12:30
Para fazer essa mudança, precisamos:
1. Inserir um comando para exibir o texto “Hora certa: “
2. Modificar o comando ‘date’ para mostrar a hora no formato HH:MM (onde HH é a hora com
dois dígitos e MM o minuto, também com dois dígitos).
A mudança no ‘date’ é fácil. Consultando a MANPAGE do comando, vemos que é possível determinar
a saída passando o formato desejado como argumento:
$ date +%H:%M
Saiba Mais
(%H será substituído pela hora e %M pelo minuto. Para mais formatos, consulte a MANPAGE do comando
‘date’).
O texto pode ser inserido usando-se o comando ‘echo’. Vamos mudar o SCRIPT para:
#!/bin/bash
echo "Hora certa"
date +%H:%M
O comando ‘echo’ envia para a saída padrão (stdout) o texto passado como parâmetro.
Ao executar, teremos como saída:
$ ./script1
Hora certa
12:30
As informações que desejamos já estão presentes, porém em linhas diferentes. Isso ocorre porque
o comando ‘echo’ insere uma quebra de linha após o texto. É possível inibir a quebra de linha passando o
parâmetro ‘-n’ para o comando ‘echo’. Porém, vamos adaptar o SCRIPT colocando tudo no mesmo ‘echo’.
#!/bin/bash
echo "Hora certa $(date +%H:%M)”
Quando colocamos o comando entre ‘$(‘ e ‘)’ estamos dizendo ao ‘echo’: Execute esse comando e
envie o resultado junto ao texto para a saída. Então, ao executarmos o script, teremos:
$ ./script1
Hora certa 12:30
Evoluindo o nosso SCRIPT para informar a data e hora:
#!/bin/bash
echo "Data: $(date +%d/%m/%Y) Hora: $(date +%H:%M)"
Ao executar, teremos a resposta:
$ ./script1
Data: 15/09/2020 Hora: 16:44
Sistemas Operacionais
Marcio Quirino - 162
Repare nesse último exemplo que o comando ‘date’ foi executado duas vezes dentro do ‘echo’.
Inserindo caracteres especiais no comando ‘echo’
O comando ‘echo -e’ permite o uso de caracteres especiais. Por exemplo, para adicionar uma quebra
de linha, pode ser usada a combinação ‘\n’.
Ainda no nosso SCRIPT anterior:
#!/bin/bash
echo -e "Data: $(date +%d/%m/%Y)\nHora: $(date +%H:%M)"
Ao executar, teremos a resposta:
$ ./script1
Data: 15/09/2020
Hora: 16:44
Criando pausas na execução de um script
Em muitas situações, pode ser interessante, ou necessário, incluir pausas na execução de um
SCRIPT. O comando ‘sleep’ permite realizar uma pausa com tempo configurável.
Vamos criar o arquivo ‘script2’ para essa demonstração:
#!/bin/bash
date +%T
sleep 1
date +%T
sleep 2
date +%T
sleep 3
date +%T
No ‘script2’ o comando ‘date’ é executado 4 vezes. Entre eles foi usado o comando ‘sleep’ com
diferentes tempos. O número passado como parâmetro ao ‘sleep’ corresponde ao tempo, em segundos, de
pausa.
Lembrando: Para executar o ‘script2’, é necessário conceder permissão de execução.
$ chmod u+x script2
Ao executá-lo, obtemos:
$ ./script2
13:05:29
13:05:30
13:05:32
13:05:35
Repare, na indicação dos segundos, os intervalos do comando ‘sleep’, como programado.
Em muitos casos, pode ser interessante aguardar uma interação do usuário. O comando ‘read’
suspende a execução do SCRIPT até que o usuário tecle ENTER.
Substituindo no SCRIPT os comandos ‘sleep’ por ‘read’.
#!/bin/bash
Sistemas Operacionais
Marcio Quirino - 163
date +%T
read
date +%T
read
date +%T
read
date +%T
O SCRIPT aguardará que o usuário digite ENTER após executar cada comando ‘date’. Comparando-
se as horas, será possível saber quanto tempo o SCRIPT aguardou por cada interação do usuário.
$ ./script2
13:09:12
13:09:14
13:09:16
13:09:18
As linhas em branco foram inseridas pela resposta do comando ‘read’ ao ENTER do usuário.
Dica
Experimente incluir o parâmetro ‘-s’ nos comandos ‘read’ para que as linhas em branco não apareçam.
Apagando todo o conteúdo do terminal
Às vezes, pode ser interessante apagar todo o conteúdo do terminal no início ou durante a execução
de um SCRIPT, facilitando a visualização das respostas. Para isso, podemos usar o comando ‘clear’.
Modificando o SCRIPT anterior, foi incluído um comando ‘clear’ no início do arquivo:
#!/bin/bash
clear
date +%T
read
date +%T
read
date +%T
read
date +%T
O resultado será igual ao anterior, porém o terminal estará limpo e a resposta começará na primeira
linha.
Terminando um script com valor de retorno
Uma característica de processos no Linux é que eles terminam retornando um valornumérico para
o sistema operacional, chamado de “return value” e que serve para indicar o sucesso da execução ou a
ocorrência de erros.
Comentário
O valor de retorno pode ser de 0 a 255, e a regra geral é de que um valor de retorno 0 indica execução com
sucesso. Os demais valores, de 1 a 255, podem ser usados para indicar o tipo de erro durante a execução.
O comando ‘exit’ permite terminar o SCRIPT retornando um valor.
Por exemplo, se um SCRIPT contiver a linha abaixo:
exit 1
Quando o ‘exit’ for executado, o SCRIPT será imediatamente encerrado e com valor de retorno igual
a ‘1’.
Se o comando ‘exit’ for executado sem o parâmetro de valor, ele retornará o resultado do último
comando no SCRIPT. Isso também vale para um SCRIPT que não termina através do comando ‘exit’.
Sistemas Operacionais
Marcio Quirino - 164
3. Variáveis de ambiente e estruturas de decisão em
SCRIPTS
Conceitos
Assim como nas linguagens de programação, o terminal BASH também permite a criação de
variáveis em seu ambiente.
Comentário
As variáveis são, de modo simplificado, um espaço para o armazenamento de informações.
Utilizando variáveis podemos compartilhar dados entre vários comandos e tomar decisões para
desviar o fluxo de execução do SCRIPT.
Toda variável possui:
A. Nome
O nome de uma variável é a chave pela qual faremos referência a ela.
B. Valor
O valor é a informação armazenada.
Atribuição de valores a uma variável
Para declarar uma variável, atribuindo valor a ela, usamos a sintaxe abaixo:
VAR=1
Nesse exemplo, declaramos uma variável com o nome “VAR” e a ela foi atribuído o valor “1”. Se a
variável VAR já existir, o valor anterior será perdido e substituído pelo novo.
Atenção
Na atribuição acima não deve haver espaços antes ou depois do sinal de igualdade, o que faria o terminal
interpretar como a tentativa de executar o comando ‘VAR’ ou ‘VAR=’
Para atribuir textos com espaços, é necessário o uso de aspas:
VAR=”Terminal do Linux”
O nome da variável é sempre case-sensitive, ou seja, o terminal distingue caracteres maiúsculos e
minúsculos no nome das variáveis.
Referenciando variáveis
Para fazer referência a uma variável, e obter o seu conteúdo, é necessário usar o símbolo $ à
esquerda do nome. Se a variável se chama VAR, faremos referência a ela como $VAR.
Por exemplo, experimente usar esses comandos no seu terminal:
VAR=”Terminal do Linux”
echo “$VAR”
A saída do comando ‘echo’ será a frase “Terminal do Linux”. O ‘echo’ substituiu a referência $VAR
pelo conteúdo da variável.
Sistemas Operacionais
Marcio Quirino - 165
Atenção 1
A variável só é utilizada sem o prefixo $ quando estamos atribuindo valor a ela. Para fazer referência, o $
sempre é necessário.
Atenção 2
No terminal BASH, ao contrário da maioria das linguagens de programação, as variáveis não possuem tipo.
Portanto, a interpretação do conteúdo como número ou texto vai depender do contexto da operação que está sendo
realizada.
Atribuição da saída de um comando a uma variável
Suponha que você está desenvolvendo um SCRIPT no qual a informação retornada por um comando
será utilizada posteriormente. Uma das formas de você armazenar essa informação temporariamente é por
meio de variáveis.
No exemplo abaixo, simulamos um SCRIPT cuja execução é demorada. Para que o usuário saiba
quanto tempo a execução demorou, esse SCRIPT apresentará no final os horários em que começou e o
atual.
No início da execução, o horário será salvo em uma variável de nome “INICIO”.
#!/bin/bash
INICIO=$(date +%T)
echo "Executando uma tarefa demorada"
sleep 5
echo "Início: $INICIO / Término: $(date +%T)"
exit 0
Comentário
Repare na segunda linha que o comando ‘date’ é executado, retornando a hora naquele momento, que será
armazenada na variável INICIO. O comando ‘sleep 5’ simula uma tarefa demorada, aqui com 5 segundos de duração.
Ao término da execução teremos os dois horários, o que nos permitirá saber quanto tempo o SCRIPT
levou para executar:
./script3>
Executando uma tarefa demorada
Início: 17:47:00 / Término: 17:47:05
Atenção
O conteúdo de variáveis é armazenado em memória, e serve muito bem para quantidades pequenas de dados.
Se precisar guardar um volume grande, prefira utilizar arquivos temporários.
Passando parâmetros para o script
O terminal do Linux permite que um SCRIPT seja executado com parâmetros. Assim, podemos
passar valores para o SCRIPT a cada execução, sem precisar modificar o seu conteúdo.
Suponha um SCRIPT sendo executado com o comando abaixo, onde foram passados 4 parâmetros
(lembre-se: Os parâmetros são separados pelo espaço).
$ ./ script4 valor1 valor2 valor3 valor4
Cada um dos parâmetros pode ser lido pelo SCRIPT consultando as variáveis $1, $2, $3 e $4,
respectivamente. A variável $0 é o nome do próprio SCRIPT que foi executado.
Considere o ‘script4’:
Sistemas Operacionais
Marcio Quirino - 166
#!/bin/bash
echo "Nome do script: $0"
echo "Parametro 1: $1"
echo "Parametro 2: $2"
echo "Parametro 3: $3"
echo "Parametro 4: $4"
Esse SCRIPT irá buscar os 4 primeiros parâmetros e os exibirá. Os parâmetros que não forem
preenchidos serão vistos como vazio pelo SCRIPT.
Ao executar o SCRIPT, teremos a saída (não esqueça de conceder permissão de execução):
$ ./script4 valor1 valor2 valor3 valor4
Nome do script: ./script4
Parametro 1: valor1
Parametro 2: valor2
Parametro 3: valor3
Parametro 4: valor4
Atenção
Para parâmetros acima do número 10 a referência deve ser feita assim:
${10}, ${11}, ${12} ...
O operador lógico if
Assim como quase todas as linguagens de programação, o BASH também permite comparações por
meio do comando ‘if’.
O comando recebe uma expressão para ser avaliada, e seu retorno dependerá do resultado da
avaliação.
A sintaxe básica é a seguinte:
if [ CONDIÇÃO ]
then
# Alguma tarefa #
fi
O comando ‘if’ avalia a condição apresentada.
A. Verdadeiro
Se o resultado for verdadeiro, ele executará o código entre ‘then’ e ‘fi’, nesse exemplo
representado pela frase “# Alguma tarefa #”.
B. Falso
Se o resultado for falso, o fluxo da execução é desviado para depois do ‘fi’, portanto os comandos
em “# Alguma tarefa #” não são executados.
O operador ‘if’ também permite o uso da declaração ‘else’, que será executada sempre que o
resultado da condição for falso.
if [ CONDIÇÃO ]
then
# Alguma tarefa executada se a condição for verdadeira #
else
# Alguma tarefa executada se a condição for falsa #
fi
Comparadores numéricos
Observe os comparadores numéricos
Sistemas Operacionais
Marcio Quirino - 167
A. IGUALDADE ( -EQ → IS EQUAL )
if [[ “$A” -eq “$B” ]]
Resulta verdadeiro se o valor da variável $A for igual ao da variável $B.
B. DIFERENTE ( -NE → IS NOT EQUAL )
if [[ “$A” -ne “$B” ]]
Resulta verdadeiro se o valor da variável $A for diferente do da variável $B.
C. MAIOR QUE ( -GT → IS GREATER THAN )
if [[ “$A” -gt “$B” ]]
Resulta verdadeiro se o valor da variável $A for maior que o valor da variável $B.
D. MAIOR OU IGUAL ( -GE → IS GREATER THAN OR EQUAL )
if [[ “$A” -ge “$B” ]]
Resulta verdadeiro se o valor da variável $A for maior que o valor da variável $B ou igual.
E. MENOR QUE ( -LT → LESS THAN )
if [[ “$A” -lt “$B” ]]
Resulta verdadeiro se o valor da variável $A for menor que o valor da variável $B.
F. MENOR OU IGUAL ( -LE → IS LESS THAN OR EQUAL)
if [[ “$A” -le “$B” ]]
Resulta verdadeiro se o valor da variável $A for menor que o valor da variável $B ou igual.
Para exemplificar, considere esse ‘script5’ que recebe dois valores como parâmetros e realiza uma
série de comparações:
#!/bin/bash
A=$1
B=$2
if [[ "$A" -eq "$B" ]]
then
echo "A e B são iguais"
fi
if [[ "$A" -ne "$B" ]]
then
echo "A e B são diferentes"
fi
if [[ "$A" -gt "$B" ]]
then
echo "A é maior que B"
fi
if [[ "$A" -lt "$B" ]]then
echo "A é menor que B"
fi
O primeiro parâmetro ($1) é atribuído como o valor da variável $A, e o segundo como o valor da
variável $B.
Sistemas Operacionais
Marcio Quirino - 168
Ao executarmos, a saída dependerá dos valores passados:
$ ./script5 10 5
A e B são diferentes
A é maior que B
$ ./script5 10 20
A e B são diferentes
A é menor que B
$ ./script5 10 10
A e B são iguais
Comparadores de cadeias de caracteres (strings)
Observe agora os comparadores de cadeias de caracteres:
A. IGUALDADE
if [[ “$A” = “$B” ]]
Resulta verdadeiro se o valor (texto) da variável $A for igual ao da variável $B.
B. DIFERENTE
if [[ “$A” != “$B” ]]
Resulta verdadeiro se o valor (texto) da variável $A for diferente do da variável $B.
C. CADEIA NULA (NULL) OU VAZIA
if [[ -z “$A” ]]
Resulta verdadeiro se o valor (texto) da variável $A tiver zero caracteres (tamanho zero).
D. CADEIA NÃO NULA (NOT NULL)
if [[ -n “$A” ]]
Resulta verdadeiro se o valor (texto) da variável $A tiver tamanho maior do que zero.
Comentário
O SCRIPT que desenvolvemos no exemplo anterior possui uma falha: Se for executado sem parâmetros vai
retornar erro, pois tentará comparar valores ($1 e $2) que não existem.
Esse problema pode ser facilmente resolvido incluindo-se um teste de validade dos parâmetros no
início da execução.
#!/bin/bash
# Atribuir os parâmetros como as variáveis A e B
A=$1
B=$2
# Teste de validade dos parâmetros. Se algum deles for nulo
# o SCRIPT aborta a execução e retorna valor 1 para indicar
# que ocorreu uma falha.
# Testa se o primeiro valor é válido.
if [[ -n "$A" ]]
then
echo "O primeiro valor é válido: $A"
else
echo "O primeiro valor é nulo"
exit 1
fi
Sistemas Operacionais
Marcio Quirino - 169
# Testa se o segundo valor é válido.
if [[ -n "$B" ]]
then
echo "O segundo valor é válido: $B"
else
echo "O segundo valor é nulo"
exit 1
fi
# Inicia as comparações entre $A e $B
if [[ "$A" -eq "$B" ]]
then
echo "A e B são iguais"
fi
if [[ "$A" -ne "$B" ]]
then
echo "A e B são diferentes"
f1
if [[ "$A" -gt "$B" ]]
then
echo "A é maior que B"
fi
if [[ "$A" -lt "$B" ]]
then
echo "A é menor que B"
fi
exit 0
Se tentarmos executar o SCRIPT sem preencher ambos os parâmetros, o erro do usuário será
detectado e indicado.
$ ./script5
O primeiro valor é nulo
$ ./script5 22
O primeiro valor é valido: 22
O segundo valor é nulo
Executando o script sem parâmetros
No último exemplo criamos um SCRIPT para comparar dois valores passados por meio de
parâmetros na linha de comando. Em algumas situações, porém, pode ser mais prático e intuitivo pedir que
o usuário digite os valores durante a execução.
Esse tipo de SCRIPT é chamado de INTERATIVO, pois exige a interação de um usuário.
O comando ‘read’, que usamos no módulo anterior para aguardar uma interação do usuário, também
pode ser usado para receber um valor e atribuí-lo a uma variável.
$ read A
Irá aguardar o usuário digitar alguma informação, e terminar com ENTER. A informação digitada será
atribuída à variável A.
$ read A
Mensagem → Texto digitado pelo usuário
$ echo “$A”
Mensagem
Vamos criar uma versão interativa do exemplo que executamos anteriormente. Nessa versão,
apenas o começo do SCRIPT será modificado – em vez de receber A e B por parâmetros, eles serão obtidos
do usuário pelo comando ‘read’.
Sistemas Operacionais
Marcio Quirino - 170
#!/bin/bash
# Pedir ao usuário a digitação dos valores
# O parâmetro '-n' no echo faz com que ele não insira uma quebra de linha apos o texto
echo -n "Digite o primeiro valor: "
read A
echo -n "Digite o segundo valor: "
read B
### O RESTANTE DO SCRIPT É IGUAL AO script5
# Teste de validade dos parâmetros. Se algum deles for nulo
# o SCRIPT aborta a execução e retorna valor 1 para indicar
# que ocorreu uma falha.
# Testa se o primeiro valor é nulo.
if [[ -n "$A" ]]
then
echo "O primeiro valor é valido: $A"
else
echo "O primeiro valor é nulo"
exit 1
fi
# Testa se o segundo valor é nulo.
if [[ -n "$B" ]]
then
echo "O segundo valor é válido: $B"
else
echo "O segundo valor é nulo"
exit 1
fi
# Inicia as comparações entre $A e $B
if [[ "$A" -eq "$B" ]]
then
echo "A e B são iguais"
fi
if [[ "$A" -ne "$B" ]]
then
echo "A e B são diferentes"
fi
if [[ "$A" -gt "$B" ]]
then
echo "A é maior que B"
fi
if [[ "$A" -lt "$B" ]]
then
echo "A é menor que B"
fi
exit 0
Realizando operações aritméticas em script
Operações aritméticas com variáveis são muito úteis em SCRIPTS. Podem ser usadas para cálculos
comuns e até controle em estruturas de repetição.
Como um primeiro exemplo, observe o ‘script6’ que solicitará dois valores e retornará a soma deles:
#!/bin/bash
# Pede a digitação dos valores e os armazena nas
# variáveis A e B
echo -n "Digite o primeiro valor (A): "
read A
echo -n "Digite o segundo valor (B): "
Sistemas Operacionais
Marcio Quirino - 171
read B
# Calcula A+B e atribui o resultado na variável C
(( C=A+B ))
# Exibe o resultado
echo "O resultado de A+B = $C"
# Exibe o mesmo resultado, porém indicando o cálculo
# no echo, sem a necessidade da variável C.
echo "O resultado de A+B = $(( A+B ))"
exit 0
Ao executar o comando (informando A=12 e B=15), temos:
$ ./script6
Digite o primeiro valor (A): 12
Digite o segundo valor (B): 15
O resultado de A+B = 27
O resultado de A+B = 27
Atenção
O cálculo foi realizado de duas formas. Na primeira, o resultado foi atribuído a uma variável (C). Na segunda
forma, o cálculo foi feito diretamente a partir do comando ‘echo’.
Incrementando variáveis
Em estruturas de repetição, precisamos de variáveis auxiliares, que são constantemente
incrementadas. O incremento de uma variável X pode ser feito com:
(( X++ ))
Exemplo:
$ X=1
$ echo $X
1
$ (( X++ ))
$ echo $X
2
Atenção
No exemplo acima, com comandos digitados diretamente no SHELL, o primeiro declara a variável X e atribui o
valor ‘1’ a ela. Confirmamos que o valor foi atribuído no segundo comando (‘echo’), mostrando o valor de X. No terceiro
comando, incrementamos o valor de X em uma unidade, o que nos é comprovado pelo último comando (‘echo’).
Operações com ponto flutuante
Uma desvantagem do SHELL é que ele não realiza operações com ponto flutuante. Se tentarmos
uma divisão, por exemplo, o resultado será sempre um inteiro, perdendo-se as casas decimais.
Um comando que pode nos auxiliar é o ‘bc’ (consulte a MANPAGE para maiores informações). No
exemplo abaixo, o comando é invocado recebendo de um ‘echo’ a expressão para ser calculada.
$ echo "scale=5; 29/3" | bc -l
9.66666
A expressão tem duas partes, separadas por ponto e vírgula. A primeira parte (scale=5) define
quantas casas decimais deverá ter a resposta. A segunda parte é o cálculo, nesse caso 29 dividido por 3.
Sistemas Operacionais
Marcio Quirino - 172
Considere o ‘script7’ abaixo, que pede ao usuário três números (A, B e C) e calcula sua média.
#!/bin/bash
echo -n "Digite o primeiro valor (A): "
read A
echo -n "Digite o segundo valor (B): "
read B
echo -n "Digite o terceiro valor (C): "
read C
MEDIA=$(echo "scale=2;($A+$B+$C)/3" | bc)
echo “A média dos três valores é: $MEDIA”
exit 0
Ao executar o SCRIPT:
$ ./script7
Digite o primeiro valor (A): 10
Digite o segundo valor (B): 9
Digite o terceiro valor (C): 8.5
A média dos três valores é: 9.16
Repare que o comando ‘bc’ realizou o cálculo utilizando ponto flutuante, sem perder as casas
decimais no resultado.
4. Tarefas complexas em SCRIPTS com o uso de estruturas
de repetição
Conceitos
Estruturas de repetição, também chamadas de LOOPS, são recursos fundamentais em qualquer
modelo de programação. Através